// Calculated as ((261.6 / 18157) << 17) for C4. If multiplied by sampling rate // we will have a u32 (15.17) fixed-point number. This should be enough to // accurately portray samples up to 75300 Hz. static u16 pitch_table[120] = { 59, 62, 66, 70, 74, 78, 83, 88, 93, 99, 105, 111, 118, 125, 132, 140, 148, 157, 166, 176, 187, 198, 210, 222, 236, 250, 264, 280, 297, 315, 333, 353, 374, 396, 420, 445, 472, 500, 529, 561, 594, 630, 667, 707, 749, 793, 841, 891, 944, 1000, 1059, 1122, 1189, 1260, 1335, 1414, 1498, 1587, 1682, 1782, 1888, 2000, 2119, 2245, 2379, 2520, 2670, 2829, 2997, 3175, 3364, 3564, 3776, 4001, 4239, 4491, 4758, 5041, 5341, 5658, 5995, 6351, 6729, 7129, 7553, 8002, 8478, 8982, 9517, 10083, 10682, 11317, 11990, 12703, 13459, 14259, 15107, 16005, 16957, 17965, 19034, 20166, 21365, 22635, 23981, 25407, 26918, 28519, 30215, 32011, 33915, 35931, 38068, 40332, 42730, 45271, 47963, 50815, 53837, 57038, }; // // REG_TM0D frequency buffer size // | | | // V V V // // Timer = 62610 = 65536 - (16777216 / 5734), buf = 96 // Timer = 63940 = 65536 - (16777216 / 10512), buf = 176 // Timer = 64282 = 65536 - (16777216 / 13379), buf = 224 // Timer = 64612 = 65536 - (16777216 / 18157), buf = 304 // Timer = 64738 = 65536 - (16777216 / 21024), buf = 352 // Timer = 64909 = 65536 - (16777216 / 26758), buf = 448 // Timer = 65004 = 65536 - (16777216 / 31536), buf = 528 // Timer = 65073 = 65536 - (16777216 / 36314), buf = 608 // Timer = 65118 = 65536 - (16777216 / 40137), buf = 672 // Timer = 65137 = 65536 - (16777216 / 42048), buf = 704 // // Source: https://deku.gbadev.org/program/sound1.html #define AUDIO_FREQ 18157 #define AUDIO_BUF_LEN 304 #define AUDIO_TIMER 64612 typedef struct Audio { s8 mix_buffer[AUDIO_BUF_LEN * 2]; s8 *current_buffer; u8 active_buffer; } Audio; typedef struct AudioChannel { // Pointer to the raw data in the ROM. s8 *data; // Current position in the data (20.12 fixed-point). u32 pos; // Increment (20.12 fixed-point). u32 inc; // Volume (0-64, 1.6 fixed-point). u32 vol; // Sound length (20.12 fixed-point). u32 length; // Length of looped portion (20.12 fixed-point, 0 to disable looping). u32 loop_length; // Pitch encoded as a MIDI note. u8 pitch; // Keeping track of the original adsr values. u16 adsr; // The filter is built with the ADSR and has a duration of 60 ticks. Since // weare synced at 60FPS it should be pretty straightforward. u8 filter[241]; // Current position in the filter (0-60). u8 filter_pos; // Duration of the filter. u8 filter_len; } AudioChannel; // NOTE: fixed-point (1.6) precision for volume adjustments. typedef u32 fp32; inline fp32 fp_mul(fp32 a, fp32 b) { return (a * b) >> 6; } inline fp32 fp_div(fp32 a, fp32 b) { return (a << 6) / b; } inline fp32 fp_lerp(fp32 y0, fp32 y1, fp32 x) { return y0 + fp_mul(x, (y1 - y0)); } IWRAM_CODE void build_adsr(AudioChannel *chan, u16 adsr) { // u8 a = (adsr >> 12); u8 a = (adsr >> 12); u8 d = (adsr >> 8) & 0xF; u8 s = (adsr >> 4) & 0xF; u8 r = (adsr >> 0) & 0xF; // Initialize the filter array. memset(chan->filter, 0, sizeof(chan->filter)); u8 k = 0; // Attack. for (u32 i = 0; i < 4 * a; ++i) { chan->filter[k++] = fp_lerp(0, chan->vol, fp_div(i << 6, (4 * a) << 6)); } // Decay. for (u32 i = 0; i < 4 * d; ++i) { chan->filter[k++] = fp_lerp(chan->vol, chan->vol / 2, fp_div(i << 6, (4 * d) << 6)); } // Sustain. for (u32 i = 0; i < 4 * s; ++i) { chan->filter[k++] = chan->vol / 2; } // Release. for (u32 i = 0; i < 4 * r; ++i) { chan->filter[k++] = fp_lerp(chan->vol / 2, 0, fp_div(i << 6, (4 * r) << 6)); } // Setup the channel vars. chan->adsr = adsr; chan->filter_pos = 0; chan->filter_len = k; } static Audio audio; #define POLYPHONY 4 static AudioChannel channels[POLYPHONY]; void init_sound(void) { // Initialize audio buffers/channels. audio = (Audio){0}; for (size_t i = 0; i < POLYPHONY; ++i) { channels[i] = (AudioChannel){0}; } // Enable the sound chip. SOUND_STATUS = SOUND_ENABLE; // Set max volume, left-right sound, fifo reset and use timer 0 for // DirectSound A. SOUND_DSOUND_MASTER = SOUND_DSOUND_RATIO_A | SOUND_DSOUND_LEFT_A | SOUND_DSOUND_RIGHT_A | SOUND_DSOUND_RESET_A; TIMER_DATA_0 = AUDIO_TIMER; TIMER_CTRL_0 = TIMER_CTRL_ENABLE; } void sound_vsync() { // buffer 1 just got over if(audio.active_buffer == 1) { // Start playing buffer 0 dma_transfer_copy(SOUND_FIFO_A, audio.mix_buffer, 1, 1, DMA_DST_FIXED | DMA_CHUNK_32 | DMA_REFRESH | DMA_REPEAT | DMA_ENABLE); // Set the current buffer pointer to the start of buffer 1 audio.current_buffer = audio.mix_buffer + AUDIO_BUF_LEN; audio.active_buffer = 0; } else { // buffer 0 just got over // DMA points to buffer 1 already, so don't bother stopping and resetting it // Set the current buffer pointer to the start of buffer 0 audio.current_buffer = audio.mix_buffer; audio.active_buffer = 1; } } IWRAM_CODE void sound_mix() { // Initialize and clear mix_buffer. s16 mix_buffer[AUDIO_BUF_LEN]; u32 fill = 0; dma_fill(mix_buffer, fill, sizeof(mix_buffer), 3); // Mix channels into the temporary buffer. for (size_t j = 0; j < POLYPHONY; ++j) { AudioChannel *ch = &channels[j]; // Check if channel is active. if (ch->data == NULL || ch->pitch >= 108) { continue; } u32 vol = ch->vol; if (ch->adsr != 0) { vol = ch->filter[ch->filter_pos++]; if (ch->filter_pos == ch->filter_len) { continue; } } if (ch->pos + ch->inc * AUDIO_BUF_LEN >= ch->length) { // Sample is going to finish, need to consider this for looping or // stopping. for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { // Remember we are using fixed point values. mix_buffer[i] += (0x80 + (u8)ch->data[ch->pos >> 12]) * vol; ch->pos += ch->inc; if (ch->pos >= ch->length) { // If looping is not active disable the channel. if (ch->loop_length == 0) { ch->data = NULL; break; } // Loop the sample. while (ch->pos >= ch->length) { ch->pos -= ch->loop_length; } } } } else { // Sample still have room to go, no need to check for looping or // end of sample. for(size_t i = 0; i < AUDIO_BUF_LEN; i++) { mix_buffer[i] += (0x80 + (u8)ch->data[ch->pos>>12]) * ch->vol; ch->pos += ch->inc; } } } // Downsample and copy to the playing buffer. for (size_t i = 0; i < AUDIO_BUF_LEN; ++i) { // >> 6 to divide off the volume, >> 2 to divide by 4 channels to // prevent overflow. audio.current_buffer[i] = mix_buffer[i] >> 8; } }