Permalink
Browse files

Merge branch 'sound'

  • Loading branch information...
2 parents 802c40c + bf753d0 commit 75e772047142eaf7a0e9e521c32a2914b5272a9b @mattgodbolt committed Jun 22, 2011
Showing with 192 additions and 1 deletion.
  1. +2 −0 miracle.html
  2. +71 −1 miracle.js
  3. +119 −0 soundchip.js
View
@@ -31,6 +31,7 @@
<script type="text/javascript" src="z80/z80_ops_full.js"></script>
<script type="text/javascript" src="z80/z80_dis.js"></script>
<script type="text/javascript" src="vdp.js"></script>
+ <script type="text/javascript" src="soundchip.js"></script>
<script type="text/javascript" src="miracle.js"></script>
<script type="text/javascript" src="roms.js"></script>
<script type="text/javascript">/* <![CDATA[ */
@@ -75,6 +76,7 @@
z80_interrupt();
}
}
+ audioRun(1/50.0);
paintScreen();
}
function updateDebug() {
View
@@ -19,9 +19,79 @@ var needDrawImage = (navigator.userAgent.indexOf('Firefox/2') !== -1);
var joystick = 0xffff;
+var soundChip;
+var playbackBuffer = [];
+var playbackIndex = 0;
+var soundBuffer;
+var soundBufferIndex = 0;
+var sampleRate;
+var audioFrameSize;
+
+function nextAudioBuffer() {
+ if (soundBuffer) playbackBuffer.push(soundBuffer);
+ soundBuffer = new Float32Array(audioFrameSize);
+}
+
+function audioRun(timeSinceLast) {
+ var end = soundBufferIndex + timeSinceLast * sampleRate;
+ for (;;) {
+ var start = Math.floor(soundBufferIndex);
+ var actualLength = Math.floor(end - soundBufferIndex);
+ if (start + actualLength > soundBuffer.length) {
+ actualLength = soundBuffer.length - start;
+ }
+ if (actualLength < 1.0) return;
+ soundChip.render(soundBuffer, start, actualLength);
+ soundBufferIndex += actualLength;
+ if (soundBufferIndex >= soundBuffer.length) {
+ soundBufferIndex -= soundBuffer.length;
+ end -= soundBuffer.length;
+ nextAudioBuffer();
+ }
+ }
+}
+
+function popNextSample() {
+ if (!playbackBuffer.length) return 0;
+ var buffer = playbackBuffer[0];
+ if (playbackIndex >= buffer.length) {
+ playbackBuffer.shift();
+ playbackIndex = 0;
+ return popNextSample();
+ }
+ return buffer[playbackIndex++];
+}
+
+function pumpAudio(event) {
+ var outBuffer = event.outputBuffer;
+ var left = outBuffer.getChannelData(0);
+ var right = outBuffer.getChannelData(1);
+ for (var j = 0; j < left.length; ++j) {
+ left[j] = right[j] = popNextSample();
+ }
+}
+
+function audio_init() {
+ if (typeof(webkitAudioContext) === 'undefined') {
+ // Disable sound without the new APIs.
+ audioRun = function() {};
+ soundChip = new SoundChip(10000);
+ return;
+ }
+ var context = new webkitAudioContext();
+ var jsAudioNode = context.createJavaScriptNode(2048, 0, 1);
+ jsAudioNode.onaudioprocess = pumpAudio;
+ jsAudioNode.connect(context.destination, 0, 0);
+ soundChip = new SoundChip(context.sampleRate);
+ sampleRate = context.sampleRate;
+ audioFrameSize = 1 / 50 * context.sampleRate;
+ nextAudioBuffer();
+}
+
function miracle_init() {
var i;
vdp_init();
+ audio_init();
ram = new Uint8Array(0x2000);
for (i = 0x0000; i < 0x2000; i++) {
ram[i] = 0;
@@ -186,7 +256,7 @@ function writeport(addr, val) {
addr &= 0xff;
switch (addr) {
case 0x7e: case 0x7f:
- // TODO: sound...
+ soundChip.poke(val);
break;
case 0xbd:
case 0xbf:
View
@@ -0,0 +1,119 @@
+function SoundChip(sampleRate) {
+ var register = [ 0, 0, 0, 0 ];
+ var counter = [ 0, 0, 0, 0 ];
+ var outputBit = [ 0, 0, 0, 0 ];
+ var volume = [ 0, 0, 0, 0 ];
+ var generators = [ null, null, null, null ];
+
+ var soundchipFreq = 3546893.0 / 16.0; // PAL
+ var sampleDecrement = soundchipFreq / sampleRate;
+
+ var volumeTable = [];
+ var f = 1.0;
+ for (var i = 0; i < 16; ++i) {
+ volumeTable[i] = f;
+ f *= Math.pow(10, -0.1);
+ }
+ volumeTable[15] = 0;
+ console.log(volumeTable);
+
+ function toneChannel(channel, out, offset, length) {
+ if (register[channel] <= 1) {
+ for (var i = 0; i < length; ++i) {
+ out[i + offset] += volume[channel];
+ }
+ return;
+ }
+ for (var i = 0; i < length; ++i) {
+ counter[channel] -= sampleDecrement;
+ if (counter[channel] < 0) {
+ counter[channel] += register[channel];
+ outputBit[channel] ^= 1;
+ }
+ out[i + offset] += outputBit[channel] ? volume[channel] : -volume[channel];
+ }
+ }
+
+ var lfsr = 0;
+ function shiftLfsrWhiteNoise() {
+ var bit = (lfsr & 1) ^ ((lfsr & (1<<3)) >> 3);
+ lfsr = (lfsr >> 1) | (bit << 15);
+ }
+ function shiftLfsrPeriodicNoise() {
+ lfsr >>= 1;
+ if (lfsr == 0) lfsr = 1<<15;
+ }
+ var shiftLfsr = shiftLfsrWhiteNoise;
+ function noisePoked() {
+ shiftLfsr = register[3] & 4 ? shiftLfsrWhiteNoise : shiftLfsrPeriodicNoise;
+ lfsr = 1<<15;
+ }
+
+ function noiseChannel(channel, out, offset, length) {
+ for (var i = 0; i < length; ++i) {
+ counter[channel] -= sampleDecrement;
+ if (counter[channel] < 0) {
+ switch (register[channel] & 3) {
+ case 0:
+ counter[channel] += 0x10;
+ break;
+ case 1:
+ counter[channel] += 0x20;
+ break;
+ case 2:
+ counter[channel] += 0x40;
+ break;
+ case 3:
+ counter[channel] += register[channel - 1];
+ break;
+ }
+ outputBit[channel] ^= 1;
+ if (outputBit[channel]) {
+ shiftLfsr();
+ }
+ }
+ out[i + offset] += (lfsr & 1) ? volume[channel] : -volume[channel];
+ }
+ }
+
+ function render(out, offset, length) {
+ var i;
+ for (i = 0; i < length; ++i) {
+ out[i + offset] = 0.0;
+ }
+ for (i = 0; i < 4; ++i) {
+ generators[i](i, out, offset, length);
+ }
+ for (i = 0; i < length; ++i) {
+ out[i + offset] *= 1.0/4.0;
+ }
+ }
+
+
+ var latchedChannel = 0;
+ function poke(value) {
+ if ((value & 0x90) == 0x90) {
+ // Volume setting
+ var channel = (value >> 5) & 3;
+ var newVolume = value & 0x0f;
+ volume[channel] = volumeTable[newVolume];
+ } else if ((value & 0x90) == 0x80) {
+ latchedChannel = (value >> 5) & 3;
+ register[latchedChannel] = (register[latchedChannel] & ~0x0f) | value & 0x0f;
+ if (latchedChannel == 3) {
+ noisePoked();
+ }
+ } else {
+ register[latchedChannel] = (register[latchedChannel] & 0x0f) | ((value & 0x3f) << 4);
+ }
+ }
+
+ for (var i = 0; i < 3; ++i) {
+ generators[i] = toneChannel;
+ }
+ generators[3] = noiseChannel;
+
+ this.render = render;
+ this.poke = poke;
+}
+

0 comments on commit 75e7720

Please sign in to comment.