mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-21 07:33:14 +00:00
working on z80 sound worker
This commit is contained in:
parent
94bf30fa7e
commit
e4e59d6c0d
@ -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>
|
||||
|
17
presets/sound_williams-z80/minimal.c
Normal file
17
presets/sound_williams-z80/minimal.c
Normal 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;
|
||||
}
|
17
presets/sound_williams-z80/skeleton.sdcc
Normal file
17
presets/sound_williams-z80/skeleton.sdcc
Normal 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
306
src/audio.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
4
src/audio/testz80worker.html
Normal file
4
src/audio/testz80worker.html
Normal file
@ -0,0 +1,4 @@
|
||||
<script>
|
||||
var worker = new Worker("./z80worker.js");
|
||||
worker.postMessage({});
|
||||
</script>
|
163
src/audio/z80worker.js
Normal file
163
src/audio/z80worker.js
Normal 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
|
||||
|
||||
**/
|
265
src/emu.js
265
src/emu.js
@ -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() {
|
||||
|
159
src/platform/sound_williams.js
Normal file
159
src/platform/sound_williams.js
Normal 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;
|
@ -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();
|
||||
|
@ -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 = {}
|
||||
|
Loading…
Reference in New Issue
Block a user