diff options
Diffstat (limited to 'src/uxn/devices/apu.c')
-rw-r--r-- | src/uxn/devices/apu.c | 79 |
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. |
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) { |