diff options
author | Bad Diode <bd@badd10de.dev> | 2021-05-27 16:46:36 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2021-05-27 16:46:36 +0200 |
commit | bc7f470714032c9e97798a5e532517c3d01adeef (patch) | |
tree | b76500ee4fbbe0deacc73944db55bf7d6d03b12e | |
parent | f32eda6df8e1df42e06eae55d28d9a45a92e7ecd (diff) | |
download | uxngba-bc7f470714032c9e97798a5e532517c3d01adeef.tar.gz uxngba-bc7f470714032c9e97798a5e532517c3d01adeef.zip |
Add ADSR filter
-rw-r--r-- | src/main.c | 16 | ||||
-rw-r--r-- | src/uxn/devices/apu.c | 79 |
2 files changed, 88 insertions, 7 deletions
@@ -155,10 +155,13 @@ audio_talk(Device *d, u8 b0, u8 w) { | |||
155 | } | 155 | } |
156 | c->inc = (pitch_table[c->pitch] * sampling_rate) >> 5; | 156 | c->inc = (pitch_table[c->pitch] * sampling_rate) >> 5; |
157 | 157 | ||
158 | u16 adsr = mempeek16(d->dat, 0x8); | ||
159 | build_adsr(c, adsr); | ||
158 | txt_position(0, 0); | 160 | txt_position(0, 0); |
159 | txt_printf("note: %d \n", c->pitch); | 161 | txt_printf("note: %d \n", c->pitch); |
160 | txt_printf("inc: %ld \n", c->inc); | 162 | txt_printf("inc: %lu \n", c->inc); |
161 | txt_printf("length: %ld \n", c->length >> 12); | 163 | txt_printf("length: %lu \n", c->length >> 12); |
164 | txt_printf("chan: %lu \n", d - devaudio); | ||
162 | } | 165 | } |
163 | } | 166 | } |
164 | 167 | ||
@@ -423,21 +426,24 @@ int main(void) { | |||
423 | int frame_counter = 0; | 426 | int frame_counter = 0; |
424 | evaluxn(&u, 0x0100); | 427 | evaluxn(&u, 0x0100); |
425 | u32 flip_cycles = 0; | 428 | u32 flip_cycles = 0; |
429 | u32 eval_cycles = 0; | ||
430 | u32 input_cycles = 0; | ||
431 | u32 mix_cycles = 0; | ||
426 | while(true) { | 432 | while(true) { |
427 | bios_vblank_wait(); | 433 | bios_vblank_wait(); |
428 | profile_start(); | 434 | profile_start(); |
429 | handle_input(&u); | 435 | handle_input(&u); |
430 | u32 input_cycles = profile_stop(); | 436 | input_cycles = MAX(profile_stop(), input_cycles); |
431 | profile_start(); | 437 | profile_start(); |
432 | evaluxn(&u, mempeek16(devscreen->dat, 0)); | 438 | evaluxn(&u, mempeek16(devscreen->dat, 0)); |
433 | u32 eval_cycles = profile_stop(); | 439 | eval_cycles = MAX(profile_stop(), eval_cycles); |
434 | txt_position(0, 8); | 440 | txt_position(0, 8); |
435 | profile_start(); | 441 | profile_start(); |
436 | flip_cycles = profile_stop(); | 442 | flip_cycles = profile_stop(); |
437 | frame_counter++; | 443 | frame_counter++; |
438 | profile_start(); | 444 | profile_start(); |
439 | sound_mix(); | 445 | sound_mix(); |
440 | u32 mix_cycles = profile_stop(); | 446 | mix_cycles = MAX(profile_stop(), mix_cycles); |
441 | 447 | ||
442 | txt_position(0, 15); | 448 | txt_position(0, 15); |
443 | txt_printf("INPUT: %lu \n", input_cycles); | 449 | txt_printf("INPUT: %lu \n", input_cycles); |
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. |
4 | static u32 pitch_table[120] = { | 4 | static 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. | ||
76 | typedef u32 fp32; | ||
77 | |||
78 | inline | ||
79 | fp32 | ||
80 | fp_mul(fp32 a, fp32 b) { | ||
81 | return (a * b) >> 6; | ||
82 | } | ||
83 | |||
84 | inline | ||
85 | fp32 | ||
86 | fp_div(fp32 a, fp32 b) { | ||
87 | return (a << 6) / b; | ||
88 | } | ||
89 | |||
90 | inline | ||
91 | fp32 | ||
92 | fp_lerp(fp32 y0, fp32 y1, fp32 x) { | ||
93 | return y0 + fp_mul(x, (y1 - y0)); | ||
94 | } | ||
95 | |||
96 | |||
97 | IWRAM_CODE | ||
98 | void | ||
99 | build_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 | |||
66 | static Audio audio; | 133 | static 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) { |