Skip to content

Commit

Permalink
Fix triangle release by switching period by tick
Browse files Browse the repository at this point in the history
  • Loading branch information
JerwuQu committed Dec 3, 2024
1 parent 7e92c5e commit 6d6261a
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 14 deletions.
25 changes: 18 additions & 7 deletions runtimes/native/src/apu.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#define MAX_VOLUME 0x1333 // ~15% of INT16_MAX
// The triangle channel sounds a bit quieter than the others, so give it higher amplitude
#define MAX_VOLUME_TRIANGLE 0x2000 // ~25% of INT16_MAX
// Also the triangle channel prevent popping on hard stops by adding a 1 ms release
// Also for the triangle channel, prevent popping on hard stops by adding a 1 ms release
#define RELEASE_TIME_TRIANGLE (SAMPLE_RATE / 1000)

typedef struct {
Expand All @@ -32,8 +32,8 @@ typedef struct {
/** Time the tone should end. */
unsigned long long releaseTime;

/** The tick the tone should end. */
unsigned long long endTick;
/** Tick at the end of the sustain period where the tone switch over to release. */
unsigned long long sustainTick;

/** Sustain volume level. */
int16_t sustainVolume;
Expand Down Expand Up @@ -100,7 +100,7 @@ static float getCurrentFrequency (const Channel* channel) {
}

static int16_t getCurrentVolume (const Channel* channel) {
if (time >= channel->sustainTime && (channel->releaseTime - channel->sustainTime) > RELEASE_TIME_TRIANGLE) {
if (ticks > channel->sustainTick) {
// Release
return ramp(channel->sustainVolume, 0, channel->sustainTime, channel->releaseTime);
} else if (time >= channel->decayTime) {
Expand Down Expand Up @@ -136,6 +136,17 @@ void w4_apuInit () {
}

void w4_apuTick () {
// Update releaseTime for channels that should begin their release period this tick.
// This fixes drift drift between ticks and samples.
for (int channelIdx = 0; channelIdx < 4; ++channelIdx) {
Channel* channel = &channels[channelIdx];
if (ticks == channel->sustainTick) {
const delta = time - channel->sustainTime;
channel->sustainTime = time;
channel->releaseTime += delta;
}
}

ticks++;
}

Expand All @@ -160,7 +171,7 @@ void w4_apuTone (int frequency, int duration, int volume, int flags) {
Channel* channel = &channels[channelIdx];

// Restart the phase if this channel wasn't already playing
if (time > channel->releaseTime && ticks != channel->endTick) {
if (time > channel->releaseTime && ticks != channel->sustainTick) {
channel->phase = (channelIdx == 2) ? 0.25 : 0;
}
if (noteMode) {
Expand All @@ -175,7 +186,7 @@ void w4_apuTone (int frequency, int duration, int volume, int flags) {
channel->decayTime = channel->attackTime + SAMPLE_RATE*decay/60;
channel->sustainTime = channel->decayTime + SAMPLE_RATE*sustain/60;
channel->releaseTime = channel->sustainTime + SAMPLE_RATE*release/60;
channel->endTick = ticks + attack + decay + sustain + release;
channel->sustainTick = ticks + attack + decay + sustain;
int16_t maxVolume = (channelIdx == 2) ? MAX_VOLUME_TRIANGLE : MAX_VOLUME;
channel->sustainVolume = maxVolume * sustainVolume/100;
channel->peakVolume = peakVolume ? maxVolume * peakVolume/100 : maxVolume;
Expand Down Expand Up @@ -208,7 +219,7 @@ void w4_apuWriteSamples (int16_t* output, unsigned long frames) {
for (int channelIdx = 0; channelIdx < 4; ++channelIdx) {
Channel* channel = &channels[channelIdx];

if (time < channel->releaseTime || ticks == channel->endTick) {
if (time < channel->releaseTime || ticks == channel->sustainTick) {
float freq = getCurrentFrequency(channel);
int16_t volume = getCurrentVolume(channel);
int16_t sample;
Expand Down
25 changes: 18 additions & 7 deletions runtimes/web/src/apu-worklet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const SAMPLE_RATE = 44100;
const MAX_VOLUME = 0.15;
// The triangle channel sounds a bit quieter than the others, so give it higher amplitude
const MAX_VOLUME_TRIANGLE = 0.25;
// Also the triangle channel prevent popping on hard stops by adding a 1 ms release
// Also for the triangle channel, prevent popping on hard stops by adding a 1 ms release
const RELEASE_TIME_TRIANGLE = Math.floor(SAMPLE_RATE / 1000);

class Channel {
Expand All @@ -30,8 +30,8 @@ class Channel {
/** Time the tone should end. */
releaseTime = 0;

/** The tick the tone should end. */
endTick = 0;
/** Tick at the end of the sustain period where the tone switch over to release. */
sustainTick = 0;

/** Sustain volume level. */
sustainVolume = 0;
Expand Down Expand Up @@ -117,7 +117,7 @@ class APUProcessor extends AudioWorkletProcessor {

getCurrentVolume (channel: Channel) {
const time = this.time;
if (time >= channel.sustainTime && (channel.releaseTime - channel.sustainTime) > RELEASE_TIME_TRIANGLE) {
if (this.ticks > channel.sustainTick) {
// Release
return this.ramp(channel.sustainVolume, 0, channel.sustainTime, channel.releaseTime);
} else if (time >= channel.decayTime) {
Expand All @@ -133,6 +133,17 @@ class APUProcessor extends AudioWorkletProcessor {
}

tick () {
// Update releaseTime for channels that should begin their release period this tick.
// This fixes drift drift between ticks and samples.
for (let channelIdx = 0; channelIdx < 4; ++channelIdx) {
const channel = this.channels[channelIdx];
if (this.ticks == channel.sustainTick) {
const delta = this.time - channel.sustainTime;
channel.sustainTime = this.time;
channel.releaseTime += delta;
}
}

this.ticks++;
}

Expand All @@ -155,7 +166,7 @@ class APUProcessor extends AudioWorkletProcessor {
const channel = this.channels[channelIdx];

// Restart the phase if this channel wasn't already playing
if (this.time > channel.releaseTime && this.ticks != channel.endTick) {
if (this.time > channel.releaseTime && this.ticks != channel.sustainTick) {
channel.phase = (channelIdx == 2) ? 0.25 : 0;
}
if (noteMode) {
Expand All @@ -170,7 +181,7 @@ class APUProcessor extends AudioWorkletProcessor {
channel.decayTime = channel.attackTime + ((SAMPLE_RATE*decay/60) >>> 0);
channel.sustainTime = channel.decayTime + ((SAMPLE_RATE*sustain/60) >>> 0);
channel.releaseTime = channel.sustainTime + ((SAMPLE_RATE*release/60) >>> 0);
channel.endTick = this.ticks + attack + decay + sustain + release;
channel.sustainTick = this.ticks + attack + decay + sustain;
channel.pan = pan;

const maxVolume = (channelIdx == 2) ? MAX_VOLUME_TRIANGLE : MAX_VOLUME;
Expand Down Expand Up @@ -204,7 +215,7 @@ class APUProcessor extends AudioWorkletProcessor {
for (let channelIdx = 0; channelIdx < 4; ++channelIdx) {
const channel = this.channels[channelIdx];

if (this.time < channel.releaseTime || this.ticks == channel.endTick) {
if (this.time < channel.releaseTime || this.ticks == channel.sustainTick) {
const freq = this.getCurrentFrequency(channel);
const volume = this.getCurrentVolume(channel);
let sample;
Expand Down

0 comments on commit 6d6261a

Please sign in to comment.