working on z80 sound worker

This commit is contained in:
Steven Hugg 2017-04-02 14:54:51 -04:00
parent 94bf30fa7e
commit e4e59d6c0d
11 changed files with 707 additions and 270 deletions

View File

@ -214,6 +214,7 @@ div.bitmap_editor {
<li><a class="dropdown-item" href="?platform=galaxian-scramble" id="item_platform_galaxian_scramble">Galaxian/Scramble</a></li>
<li><a class="dropdown-item" href="?platform=vector-z80color" id="item_platform_vector_z80color">Atari Color Vector (Z80)</a></li>
<li><a class="dropdown-item" href="?platform=williams-z80" id="item_platform_williams_z80">Williams (Z80)</a></li>
<li><a class="dropdown-item" href="?platform=sound_williams-z80" id="item_platform_sound_williams_z80">Williams Sound (Z80)</a></li>
</ul>
</li>
<li class="dropdown dropdown-submenu">
@ -315,6 +316,7 @@ Dest: $<span id="bitmap_editor_dest">0000/0</span>
<script src="octokat.js/dist/octokat.js"></script>
<script src="src/emu.js"></script>
<script src="src/audio.js"></script>
<script src="src/util.js"></script>
<script src="src/disasm.js"></script>
<script src="src/ui.js"></script>

View File

@ -0,0 +1,17 @@
const __sfr __at (0x0) command;
__sfr __at (0x0) dac;
#define HALT __asm halt __endasm;
// press "2" or higher to activate...
void main() {
char i;
char j;
if (command == 0) HALT;
for (i=0; i<255; i++) {
for (j=0; j<i; j++) dac=j^i;
}
HALT;
}

View File

@ -0,0 +1,17 @@
const __sfr __at (0x0) command;
__sfr __at (0x0) dac;
#define HALT __asm halt __endasm;
// press "2" or higher to activate...
void main() {
char i;
char j;
if (command == 0) HALT;
for (i=0; i<255; i++) {
for (j=0; j<i; j++) dac=j^i;
}
HALT;
}

File diff suppressed because one or more lines are too long

306
src/audio.js Normal file
View File

@ -0,0 +1,306 @@
"use strict";
var MasterAudio = function() {
this.master = new MasterChannel();
this.start = function() {
this.looper = new AudioLooper(512);
this.looper.setChannel(this.master);
this.looper.activate();
}
this.stop = function() {
this.looper.setChannel(null);
}
}
var AY38910_Audio = function(master) {
this.psg = new PsgDeviceChannel();
this.psg.setMode(PsgDeviceChannel.MODE_SIGNED);
this.psg.setDevice(PsgDeviceChannel.DEVICE_AY_3_8910);
master.master.addChannel(this.psg);
var curreg = 0;
this.reset = function() {
for (var i=15; i>=0; i--) {
this.selectRegister(i);
this.setData(0);
}
}
this.selectRegister = function(val) {
curreg = val & 0xf;
}
this.setData = function(val) {
this.psg.writeRegisterAY(curreg, val & 0xff);
}
}
// https://en.wikipedia.org/wiki/POKEY
// https://user.xmission.com/~trevin/atari/pokey_regs.html
// http://krap.pl/mirrorz/atari/homepage.ntlworld.com/kryten_droid/Atari/800XL/atari_hw/pokey.htm
var POKEYDeviceChannel = function() {
/* definitions for AUDCx (D201, D203, D205, D207) */
var NOTPOLY5 = 0x80 /* selects POLY5 or direct CLOCK */
var POLY4 = 0x40 /* selects POLY4 or POLY17 */
var PURE = 0x20 /* selects POLY4/17 or PURE tone */
var VOL_ONLY = 0x10 /* selects VOLUME OUTPUT ONLY */
var VOLUME_MASK = 0x0f /* volume mask */
/* definitions for AUDCTL (D208) */
var POLY9 = 0x80 /* selects POLY9 or POLY17 */
var CH1_179 = 0x40 /* selects 1.78979 MHz for Ch 1 */
var CH3_179 = 0x20 /* selects 1.78979 MHz for Ch 3 */
var CH1_CH2 = 0x10 /* clocks channel 1 w/channel 2 */
var CH3_CH4 = 0x08 /* clocks channel 3 w/channel 4 */
var CH1_FILTER = 0x04 /* selects channel 1 high pass filter */
var CH2_FILTER = 0x02 /* selects channel 2 high pass filter */
var CLOCK_15 = 0x01 /* selects 15.6999kHz or 63.9210kHz */
/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of
the 1.79MHz clock */
var DIV_64 = 28 /* divisor for 1.79MHz clock to 64 kHz */
var DIV_15 = 114 /* divisor for 1.79MHz clock to 15 kHz */
/* the size (in entries) of the 4 polynomial tables */
var POLY4_SIZE = 0x000f
var POLY5_SIZE = 0x001f
var POLY9_SIZE = 0x01ff
var POLY17_SIZE = 0x0001ffff /* else use the full 17 bits */
/* channel/chip definitions */
var CHAN1 = 0
var CHAN2 = 1
var CHAN3 = 2
var CHAN4 = 3
var CHIP1 = 0
var CHIP2 = 4
var CHIP3 = 8
var CHIP4 = 12
var SAMPLE = 127
var FREQ_17_EXACT = 1789790.0 /* exact 1.79 MHz clock freq */
var FREQ_17_APPROX = 1787520.0 /* approximate 1.79 MHz clock freq */
// LFSR sequences
var bit1 = [ 0,1 ];
var bit4 = [ 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 ];
var bit5 = [ 0,0,1,1,0,0,0,1,1,1,1,0,0,1,0,1,0,1,1,0,1,1,1,0,1,0,0,0,0,0,1 ];
var bit17 = new Uint8Array(1<<17);
var bit17_5 = new Uint8Array(1<<17);
var bit5_4 = new Uint8Array(1<<17);
for (var i=0; i<bit17.length; i++) {
bit17[i] = Math.random() > 0.5;
bit17_5[i] = bit17[i] & bit5[i % bit5.length];
bit5_4[i] = bit5[i % bit5.length] & bit4[i % bit4.length];
}
var wavetones = [
bit17_5, bit5, bit5_4, bit5,
bit17, bit1, bit4, bit1
];
// registers
var regs = new Uint8Array(16);
var counters = new Float32Array(4);
var deltas = new Float32Array(4);
var volume = new Float32Array(4);
var audc = new Uint8Array(4);
var waveforms = [bit1, bit1, bit1, bit1];
var buffer;
var sampleRate;
var clock, baseDelta;
var dirty = true;
//
this.setBufferLength = function (length) {
buffer = new Int32Array(length);
};
this.getBuffer = function () {
return buffer;
};
this.setSampleRate = function (rate) {
sampleRate = rate;
baseDelta = FREQ_17_EXACT / rate / 1.2; // TODO?
};
function updateValues(addr) {
var ctrl = regs[8];
var base = (ctrl & CLOCK_15) ? DIV_15 : DIV_64;
var div;
var i = addr & 4;
var j = i>>1;
var k = i>>2;
if (ctrl & (CH1_CH2>>k)) {
if (ctrl & (CH1_179>>k))
div = regs[i+2] * 256 + regs[i+0] + 7;
else
div = (regs[i+2] * 256 + regs[i+0] + 1) * base;
deltas[j+1] = baseDelta / div;
deltas[j+0] = 0;
} else {
if (ctrl & (CH1_179>>k)) {
div = regs[i+0] + 4;
} else {
div = (regs[i+0] + 1) * base;
}
deltas[j+0] = baseDelta / div;
div = (regs[i+2] + 1) * base;
deltas[j+1] = baseDelta / div;
}
//console.log(addr, ctrl.toString(16), div, deltas[j+0], deltas[j+1]);
}
this.setRegister = function(addr, value) {
addr &= 0xf;
value &= 0xff;
if (regs[addr] != value) {
regs[addr] = value;
switch (addr) {
case 0:
case 2:
case 4:
case 6: // AUDF
case 8: // ctrl
dirty = true;
break;
case 1:
case 3:
case 5:
case 7: // AUDC
volume[addr>>1] = value & 0xf;
waveforms[addr>>1] = wavetones[value>>5];
break;
}
}
}
this.generate = function (length) {
if (dirty) {
updateValues(0);
updateValues(4);
dirty = false;
}
for (var s=0; s<length; s+=2) {
var sample = 0;
for (var i=0; i<4; i++) {
var d = deltas[i];
var v = volume[i];
if (d > 0 && d < 1 && v > 0) {
var wav = waveforms[i];
var cnt = counters[i] += d;
if (cnt > POLY17_SIZE+1) {
counters[i] -= POLY17_SIZE+1;
}
var on = wav[Math.floor(cnt % wav.length)];
if (on) {
sample += v;
}
}
}
sample *= 273;
buffer[s] = sample;
buffer[s+1] = sample;
}
}
}
////// CPU sound (unused)
var CPUSoundChannel = function(cpu, clockRate) {
var sampleRate;
var buffer;
var lastbufpos=0;
var curSample=0;
var clocksPerSample;
this.setBufferLength = function (length) {
buffer = new Int32Array(length);
};
this.getBuffer = function () {
return buffer;
};
this.setSampleRate = function (rate) {
sampleRate = rate;
};
this.getSetDACFunction = function() {
return function(a,v) {
var bufpos = Math.floor(cpu.getTstates() / clocksPerSample);
while (lastbufpos < bufpos)
buffer[lastbufpos++] = curSample;
lastbufpos = bufpos;
curSample = v;
};
};
this.generate = function (length) {
clocksPerSample = clockRate * 1.0 / sampleRate;
var clocks = Math.round(length * clocksPerSample);
if (cpu.getTstates && cpu.runFrame) {
cpu.setTstates(0);
lastbufpos = 0;
cpu.runFrame(cpu.getTstates() + totalClocks);
while (lastbufpos < length)
buffer[lastbufpos++] = curSample;
}
};
}
////// Worker sound
var WorkerSoundChannel = function(worker) {
var sampleRate;
var output;
var pending = [];
var pendingLength = 0;
worker.onmessage = function(e) {
if (e && e.data && e.data.samples && output) {
pending.push(e.data.samples);
pendingLength += e.data.samples.length;
}
};
this.setBufferLength = function (length) {
output = new Int16Array(length);
//worker.postMessage({bufferLength:length,numChannels:2});
pendingLength = 0;
};
this.getBuffer = function () {
return output;
};
this.setSampleRate = function (rate) {
sampleRate = rate;
worker.postMessage({sampleRate:rate});
};
this.generate = function (length) {
if (pendingLength < length*3) {
//console.log(length, pendingLength);
output.fill(0);
return; // TODO: send sync msg?
}
for (var i=0; i<output.length;) {
if (pending.length == 0) break; // TODO?
var buf = pending.shift();
pendingLength -= buf.length;
var l = output.length-i;
if (buf.length < l) {
output.set(buf, i);
} else {
output.set(buf.slice(0, l), i);
pending.unshift(buf.slice(l));
pendingLength += buf.length-l;
}
i += buf.length;
}
}
}

View File

@ -0,0 +1,4 @@
<script>
var worker = new Worker("./z80worker.js");
worker.postMessage({});
</script>

163
src/audio/z80worker.js Normal file
View File

@ -0,0 +1,163 @@
"use strict";
/****************************************************************************
Midway/Williams Audio Boards
----------------------------
6809 MEMORY MAP
Function Address R/W Data
---------------------------------------------------------------
Program RAM 0000-07FF R/W D0-D7
Music (YM-2151) 2000-2001 R/W D0-D7
6821 PIA 4000-4003 R/W D0-D7
HC55516 clock low, digit latch 6000 W D0
HC55516 clock high 6800 W xx
Bank select 7800 W D0-D2
Banked Program ROM 8000-FFFF R D0-D7
****************************************************************************/
var window = {};
importScripts("../emu.js");
importScripts("../cpu/z80.js");
window.buildZ80({
applyContention: false
});
var cpu, ram, rom, membus, iobus;
var audio;
var command = 0;
var dac = 0;
var current_buffer;
var last_tstate;
var timer;
var curTime;
var timerPeriod = 20;
var sampleRate;
var numChannels = 2;
var bufferLength;
var cpuFrequency = 18432000/6; // 3.072 MHz
var cpuCyclesPerFrame = cpuFrequency/60;
var cpuAudioFactor = 32;
function fillBuffer() {
var t = cpu.getTstates() / cpuAudioFactor;
while (last_tstate < t) {
current_buffer[last_tstate*2] = dac;
current_buffer[last_tstate*2+1] = dac;
last_tstate++;
}
}
function start() {
ram = new RAM(0x400);
rom = [0xe,0x0,0x6,0x0,0x78,0xb9,0x30,0x06,0xa9,0xd3,0x00,0x04,0x18,0xf6,0x0c,0x79,0xd6,0xff,0x38,0xee,0x76,0x18,0xea]; // TODO
membus = {
read: new AddressDecoder([
[0x0000, 0x3fff, 0x3fff, function(a) { return rom ? rom[a] : null; }],
[0x4000, 0x5fff, 0xfff, function(a) { return ram.mem[a]; }]
]),
write: new AddressDecoder([
[0x4000, 0x5fff, 0xfff, function(a,v) { ram.mem[a] = v; }],
]),
isContended: function() { return false; },
};
iobus = {
read: function(addr) {
return command & 0xff;
},
write: function(addr, val) {
dac = (val & 0xff) << 8;
fillBuffer();
}
};
cpu = window.Z80({
display: {},
memory: membus,
ioBus: iobus
});
current_buffer = new Int16Array(bufferLength);
}
function timerCallback() {
cpu.setTstates(0);
last_tstate = 0;
var numStates = Math.floor(bufferLength * cpuAudioFactor / numChannels);
cpu.runFrame(numStates);
//console.log(numStates, cpu.getTstates());
cpu.setTstates(numStates); // TODO?
fillBuffer();
postMessage({samples:current_buffer});
if (!cpu.getHalted()) {
curTime += timerPeriod;
var dt = curTime - new Date().getTime();
if (dt < 0) dt = 0;
timer = setTimeout(timerCallback, dt);
} else {
timer = 0;
}
}
function reset() {
if (!bufferLength) return;
cpu.reset();
if (!timer) {
curTime = new Date().getTime() - timerPeriod*4;
timerCallback();
}
}
onmessage = function(e) {
if (e && e.data) {
if (e.data.command) {
command = e.data.command & 0xff;
reset();
} else if (e.data.sampleRate) {
console.log(e.data);
sampleRate = e.data.sampleRate;
bufferLength = numChannels*sampleRate*timerPeriod/1000;
start();
reset();
}
}
}
/**
0000 56 _main::
57 ;<stdin>:10:
0000 0E 00 [ 7] 58 ld c,#0x00
59 ;<stdin>:11:
0002 60 00111$:
0002 06 00 [ 7] 61 ld b,#0x00
0004 62 00104$:
0004 78 [ 4] 63 ld a,b
0005 B9 [ 4] 64 cp a,c
0006 30 06 [12] 65 jr NC,00107$
0008 A9 [ 4] 66 xor a, c
0009 D3 00 [11] 67 out (_dac),a
000B 04 [ 4] 68 inc b
000C 18 F6 [12] 69 jr 00104$
000E 70 00107$:
71 ;<stdin>:10:
000E 0C [ 4] 72 inc c
000F 79 [ 4] 73 ld a,c
0010 D6 FF [ 7] 74 sub a, #0xff
0012 38 EE [12] 75 jr C,00111$
76 ;<stdin>:13:
0014 18 EA [12] 77 jr _main
**/

View File

@ -248,15 +248,19 @@ var SampleAudio = function(clockfreq) {
var m = this.module;
if (!m) m = ape.srcElement.module;
if (!m) return;
var buf = bufferlist[1];
for (var i=0; i<lbuf.length; i++) {
lbuf[i] = buf[i];
if (m.callback) {
m.callback(lbuf);
return;
} else {
var buf = bufferlist[1];
for (var i=0; i<lbuf.length; i++) {
lbuf[i] = buf[i];
}
}
}
function createContext() {
var AudioContext = AudioContext || webkitAudioContext || mozAudioContext;
var AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
if (! AudioContext) {
console.log("no web audio context");
return;
@ -355,257 +359,6 @@ function cpuStateToLongString_6502(c) {
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
}
var MasterAudio = function() {
this.master = new MasterChannel();
this.start = function() {
this.looper = new AudioLooper(256);
this.looper.setChannel(this.master);
this.looper.activate();
}
this.stop = function() {
this.looper.setChannel(null);
}
}
var AY38910_Audio = function(master) {
this.psg = new PsgDeviceChannel();
this.psg.setMode(PsgDeviceChannel.MODE_SIGNED);
this.psg.setDevice(PsgDeviceChannel.DEVICE_AY_3_8910);
master.master.addChannel(this.psg);
var curreg = 0;
this.reset = function() {
for (var i=15; i>=0; i--) {
this.selectRegister(i);
this.setData(0);
}
}
this.selectRegister = function(val) {
curreg = val & 0xf;
}
this.setData = function(val) {
this.psg.writeRegisterAY(curreg, val & 0xff);
}
}
// https://en.wikipedia.org/wiki/POKEY
// https://user.xmission.com/~trevin/atari/pokey_regs.html
// http://krap.pl/mirrorz/atari/homepage.ntlworld.com/kryten_droid/Atari/800XL/atari_hw/pokey.htm
var POKEYDeviceChannel = function() {
/* definitions for AUDCx (D201, D203, D205, D207) */
var NOTPOLY5 = 0x80 /* selects POLY5 or direct CLOCK */
var POLY4 = 0x40 /* selects POLY4 or POLY17 */
var PURE = 0x20 /* selects POLY4/17 or PURE tone */
var VOL_ONLY = 0x10 /* selects VOLUME OUTPUT ONLY */
var VOLUME_MASK = 0x0f /* volume mask */
/* definitions for AUDCTL (D208) */
var POLY9 = 0x80 /* selects POLY9 or POLY17 */
var CH1_179 = 0x40 /* selects 1.78979 MHz for Ch 1 */
var CH3_179 = 0x20 /* selects 1.78979 MHz for Ch 3 */
var CH1_CH2 = 0x10 /* clocks channel 1 w/channel 2 */
var CH3_CH4 = 0x08 /* clocks channel 3 w/channel 4 */
var CH1_FILTER = 0x04 /* selects channel 1 high pass filter */
var CH2_FILTER = 0x02 /* selects channel 2 high pass filter */
var CLOCK_15 = 0x01 /* selects 15.6999kHz or 63.9210kHz */
/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of
the 1.79MHz clock */
var DIV_64 = 28 /* divisor for 1.79MHz clock to 64 kHz */
var DIV_15 = 114 /* divisor for 1.79MHz clock to 15 kHz */
/* the size (in entries) of the 4 polynomial tables */
var POLY4_SIZE = 0x000f
var POLY5_SIZE = 0x001f
var POLY9_SIZE = 0x01ff
var POLY17_SIZE = 0x0001ffff /* else use the full 17 bits */
/* channel/chip definitions */
var CHAN1 = 0
var CHAN2 = 1
var CHAN3 = 2
var CHAN4 = 3
var CHIP1 = 0
var CHIP2 = 4
var CHIP3 = 8
var CHIP4 = 12
var SAMPLE = 127
var FREQ_17_EXACT = 1789790.0 /* exact 1.79 MHz clock freq */
var FREQ_17_APPROX = 1787520.0 /* approximate 1.79 MHz clock freq */
// LFSR sequences
var bit1 = [ 0,1 ];
var bit4 = [ 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 ];
var bit5 = [ 0,0,1,1,0,0,0,1,1,1,1,0,0,1,0,1,0,1,1,0,1,1,1,0,1,0,0,0,0,0,1 ];
var bit17 = new Uint8Array(1<<17);
var bit17_5 = new Uint8Array(1<<17);
var bit5_4 = new Uint8Array(1<<17);
for (var i=0; i<bit17.length; i++) {
bit17[i] = Math.random() > 0.5;
bit17_5[i] = bit17[i] & bit5[i % bit5.length];
bit5_4[i] = bit5[i % bit5.length] & bit4[i % bit4.length];
}
var wavetones = [
bit17_5, bit5, bit5_4, bit5,
bit17, bit1, bit4, bit1
];
// registers
var regs = new Uint8Array(16);
var counters = new Float32Array(4);
var deltas = new Float32Array(4);
var volume = new Float32Array(4);
var audc = new Uint8Array(4);
var waveforms = [bit1, bit1, bit1, bit1];
var buffer;
var sampleRate = 44100;
var clock, baseDelta;
var dirty = true;
//
this.setBufferLength = function (length) {
buffer = new Int32Array(length);
};
this.getBuffer = function () {
return buffer;
};
this.setSampleRate = function (rate) {
sampleRate = rate;
baseDelta = FREQ_17_EXACT / rate / 1.2; // TODO?
};
function updateValues(addr) {
var ctrl = regs[8];
var base = (ctrl & CLOCK_15) ? DIV_15 : DIV_64;
var div;
var i = addr & 4;
var j = i>>1;
var k = i>>2;
if (ctrl & (CH1_CH2>>k)) {
if (ctrl & (CH1_179>>k))
div = regs[i+2] * 256 + regs[i+0] + 7;
else
div = (regs[i+2] * 256 + regs[i+0] + 1) * base;
deltas[j+1] = baseDelta / div;
deltas[j+0] = 0;
} else {
if (ctrl & (CH1_179>>k)) {
div = regs[i+0] + 4;
} else {
div = (regs[i+0] + 1) * base;
}
deltas[j+0] = baseDelta / div;
div = (regs[i+2] + 1) * base;
deltas[j+1] = baseDelta / div;
}
//console.log(addr, ctrl.toString(16), div, deltas[j+0], deltas[j+1]);
}
this.setRegister = function(addr, value) {
addr &= 0xf;
value &= 0xff;
if (regs[addr] != value) {
regs[addr] = value;
switch (addr) {
case 0:
case 2:
case 4:
case 6: // AUDF
case 8: // ctrl
dirty = true;
break;
case 1:
case 3:
case 5:
case 7: // AUDC
volume[addr>>1] = value & 0xf;
waveforms[addr>>1] = wavetones[value>>5];
break;
}
}
}
this.generate = function (length) {
if (dirty) {
updateValues(0);
updateValues(4);
dirty = false;
}
for (var s=0; s<length; s+=2) {
var sample = 0;
for (var i=0; i<4; i++) {
var d = deltas[i];
var v = volume[i];
if (d > 0 && d < 1 && v > 0) {
var wav = waveforms[i];
var cnt = counters[i] += d;
if (cnt > POLY17_SIZE+1) {
counters[i] -= POLY17_SIZE+1;
}
var on = wav[Math.floor(cnt % wav.length)];
if (on) {
sample += v;
}
}
}
sample *= 273;
buffer[s] = sample;
buffer[s+1] = sample;
}
}
}
////// CPU sound
var CPUSoundChannel = function(cpu, clockRate) {
var sampleRate;
var buffer;
var lastbufpos=0;
var curSample=0;
var clocksPerSample;
this.setBufferLength = function (length) {
buffer = new Int32Array(length);
};
this.getBuffer = function () {
return buffer;
};
this.setSampleRate = function (rate) {
sampleRate = rate;
};
this.getSetDACFunction = function() {
return function(a,v) {
var bufpos = Math.floor(cpu.getTstates() / clocksPerSample);
while (lastbufpos < bufpos)
buffer[lastbufpos++] = curSample;
lastbufpos = bufpos;
curSample = v;
};
};
this.generate = function (length) {
clocksPerSample = clockRate * 1.0 / sampleRate;
var clocks = Math.round(length * clocksPerSample);
if (cpu.getTstates && cpu.runFrame) {
cpu.setTstates(0);
lastbufpos = 0;
cpu.runFrame(cpu.getTstates() + totalClocks);
while (lastbufpos < length)
buffer[lastbufpos++] = curSample;
}
};
}
////// 6502
var Base6502Platform = function() {

View File

@ -0,0 +1,159 @@
"use strict";
var WILLIAMS_SOUND_PRESETS = [
{id:'minimal.c', name:'Minimal Test'},
];
/****************************************************************************
Midway/Williams Audio Boards
----------------------------
6809 MEMORY MAP
Function Address R/W Data
---------------------------------------------------------------
Program RAM 0000-07FF R/W D0-D7
Music (YM-2151) 2000-2001 R/W D0-D7
6821 PIA 4000-4003 R/W D0-D7
HC55516 clock low, digit latch 6000 W D0
HC55516 clock high 6800 W xx
Bank select 7800 W D0-D2
Banked Program ROM 8000-FFFF R D0-D7
****************************************************************************/
var WilliamsSoundPlatform = function(mainElement) {
var self = this;
this.__proto__ = new BaseZ80Platform();
var cpu, ram, rom, membus, iobus;
var audio, master;
var video, timer;
var command = 0;
var dac = 0;
var dac_float = 0.0
var current_buffer;
var last_tstate;
var pixels;
var cpuFrequency = 18432000/6; // 3.072 MHz
var cpuCyclesPerFrame = cpuFrequency/60;
var cpuAudioFactor = 32;
function fillBuffer() {
var t = cpu.getTstates() / cpuAudioFactor;
while (last_tstate < t) {
current_buffer[last_tstate++] = dac_float;
}
}
this.getPresets = function() {
return WILLIAMS_SOUND_PRESETS;
}
this.start = function() {
ram = new RAM(0x400);
membus = {
read: new AddressDecoder([
[0x0000, 0x3fff, 0x3fff, function(a) { return rom ? rom[a] : null; }],
[0x4000, 0x5fff, 0xfff, function(a) { return ram.mem[a]; }]
]),
write: new AddressDecoder([
[0x4000, 0x5fff, 0xfff, function(a,v) { ram.mem[a] = v; }],
]),
isContended: function() { return false; },
};
iobus = {
read: function(addr) {
return command & 0xff;
},
write: function(addr, val) {
dac = val & 0xff;
dac_float = ((dac & 0x80) ? -256+dac : dac) / 128.0;
fillBuffer();
}
};
cpu = window.Z80({
display: {},
memory: membus,
ioBus: iobus
});
audio = new SampleAudio(cpuFrequency / cpuAudioFactor);
audio.callback = function(lbuf) {
if (self.isRunning()) {
cpu.setTstates(0);
current_buffer = lbuf;
last_tstate = 0;
self.runCPU(cpu, lbuf.length * cpuAudioFactor);
cpu.setTstates(lbuf.length * cpuAudioFactor); // TODO?
fillBuffer();
for (var i=0; i<256; i++) {
var y = Math.round((current_buffer[i] * 127) + 128);
pixels[i + y*256] = 0xff33ff33;
}
}
};
video = new RasterVideo(mainElement,256,256);
video.create();
video.setKeyboardEvents(function(key,code,flags) {
var intr = (key-49);
if (intr >= 0 && (flags & 1)) {
command = intr & 0xff;
cpu.reset();
}
});
pixels = video.getFrameData();
timer = new AnimationTimer(30, function() {
if (self.isRunning()) {
video.updateFrame();
pixels.fill(0);
}
});
}
this.loadROM = function(title, data) {
rom = padBytes(data, 0x4000);
cpu.reset();
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
}
this.saveState = function() {
return {
c:self.getCPUState(),
b:ram.mem.slice(0),
};
}
this.getCPUState = function() {
return cpu.saveState();
}
this.isRunning = function() {
return timer.isRunning();
}
this.pause = function() {
timer.stop();
audio.stop();
}
this.resume = function() {
timer.start();
audio.start();
}
this.reset = function() {
cpu.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
this.readAddress = function(addr) {
return membus.read(addr); // TODO?
}
}
PLATFORMS['sound_williams-z80'] = WilliamsSoundPlatform;

View File

@ -25,6 +25,8 @@ var WilliamsPlatform = function(mainElement, proto) {
var membus, iobus;
var video_counter;
var audio, worker, workerchannel;
var xtal = 12000000;
var cpuFrequency = xtal/3/4;
var cpuCyclesPerFrame = cpuFrequency/60; // TODO
@ -126,8 +128,9 @@ var WilliamsPlatform = function(mainElement, proto) {
var iowrite_williams = new AddressDecoder([
[0x0, 0xf, 0xf, setPalette],
[0x804, 0x807, 0x3, function(a,v) { console.log('iowrite',a); }], // TODO: sound
[0x80c, 0x80f, 0x3, function(a,v) { console.log('iowrite',a+4); }], // TODO: sound
[0x803, 0x803, 0xf, function(a,v) { if (worker) worker.postMessage({command:v}); }],
//[0x804, 0x807, 0x3, function(a,v) { console.log('iowrite',a); }], // TODO: sound
//[0x80c, 0x80f, 0x3, function(a,v) { console.log('iowrite',a+4); }], // TODO: sound
[0x900, 0x9ff, 0, function(a,v) { banksel = v & 0x1; }],
[0xa00, 0xa07, 0x7, setBlitter],
[0xbff, 0xbff, 0, function(a,v) { if (v == 0x39) watchdog_counter = INITIAL_WATCHDOG; }],
@ -275,6 +278,12 @@ var WilliamsPlatform = function(mainElement, proto) {
write: memwrite_williams,
};
cpu = self.newCPU(membus);
audio = new MasterAudio();
worker = new Worker("./src/audio/z80worker.js");
workerchannel = new WorkerSoundChannel(worker);
audio.master.addChannel(workerchannel);
video = new RasterVideo(mainElement, SCREEN_WIDTH, SCREEN_HEIGHT, {rotate:-90});
video.create();
$(video.canvas).click(function(e) {
@ -354,9 +363,11 @@ var WilliamsPlatform = function(mainElement, proto) {
}
this.pause = function() {
timer.stop();
audio.stop();
}
this.resume = function() {
timer.start();
audio.start();
}
this.reset = function() {
cpu.reset();

View File

@ -37,6 +37,12 @@ var PLATFORM_PARAMS = {
data_start: 0xe000,
data_size: 0x2000,
},
'sound_williams-z80': {
code_start: 0x0,
code_size: 0x4000,
data_start: 0x4000,
data_size: 0x1000,
},
};
var loaded = {}