| | function GameBoyAdvanceAudio() { |
| | window.AudioContext = window.AudioContext || window.webkitAudioContext; |
| | if (window.AudioContext) { |
| | var self = this; |
| | this.context = new AudioContext(); |
| | this.bufferSize = 0; |
| | this.bufferSize = 4096; |
| | this.maxSamples = this.bufferSize << 2; |
| | this.buffers = [new Float32Array(this.maxSamples), new Float32Array(this.maxSamples)]; |
| | this.sampleMask = this.maxSamples - 1; |
| | if (this.context.createScriptProcessor) { |
| | this.jsAudio = this.context.createScriptProcessor(this.bufferSize); |
| | } else { |
| | this.jsAudio = this.context.createJavaScriptNode(this.bufferSize); |
| | } |
| | this.jsAudio.onaudioprocess = function(e) { self.audioProcess(e) }; |
| | } else { |
| | this.context = null; |
| | } |
| |
|
| | this.masterEnable = true; |
| | this.masterVolume = 1.0; |
| |
|
| | this.SOUND_MAX = 0x400; |
| | this.FIFO_MAX = 0x200; |
| | this.PSG_MAX = 0x080; |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.clear = function() { |
| | this.fifoA = []; |
| | this.fifoB = []; |
| | this.fifoASample = 0; |
| | this.fifoBSample = 0; |
| |
|
| | this.enabled = false; |
| | if (this.context) { |
| | try { |
| | this.jsAudio.disconnect(this.context.destination); |
| | } catch (e) { |
| | } |
| | } |
| |
|
| | this.enableChannel3 = false; |
| | this.enableChannel4 = false; |
| | this.enableChannelA = false; |
| | this.enableChannelB = false; |
| | this.enableRightChannelA = false; |
| | this.enableLeftChannelA = false; |
| | this.enableRightChannelB = false; |
| | this.enableLeftChannelB = false; |
| |
|
| | this.playingChannel3 = false; |
| | this.playingChannel4 = false; |
| |
|
| | this.volumeLeft = 0; |
| | this.volumeRight = 0; |
| | this.ratioChannelA = 1; |
| | this.ratioChannelB = 1; |
| | this.enabledLeft = 0; |
| | this.enabledRight = 0; |
| |
|
| | this.dmaA = -1; |
| | this.dmaB = -1; |
| | this.soundTimerA = 0; |
| | this.soundTimerB = 0; |
| |
|
| | this.soundRatio = 1; |
| | this.soundBias = 0x200; |
| |
|
| | this.squareChannels = new Array(); |
| | for (var i = 0; i < 2; ++i) { |
| | this.squareChannels[i] = { |
| | enabled: false, |
| | playing: false, |
| | sample: 0, |
| | duty: 0.5, |
| | increment: 0, |
| | step: 0, |
| | initialVolume: 0, |
| | volume: 0, |
| | frequency: 0, |
| | interval: 0, |
| | sweepSteps: 0, |
| | sweepIncrement: 0, |
| | sweepInterval: 0, |
| | doSweep: false, |
| | raise: 0, |
| | lower: 0, |
| | nextStep: 0, |
| | timed: false, |
| | length: 0, |
| | end: 0 |
| | } |
| | } |
| |
|
| | this.waveData = new Uint8Array(32); |
| | this.channel3Dimension = 0; |
| | this.channel3Bank = 0; |
| | this.channel3Volume = 0; |
| | this.channel3Interval = 0; |
| | this.channel3Next = 0; |
| | this.channel3Length = 0; |
| | this.channel3Timed = false; |
| | this.channel3End = 0; |
| | this.channel3Pointer =0; |
| | this.channel3Sample = 0; |
| |
|
| | this.cpuFrequency = this.core.irq.FREQUENCY; |
| |
|
| | this.channel4 = { |
| | sample: 0, |
| | lfsr: 0, |
| | width: 15, |
| | interval: this.cpuFrequency / 524288, |
| | increment: 0, |
| | step: 0, |
| | initialVolume: 0, |
| | volume: 0, |
| | nextStep: 0, |
| | timed: false, |
| | length: 0, |
| | end: 0 |
| | }; |
| |
|
| | this.nextEvent = 0; |
| |
|
| | this.nextSample = 0; |
| | this.outputPointer = 0; |
| | this.samplePointer = 0; |
| |
|
| | this.backup = 0; |
| | this.totalSamples = 0; |
| |
|
| | this.sampleRate = 32768; |
| | this.sampleInterval = this.cpuFrequency / this.sampleRate; |
| | this.resampleRatio = 1; |
| | if (this.context) { |
| | this.resampleRatio = this.sampleRate / this.context.sampleRate; |
| | } |
| |
|
| | this.writeSquareChannelFC(0, 0); |
| | this.writeSquareChannelFC(1, 0); |
| | this.writeChannel4FC(0); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.freeze = function() { |
| | return { |
| | nextSample: this.nextSample |
| | }; |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.defrost = function(frost) { |
| | this.nextSample = frost.nextSample; |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.pause = function(paused) { |
| | if (this.context) { |
| | if (paused) { |
| | try { |
| | this.jsAudio.disconnect(this.context.destination); |
| | } catch (e) { |
| | |
| | } |
| | } else if (this.enabled) { |
| | this.jsAudio.connect(this.context.destination); |
| | } |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.updateTimers = function() { |
| | var cycles = this.cpu.cycles; |
| | if (!this.enabled || (cycles < this.nextEvent && cycles < this.nextSample)) { |
| | return; |
| | } |
| |
|
| | if (cycles >= this.nextEvent) { |
| | var channel = this.squareChannels[0]; |
| | this.nextEvent = Infinity; |
| | if (channel.playing) { |
| | this.updateSquareChannel(channel, cycles); |
| | } |
| |
|
| | channel = this.squareChannels[1]; |
| | if (channel.playing) { |
| | this.updateSquareChannel(channel, cycles); |
| | } |
| |
|
| | if (this.enableChannel3 && this.playingChannel3) { |
| | if (cycles >= this.channel3Next) { |
| | if (this.channel3Write) { |
| | var sample = this.waveData[this.channel3Pointer >> 1]; |
| | this.channel3Sample = (((sample >> ((this.channel3Pointer & 1) << 2)) & 0xF) - 0x8) / 8; |
| | this.channel3Pointer = (this.channel3Pointer + 1); |
| | if (this.channel3Dimension && this.channel3Pointer >= 64) { |
| | this.channel3Pointer -= 64; |
| | } else if (!this.channel3Bank && this.channel3Pointer >= 32) { |
| | this.channel3Pointer -= 32; |
| | } else if (this.channel3Pointer >= 64) { |
| | this.channel3Pointer -= 32; |
| | } |
| | } |
| | this.channel3Next += this.channel3Interval; |
| | if (this.channel3Interval && this.nextEvent > this.channel3Next) { |
| | this.nextEvent = this.channel3Next; |
| | } |
| | } |
| | if (this.channel3Timed && cycles >= this.channel3End) { |
| | this.playingChannel3 = false; |
| | } |
| | } |
| |
|
| | if (this.enableChannel4 && this.playingChannel4) { |
| | if (this.channel4.timed && cycles >= this.channel4.end) { |
| | this.playingChannel4 = false; |
| | } else { |
| | if (cycles >= this.channel4.next) { |
| | this.channel4.lfsr >>= 1; |
| | var sample = this.channel4.lfsr & 1; |
| | this.channel4.lfsr |= (((this.channel4.lfsr >> 1) & 1) ^ sample) << (this.channel4.width - 1); |
| | this.channel4.next += this.channel4.interval; |
| | this.channel4.sample = (sample - 0.5) * 2 * this.channel4.volume; |
| | } |
| | this.updateEnvelope(this.channel4, cycles); |
| | if (this.nextEvent > this.channel4.next) { |
| | this.nextEvent = this.channel4.next; |
| | } |
| | if (this.channel4.timed && this.nextEvent > this.channel4.end) { |
| | this.nextEvent = this.channel4.end; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | if (cycles >= this.nextSample) { |
| | this.sample(); |
| | this.nextSample += this.sampleInterval; |
| | } |
| |
|
| | this.nextEvent = Math.ceil(this.nextEvent); |
| | if ((this.nextEvent < cycles) || (this.nextSample < cycles)) { |
| | |
| | this.updateTimers(); |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeEnable = function(value) { |
| | this.enabled = !!value; |
| | this.nextEvent = this.cpu.cycles; |
| | this.nextSample = this.nextEvent; |
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | if (this.context) { |
| | if (value) { |
| | this.jsAudio.connect(this.context.destination); |
| | } else { |
| | try { |
| | this.jsAudio.disconnect(this.context.destination); |
| | } catch (e) { |
| | } |
| | } |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeSoundControlLo = function(value) { |
| | this.masterVolumeLeft = value & 0x7; |
| | this.masterVolumeRight = (value >> 4) & 0x7; |
| | this.enabledLeft = (value >> 8) & 0xF; |
| | this.enabledRight = (value >> 12) & 0xF; |
| |
|
| | this.setSquareChannelEnabled(this.squareChannels[0], (this.enabledLeft | this.enabledRight) & 0x1); |
| | this.setSquareChannelEnabled(this.squareChannels[1], (this.enabledLeft | this.enabledRight) & 0x2); |
| | this.enableChannel3 = (this.enabledLeft | this.enabledRight) & 0x4; |
| | this.setChannel4Enabled((this.enabledLeft | this.enabledRight) & 0x8); |
| |
|
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeSoundControlHi = function(value) { |
| | switch (value & 0x0003) { |
| | case 0: |
| | this.soundRatio = 0.25; |
| | break; |
| | case 1: |
| | this.soundRatio = 0.50; |
| | break; |
| | case 2: |
| | this.soundRatio = 1; |
| | break; |
| | } |
| | this.ratioChannelA = (((value & 0x0004) >> 2) + 1) * 0.5; |
| | this.ratioChannelB = (((value & 0x0008) >> 3) + 1) * 0.5; |
| |
|
| | this.enableRightChannelA = value & 0x0100; |
| | this.enableLeftChannelA = value & 0x0200; |
| | this.enableChannelA = value & 0x0300; |
| | this.soundTimerA = value & 0x0400; |
| | if (value & 0x0800) { |
| | this.fifoA = []; |
| | } |
| | this.enableRightChannelB = value & 0x1000; |
| | this.enableLeftChannelB = value & 0x2000; |
| | this.enableChannelB = value & 0x3000; |
| | this.soundTimerB = value & 0x4000; |
| | if (value & 0x8000) { |
| | this.fifoB = []; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.resetSquareChannel = function(channel) { |
| | if (channel.step) { |
| | channel.nextStep = this.cpu.cycles + channel.step; |
| | } |
| | if (channel.enabled && !channel.playing) { |
| | channel.raise = this.cpu.cycles; |
| | channel.lower = channel.raise + channel.duty * channel.interval; |
| | channel.end = this.cpu.cycles + channel.length; |
| | this.nextEvent = this.cpu.cycles; |
| | } |
| | channel.playing = channel.enabled; |
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.setSquareChannelEnabled = function(channel, enable) { |
| | if (!(channel.enabled && channel.playing) && enable) { |
| | channel.enabled = !!enable; |
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | } else { |
| | channel.enabled = !!enable; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeSquareChannelSweep = function(channelId, value) { |
| | var channel = this.squareChannels[channelId]; |
| | channel.sweepSteps = value & 0x07; |
| | channel.sweepIncrement = (value & 0x08) ? -1 : 1; |
| | channel.sweepInterval = ((value >> 4) & 0x7) * this.cpuFrequency / 128; |
| | channel.doSweep = !!channel.sweepInterval; |
| | channel.nextSweep = this.cpu.cycles + channel.sweepInterval; |
| | this.resetSquareChannel(channel); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeSquareChannelDLE = function(channelId, value) { |
| | var channel = this.squareChannels[channelId]; |
| | var duty = (value >> 6) & 0x3; |
| | switch (duty) { |
| | case 0: |
| | channel.duty = 0.125; |
| | break; |
| | case 1: |
| | channel.duty = 0.25; |
| | break; |
| | case 2: |
| | channel.duty = 0.5; |
| | break; |
| | case 3: |
| | channel.duty = 0.75; |
| | break; |
| | } |
| | this.writeChannelLE(channel, value); |
| | this.resetSquareChannel(channel); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeSquareChannelFC = function(channelId, value) { |
| | var channel = this.squareChannels[channelId]; |
| | var frequency = value & 2047; |
| | channel.frequency = frequency; |
| | channel.interval = this.cpuFrequency * (2048 - frequency) / 131072; |
| | channel.timed = !!(value & 0x4000); |
| |
|
| | if (value & 0x8000) { |
| | this.resetSquareChannel(channel); |
| | channel.volume = channel.initialVolume; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.updateSquareChannel = function(channel, cycles) { |
| | if (channel.timed && cycles >= channel.end) { |
| | channel.playing = false; |
| | return; |
| | } |
| |
|
| | if (channel.doSweep && cycles >= channel.nextSweep) { |
| | channel.frequency += channel.sweepIncrement * (channel.frequency >> channel.sweepSteps); |
| | if (channel.frequency < 0) { |
| | channel.frequency = 0; |
| | } else if (channel.frequency > 2047) { |
| | channel.frequency = 2047; |
| | channel.playing = false; |
| | return; |
| | } |
| | channel.interval = this.cpuFrequency * (2048 - channel.frequency) / 131072; |
| | channel.nextSweep += channel.sweepInterval; |
| | } |
| |
|
| | if (cycles >= channel.raise) { |
| | channel.sample = channel.volume; |
| | channel.lower = channel.raise + channel.duty * channel.interval; |
| | channel.raise += channel.interval; |
| | } else if (cycles >= channel.lower) { |
| | channel.sample = -channel.volume; |
| | channel.lower += channel.interval; |
| | } |
| |
|
| | this.updateEnvelope(channel, cycles); |
| |
|
| | if (this.nextEvent > channel.raise) { |
| | this.nextEvent = channel.raise; |
| | } |
| | if (this.nextEvent > channel.lower) { |
| | this.nextEvent = channel.lower; |
| | } |
| | if (channel.timed && this.nextEvent > channel.end) { |
| | this.nextEvent = channel.end; |
| | } |
| | if (channel.doSweep && this.nextEvent > channel.nextSweep) { |
| | this.nextEvent = channel.nextSweep; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeChannel3Lo = function(value) { |
| | this.channel3Dimension = value & 0x20; |
| | this.channel3Bank = value & 0x40; |
| | var enable = value & 0x80; |
| | if (!this.channel3Write && enable) { |
| | this.channel3Write = enable; |
| | this.resetChannel3(); |
| | } else { |
| | this.channel3Write = enable; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeChannel3Hi = function(value) { |
| | this.channel3Length = this.cpuFrequency * (0x100 - (value & 0xFF)) / 256; |
| | var volume = (value >> 13) & 0x7; |
| | switch (volume) { |
| | case 0: |
| | this.channel3Volume = 0; |
| | break; |
| | case 1: |
| | this.channel3Volume = 1; |
| | break; |
| | case 2: |
| | this.channel3Volume = 0.5; |
| | break; |
| | case 3: |
| | this.channel3Volume = 0.25; |
| | break; |
| | default: |
| | this.channel3Volume = 0.75; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeChannel3X = function(value) { |
| | this.channel3Interval = this.cpuFrequency * (2048 - (value & 0x7FF)) / 2097152; |
| | this.channel3Timed = !!(value & 0x4000); |
| | if (this.channel3Write) { |
| | this.resetChannel3(); |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.resetChannel3 = function() { |
| | this.channel3Next = this.cpu.cycles; |
| | this.nextEvent = this.channel3Next; |
| | this.channel3End = this.cpu.cycles + this.channel3Length; |
| | this.playingChannel3 = this.channel3Write; |
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeWaveData = function(offset, data, width) { |
| | if (!this.channel3Bank) { |
| | offset += 16; |
| | } |
| | if (width == 2) { |
| | this.waveData[offset] = data & 0xFF; |
| | data >>= 8; |
| | ++offset; |
| | } |
| | this.waveData[offset] = data & 0xFF; |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.setChannel4Enabled = function(enable) { |
| | if (!this.enableChannel4 && enable) { |
| | this.channel4.next = this.cpu.cycles; |
| | this.channel4.end = this.cpu.cycles + this.channel4.length; |
| | this.enableChannel4 = true; |
| | this.playingChannel4 = true; |
| | this.nextEvent = this.cpu.cycles; |
| | this.updateEnvelope(this.channel4); |
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | } else { |
| | this.enableChannel4 = enable; |
| | } |
| | } |
| |
|
| | GameBoyAdvanceAudio.prototype.writeChannel4LE = function(value) { |
| | this.writeChannelLE(this.channel4, value); |
| | this.resetChannel4(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeChannel4FC = function(value) { |
| | this.channel4.timed = !!(value & 0x4000); |
| |
|
| | var r = value & 0x7; |
| | if (!r) { |
| | r = 0.5; |
| | } |
| | var s = (value >> 4) & 0xF; |
| | var interval = this.cpuFrequency * (r * (2 << s)) / 524288; |
| | if (interval != this.channel4.interval) { |
| | this.channel4.interval = interval; |
| | this.resetChannel4(); |
| | } |
| |
|
| | var width = (value & 0x8) ? 7 : 15; |
| | if (width != this.channel4.width) { |
| | this.channel4.width = width; |
| | this.resetChannel4(); |
| | } |
| |
|
| | if (value & 0x8000) { |
| | this.resetChannel4(); |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.resetChannel4 = function() { |
| | if (this.channel4.width == 15) { |
| | this.channel4.lfsr = 0x4000; |
| | } else { |
| | this.channel4.lfsr = 0x40; |
| | } |
| | this.channel4.volume = this.channel4.initialVolume; |
| | if (this.channel4.step) { |
| | this.channel4.nextStep = this.cpu.cycles + this.channel4.step; |
| | } |
| | this.channel4.end = this.cpu.cycles + this.channel4.length; |
| | this.channel4.next = this.cpu.cycles; |
| | this.nextEvent = this.channel4.next; |
| |
|
| | this.playingChannel4 = this.enableChannel4; |
| | this.updateTimers(); |
| | this.core.irq.pollNextEvent(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.writeChannelLE = function(channel, value) { |
| | channel.length = this.cpuFrequency * ((0x40 - (value & 0x3F)) / 256); |
| |
|
| | if (value & 0x0800) { |
| | channel.increment = 1 / 16; |
| | } else { |
| | channel.increment = -1 / 16; |
| | } |
| | channel.initialVolume = ((value >> 12) & 0xF) / 16; |
| |
|
| | channel.step = this.cpuFrequency * (((value >> 8) & 0x7) / 64); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.updateEnvelope = function(channel, cycles) { |
| | if (channel.step) { |
| | if (cycles >= channel.nextStep) { |
| | channel.volume += channel.increment; |
| | if (channel.volume > 1) { |
| | channel.volume = 1; |
| | } else if (channel.volume < 0) { |
| | channel.volume = 0; |
| | } |
| | channel.nextStep += channel.step; |
| | } |
| |
|
| | if (this.nextEvent > channel.nextStep) { |
| | this.nextEvent = channel.nextStep; |
| | } |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.appendToFifoA = function(value) { |
| | var b; |
| | if (this.fifoA.length > 28) { |
| | this.fifoA = this.fifoA.slice(-28); |
| | } |
| | for (var i = 0; i < 4; ++i) { |
| | b = (value & 0xFF) << 24; |
| | value >>= 8; |
| | this.fifoA.push(b / 0x80000000); |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.appendToFifoB = function(value) { |
| | var b; |
| | if (this.fifoB.length > 28) { |
| | this.fifoB = this.fifoB.slice(-28); |
| | } |
| | for (var i = 0; i < 4; ++i) { |
| | b = (value & 0xFF) << 24; |
| | value >>= 8; |
| | this.fifoB.push(b / 0x80000000); |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.sampleFifoA = function() { |
| | if (this.fifoA.length <= 16) { |
| | var dma = this.core.irq.dma[this.dmaA]; |
| | dma.nextCount = 4; |
| | this.core.mmu.serviceDma(this.dmaA, dma); |
| | } |
| | this.fifoASample = this.fifoA.shift(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.sampleFifoB = function() { |
| | if (this.fifoB.length <= 16) { |
| | var dma = this.core.irq.dma[this.dmaB]; |
| | dma.nextCount = 4; |
| | this.core.mmu.serviceDma(this.dmaB, dma); |
| | } |
| | this.fifoBSample = this.fifoB.shift(); |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.scheduleFIFODma = function(number, info) { |
| | switch (info.dest) { |
| | case this.cpu.mmu.BASE_IO | this.cpu.irq.io.FIFO_A_LO: |
| | |
| | info.dstControl = 2; |
| | this.dmaA = number; |
| | break; |
| | case this.cpu.mmu.BASE_IO | this.cpu.irq.io.FIFO_B_LO: |
| | info.dstControl = 2; |
| | this.dmaB = number; |
| | break; |
| | default: |
| | this.core.WARN('Tried to schedule FIFO DMA for non-FIFO destination'); |
| | break; |
| | } |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.sample = function() { |
| | var sampleLeft = 0; |
| | var sampleRight = 0; |
| | var sample; |
| | var channel; |
| |
|
| | channel = this.squareChannels[0]; |
| | if (channel.playing) { |
| | sample = channel.sample * this.soundRatio * this.PSG_MAX; |
| | if (this.enabledLeft & 0x1) { |
| | sampleLeft += sample; |
| | } |
| | if (this.enabledRight & 0x1) { |
| | sampleRight += sample; |
| | } |
| | } |
| |
|
| | channel = this.squareChannels[1]; |
| | if (channel.playing) { |
| | sample = channel.sample * this.soundRatio * this.PSG_MAX; |
| | if (this.enabledLeft & 0x2) { |
| | sampleLeft += sample; |
| | } |
| | if (this.enabledRight & 0x2) { |
| | sampleRight += sample; |
| | } |
| | } |
| |
|
| | if (this.playingChannel3) { |
| | sample = this.channel3Sample * this.soundRatio * this.channel3Volume * this.PSG_MAX; |
| | if (this.enabledLeft & 0x4) { |
| | sampleLeft += sample; |
| | } |
| | if (this.enabledRight & 0x4) { |
| | sampleRight += sample; |
| | } |
| | } |
| |
|
| | if (this.playingChannel4) { |
| | sample = this.channel4.sample * this.soundRatio * this.PSG_MAX; |
| | if (this.enabledLeft & 0x8) { |
| | sampleLeft += sample; |
| | } |
| | if (this.enabledRight & 0x8) { |
| | sampleRight += sample; |
| | } |
| | } |
| |
|
| | if (this.enableChannelA) { |
| | sample = this.fifoASample * this.FIFO_MAX * this.ratioChannelA; |
| | if (this.enableLeftChannelA) { |
| | sampleLeft += sample; |
| | } |
| | if (this.enableRightChannelA) { |
| | sampleRight += sample; |
| | } |
| | } |
| |
|
| | if (this.enableChannelB) { |
| | sample = this.fifoBSample * this.FIFO_MAX * this.ratioChannelB; |
| | if (this.enableLeftChannelB) { |
| | sampleLeft += sample; |
| | } |
| | if (this.enableRightChannelB) { |
| | sampleRight += sample; |
| | } |
| | } |
| |
|
| | var samplePointer = this.samplePointer; |
| | sampleLeft *= this.masterVolume / this.SOUND_MAX; |
| | sampleLeft = Math.max(Math.min(sampleLeft, 1), -1); |
| | sampleRight *= this.masterVolume / this.SOUND_MAX; |
| | sampleRight = Math.max(Math.min(sampleRight, 1), -1); |
| | if (this.buffers) { |
| | this.buffers[0][samplePointer] = sampleLeft; |
| | this.buffers[1][samplePointer] = sampleRight; |
| | } |
| | this.samplePointer = (samplePointer + 1) & this.sampleMask; |
| | }; |
| |
|
| | GameBoyAdvanceAudio.prototype.audioProcess = function(audioProcessingEvent) { |
| | var left = audioProcessingEvent.outputBuffer.getChannelData(0); |
| | var right = audioProcessingEvent.outputBuffer.getChannelData(1); |
| | if (this.masterEnable) { |
| | var i; |
| | var o = this.outputPointer; |
| | for (i = 0; i < this.bufferSize; ++i, o += this.resampleRatio) { |
| | if (o >= this.maxSamples) { |
| | o -= this.maxSamples; |
| | } |
| | if ((o | 0) == this.samplePointer) { |
| | ++this.backup; |
| | break; |
| | } |
| | left[i] = this.buffers[0][o | 0]; |
| | right[i] = this.buffers[1][o | 0]; |
| | } |
| | for (; i < this.bufferSize; ++i) { |
| | left[i] = 0; |
| | right[i] = 0; |
| | } |
| | this.outputPointer = o; |
| | ++this.totalSamples; |
| | } else { |
| | for (i = 0; i < this.bufferSize; ++i) { |
| | left[i] = 0; |
| | right[i] = 0; |
| | } |
| | } |
| | }; |
| |
|