diff options
author | Bad Diode <bd@badd10de.dev> | 2021-05-26 15:08:23 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2021-05-26 15:08:23 +0200 |
commit | e6235422f3746683e679abd353d9385835c8187f (patch) | |
tree | 6d83a3520a5ab10390d52487b90a92f32a865e85 | |
parent | 2a7893e230c521bc0f8aed7c4307ccf4cacaa767 (diff) | |
download | uxngba-e6235422f3746683e679abd353d9385835c8187f.tar.gz uxngba-e6235422f3746683e679abd353d9385835c8187f.zip |
[WIP] Prototype of audio mixer following deku tutorial
-rw-r--r-- | src/main.c | 41 | ||||
-rw-r--r-- | src/uxn/devices/apu.c | 289 |
2 files changed, 181 insertions, 149 deletions
@@ -184,7 +184,7 @@ init_uxn(Uxn *u) { | |||
184 | initppu(&ppu, 30, 20, 0); | 184 | initppu(&ppu, 30, 20, 0); |
185 | 185 | ||
186 | // Enable sound. | 186 | // Enable sound. |
187 | SOUND_STATUS = SOUND_ENABLE; | 187 | init_sound(); |
188 | 188 | ||
189 | // Copy rom to VM. | 189 | // Copy rom to VM. |
190 | memcpy(u->ram.dat + PAGE_PROGRAM, uxn_rom, sizeof(uxn_rom)); | 190 | memcpy(u->ram.dat + PAGE_PROGRAM, uxn_rom, sizeof(uxn_rom)); |
@@ -388,8 +388,8 @@ int main(void) { | |||
388 | 388 | ||
389 | // Register interrupts. | 389 | // Register interrupts. |
390 | irq_init(); | 390 | irq_init(); |
391 | irs_set(IRQ_VBLANK, irs_stub); | 391 | irs_set(IRQ_VBLANK, sound_vsync); |
392 | irs_set(IRQ_TIMER_1, irs_stop_sample); | 392 | // irs_set(IRQ_TIMER_1, irs_stop_sample); |
393 | 393 | ||
394 | // Initialize VM. | 394 | // Initialize VM. |
395 | memset(&u, 0, sizeof(u)); | 395 | memset(&u, 0, sizeof(u)); |
@@ -400,34 +400,43 @@ int main(void) { | |||
400 | txt_init(1, TEXT_LAYER); | 400 | txt_init(1, TEXT_LAYER); |
401 | txt_position(0,0); | 401 | txt_position(0,0); |
402 | 402 | ||
403 | // init_sound(&apu.chan_0); | 403 | init_sound(); |
404 | // reset_sound(&apu.chan_0); | 404 | // reset_sound(&apu.chan_0); |
405 | // txt_printf("DING\n"); | ||
406 | // sound_mix(); | ||
405 | 407 | ||
406 | // Main loop. | 408 | // Main loop. |
407 | int frame_counter = 0; | 409 | int frame_counter = 0; |
408 | evaluxn(&u, 0x0100); | 410 | // evaluxn(&u, 0x0100); |
409 | u32 flip_cycles = 0; | 411 | // u32 flip_cycles = 0; |
410 | while(true) { | 412 | while(true) { |
411 | bios_vblank_wait(); | 413 | bios_vblank_wait(); |
412 | profile_start(); | 414 | // profile_start(); |
413 | handle_input(&u); | 415 | // handle_input(&u); |
414 | u32 input_cycles = profile_stop(); | 416 | // u32 input_cycles = profile_stop(); |
415 | profile_start(); | 417 | // profile_start(); |
416 | evaluxn(&u, mempeek16(devscreen->dat, 0)); | 418 | // evaluxn(&u, mempeek16(devscreen->dat, 0)); |
417 | u32 eval_cycles = profile_stop(); | 419 | // u32 eval_cycles = profile_stop(); |
418 | txt_position(0, 8); | 420 | // txt_position(0, 8); |
419 | // txt_printf("INPUT: %lu \n", input_cycles); | 421 | // txt_printf("INPUT: %lu \n", input_cycles); |
420 | // txt_printf("EVAL: %lu \n", eval_cycles); | 422 | // txt_printf("EVAL: %lu \n", eval_cycles); |
421 | // txt_printf("FLIP: %lu \n", flip_cycles); | 423 | // txt_printf("FLIP: %lu \n", flip_cycles); |
422 | profile_start(); | 424 | // profile_start(); |
423 | flipbuf(&ppu); | 425 | // flip_cycles = profile_stop(); |
424 | flip_cycles = profile_stop(); | ||
425 | frame_counter++; | 426 | frame_counter++; |
426 | // if (frame_counter == 120) { | 427 | // if (frame_counter == 120) { |
427 | // init_sound(&apu.chan_0); | 428 | // init_sound(&apu.chan_0); |
428 | // reset_sound(&apu.chan_0); | 429 | // reset_sound(&apu.chan_0); |
429 | // frame_counter = 0; | 430 | // frame_counter = 0; |
430 | // } | 431 | // } |
432 | profile_start(); | ||
433 | sound_mix(); | ||
434 | u32 mix_cycles = profile_stop(); | ||
435 | |||
436 | txt_position(0, 18); | ||
437 | txt_printf("MIX CYCLES: %lu \n", mix_cycles); | ||
438 | txt_printf("FRAME: %lu \n", frame_counter); | ||
439 | flipbuf(&ppu); | ||
431 | } | 440 | } |
432 | 441 | ||
433 | return 0; | 442 | return 0; |
diff --git a/src/uxn/devices/apu.c b/src/uxn/devices/apu.c index b2ccc18..ba9fe88 100644 --- a/src/uxn/devices/apu.c +++ b/src/uxn/devices/apu.c | |||
@@ -4,6 +4,7 @@ | |||
4 | #define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025) | 4 | #define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025) |
5 | #define ADSR_STEP (SAMPLE_FREQUENCY / 0xf) | 5 | #define ADSR_STEP (SAMPLE_FREQUENCY / 0xf) |
6 | 6 | ||
7 | // FIXME: We have two structs for now, needs fixing. | ||
7 | typedef struct AudioChannel { | 8 | typedef struct AudioChannel { |
8 | u8 *samples; | 9 | u8 *samples; |
9 | u16 n_samples; | 10 | u16 n_samples; |
@@ -31,127 +32,22 @@ static APU apu; | |||
31 | 32 | ||
32 | 33 | ||
33 | static u16 pitch_table[] = { | 34 | static u16 pitch_table[] = { |
34 | 12173, | 35 | 12173, 11490, 10845, 10237, 9662, 9120, 8608, 8125, |
35 | 11490, | 36 | 7669, 7238, 6832, 6448, 6086, 5745, 5422, 5118, |
36 | 10845, | 37 | 4831, 4560, 4304, 4062, 3834, 3619, 3416, 3224, |
37 | 10237, | 38 | 3043, 2872, 2711, 2559, 2415, 2280, 2152, 2031, |
38 | 9662, | 39 | 1917, 1809, 1708, 1612, 1521, 1436, 1355, 1279, |
39 | 9120, | 40 | 1207, 1140, 1076, 1015, 958, 904, 854, 806, |
40 | 8608, | 41 | 760, 718, 677, 639, 603, 570, 538, 507, |
41 | 8125, | 42 | 479, 452, 427, 403, 380, 359, 338, 319, |
42 | 7669, | 43 | 301, 285, 269, 253, 239, 226, 213, 201, |
43 | 7238, | 44 | 190, 179, 169, 159, 150, 142, 134, 126, |
44 | 6832, | 45 | 119, 113, 106, 100, 95, 89, 84, 79, |
45 | 6448, | 46 | 75, 71, 67, 63, 59, 56, 53, 50, |
46 | 6086, | 47 | 47, 44, 42, 39, 37, 35, 33, 31, |
47 | 5745, | 48 | 29, 28, 26, 25, 23, 22, 21, 19, |
48 | 5422, | 49 | 18, 17, 16, 15, 14, 14, 13, 12, |
49 | 5118, | 50 | 2, |
50 | 4831, | ||
51 | 4560, | ||
52 | 4304, | ||
53 | 4062, | ||
54 | 3834, | ||
55 | 3619, | ||
56 | 3416, | ||
57 | 3224, | ||
58 | 3043, | ||
59 | 2872, | ||
60 | 2711, | ||
61 | 2559, | ||
62 | 2415, | ||
63 | 2280, | ||
64 | 2152, | ||
65 | 2031, | ||
66 | 1917, | ||
67 | 1809, | ||
68 | 1708, | ||
69 | 1612, | ||
70 | 1521, | ||
71 | 1436, | ||
72 | 1355, | ||
73 | 1279, | ||
74 | 1207, | ||
75 | 1140, | ||
76 | 1076, | ||
77 | 1015, | ||
78 | 958, | ||
79 | 904, | ||
80 | 854, | ||
81 | 806, | ||
82 | 760, | ||
83 | 718, | ||
84 | 677, | ||
85 | 639, | ||
86 | 603, | ||
87 | 570, | ||
88 | 538, | ||
89 | 507, | ||
90 | 479, | ||
91 | 452, | ||
92 | 427, | ||
93 | 403, | ||
94 | 380, | ||
95 | 359, | ||
96 | 338, | ||
97 | 319, | ||
98 | 301, | ||
99 | 285, | ||
100 | 269, | ||
101 | 253, | ||
102 | 239, | ||
103 | 226, | ||
104 | 213, | ||
105 | 201, | ||
106 | 190, | ||
107 | 179, | ||
108 | 169, | ||
109 | 159, | ||
110 | 150, | ||
111 | 142, | ||
112 | 134, | ||
113 | 126, | ||
114 | 119, | ||
115 | 113, | ||
116 | 106, | ||
117 | 100, | ||
118 | 95, | ||
119 | 89, | ||
120 | 84, | ||
121 | 79, | ||
122 | 75, | ||
123 | 71, | ||
124 | 67, | ||
125 | 63, | ||
126 | 59, | ||
127 | 56, | ||
128 | 53, | ||
129 | 50, | ||
130 | 47, | ||
131 | 44, | ||
132 | 42, | ||
133 | 39, | ||
134 | 37, | ||
135 | 35, | ||
136 | 33, | ||
137 | 31, | ||
138 | 29, | ||
139 | 28, | ||
140 | 26, | ||
141 | 25, | ||
142 | 23, | ||
143 | 22, | ||
144 | 21, | ||
145 | 19, | ||
146 | 18, | ||
147 | 17, | ||
148 | 16, | ||
149 | 15, | ||
150 | 14, | ||
151 | 14, | ||
152 | 13, | ||
153 | 12, | ||
154 | 0, | ||
155 | }; | 51 | }; |
156 | 52 | ||
157 | void | 53 | void |
@@ -188,23 +84,150 @@ reset_sound(AudioChannel *chan) { | |||
188 | TIMER_CTRL_0 = TIMER_CTRL_ENABLE; | 84 | TIMER_CTRL_0 = TIMER_CTRL_ENABLE; |
189 | } | 85 | } |
190 | 86 | ||
191 | u8 square_wave[] = { | 87 | s8 square_wave[] = { |
192 | 0x00 + 0x80, 0xFF + 0x80 | 88 | (s8)0x00 + (s8)0x80, (s8)0xFF + (s8)0x80 |
193 | }; | 89 | }; |
90 | |||
194 | #include "text.h" | 91 | #include "text.h" |
195 | 92 | ||
196 | 93 | ||
94 | |||
95 | // | ||
96 | // REG_TM0D frequency buffer size | ||
97 | // | | | | ||
98 | // V V V | ||
99 | // | ||
100 | // Timer = 62610 = 65536 - (16777216 / 5734), buf = 96 | ||
101 | // Timer = 63940 = 65536 - (16777216 / 10512), buf = 176 | ||
102 | // Timer = 64282 = 65536 - (16777216 / 13379), buf = 224 | ||
103 | // Timer = 64612 = 65536 - (16777216 / 18157), buf = 304 | ||
104 | // Timer = 64738 = 65536 - (16777216 / 21024), buf = 352 | ||
105 | // Timer = 64909 = 65536 - (16777216 / 26758), buf = 448 | ||
106 | // Timer = 65004 = 65536 - (16777216 / 31536), buf = 528 | ||
107 | // Timer = 65073 = 65536 - (16777216 / 36314), buf = 608 | ||
108 | // Timer = 65118 = 65536 - (16777216 / 40137), buf = 672 | ||
109 | // Timer = 65137 = 65536 - (16777216 / 42048), buf = 704 | ||
110 | // | ||
111 | // Source: https://deku.gbadev.org/program/sound1.html | ||
112 | #define AUDIO_FREQ 18157 | ||
113 | #define AUDIO_BUF_LEN 304 | ||
114 | #define AUDIO_TIMER 64612 | ||
115 | |||
116 | typedef struct Audio { | ||
117 | s8 mix_buffer[AUDIO_BUF_LEN * 2]; | ||
118 | s8 *current_buffer; | ||
119 | u8 active_buffer; | ||
120 | } Audio; | ||
121 | |||
122 | typedef struct Channel { | ||
123 | // Pointer to the raw data in the ROM. | ||
124 | s8 *data; | ||
125 | // Current position in the data (20.12 fixed-point). | ||
126 | u32 pos; | ||
127 | // Increment (20.12 fixed-point). | ||
128 | u32 inc; | ||
129 | // Volume (0-64, 1.6 fixed-point). | ||
130 | u32 vol; | ||
131 | // Sound length (20.12 fixed-point). | ||
132 | u32 length; | ||
133 | // Length of looped portion (20.12 fixed-point, 0 to disable looping). | ||
134 | u32 loop_length; | ||
135 | } Channel; | ||
136 | |||
137 | static Audio audio; | ||
138 | |||
139 | #define POLYPHONY 4 | ||
140 | static Channel channels[POLYPHONY]; | ||
141 | |||
197 | void | 142 | void |
198 | init_sound(AudioChannel *chan) { | 143 | init_sound(void) { |
199 | // chan->samples = &square_wave; | 144 | // Initialize audio buffers/channels. |
200 | // chan->n_samples = 2; | 145 | audio = (Audio){0}; |
201 | // chan->samples = &samples; | 146 | for (size_t i = 0; i < POLYPHONY; ++i) { |
202 | // chan->n_samples = LEN(samples); | 147 | channels[i] = (Channel){0}; |
203 | // chan->pitch++; | 148 | } |
204 | // chan->pitch = chan->pitch == LEN(pitch_table) - 1 ? 0 : chan->pitch + 1; | 149 | |
205 | // chan->loop = true; | 150 | // DEBUG: testing channel 0 with square wave |
206 | // chan->sampling_freq = pitch_table[chan->pitch]; | 151 | channels[0].data = samples; |
207 | // txt_printf("RATE: %lu", pitch_table[chan->pitch]) | 152 | channels[0].inc = (8363 << 12) / AUDIO_FREQ; |
153 | channels[0].length = (LEN(samples) - 1) << 12; | ||
154 | channels[0].pos = 0; | ||
155 | channels[0].vol = 64; | ||
156 | channels[0].loop_length = channels[0].length; | ||
157 | |||
158 | // Enable the sound chip. | ||
159 | SOUND_STATUS = SOUND_ENABLE; | ||
160 | |||
161 | // Set max volume, left-right sound, fifo reset and use timer 0 for | ||
162 | // DirectSound A. | ||
163 | SOUND_DSOUND_MASTER = SOUND_DSOUND_RATIO_A | ||
164 | | SOUND_DSOUND_LEFT_A | ||
165 | | SOUND_DSOUND_RIGHT_A | ||
166 | | SOUND_DSOUND_RESET_A; | ||
167 | |||
168 | // TODO: No pitch selection yet. | ||
169 | TIMER_DATA_0 = AUDIO_TIMER; | ||
170 | TIMER_CTRL_0 = TIMER_CTRL_ENABLE; | ||
171 | } | ||
172 | |||
173 | void sound_vsync() { | ||
174 | // buffer 1 just got over | ||
175 | if(audio.active_buffer == 1) { | ||
176 | // Start playing buffer 0 | ||
177 | dma_transfer_copy(SOUND_FIFO_A, audio.mix_buffer, 1, 1, | ||
178 | DMA_DST_FIXED | DMA_CHUNK_32 | DMA_REFRESH | DMA_REPEAT | DMA_ENABLE); | ||
179 | |||
180 | // Set the current buffer pointer to the start of buffer 1 | ||
181 | audio.current_buffer = audio.mix_buffer + AUDIO_BUF_LEN; | ||
182 | audio.active_buffer = 0; | ||
183 | } else { | ||
184 | // buffer 0 just got over | ||
185 | // DMA points to buffer 1 already, so don't bother stopping and resetting it | ||
186 | // Set the current buffer pointer to the start of buffer 0 | ||
187 | audio.current_buffer = audio.mix_buffer; | ||
188 | audio.active_buffer = 1; | ||
189 | } | ||
190 | } | ||
191 | |||
192 | void sound_mix() { | ||
193 | // Initialize and clear mix_buffer. | ||
194 | s16 mix_buffer[AUDIO_BUF_LEN]; | ||
195 | u32 fill = 0; | ||
196 | dma_fill(mix_buffer, fill, sizeof(mix_buffer), 3); | ||
197 | |||
198 | // Mix channels into the temporary buffer. | ||
199 | for (size_t j = 0; j < POLYPHONY; ++j) { | ||
200 | Channel *chan = &channels[j]; | ||
201 | // Check if channel is active. | ||
202 | if (chan->data == NULL) { | ||
203 | continue; | ||
204 | } | ||
205 | for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { | ||
206 | // Remember we are using fixed point values. | ||
207 | mix_buffer[i] += chan->data[chan->pos >> 12] * chan->vol; | ||
208 | chan->pos += chan->inc; | ||
209 | |||
210 | if (chan->pos >= chan->length) { | ||
211 | // If looping is not active disable the channel. | ||
212 | if (chan->loop_length == 0) { | ||
213 | chan->data = NULL; | ||
214 | break; | ||
215 | } | ||
216 | |||
217 | // Loop the sample. | ||
218 | while (chan->pos >= chan->length) { | ||
219 | chan->pos -= chan->loop_length; | ||
220 | } | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | |||
225 | // Downsample and copy to the playing buffer. | ||
226 | for (size_t i = 0; i < AUDIO_BUF_LEN; ++i) { | ||
227 | // >> 8 to divide off the volume, >> 2 to divide by 4 channels to | ||
228 | // prevent overflow. | ||
229 | audio.current_buffer[i] = mix_buffer[i] >> 8; | ||
230 | } | ||
208 | } | 231 | } |
209 | 232 | ||
210 | void | 233 | void |