japanese-drum-game/public/src/js/soundbuffer.js
2018-09-11 01:25:44 +03:00

194 lines
4.7 KiB
JavaScript

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)
}
}
}
}