aboutsummaryrefslogtreecommitdiffstats
path: root/src/uxn/devices/apu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uxn/devices/apu.c')
-rw-r--r--src/uxn/devices/apu.c79
1 files changed, 77 insertions, 2 deletions
diff --git a/src/uxn/devices/apu.c b/src/uxn/devices/apu.c
index 4447e12..d968840 100644
--- a/src/uxn/devices/apu.c
+++ b/src/uxn/devices/apu.c
@@ -1,7 +1,7 @@
1// Calculated as ((261.6 / 18157) << 17) for C4. If multiplied by sampling rate 1// Calculated as ((261.6 / 18157) << 17) for C4. If multiplied by sampling rate
2// we will have a u32 (15.17) fixed-point number. This should be enough to 2// we will have a u32 (15.17) fixed-point number. This should be enough to
3// accurately portray samples up to 75300 Hz. 3// accurately portray samples up to 75300 Hz.
4static u32 pitch_table[120] = { 4static u16 pitch_table[120] = {
5 59, 62, 66, 70, 74, 78, 83, 88, 5 59, 62, 66, 70, 74, 78, 83, 88,
6 93, 99, 105, 111, 118, 125, 132, 140, 6 93, 99, 105, 111, 118, 125, 132, 140,
7 148, 157, 166, 176, 187, 198, 210, 222, 7 148, 157, 166, 176, 187, 198, 210, 222,
@@ -61,8 +61,75 @@ typedef struct AudioChannel {
61 u32 loop_length; 61 u32 loop_length;
62 // Pitch encoded as a MIDI note. 62 // Pitch encoded as a MIDI note.
63 u8 pitch; 63 u8 pitch;
64 // Keeping track of the original adsr values.
65 u16 adsr;
66 // The filter is built with the ADSR and has a duration of 60 ticks. Since
67 // weare synced at 60FPS it should be pretty straightforward.
68 u8 filter[241];
69 // Current position in the filter (0-60).
70 u8 filter_pos;
71 // Duration of the filter.
72 u8 filter_len;
64} AudioChannel; 73} AudioChannel;
65 74
75// NOTE: fixed-point (1.6) precision for volume adjustments.
76typedef u32 fp32;
77
78inline
79fp32
80fp_mul(fp32 a, fp32 b) {
81 return (a * b) >> 6;
82}
83
84inline
85fp32
86fp_div(fp32 a, fp32 b) {
87 return (a << 6) / b;
88}
89
90inline
91fp32
92fp_lerp(fp32 y0, fp32 y1, fp32 x) {
93 return y0 + fp_mul(x, (y1 - y0));
94}
95
96
97IWRAM_CODE
98void
99build_adsr(AudioChannel *chan, u16 adsr) {
100 // u8 a = (adsr >> 12);
101 u8 a = (adsr >> 12);
102 u8 d = (adsr >> 8) & 0xF;
103 u8 s = (adsr >> 4) & 0xF;
104 u8 r = (adsr >> 0) & 0xF;
105
106 // Initialize the filter array.
107 memset(chan->filter, 0, sizeof(chan->filter));
108 u8 k = 0;
109
110 // Attack.
111 for (u32 i = 0; i < 4 * a; ++i) {
112 chan->filter[k++] = fp_lerp(0, chan->vol, fp_div(i << 6, (4 * a) << 6));
113 }
114 // Decay.
115 for (u32 i = 0; i < 4 * d; ++i) {
116 chan->filter[k++] = fp_lerp(chan->vol, chan->vol / 2, fp_div(i << 6, (4 * d) << 6));
117 }
118 // Sustain.
119 for (u32 i = 0; i < 4 * s; ++i) {
120 chan->filter[k++] = chan->vol / 2;
121 }
122 // Release.
123 for (u32 i = 0; i < 4 * r; ++i) {
124 chan->filter[k++] = fp_lerp(chan->vol / 2, 0, fp_div(i << 6, (4 * r) << 6));
125 }
126
127 // Setup the channel vars.
128 chan->adsr = adsr;
129 chan->filter_pos = 0;
130 chan->filter_len = k;
131}
132
66static Audio audio; 133static Audio audio;
67 134
68#define POLYPHONY 4 135#define POLYPHONY 4
@@ -123,12 +190,20 @@ void sound_mix() {
123 if (ch->data == NULL || ch->pitch >= 108) { 190 if (ch->data == NULL || ch->pitch >= 108) {
124 continue; 191 continue;
125 } 192 }
193
194 u32 vol = ch->vol;
195 if (ch->adsr != 0) {
196 vol = ch->filter[ch->filter_pos++];
197 if (ch->filter_pos == ch->filter_len) {
198 continue;
199 }
200 }
126 if (ch->pos + ch->inc * AUDIO_BUF_LEN >= ch->length) { 201 if (ch->pos + ch->inc * AUDIO_BUF_LEN >= ch->length) {
127 // Sample is going to finish, need to consider this for looping or 202 // Sample is going to finish, need to consider this for looping or
128 // stopping. 203 // stopping.
129 for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { 204 for(size_t i = 0; i < AUDIO_BUF_LEN; i++) {
130 // Remember we are using fixed point values. 205 // Remember we are using fixed point values.
131 mix_buffer[i] += (0x80 + (u8)ch->data[ch->pos >> 12]) * ch->vol; 206 mix_buffer[i] += (0x80 + (u8)ch->data[ch->pos >> 12]) * vol;
132 ch->pos += ch->inc; 207 ch->pos += ch->inc;
133 208
134 if (ch->pos >= ch->length) { 209 if (ch->pos >= ch->length) {