Skip to content

Commit 0e8954a

Browse files
committed
More sound chip tidying
1 parent 6463942 commit 0e8954a

File tree

1 file changed

+67
-70
lines changed

1 file changed

+67
-70
lines changed

src/soundchip.js

Lines changed: 67 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1+
const FloatType = typeof Float64Array !== "undefined" ? Float64Array : Float32Array;
2+
3+
const volumeTable = new FloatType(16);
4+
(() => {
5+
let f = 1.0;
6+
for (let i = 0; i < 15; ++i) {
7+
volumeTable[i] = f / 4; // Bakes in the per channel volume
8+
f *= Math.pow(10, -0.1);
9+
}
10+
volumeTable[15] = 0;
11+
})();
12+
13+
function makeSineTable(attenuation) {
14+
const sineTable = new FloatType(8192);
15+
for (let i = 0; i < sineTable.length; ++i) {
16+
sineTable[i] = Math.sin((2 * Math.PI * i) / sineTable.length) * attenuation;
17+
}
18+
return sineTable;
19+
}
20+
121
export class SoundChip {
222
constructor(sampleRate) {
323
this.cpuFreq = 1 / (2 * 1000 * 1000); // TODO hacky here
424
// 4MHz input signal. Internal divide-by-8
525
this.soundchipFreq = 4000000.0 / 8;
6-
// Square wave changes every time a counter hits zero. Thus a full wave
7-
// needs to be 2x counter zeros.
26+
// Square wave changes every time a counter hits zero: A full wave needs to be 2x counter zeros.
827
this.waveDecrementPerSecond = this.soundchipFreq / 2;
928
// Each sample in the buffer represents (1/sampleRate) time, so each time
1029
// we generate a sample, we need to decrement the counters by this amount:
@@ -13,32 +32,19 @@ export class SoundChip {
1332
this.samplesPerCycle = sampleRate * this.cpuFreq;
1433
this.minCyclesWELow = 14; // Somewhat empirically derived; Repton 2 has only 14 cycles between WE low and WE high (@0x2caa)
1534

16-
this.registers = [0, 0, 0, 0];
17-
this.counter = [0, 0, 0, 0];
35+
this.registers = new Uint16Array(4);
36+
this.counter = new FloatType(4);
1837
this.outputBit = [false, false, false, false];
19-
this.volume = [0, 0, 0, 0];
20-
this.generators = [];
21-
for (let i = 0; i < 3; ++i) {
22-
this.generators[i] = this.toneChannel.bind(this);
23-
}
24-
this.generators[3] = this.noiseChannel.bind(this);
25-
this.generators[4] = this.sineChannel.bind(this);
26-
27-
this.volumeTable = [];
28-
let f = 1.0;
29-
for (let i = 0; i < 16; ++i) {
30-
this.volumeTable[i] = f / this.generators.length; // Bakes in the per channel volume
31-
f *= Math.pow(10, -0.1);
32-
}
33-
this.volumeTable[15] = 0;
34-
35-
const floatType = typeof Float64Array !== "undefined" ? Float64Array : Float32Array;
36-
37-
this.sineTable = new floatType(8192);
38-
39-
for (let i = 0; i < this.sineTable.length; ++i) {
40-
this.sineTable[i] = Math.sin((2 * Math.PI * i) / this.sineTable.length) / this.generators.length;
41-
}
38+
this.volume = new FloatType(4);
39+
this.generators = [
40+
this.toneChannel.bind(this),
41+
this.toneChannel.bind(this),
42+
this.toneChannel.bind(this),
43+
this.noiseChannel.bind(this),
44+
this.sineChannel.bind(this),
45+
];
46+
47+
this.sineTable = makeSineTable(1 / this.generators.length);
4248
this.sineStep = 0;
4349
this.sineOn = false;
4450
this.sineTime = 0;
@@ -53,8 +59,7 @@ export class SoundChip {
5359

5460
this.residual = 0;
5561
this.position = 0;
56-
this.maxBufferSize = 4096;
57-
this.buffer = new floatType(this.maxBufferSize);
62+
this.buffer = new FloatType(4096);
5863

5964
this.latchedRegister = 0;
6065
this.slowDataBus = 0;
@@ -74,27 +79,32 @@ export class SoundChip {
7479
}
7580

7681
sineChannel(channel, out, offset, length) {
77-
if (!this.sineOn) {
78-
return;
79-
}
82+
if (!this.sineOn) return;
83+
8084
for (let i = 0; i < length; ++i) {
8185
out[i + offset] += this.sineTable[this.sineTime & (this.sineTable.length - 1)];
8286
this.sineTime += this.sineStep;
8387
}
8488
while (this.sineTime > this.sineTable.length) this.sineTime -= this.sineTable.length;
8589
}
8690

91+
_doChannelStep(channel, addAmount) {
92+
const newValue = this.counter[channel] - this.sampleDecrement;
93+
if (newValue < 0) {
94+
this.counter[channel] = Math.max(0, newValue + addAmount);
95+
this.outputBit[channel] = !this.outputBit[channel];
96+
return this.outputBit[channel];
97+
} else {
98+
this.counter[channel] = newValue;
99+
return false;
100+
}
101+
}
102+
87103
toneChannel(channel, out, offset, length) {
88-
let reg = this.registers[channel];
104+
const reg = this.registers[channel] === 0 ? 1024 : this.registers[channel];
89105
const vol = this.volume[channel];
90-
if (reg === 0) reg = 1024;
91106
for (let i = 0; i < length; ++i) {
92-
this.counter[channel] -= this.sampleDecrement;
93-
if (this.counter[channel] < 0) {
94-
this.counter[channel] += reg;
95-
if (this.counter[channel] < 0) this.counter[channel] = 0;
96-
this.outputBit[channel] = !this.outputBit[channel];
97-
}
107+
this._doChannelStep(channel, reg);
98108
out[i + offset] += this.outputBit[channel] * vol;
99109
}
100110
}
@@ -133,13 +143,7 @@ export class SoundChip {
133143
const add = this.addFor(channel),
134144
vol = this.volume[channel];
135145
for (let i = 0; i < length; ++i) {
136-
this.counter[channel] -= this.sampleDecrement;
137-
if (this.counter[channel] < 0) {
138-
this.counter[channel] += add;
139-
if (this.counter[channel] < 0) this.counter[channel] = 0;
140-
this.outputBit[channel] = !this.outputBit[channel];
141-
if (this.outputBit[channel]) this.shiftLfsr();
142-
}
146+
if (this._doChannelStep(channel, add)) this.shiftLfsr();
143147
out[i + offset] += (this.lfsr & 1) * vol;
144148
}
145149
}
@@ -150,10 +154,10 @@ export class SoundChip {
150154
this.registers[1] = c1 & 0xffffff;
151155
this.registers[2] = c2 & 0xffffff;
152156
this.registers[3] = c3 & 0xffffff;
153-
this.volume[0] = this.volumeTable[v0];
154-
this.volume[1] = this.volumeTable[v1];
155-
this.volume[2] = this.volumeTable[v2];
156-
this.volume[3] = this.volumeTable[v3];
157+
this.volume[0] = volumeTable[v0];
158+
this.volume[1] = volumeTable[v1];
159+
this.volume[2] = volumeTable[v2];
160+
this.volume[3] = volumeTable[v3];
157161
this.noisePoked();
158162
}
159163

@@ -171,22 +175,16 @@ export class SoundChip {
171175

172176
catchUp() {
173177
const cyclesPending = this.scheduler.epoch - this.lastRunEpoch;
174-
if (cyclesPending > 0) {
175-
this.advance(cyclesPending);
176-
}
178+
if (cyclesPending > 0) this.advance(cyclesPending);
177179
this.lastRunEpoch = this.scheduler.epoch;
178180
}
179181

180182
setScheduler(scheduler_) {
181183
this.scheduler = scheduler_;
182184
this.lastRunEpoch = this.scheduler.epoch;
183-
this.activeTask = this.scheduler.newTask(
184-
function () {
185-
if (this.active) {
186-
this.poke(this.slowDataBus);
187-
}
188-
}.bind(this),
189-
);
185+
this.activeTask = this.scheduler.newTask(() => {
186+
if (this.active) this.poke(this.slowDataBus);
187+
});
190188
}
191189

192190
render(out, offset, length) {
@@ -210,8 +208,8 @@ export class SoundChip {
210208
const num = time * this.samplesPerCycle + this.residual;
211209
let rounded = num | 0;
212210
this.residual = num - rounded;
213-
if (this.position + rounded >= this.maxBufferSize) {
214-
rounded = this.maxBufferSize - this.position;
211+
if (this.position + rounded >= this.buffer.length) {
212+
rounded = this.buffer.length - this.position;
215213
}
216214
if (rounded === 0) return;
217215
this.generate(this.buffer, this.position, rounded);
@@ -233,7 +231,7 @@ export class SoundChip {
233231
if (command & 0x10) {
234232
// Volume setting
235233
const newVolume = value & 0x0f;
236-
this.volume[channel] = this.volumeTable[newVolume];
234+
this.volume[channel] = volumeTable[newVolume];
237235
} else if (channel === 3) {
238236
// For noise channel we always update the bottom bits.
239237
this.registers[channel] = value & 0x0f;
@@ -250,11 +248,9 @@ export class SoundChip {
250248
updateSlowDataBus(slowDataBus, active) {
251249
this.slowDataBus = slowDataBus;
252250
this.active = active;
253-
// TODO: this probably isn't modeled correctly. Currently the
254-
// sound chip "notices" a new data bus value some fixed number of
255-
// cycles after WE (write enable) is triggered.
256-
// In reality, the sound chip likely pulls data off the bus at a
257-
// fixed point in its cycle, iff WE is active.
251+
// TODO: this probably isn't modeled correctly. Currently the sound chip "notices" a new data bus value some
252+
// fixed number of cycles after WE (write enable) is triggered. In reality, the sound chip likely pulls data off
253+
// the bus at a fixed point in its cycle, iff WE is active.
258254
if (active) {
259255
this.activeTask.ensureScheduled(true, this.minCyclesWELow);
260256
}
@@ -265,7 +261,8 @@ export class SoundChip {
265261
for (let i = 0; i < 4; ++i) {
266262
this.counter[i] = 0;
267263
this.registers[i] = 0;
268-
this.volume[i] = this.volumeTable[8]; // Real hardware would be volumeTable[0] but that's really quite loud and surprising...
264+
// Real hardware would be volumeTable[0] but that's really quite loud and surprising...
265+
this.volume[i] = volumeTable[8];
269266
}
270267
this.noisePoked();
271268
this.advance(100000);

0 commit comments

Comments
 (0)