Skip to content

Commit f86cdac

Browse files
authored
Merge pull request #705 from JerwuQu/jwq_tick-aware-apu
✨ Make APU tick aware ✨
2 parents b874b41 + 9147b26 commit f86cdac

File tree

6 files changed

+48
-13
lines changed

6 files changed

+48
-13
lines changed

runtimes/native/src/apu.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ typedef struct {
2929
/** Time the tone should end. */
3030
unsigned long long releaseTime;
3131

32+
/** The tick the tone should end. */
33+
unsigned long long endTick;
34+
3235
/** Sustain volume level. */
3336
int16_t sustainVolume;
3437

@@ -59,8 +62,9 @@ typedef struct {
5962

6063
static Channel channels[4] = { 0 };
6164

62-
/** The current time, in samples. */
65+
/** The current time in samples and ticks respectively. */
6366
static unsigned long long time = 0;
67+
static unsigned long long ticks = 0;
6468

6569
static int w4_min (int a, int b) {
6670
return a < b ? a : b;
@@ -71,6 +75,7 @@ static int lerp (int value1, int value2, float t) {
7175
}
7276

7377
static int ramp (int value1, int value2, unsigned long long time1, unsigned long long time2) {
78+
if (time >= time2) return value2;
7479
float t = (float)(time - time1) / (time2 - time1);
7580
return lerp(value1, value2, t);
7681
}
@@ -84,7 +89,7 @@ static uint16_t getCurrentFrequency (const Channel* channel) {
8489
}
8590

8691
static int16_t getCurrentVolume (const Channel* channel) {
87-
if (time >= channel->sustainTime) {
92+
if (time >= channel->sustainTime && channel->releaseTime != channel->sustainTime) {
8893
// Release
8994
return ramp(channel->sustainVolume, 0, channel->sustainTime, channel->releaseTime);
9095
} else if (time >= channel->decayTime) {
@@ -115,6 +120,10 @@ void w4_apuInit () {
115120
channels[3].noise.seed = 0x0001;
116121
}
117122

123+
void w4_apuTick () {
124+
ticks++;
125+
}
126+
118127
void w4_apuTone (int frequency, int duration, int volume, int flags) {
119128
int freq1 = frequency & 0xffff;
120129
int freq2 = (frequency >> 16) & 0xffff;
@@ -135,7 +144,7 @@ void w4_apuTone (int frequency, int duration, int volume, int flags) {
135144
Channel* channel = &channels[channelIdx];
136145

137146
// Restart the phase if this channel wasn't already playing
138-
if (time > channel->releaseTime) {
147+
if (time > channel->releaseTime && ticks != channel->endTick) {
139148
channel->phase = (channelIdx == 2) ? 0.25 : 0;
140149
}
141150

@@ -146,6 +155,7 @@ void w4_apuTone (int frequency, int duration, int volume, int flags) {
146155
channel->decayTime = channel->attackTime + SAMPLE_RATE*decay/60;
147156
channel->sustainTime = channel->decayTime + SAMPLE_RATE*sustain/60;
148157
channel->releaseTime = channel->sustainTime + SAMPLE_RATE*release/60;
158+
channel->endTick = ticks + attack + decay + sustain + release;
149159
int16_t maxVolume = (channelIdx == 2) ? MAX_VOLUME_TRIANGLE : MAX_VOLUME;
150160
channel->sustainVolume = maxVolume * sustainVolume/100;
151161
channel->peakVolume = peakVolume ? maxVolume * peakVolume/100 : maxVolume;
@@ -179,7 +189,7 @@ void w4_apuWriteSamples (int16_t* output, unsigned long frames) {
179189
for (int channelIdx = 0; channelIdx < 4; ++channelIdx) {
180190
Channel* channel = &channels[channelIdx];
181191

182-
if (time < channel->releaseTime) {
192+
if (time < channel->releaseTime || ticks == channel->endTick) {
183193
uint16_t freq = getCurrentFrequency(channel);
184194
int16_t volume = getCurrentVolume(channel);
185195
int16_t sample;

runtimes/native/src/apu.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
void w4_apuInit ();
77

8+
void w4_apuTick ();
9+
810
void w4_apuTone (int frequency, int duration, int volume, int flags);
911

1012
void w4_apuWriteSamples (int16_t* output, unsigned long frames);

runtimes/native/src/runtime.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ void w4_runtimeUpdate () {
184184
w4_framebufferClear();
185185
}
186186
w4_wasmCallUpdate();
187+
w4_apuTick();
187188
uint32_t palette[4] = {
188189
w4_read32LE(&memory->palette[0]),
189190
w4_read32LE(&memory->palette[1]),

runtimes/web/src/apu-worklet.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class Channel {
2727
/** Time the tone should end. */
2828
releaseTime = 0;
2929

30+
/** The tick the tone should end. */
31+
endTick = 0;
32+
3033
/** Sustain volume level. */
3134
sustainVolume = 0;
3235

@@ -67,25 +70,32 @@ function polyblep (phase: number, phaseInc: number) {
6770

6871
class APUProcessor extends AudioWorkletProcessor {
6972
time: number;
73+
ticks: number;
7074
channels: Channel[];
7175

7276
constructor () {
7377
super();
7478

7579
this.time = 0;
80+
this.ticks = 0;
7681
this.channels = new Array(4);
7782
for (let ii = 0; ii < 4; ++ii) {
7883
this.channels[ii] = new Channel();
7984
}
8085

8186
if (this.port != null) {
82-
this.port.onmessage = (event: MessageEvent<[number, number, number, number]>) => {
83-
this.tone(...event.data);
87+
this.port.onmessage = (event: MessageEvent<'tick' | [number, number, number, number]>) => {
88+
if (event.data === 'tick') {
89+
this.tick();
90+
} else {
91+
this.tone(...event.data);
92+
}
8493
};
8594
}
8695
}
8796

8897
ramp (value1: number, value2: number, time1: number, time2: number) {
98+
if (this.time >= time2) return value2;
8999
const t = (this.time - time1) / (time2 - time1);
90100
return lerp(value1, value2, t);
91101
}
@@ -100,7 +110,7 @@ class APUProcessor extends AudioWorkletProcessor {
100110

101111
getCurrentVolume (channel: Channel) {
102112
const time = this.time;
103-
if (time >= channel.sustainTime) {
113+
if (time >= channel.sustainTime && channel.releaseTime != channel.sustainTime) {
104114
// Release
105115
return this.ramp(channel.sustainVolume, 0, channel.sustainTime, channel.releaseTime);
106116
} else if (time >= channel.decayTime) {
@@ -115,6 +125,10 @@ class APUProcessor extends AudioWorkletProcessor {
115125
}
116126
}
117127

128+
tick () {
129+
this.ticks++;
130+
}
131+
118132
tone (frequency: number, duration: number, volume: number, flags: number) {
119133
const freq1 = frequency & 0xffff;
120134
const freq2 = (frequency >> 16) & 0xffff;
@@ -134,7 +148,7 @@ class APUProcessor extends AudioWorkletProcessor {
134148
const channel = this.channels[channelIdx];
135149

136150
// Restart the phase if this channel wasn't already playing
137-
if (this.time > channel.releaseTime) {
151+
if (this.time > channel.releaseTime && this.ticks != channel.endTick) {
138152
channel.phase = (channelIdx == 2) ? 0.25 : 0;
139153
}
140154

@@ -145,6 +159,7 @@ class APUProcessor extends AudioWorkletProcessor {
145159
channel.decayTime = channel.attackTime + ((SAMPLE_RATE*decay/60) >>> 0);
146160
channel.sustainTime = channel.decayTime + ((SAMPLE_RATE*sustain/60) >>> 0);
147161
channel.releaseTime = channel.sustainTime + ((SAMPLE_RATE*release/60) >>> 0);
162+
channel.endTick = this.ticks + attack + decay + sustain + release;
148163
channel.pan = pan;
149164

150165
const maxVolume = (channelIdx == 2) ? MAX_VOLUME_TRIANGLE : MAX_VOLUME;
@@ -179,7 +194,7 @@ class APUProcessor extends AudioWorkletProcessor {
179194
for (let channelIdx = 0; channelIdx < 4; ++channelIdx) {
180195
const channel = this.channels[channelIdx];
181196

182-
if (this.time < channel.releaseTime) {
197+
if (this.time < channel.releaseTime || this.ticks == channel.endTick) {
183198
const freq = this.getCurrentFrequency(channel);
184199
const volume = this.getCurrentVolume(channel);
185200
let sample;

runtimes/web/src/apu.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,18 @@ export class APU {
5555
}
5656
}
5757

58+
tick() {
59+
if (this.processorPort != null) {
60+
this.processorPort.postMessage('tick');
61+
} else {
62+
this.processor.tick();
63+
}
64+
}
65+
5866
tone (frequency: number, duration: number, volume: number, flags: number) {
59-
const processorPort = this.processorPort;
60-
if (processorPort != null) {
67+
if (this.processorPort != null) {
6168
// Send params out to the worker
62-
processorPort.postMessage([frequency, duration, volume, flags]);
63-
69+
this.processorPort.postMessage([frequency, duration, volume, flags]);
6470
} else {
6571
// For the ScriptProcessorNode fallback, just call tone() directly
6672
this.processor.tone(frequency, duration, volume, flags);

runtimes/web/src/runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ export class Runtime {
334334
let update_function = this.wasm!.exports["update"];
335335
if (typeof update_function === "function") {
336336
this.bluescreenOnError(update_function);
337+
this.apu.tick();
337338
}
338339
}
339340

0 commit comments

Comments
 (0)