japanese-drum-game/public/src/js/soundbuffer.js

227 lines
5.5 KiB
JavaScript
Raw Normal View History

2018-09-11 06:17:13 +08:00
class SoundBuffer{
constructor(){
var AudioContext = window.AudioContext || window.webkitAudioContext
2018-09-11 06:17:13 +08:00
this.context = new AudioContext()
2018-10-13 02:04:28 +08:00
pageEvents.add(window, ["click", "touchend"], this.pageClicked.bind(this))
2018-09-11 06:17:13 +08:00
}
load(url, local, gain){
if(local){
var reader = new FileReader()
var loadPromise = pageEvents.load(reader).then(event => {
return event.target.result
})
reader.readAsArrayBuffer(url)
}else{
var loadPromise = loader.ajax(url, request => {
request.responseType = "arraybuffer"
})
}
return loadPromise.then(response => {
return new Promise((resolve, reject) => {
return this.context.decodeAudioData(response, resolve, reject)
2018-12-04 06:23:11 +08:00
}).catch(error => {
throw [error, url]
})
2018-09-18 06:37:59 +08:00
}).then(buffer => {
return new Sound(gain || {soundBuffer: this}, buffer)
2018-09-11 06:17:13 +08:00
})
}
2018-10-03 17:48:18 +08:00
createGain(channel){
return new SoundGain(this, channel)
2018-09-11 06:17:13 +08:00
}
setCrossfade(gain1, gain2, median){
2018-10-03 17:48:18 +08:00
if(!Array.isArray(gain1)){
gain1 = [gain1]
}
if(!Array.isArray(gain2)){
gain2 = [gain2]
}
gain1.forEach(gain => gain.setCrossfade(1 - median))
gain2.forEach(gain => gain.setCrossfade(median))
2018-09-11 06:17:13 +08:00
}
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
}
2018-10-13 02:04:28 +08:00
pageClicked(){
if(this.context.state === "suspended"){
this.context.resume()
}
}
2018-09-11 06:17:13 +08:00
}
class SoundGain{
2018-10-03 17:48:18 +08:00
constructor(soundBuffer, channel){
2018-09-11 06:17:13 +08:00
this.soundBuffer = soundBuffer
this.gainNode = soundBuffer.context.createGain()
2018-10-03 17:48:18 +08:00
if(channel){
var index = channel === "left" ? 0 : 1
this.merger = soundBuffer.context.createChannelMerger(2)
this.merger.connect(soundBuffer.context.destination)
this.gainNode.connect(this.merger, 0, index)
}else{
this.gainNode.connect(soundBuffer.context.destination)
}
2018-09-11 06:17:13 +08:00
this.setVolume(1)
}
load(url, local){
return this.soundBuffer.load(url, local, this)
2018-09-11 06:17:13 +08:00
}
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()
}
2018-10-03 17:48:18 +08:00
copy(gain){
return new Sound(gain, this.buffer)
}
2018-09-11 06:17:13 +08:00
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
2018-12-13 17:18:52 +08:00
if(typeof seek2 === "undefined"){
2018-09-12 01:24:40 +08:00
seek2 = seek1
}
2018-09-11 06:17:13 +08:00
until = until || this.duration
2018-12-13 17:18:52 +08:00
if(seek1 >= until || seek2 >= until){
return
}
2018-09-11 06:17:13 +08:00
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(){
2018-10-03 17:48:18 +08:00
while(this.getTime() > this.loop.started - 1){
2018-09-11 06:17:13 +08:00
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 => {
2018-10-13 06:48:53 +08:00
try{
source.stop(Math.max(source.startTime, time))
}catch(e){}
2018-09-11 06:17:13 +08:00
})
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)
}
}
}
2018-11-11 03:12:29 +08:00
clean(){
delete this.buffer
}
2018-09-11 06:17:13 +08:00
}