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
+
1
21
export class SoundChip {
2
22
constructor ( sampleRate ) {
3
23
this . cpuFreq = 1 / ( 2 * 1000 * 1000 ) ; // TODO hacky here
4
24
// 4MHz input signal. Internal divide-by-8
5
25
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.
8
27
this . waveDecrementPerSecond = this . soundchipFreq / 2 ;
9
28
// Each sample in the buffer represents (1/sampleRate) time, so each time
10
29
// we generate a sample, we need to decrement the counters by this amount:
@@ -13,32 +32,19 @@ export class SoundChip {
13
32
this . samplesPerCycle = sampleRate * this . cpuFreq ;
14
33
this . minCyclesWELow = 14 ; // Somewhat empirically derived; Repton 2 has only 14 cycles between WE low and WE high (@0x2caa)
15
34
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 ) ;
18
37
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 ) ;
42
48
this . sineStep = 0 ;
43
49
this . sineOn = false ;
44
50
this . sineTime = 0 ;
@@ -53,8 +59,7 @@ export class SoundChip {
53
59
54
60
this . residual = 0 ;
55
61
this . position = 0 ;
56
- this . maxBufferSize = 4096 ;
57
- this . buffer = new floatType ( this . maxBufferSize ) ;
62
+ this . buffer = new FloatType ( 4096 ) ;
58
63
59
64
this . latchedRegister = 0 ;
60
65
this . slowDataBus = 0 ;
@@ -74,27 +79,32 @@ export class SoundChip {
74
79
}
75
80
76
81
sineChannel ( channel , out , offset , length ) {
77
- if ( ! this . sineOn ) {
78
- return ;
79
- }
82
+ if ( ! this . sineOn ) return ;
83
+
80
84
for ( let i = 0 ; i < length ; ++ i ) {
81
85
out [ i + offset ] += this . sineTable [ this . sineTime & ( this . sineTable . length - 1 ) ] ;
82
86
this . sineTime += this . sineStep ;
83
87
}
84
88
while ( this . sineTime > this . sineTable . length ) this . sineTime -= this . sineTable . length ;
85
89
}
86
90
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
+
87
103
toneChannel ( channel , out , offset , length ) {
88
- let reg = this . registers [ channel ] ;
104
+ const reg = this . registers [ channel ] === 0 ? 1024 : this . registers [ channel ] ;
89
105
const vol = this . volume [ channel ] ;
90
- if ( reg === 0 ) reg = 1024 ;
91
106
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 ) ;
98
108
out [ i + offset ] += this . outputBit [ channel ] * vol ;
99
109
}
100
110
}
@@ -133,13 +143,7 @@ export class SoundChip {
133
143
const add = this . addFor ( channel ) ,
134
144
vol = this . volume [ channel ] ;
135
145
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 ( ) ;
143
147
out [ i + offset ] += ( this . lfsr & 1 ) * vol ;
144
148
}
145
149
}
@@ -150,10 +154,10 @@ export class SoundChip {
150
154
this . registers [ 1 ] = c1 & 0xffffff ;
151
155
this . registers [ 2 ] = c2 & 0xffffff ;
152
156
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 ] ;
157
161
this . noisePoked ( ) ;
158
162
}
159
163
@@ -171,22 +175,16 @@ export class SoundChip {
171
175
172
176
catchUp ( ) {
173
177
const cyclesPending = this . scheduler . epoch - this . lastRunEpoch ;
174
- if ( cyclesPending > 0 ) {
175
- this . advance ( cyclesPending ) ;
176
- }
178
+ if ( cyclesPending > 0 ) this . advance ( cyclesPending ) ;
177
179
this . lastRunEpoch = this . scheduler . epoch ;
178
180
}
179
181
180
182
setScheduler ( scheduler_ ) {
181
183
this . scheduler = scheduler_ ;
182
184
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
+ } ) ;
190
188
}
191
189
192
190
render ( out , offset , length ) {
@@ -210,8 +208,8 @@ export class SoundChip {
210
208
const num = time * this . samplesPerCycle + this . residual ;
211
209
let rounded = num | 0 ;
212
210
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 ;
215
213
}
216
214
if ( rounded === 0 ) return ;
217
215
this . generate ( this . buffer , this . position , rounded ) ;
@@ -233,7 +231,7 @@ export class SoundChip {
233
231
if ( command & 0x10 ) {
234
232
// Volume setting
235
233
const newVolume = value & 0x0f ;
236
- this . volume [ channel ] = this . volumeTable [ newVolume ] ;
234
+ this . volume [ channel ] = volumeTable [ newVolume ] ;
237
235
} else if ( channel === 3 ) {
238
236
// For noise channel we always update the bottom bits.
239
237
this . registers [ channel ] = value & 0x0f ;
@@ -250,11 +248,9 @@ export class SoundChip {
250
248
updateSlowDataBus ( slowDataBus , active ) {
251
249
this . slowDataBus = slowDataBus ;
252
250
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.
258
254
if ( active ) {
259
255
this . activeTask . ensureScheduled ( true , this . minCyclesWELow ) ;
260
256
}
@@ -265,7 +261,8 @@ export class SoundChip {
265
261
for ( let i = 0 ; i < 4 ; ++ i ) {
266
262
this . counter [ i ] = 0 ;
267
263
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 ] ;
269
266
}
270
267
this . noisePoked ( ) ;
271
268
this . advance ( 100000 ) ;
0 commit comments