aboutsummaryrefslogtreecommitdiffstats
path: root/src/apu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/apu.c')
-rw-r--r--src/apu.c238
1 files changed, 238 insertions, 0 deletions
diff --git a/src/apu.c b/src/apu.c
new file mode 100644
index 0000000..8dd4ce5
--- /dev/null
+++ b/src/apu.c
@@ -0,0 +1,238 @@
1//
2// REG_TM0D frequency buffer size
3// | | |
4// V V V
5//
6// Timer = 62610 = 65536 - (16777216 / 5734), buf = 96
7// Timer = 63940 = 65536 - (16777216 / 10512), buf = 176
8// Timer = 64282 = 65536 - (16777216 / 13379), buf = 224
9// Timer = 64612 = 65536 - (16777216 / 18157), buf = 304
10// Timer = 64738 = 65536 - (16777216 / 21024), buf = 352
11// Timer = 64909 = 65536 - (16777216 / 26758), buf = 448
12// Timer = 65004 = 65536 - (16777216 / 31536), buf = 528
13// Timer = 65073 = 65536 - (16777216 / 36314), buf = 608
14// Timer = 65118 = 65536 - (16777216 / 40137), buf = 672
15// Timer = 65137 = 65536 - (16777216 / 42048), buf = 704
16//
17// Source: https://deku.gbadev.org/program/sound1.html
18#define AUDIO_FREQ 18157
19#define AUDIO_BUF_LEN 304
20#define AUDIO_TIMER 64612
21
22typedef struct Audio {
23 s8 mix_buffer[AUDIO_BUF_LEN * 2];
24 s8 *current_buffer;
25 u8 active_buffer;
26} Audio;
27
28typedef struct AudioChannel {
29 // Pointer to the raw data in the ROM.
30 u8 *data;
31 // Current position in the data (20.12 fixed-point).
32 u32 pos;
33 // Increment (20.12 fixed-point).
34 u32 inc;
35 // Volume (0-64, 1.6 fixed-point).
36 u32 vol;
37 // Sound length (20.12 fixed-point).
38 u32 length;
39 // Length of looped portion (20.12 fixed-point, 0 to disable looping).
40 u32 loop_length;
41 // Pitch encoded as a MIDI note.
42 u8 pitch;
43 // Keeping track of the original adsr values.
44 u16 adsr;
45 // The filter is built with the ADSR and has a maximum duration of
46 // 4 seconds. Each component can last up to 1 second.
47 u8 filter[240];
48 // Current position in the filter (0-60).
49 u8 filter_pos;
50 // Duration of the filter.
51 u8 filter_len;
52} AudioChannel;
53
54// Calculated as ((261.6 / 18157) << 17) for C4. If multiplied by sampling rate
55// we will have a u32 (15.17) fixed-point number. This should be enough to
56// accurately portray samples up to 75300 Hz.
57static u16 pitch_table[120] = {
58 59, 62, 66, 70, 74, 78, 83, 88,
59 93, 99, 105, 111, 118, 125, 132, 140,
60 148, 157, 166, 176, 187, 198, 210, 222,
61 236, 250, 264, 280, 297, 315, 333, 353,
62 374, 396, 420, 445, 472, 500, 529, 561,
63 594, 630, 667, 707, 749, 793, 841, 891,
64 944, 1000, 1059, 1122, 1189, 1260, 1335, 1414,
65 1498, 1587, 1682, 1782, 1888, 2000, 2119, 2245,
66 2379, 2520, 2670, 2829, 2997, 3175, 3364, 3564,
67 3776, 4001, 4239, 4491, 4758, 5041, 5341, 5658,
68 5995, 6351, 6729, 7129, 7553, 8002, 8478, 8982,
69 9517, 10083, 10682, 11317, 11990, 12703, 13459, 14259,
70 15107, 16005, 16957, 17965, 19034, 20166, 21365, 22635,
71 23981, 25407, 26918, 28519, 30215, 32011, 33915, 35931,
72 38068, 40332, 42730, 45271, 47963, 50815, 53837, 57038,
73};
74
75IWRAM_CODE
76void
77build_adsr(AudioChannel *chan, u16 adsr) {
78 chan->filter_pos = 0;
79 if (adsr == chan->adsr) {
80 return;
81 }
82
83 u8 a = (adsr >> 12);
84 u8 d = (adsr >> 8) & 0xF;
85 u8 s = (adsr >> 4) & 0xF;
86 u8 r = (adsr >> 0) & 0xF;
87
88 // Initialize the filter array.
89 dma_fill(chan->filter, 0, sizeof(chan->filter), 3);
90 u8 k = 0;
91
92 // Attack.
93 u32 interval = FP_DIV(1 << 6, (4 * a) << 6, 6);
94 for (u32 i = 0; i < 4 * a; ++i) {
95 chan->filter[k++] = FP_LERP(0, chan->vol, i * interval, 6);
96 }
97 // Decay.
98 interval = FP_DIV(1 << 6, (4 * d) << 6, 6);
99 for (u32 i = 0; i < 4 * d; ++i) {
100 chan->filter[k++] = FP_LERP(chan->vol, chan->vol / 2, i * interval, 6);
101 }
102 // Sustain.
103 for (u32 i = 0; i < 4 * s; ++i) {
104 chan->filter[k++] = chan->vol / 2;
105 }
106 // Release.
107 interval = FP_DIV(1 << 6, (4 * r) << 6, 6);
108 for (u32 i = 0; i < 4 * r; ++i) {
109 chan->filter[k++] = FP_LERP(chan->vol / 2, 0, i * interval, 6);
110 }
111
112 // Setup the channel vars.
113 chan->adsr = adsr;
114 chan->filter_len = k;
115}
116
117static Audio audio;
118
119#define POLYPHONY 4
120static AudioChannel channels[POLYPHONY];
121
122void
123init_sound(void) {
124 // Initialize audio buffers/channels.
125 audio = (Audio){0};
126 for (size_t i = 0; i < POLYPHONY; ++i) {
127 channels[i] = (AudioChannel){0};
128 }
129
130 // Enable the sound chip.
131 SOUND_STATUS = SOUND_ENABLE;
132
133 // Set max volume, left-right sound, fifo reset and use timer 0 for
134 // DirectSound A.
135 SOUND_DSOUND_MASTER = SOUND_DSOUND_RATIO_A
136 | SOUND_DSOUND_LEFT_A
137 | SOUND_DSOUND_RIGHT_A
138 | SOUND_DSOUND_RESET_A;
139
140 // The timer depends on the buffer length.
141 TIMER_DATA_0 = AUDIO_TIMER;
142 TIMER_CTRL_0 = TIMER_CTRL_ENABLE;
143}
144
145void
146sound_vsync() {
147 if(audio.active_buffer == 1) {
148 // Start playing and set the backbuffer.
149 dma_transfer_copy(SOUND_FIFO_A, audio.mix_buffer, 1, 1, DMA_DST_FIXED
150 | DMA_CHUNK_32 | DMA_REFRESH | DMA_REPEAT | DMA_ENABLE);
151 audio.current_buffer = audio.mix_buffer + AUDIO_BUF_LEN;
152 audio.active_buffer = 0;
153 } else {
154 // Flip front/backbuffer.
155 audio.current_buffer = audio.mix_buffer;
156 audio.active_buffer = 1;
157 }
158}
159
160IWRAM_CODE
161void
162update_channel(AudioChannel *c, u8 *data, u16 length, u8 pitch, u16 adsr,
163 u8 vol, bool loop) {
164 c->pos = 0;
165 c->length = length << 12;
166 c->data = data;
167 c->vol = vol;
168 c->pitch = pitch;
169
170 if (loop) {
171 c->loop_length = c->length;
172 } else {
173 c->loop_length = 0;
174 }
175
176 u32 sampling_rate = length;
177 if (length > 256) {
178 sampling_rate = 44100;
179 }
180 c->inc = (pitch_table[c->pitch] * sampling_rate) >> 5;
181
182 build_adsr(c, adsr);
183}
184
185IWRAM_CODE
186void
187sound_mix(void) {
188 // Clear mix_buffer.
189 u16 mix_buffer[AUDIO_BUF_LEN];
190 dma_fill(mix_buffer, 0, sizeof(mix_buffer), 3);
191
192 // Mix channels into the temporary buffer.
193 for (size_t j = 0; j < POLYPHONY; ++j) {
194 AudioChannel *ch = &channels[j];
195 // Check if channel is active.
196 if (ch->data == NULL || ch->pitch >= 108) {
197 continue;
198 }
199
200 u32 vol = ch->vol;
201 if (ch->adsr != 0) {
202 vol = ch->filter[ch->filter_pos++];
203 if (ch->filter_pos == ch->filter_len) {
204 continue;
205 }
206 }
207
208 for(size_t i = 0; i < AUDIO_BUF_LEN; i++) {
209 // Remember we are using fixed point values.
210 mix_buffer[i] += (0x80 ^ (s8)ch->data[ch->pos >> 12]) * vol;
211 ch->pos += ch->inc;
212
213 if (ch->pos >= ch->length) {
214 // If looping is not active disable the channel.
215 if (ch->loop_length == 0) {
216 ch->data = NULL;
217 break;
218 }
219
220 // Loop the sample.
221 while (ch->pos >= ch->length) {
222 ch->pos -= ch->loop_length;
223 }
224 }
225 }
226 }
227
228 // Downsample and copy to the playing buffer (Vectorized).
229 u64 *mix_ptr = mix_buffer;
230 u32 *buf_ptr = audio.current_buffer;
231 for (size_t i = 0; i < AUDIO_BUF_LEN / 4; i++) {
232 u64 mix = mix_ptr[i];
233 buf_ptr[i] = ((mix >> 8) & 0xFF)
234 | ((mix >> 16) & 0xFF00)
235 | ((mix >> 24) & 0xFF0000)
236 | ((mix >> 32) & 0xFF000000);
237 }
238}