2018-09-11 06:17:13 +08:00
|
|
|
|
class SoundBuffer{
|
|
|
|
|
constructor(){
|
2018-10-07 02:33:42 +08:00
|
|
|
|
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, gain){
|
2018-09-18 06:37:59 +08:00
|
|
|
|
return loader.ajax(url, request => {
|
2018-09-11 06:17:13 +08:00
|
|
|
|
request.responseType = "arraybuffer"
|
2018-09-18 06:37:59 +08:00
|
|
|
|
}).then(response => {
|
2018-10-07 12:22:44 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
return this.context.decodeAudioData(response, resolve, reject)
|
|
|
|
|
})
|
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){
|
|
|
|
|
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()
|
|
|
|
|
}
|
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-09-12 01:24:40 +08:00
|
|
|
|
if(typeof seek2 == "undefined"){
|
|
|
|
|
seek2 = seek1
|
|
|
|
|
}
|
2018-09-11 06:17:13 +08:00
|
|
|
|
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(){
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|