Buffer every sound asset
This commit is contained in:
193
public/src/js/soundbuffer.js
Normal file
193
public/src/js/soundbuffer.js
Normal file
@@ -0,0 +1,193 @@
|
||||
class SoundBuffer{
|
||||
constructor(){
|
||||
this.context = new AudioContext()
|
||||
var resume = () => {
|
||||
if(this.context.state == "suspended"){
|
||||
this.context.resume()
|
||||
}
|
||||
removeEventListener("click", resume)
|
||||
}
|
||||
addEventListener("click", resume)
|
||||
}
|
||||
load(url, gain){
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = new XMLHttpRequest()
|
||||
request.open("GET", url)
|
||||
request.responseType = "arraybuffer"
|
||||
request.addEventListener("load", () => {
|
||||
this.context.decodeAudioData(request.response, buffer => {
|
||||
resolve(new Sound(gain || {soundBuffer: this}, buffer))
|
||||
}, reject)
|
||||
})
|
||||
request.addEventListener("error", reject)
|
||||
request.addEventListener("abort", reject)
|
||||
request.send()
|
||||
})
|
||||
}
|
||||
createGain(){
|
||||
return new SoundGain(this)
|
||||
}
|
||||
setCrossfade(gain1, gain2, median){
|
||||
gain1.setCrossfade(1 - median)
|
||||
gain2.setCrossfade(median)
|
||||
}
|
||||
getTime(){
|
||||
return this.context.currentTime
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
time = (time || 0)
|
||||
if(time < 0){
|
||||
time = 0
|
||||
}
|
||||
return time + (absolute ? 0 : this.getTime())
|
||||
}
|
||||
createSource(sound){
|
||||
var source = this.context.createBufferSource()
|
||||
source.buffer = sound.buffer
|
||||
source.connect(sound.gain.gainNode || this.context.destination)
|
||||
return source
|
||||
}
|
||||
}
|
||||
class SoundGain{
|
||||
constructor(soundBuffer){
|
||||
this.soundBuffer = soundBuffer
|
||||
this.gainNode = soundBuffer.context.createGain()
|
||||
this.gainNode.connect(soundBuffer.context.destination)
|
||||
this.setVolume(1)
|
||||
}
|
||||
load(url){
|
||||
return this.soundBuffer.load(url, this)
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
return this.soundBuffer.convertTime(time, absolute)
|
||||
}
|
||||
setVolume(amount){
|
||||
this.gainNode.gain.value = amount * amount
|
||||
this.volume = amount
|
||||
}
|
||||
setCrossfade(amount){
|
||||
this.setVolume(Math.pow(Math.sin(Math.PI / 2 * amount), 1 / 4))
|
||||
}
|
||||
fadeIn(duration, time, absolute){
|
||||
this.fadeVolume(0, this.volume * this.volume, duration, time, absolute)
|
||||
}
|
||||
fadeOut(duration, time, absolute){
|
||||
this.fadeVolume(this.volume * this.volume, 0, duration, time, absolute)
|
||||
}
|
||||
fadeVolume(vol1, vol2, duration, time, absolute){
|
||||
time = this.convertTime(time, absolute)
|
||||
this.gainNode.gain.linearRampToValueAtTime(vol1, time)
|
||||
this.gainNode.gain.linearRampToValueAtTime(vol2, time + (duration || 0))
|
||||
}
|
||||
mute(){
|
||||
this.gainNode.gain.value = 0
|
||||
}
|
||||
unmute(){
|
||||
this.setVolume(this.volume)
|
||||
}
|
||||
}
|
||||
class Sound{
|
||||
constructor(gain, buffer){
|
||||
this.gain = gain
|
||||
this.buffer = buffer
|
||||
this.soundBuffer = gain.soundBuffer
|
||||
this.duration = buffer.duration
|
||||
this.timeouts = new Set()
|
||||
this.sources = new Set()
|
||||
}
|
||||
getTime(){
|
||||
return this.soundBuffer.getTime()
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
return this.soundBuffer.convertTime(time, absolute)
|
||||
}
|
||||
setTimeouts(time){
|
||||
return new Promise(resolve => {
|
||||
var relTime = time - this.getTime()
|
||||
if(relTime > 0){
|
||||
var timeout = setTimeout(() => {
|
||||
this.timeouts.delete(timeout)
|
||||
resolve()
|
||||
}, relTime * 1000)
|
||||
this.timeouts.add(timeout)
|
||||
}else{
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
clearTimeouts(){
|
||||
this.timeouts.forEach(timeout => {
|
||||
clearTimeout(timeout)
|
||||
this.timeouts.delete(timeout)
|
||||
})
|
||||
}
|
||||
playLoop(time, absolute, seek1, seek2, until){
|
||||
time = this.convertTime(time, absolute)
|
||||
seek1 = seek1 || 0
|
||||
seek2 = seek2 || seek1
|
||||
until = until || this.duration
|
||||
this.loop = {
|
||||
started: time + until - seek1,
|
||||
seek: seek2,
|
||||
until: until
|
||||
}
|
||||
this.play(time, true, seek1, until)
|
||||
this.addLoop()
|
||||
this.loop.interval = setInterval(() => {
|
||||
this.addLoop()
|
||||
}, 100)
|
||||
}
|
||||
addLoop(){
|
||||
if(this.getTime() > this.loop.started - 1){
|
||||
this.play(this.loop.started, true, this.loop.seek, this.loop.until)
|
||||
this.loop.started += this.loop.until - this.loop.seek
|
||||
}
|
||||
}
|
||||
play(time, absolute, seek, until){
|
||||
time = this.convertTime(time, absolute)
|
||||
var source = this.soundBuffer.createSource(this)
|
||||
seek = seek || 0
|
||||
until = until || this.duration
|
||||
this.setTimeouts(time).then(() => {
|
||||
this.cfg = {
|
||||
started: time,
|
||||
seek: seek,
|
||||
until: until
|
||||
}
|
||||
})
|
||||
source.start(time, Math.max(0, seek || 0), Math.max(0, until - seek))
|
||||
source.startTime = time
|
||||
this.sources.add(source)
|
||||
source.onended = () => {
|
||||
this.sources.delete(source)
|
||||
}
|
||||
}
|
||||
stop(time, absolute){
|
||||
time = this.convertTime(time, absolute)
|
||||
this.sources.forEach(source => {
|
||||
source.stop(Math.max(source.startTime, time))
|
||||
})
|
||||
this.setTimeouts(time).then(() => {
|
||||
if(this.loop){
|
||||
clearInterval(this.loop.interval)
|
||||
}
|
||||
this.clearTimeouts()
|
||||
})
|
||||
}
|
||||
pause(time, absolute){
|
||||
if(this.cfg){
|
||||
time = this.convertTime(time, absolute)
|
||||
this.stop(time, true)
|
||||
this.cfg.pauseSeek = time - this.cfg.started + this.cfg.seek
|
||||
}
|
||||
}
|
||||
resume(time, absolute){
|
||||
if(this.cfg){
|
||||
if(this.loop){
|
||||
this.playLoop(time, absolute, this.cfg.pauseSeek, this.loop.seek, this.loop.until)
|
||||
}else{
|
||||
this.play(time, absolute, this.cfg.pauseSeek, this.cfg.until)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user