| 1 | // MIT License |
| 2 | |
| 3 | // Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
| 4 | |
| 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
| 6 | // of this software and associated documentation files (the "Software"), to deal |
| 7 | // in the Software without restriction, including without limitation the rights |
| 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 9 | // copies of the Software, and to permit persons to whom the Software is |
| 10 | // furnished to do so, subject to the following conditions: |
| 11 | |
| 12 | // The above copyright notice and this permission notice shall be included in all |
| 13 | // copies or substantial portions of the Software. |
| 14 | |
| 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 21 | // SOFTWARE. |
| 22 | |
| 23 | |
| 24 | #include "api.h" |
| 25 | #include "core.h" |
| 26 | |
| 27 | #include <string.h> |
| 28 | #include "tic_assert.h" |
| 29 | |
| 30 | #define ENVELOPE_FREQ_SCALE 2 |
| 31 | #define SECONDS_PER_MINUTE 60 |
| 32 | #define NOTES_PER_MUNUTE (TIC80_FRAMERATE / NOTES_PER_BEAT * SECONDS_PER_MINUTE) |
| 33 | #define PIANO_START 8 |
| 34 | |
| 35 | static const u16 NoteFreqs[] = { 0x10, 0x11, 0x12, 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1c, 0x1d, 0x1f, 0x21, 0x23, 0x25, 0x27, 0x29, 0x2c, 0x2e, 0x31, 0x34, 0x37, 0x3a, 0x3e, 0x41, 0x45, 0x49, 0x4e, 0x52, 0x57, 0x5c, 0x62, 0x68, 0x6e, 0x75, 0x7b, 0x83, 0x8b, 0x93, 0x9c, 0xa5, 0xaf, 0xb9, 0xc4, 0xd0, 0xdc, 0xe9, 0xf7, 0x106, 0x115, 0x126, 0x137, 0x14a, 0x15d, 0x172, 0x188, 0x19f, 0x1b8, 0x1d2, 0x1ee, 0x20b, 0x22a, 0x24b, 0x26e, 0x293, 0x2ba, 0x2e4, 0x310, 0x33f, 0x370, 0x3a4, 0x3dc, 0x417, 0x455, 0x497, 0x4dd, 0x527, 0x575, 0x5c8, 0x620, 0x67d, 0x6e0, 0x749, 0x7b8, 0x82d, 0x8a9, 0x92d, 0x9b9, 0xa4d, 0xaea, 0xb90, 0xc40, 0xcfa, 0xdc0, 0xe91, 0xf6f, 0x105a, 0x1153, 0x125b, 0x1372, 0x149a, 0x15d4, 0x1720, 0x1880 }; |
| 36 | static_assert(COUNT_OF(NoteFreqs) == NOTES * OCTAVES + PIANO_START, "count_of_freqs" ); |
| 37 | static_assert(sizeof(tic_sound_register) == 16 + 2, "tic_sound_register" ); |
| 38 | static_assert(sizeof(tic_sample) == 66, "tic_sample" ); |
| 39 | static_assert(sizeof(tic_track_pattern) == 3 * MUSIC_PATTERN_ROWS, "tic_track_pattern" ); |
| 40 | static_assert(sizeof(tic_track) == 3 * MUSIC_FRAMES + 3, "tic_track" ); |
| 41 | static_assert(tic_music_cmd_count == 1 << MUSIC_CMD_BITS, "tic_music_cmd_count" ); |
| 42 | static_assert(sizeof(tic_music_state) == 4, "tic_music_state_size" ); |
| 43 | |
| 44 | static s32 getTempo(tic_core* core, const tic_track* track) |
| 45 | { |
| 46 | return core->state.music.tempo < 0 |
| 47 | ? track->tempo + DEFAULT_TEMPO |
| 48 | : core->state.music.tempo; |
| 49 | } |
| 50 | |
| 51 | static s32 getSpeed(tic_core* core, const tic_track* track) |
| 52 | { |
| 53 | return core->state.music.speed < 0 |
| 54 | ? track->speed + DEFAULT_SPEED |
| 55 | : core->state.music.speed; |
| 56 | } |
| 57 | |
| 58 | static s32 tick2row(tic_core* core, const tic_track* track, s32 tick) |
| 59 | { |
| 60 | // BPM = tempo * 6 / speed |
| 61 | s32 speed = getSpeed(core, track); |
| 62 | return speed |
| 63 | ? tick * getTempo(core, track) * DEFAULT_SPEED / speed / NOTES_PER_MUNUTE |
| 64 | : 0; |
| 65 | } |
| 66 | |
| 67 | static s32 row2tick(tic_core* core, const tic_track* track, s32 row) |
| 68 | { |
| 69 | s32 tempo = getTempo(core, track); |
| 70 | return tempo |
| 71 | ? row * getSpeed(core, track) * NOTES_PER_MUNUTE / tempo / DEFAULT_SPEED |
| 72 | : 0; |
| 73 | } |
| 74 | |
| 75 | static inline s32 param2val(const tic_track_row* row) |
| 76 | { |
| 77 | return (row->param1 << 4) | row->param2; |
| 78 | } |
| 79 | |
| 80 | static void update_amp(blip_buffer_t* blip, tic_sound_register_data* data, s32 new_amp) |
| 81 | { |
| 82 | s32 delta = new_amp - data->amp; |
| 83 | data->amp += delta; |
| 84 | blip_add_delta(blip, data->time, delta); |
| 85 | } |
| 86 | |
| 87 | static inline s32 freq2period(s32 freq) |
| 88 | { |
| 89 | enum |
| 90 | { |
| 91 | MinPeriodValue = 10, |
| 92 | MaxPeriodValue = 4096, |
| 93 | Rate = CLOCKRATE * ENVELOPE_FREQ_SCALE / WAVE_VALUES |
| 94 | }; |
| 95 | |
| 96 | if (freq == 0) return MaxPeriodValue; |
| 97 | |
| 98 | return CLAMP(Rate / freq - 1, MinPeriodValue, MaxPeriodValue); |
| 99 | } |
| 100 | |
| 101 | static inline s32 getAmp(const tic_sound_register* reg, s32 amp) |
| 102 | { |
| 103 | enum { AmpMax = (u16)-1 / 2 }; |
| 104 | return (amp * AmpMax / MAX_VOLUME) * reg->volume / MAX_VOLUME / TIC_SOUND_CHANNELS; |
| 105 | } |
| 106 | |
| 107 | static void runEnvelope(blip_buffer_t* blip, const tic_sound_register* reg, tic_sound_register_data* data, s32 end_time, u8 volume) |
| 108 | { |
| 109 | s32 period = freq2period(tic_sound_register_get_freq(reg) * ENVELOPE_FREQ_SCALE); |
| 110 | |
| 111 | for (; data->time < end_time; data->time += period) |
| 112 | { |
| 113 | data->phase = (data->phase + 1) % WAVE_VALUES; |
| 114 | |
| 115 | update_amp(blip, data, getAmp(reg, tic_tool_peek4(reg->waveform.data, data->phase) * volume / MAX_VOLUME)); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | static void runNoise(blip_buffer_t* blip, const tic_sound_register* reg, tic_sound_register_data* data, s32 end_time, u8 volume) |
| 120 | { |
| 121 | // phase is noise LFSR, which must never be zero |
| 122 | if (data->phase == 0) |
| 123 | data->phase = 1; |
| 124 | |
| 125 | s32 period = freq2period(tic_sound_register_get_freq(reg)); |
| 126 | s32 fb = *reg->waveform.data ? 0x14 : 0x12000; |
| 127 | |
| 128 | for (; data->time < end_time; data->time += period) |
| 129 | { |
| 130 | data->phase = ((data->phase & 1) * fb) ^ (data->phase >> 1); |
| 131 | update_amp(blip, data, getAmp(reg, (data->phase & 1) ? volume : 0)); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | static s32 calcLoopPos(const tic_sound_loop* loop, s32 pos) |
| 136 | { |
| 137 | s32 offset = 0; |
| 138 | |
| 139 | if (loop->size > 0) |
| 140 | { |
| 141 | for (s32 i = 0; i < pos; i++) |
| 142 | { |
| 143 | if (offset < (loop->start + loop->size - 1)) |
| 144 | offset++; |
| 145 | else offset = loop->start; |
| 146 | } |
| 147 | } |
| 148 | else offset = pos >= SFX_TICKS ? SFX_TICKS - 1 : pos; |
| 149 | |
| 150 | return offset; |
| 151 | } |
| 152 | |
| 153 | static void resetSfxPos(tic_channel_data* channel) |
| 154 | { |
| 155 | memset(channel->pos->data, -1, sizeof(tic_sfx_pos)); |
| 156 | channel->tick = -1; |
| 157 | } |
| 158 | |
| 159 | static void sfx(tic_mem* memory, s32 index, s32 note, s32 pitch, tic_channel_data* channel, tic_sound_register* reg, s32 channelIndex) |
| 160 | { |
| 161 | tic_core* core = (tic_core*)memory; |
| 162 | |
| 163 | if (channel->duration > 0) |
| 164 | channel->duration--; |
| 165 | |
| 166 | if (index < 0 || channel->duration == 0) |
| 167 | { |
| 168 | resetSfxPos(channel); |
| 169 | return; |
| 170 | } |
| 171 | |
| 172 | const tic_sample* effect = &memory->ram->sfx.samples.data[index]; |
| 173 | s32 pos = tic_tool_sfx_pos(channel->speed, ++channel->tick); |
| 174 | |
| 175 | for (s32 i = 0; i < sizeof(tic_sfx_pos); i++) |
| 176 | *(channel->pos->data + i) = calcLoopPos(effect->loops + i, pos); |
| 177 | |
| 178 | u8 volume = MAX_VOLUME - effect->data[channel->pos->volume].volume; |
| 179 | |
| 180 | if (volume > 0) |
| 181 | { |
| 182 | s8 arp = effect->data[channel->pos->chord].chord * (effect->reverse ? -1 : 1); |
| 183 | if (arp) note += arp; |
| 184 | |
| 185 | note = CLAMP(note, 0, COUNT_OF(NoteFreqs) - 1); |
| 186 | |
| 187 | tic_sound_register_set_freq(reg, NoteFreqs[note] + effect->data[channel->pos->pitch].pitch * (effect->pitch16x ? 16 : 1) + pitch); |
| 188 | reg->volume = volume; |
| 189 | |
| 190 | u8 wave = effect->data[channel->pos->wave].wave; |
| 191 | const tic_waveform* waveform = &memory->ram->sfx.waveforms.items[wave]; |
| 192 | memcpy(reg->waveform.data, waveform->data, sizeof(tic_waveform)); |
| 193 | |
| 194 | tic_tool_poke4(&memory->ram->stereo.data, channelIndex * 2, channel->volume.left * !effect->stereo_left); |
| 195 | tic_tool_poke4(&memory->ram->stereo.data, channelIndex * 2 + 1, channel->volume.right * !effect->stereo_right); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | static void setChannelData(tic_mem* memory, s32 index, s32 note, s32 octave, s32 duration, tic_channel_data* channel, s32 volumeLeft, s32 volumeRight, s32 speed) |
| 200 | { |
| 201 | tic_core* core = (tic_core*)memory; |
| 202 | |
| 203 | channel->volume.left = volumeLeft; |
| 204 | channel->volume.right = volumeRight; |
| 205 | |
| 206 | if (index >= 0) |
| 207 | { |
| 208 | struct { s8 speed : SFX_SPEED_BITS; } temp = { speed }; |
| 209 | channel->speed = speed == temp.speed ? speed : memory->ram->sfx.samples.data[index].speed; |
| 210 | } |
| 211 | |
| 212 | channel->note = note + octave * NOTES; |
| 213 | channel->duration = duration; |
| 214 | channel->index = index; |
| 215 | |
| 216 | resetSfxPos(channel); |
| 217 | } |
| 218 | |
| 219 | |
| 220 | static void setMusicChannelData(tic_mem* memory, s32 index, s32 note, s32 octave, s32 left, s32 right, s32 channel) |
| 221 | { |
| 222 | tic_core* core = (tic_core*)memory; |
| 223 | setChannelData(memory, index, note, octave, -1, &core->state.music.channels[channel], left, right, SFX_DEF_SPEED); |
| 224 | } |
| 225 | |
| 226 | static void resetMusicChannels(tic_mem* memory) |
| 227 | { |
| 228 | for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++) |
| 229 | setMusicChannelData(memory, -1, 0, 0, 0, 0, c); |
| 230 | |
| 231 | tic_core* core = (tic_core*)memory; |
| 232 | memset(core->state.music.commands, 0, sizeof core->state.music.commands); |
| 233 | memset(&core->state.music.jump, 0, sizeof(tic_jump_command)); |
| 234 | } |
| 235 | |
| 236 | static void stopMusic(tic_mem* memory) |
| 237 | { |
| 238 | tic_api_music(memory, -1, 0, 0, false, false, -1, -1); |
| 239 | } |
| 240 | |
| 241 | static void processMusic(tic_mem* memory) |
| 242 | { |
| 243 | tic_core* core = (tic_core*)memory; |
| 244 | tic_music_state* music_state = &memory->ram->music_state; |
| 245 | |
| 246 | if (music_state->flag.music_status == tic_music_stop) return; |
| 247 | |
| 248 | const tic_track* track = &memory->ram->music.tracks.data[music_state->music.track]; |
| 249 | s32 row = tick2row(core, track, core->state.music.ticks); |
| 250 | tic_jump_command* jumpCmd = &core->state.music.jump; |
| 251 | |
| 252 | if (row != music_state->music.row |
| 253 | && jumpCmd->active) |
| 254 | { |
| 255 | music_state->music.frame = jumpCmd->frame; |
| 256 | row = jumpCmd->beat * NOTES_PER_BEAT; |
| 257 | core->state.music.ticks = row2tick(core, track, row); |
| 258 | memset(jumpCmd, 0, sizeof(tic_jump_command)); |
| 259 | } |
| 260 | |
| 261 | s32 rows = MUSIC_PATTERN_ROWS - track->rows; |
| 262 | if (row >= rows) |
| 263 | { |
| 264 | row = 0; |
| 265 | core->state.music.ticks = 0; |
| 266 | |
| 267 | // If music is in sustain mode, we only reset the channels if the music stopped. |
| 268 | // Otherwise, we reset it on every new frame. |
| 269 | if (music_state->flag.music_status == tic_music_stop || !music_state->flag.music_sustain) |
| 270 | { |
| 271 | resetMusicChannels(memory); |
| 272 | |
| 273 | for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++) |
| 274 | setMusicChannelData(memory, -1, 0, 0, MAX_VOLUME, MAX_VOLUME, c); |
| 275 | } |
| 276 | |
| 277 | if (music_state->flag.music_status == tic_music_play) |
| 278 | { |
| 279 | music_state->music.frame++; |
| 280 | |
| 281 | if (music_state->music.frame >= MUSIC_FRAMES) |
| 282 | { |
| 283 | if (music_state->flag.music_loop) |
| 284 | music_state->music.frame = 0; |
| 285 | else |
| 286 | { |
| 287 | stopMusic(memory); |
| 288 | return; |
| 289 | } |
| 290 | } |
| 291 | else |
| 292 | { |
| 293 | s32 val = 0; |
| 294 | for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++) |
| 295 | val += tic_tool_get_pattern_id(track, music_state->music.frame, c); |
| 296 | |
| 297 | // empty frame detected |
| 298 | if (!val) |
| 299 | { |
| 300 | if (music_state->flag.music_loop) |
| 301 | music_state->music.frame = 0; |
| 302 | else |
| 303 | { |
| 304 | stopMusic(memory); |
| 305 | return; |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | else if (music_state->flag.music_status == tic_music_play_frame) |
| 311 | { |
| 312 | if (!music_state->flag.music_loop) |
| 313 | { |
| 314 | stopMusic(memory); |
| 315 | return; |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | if (row != music_state->music.row) |
| 321 | { |
| 322 | music_state->music.row = row; |
| 323 | |
| 324 | for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++) |
| 325 | { |
| 326 | s32 patternId = tic_tool_get_pattern_id(track, music_state->music.frame, c); |
| 327 | if (!patternId) continue; |
| 328 | |
| 329 | const tic_track_pattern* pattern = &memory->ram->music.patterns.data[patternId - PATTERN_START]; |
| 330 | const tic_track_row* trackRow = &pattern->rows[music_state->music.row]; |
| 331 | tic_channel_data* channel = &core->state.music.channels[c]; |
| 332 | tic_command_data* cmdData = &core->state.music.commands[c]; |
| 333 | |
| 334 | if (trackRow->command == tic_music_cmd_delay) |
| 335 | { |
| 336 | cmdData->delay.row = trackRow; |
| 337 | cmdData->delay.ticks = param2val(trackRow); |
| 338 | trackRow = NULL; |
| 339 | } |
| 340 | |
| 341 | if (cmdData->delay.row && cmdData->delay.ticks == 0) |
| 342 | { |
| 343 | trackRow = cmdData->delay.row; |
| 344 | cmdData->delay.row = NULL; |
| 345 | } |
| 346 | |
| 347 | if (trackRow) |
| 348 | { |
| 349 | // reset commands data |
| 350 | if (trackRow->note) |
| 351 | { |
| 352 | cmdData->slide.tick = 0; |
| 353 | cmdData->slide.note = channel->note; |
| 354 | } |
| 355 | |
| 356 | if (trackRow->note == NoteStop) |
| 357 | setMusicChannelData(memory, -1, 0, 0, channel->volume.left, channel->volume.right, c); |
| 358 | else if (trackRow->note >= NoteStart) |
| 359 | setMusicChannelData(memory, tic_tool_get_track_row_sfx(trackRow), trackRow->note - NoteStart, trackRow->octave, |
| 360 | channel->volume.left, channel->volume.right, c); |
| 361 | |
| 362 | switch (trackRow->command) |
| 363 | { |
| 364 | case tic_music_cmd_volume: |
| 365 | channel->volume.left = trackRow->param1; |
| 366 | channel->volume.right = trackRow->param2; |
| 367 | break; |
| 368 | |
| 369 | case tic_music_cmd_chord: |
| 370 | cmdData->chord.tick = 0; |
| 371 | cmdData->chord.note1 = trackRow->param1; |
| 372 | cmdData->chord.note2 = trackRow->param2; |
| 373 | break; |
| 374 | |
| 375 | case tic_music_cmd_jump: |
| 376 | core->state.music.jump.active = true; |
| 377 | core->state.music.jump.frame = trackRow->param1; |
| 378 | core->state.music.jump.beat = trackRow->param2; |
| 379 | break; |
| 380 | |
| 381 | case tic_music_cmd_vibrato: |
| 382 | cmdData->vibrato.tick = 0; |
| 383 | cmdData->vibrato.period = trackRow->param1; |
| 384 | cmdData->vibrato.depth = trackRow->param2; |
| 385 | break; |
| 386 | |
| 387 | case tic_music_cmd_slide: |
| 388 | cmdData->slide.duration = param2val(trackRow); |
| 389 | break; |
| 390 | |
| 391 | case tic_music_cmd_pitch: |
| 392 | cmdData->finepitch.value = param2val(trackRow) - PITCH_DELTA; |
| 393 | break; |
| 394 | |
| 395 | default: break; |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i) |
| 402 | { |
| 403 | tic_channel_data* channel = &core->state.music.channels[i]; |
| 404 | tic_command_data* cmdData = &core->state.music.commands[i]; |
| 405 | |
| 406 | if (channel->index >= 0) |
| 407 | { |
| 408 | s32 note = channel->note; |
| 409 | s32 pitch = 0; |
| 410 | |
| 411 | // process chord commmand |
| 412 | { |
| 413 | s32 chord[] = |
| 414 | { |
| 415 | 0, |
| 416 | cmdData->chord.note1, |
| 417 | cmdData->chord.note2 |
| 418 | }; |
| 419 | |
| 420 | note += chord[cmdData->chord.tick % (cmdData->chord.note2 == 0 ? 2 : 3)]; |
| 421 | } |
| 422 | |
| 423 | // process vibrato commmand |
| 424 | if (cmdData->vibrato.period && cmdData->vibrato.depth) |
| 425 | { |
| 426 | static const s32 VibData[] = { 0x0, 0x31f1, 0x61f8, 0x8e3a, 0xb505, 0xd4db, 0xec83, 0xfb15, 0x10000, 0xfb15, 0xec83, 0xd4db, 0xb505, 0x8e3a, 0x61f8, 0x31f1, 0x0, 0xffffce0f, 0xffff9e08, 0xffff71c6, 0xffff4afb, 0xffff2b25, 0xffff137d, 0xffff04eb, 0xffff0000, 0xffff04eb, 0xffff137d, 0xffff2b25, 0xffff4afb, 0xffff71c6, 0xffff9e08, 0xffffce0f }; |
| 427 | static_assert(COUNT_OF(VibData) == 32, "VibData" ); |
| 428 | |
| 429 | s32 p = cmdData->vibrato.period << 1; |
| 430 | pitch += (VibData[(cmdData->vibrato.tick % p) * COUNT_OF(VibData) / p] * cmdData->vibrato.depth) >> 16; |
| 431 | } |
| 432 | |
| 433 | // process slide command |
| 434 | if (cmdData->slide.tick < cmdData->slide.duration) |
| 435 | pitch += (NoteFreqs[channel->note] - NoteFreqs[note = cmdData->slide.note]) * cmdData->slide.tick / cmdData->slide.duration; |
| 436 | |
| 437 | pitch += cmdData->finepitch.value; |
| 438 | |
| 439 | sfx(memory, channel->index, note, pitch, channel, &memory->ram->registers[i], i); |
| 440 | } |
| 441 | |
| 442 | ++cmdData->chord.tick; |
| 443 | ++cmdData->vibrato.tick; |
| 444 | ++cmdData->slide.tick; |
| 445 | |
| 446 | if (cmdData->delay.ticks) |
| 447 | cmdData->delay.ticks--; |
| 448 | } |
| 449 | |
| 450 | core->state.music.ticks++; |
| 451 | } |
| 452 | |
| 453 | static void setSfxChannelData(tic_mem* memory, s32 index, s32 note, s32 octave, s32 duration, s32 channel, s32 left, s32 right, s32 speed) |
| 454 | { |
| 455 | tic_core* core = (tic_core*)memory; |
| 456 | setChannelData(memory, index, note, octave, duration, &core->state.sfx.channels[channel], left, right, speed); |
| 457 | } |
| 458 | |
| 459 | static void setMusic(tic_core* core, s32 index, s32 frame, s32 row, bool loop, bool sustain, s32 tempo, s32 speed) |
| 460 | { |
| 461 | tic_mem* memory = (tic_mem*)core; |
| 462 | tic_ram* ram = memory->ram; |
| 463 | |
| 464 | ram->music_state.music.track = index; |
| 465 | |
| 466 | if (index < 0) |
| 467 | { |
| 468 | ram->music_state.flag.music_status = tic_music_stop; |
| 469 | resetMusicChannels(memory); |
| 470 | } |
| 471 | else |
| 472 | { |
| 473 | for (s32 c = 0; c < TIC_SOUND_CHANNELS; c++) |
| 474 | setMusicChannelData(memory, -1, 0, 0, MAX_VOLUME, MAX_VOLUME, c); |
| 475 | |
| 476 | ram->music_state.music.row = -1; |
| 477 | ram->music_state.music.frame = frame < 0 ? 0 : frame; |
| 478 | ram->music_state.flag.music_loop = loop; |
| 479 | ram->music_state.flag.music_sustain = sustain; |
| 480 | ram->music_state.flag.music_status = tic_music_play; |
| 481 | |
| 482 | const tic_track* track = &ram->music.tracks.data[index]; |
| 483 | core->state.music.tempo = tempo; |
| 484 | core->state.music.speed = speed; |
| 485 | core->state.music.ticks = row >= 0 ? row2tick(core, track, row) : 0; |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | void tic_api_music(tic_mem* memory, s32 index, s32 frame, s32 row, bool loop, bool sustain, s32 tempo, s32 speed) |
| 490 | { |
| 491 | tic_core* core = (tic_core*)memory; |
| 492 | |
| 493 | setMusic(core, index, frame, row, loop, sustain, tempo, speed); |
| 494 | |
| 495 | if (index >= 0) |
| 496 | memory->ram->music_state.flag.music_status = tic_music_play; |
| 497 | } |
| 498 | |
| 499 | void tic_api_sfx(tic_mem* memory, s32 index, s32 note, s32 octave, s32 duration, s32 channel, s32 left, s32 right, s32 speed) |
| 500 | { |
| 501 | tic_core* core = (tic_core*)memory; |
| 502 | setSfxChannelData(memory, index, note, octave, duration, channel, left, right, speed); |
| 503 | } |
| 504 | |
| 505 | static void stereo_synthesize(tic_core* core, tic_sound_register_data* registers, blip_buffer_t* blip, u8 stereoRight) |
| 506 | { |
| 507 | enum { EndTime = CLOCKRATE / TIC80_FRAMERATE }; |
| 508 | s32 bufpos = (core->state.sound_ringbuf_tail + TIC_SOUND_RINGBUF_LEN - 1) % TIC_SOUND_RINGBUF_LEN; |
| 509 | for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i) |
| 510 | { |
| 511 | u8 volume = tic_tool_peek4(&core->state.sound_ringbuf[bufpos].stereo, stereoRight + i * 2); |
| 512 | |
| 513 | const tic_sound_register* reg = &core->state.sound_ringbuf[bufpos].registers[i]; |
| 514 | tic_sound_register_data* data = registers + i; |
| 515 | |
| 516 | tic_tool_noise(®->waveform) |
| 517 | ? runNoise(blip, reg, data, EndTime, volume) |
| 518 | : runEnvelope(blip, reg, data, EndTime, volume); |
| 519 | |
| 520 | data->time -= EndTime; |
| 521 | } |
| 522 | |
| 523 | blip_end_frame(blip, EndTime); |
| 524 | } |
| 525 | |
| 526 | void tic_core_synth_sound(tic_mem* memory) |
| 527 | { |
| 528 | tic_core* core = (tic_core*)memory; |
| 529 | |
| 530 | // synthesize sound using the register values found from the tail of the ring buffer |
| 531 | stereo_synthesize(core, core->state.registers.left, core->blip.left, 0); |
| 532 | stereo_synthesize(core, core->state.registers.right, core->blip.right, 1); |
| 533 | |
| 534 | blip_read_samples(core->blip.left, core->memory.product.samples.buffer, core->samplerate / TIC80_FRAMERATE, TIC80_SAMPLE_CHANNELS); |
| 535 | blip_read_samples(core->blip.right, core->memory.product.samples.buffer + 1, core->samplerate / TIC80_FRAMERATE, TIC80_SAMPLE_CHANNELS); |
| 536 | |
| 537 | // if the head has advanced, we can advance the tail too. Otherwise, we just |
| 538 | // keep synthesizing audio using the last known register values, so at least we don't get crackles |
| 539 | if (core->state.sound_ringbuf_tail != core->state.sound_ringbuf_head) { |
| 540 | // note: we assume storing a 32 bit integer is atomic, that should hold on pretty much any modern processor |
| 541 | // assuming it is aligned in memory (which it should be) |
| 542 | core->state.sound_ringbuf_tail = (core->state.sound_ringbuf_tail + 1) % TIC_SOUND_RINGBUF_LEN; |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | void tic_core_sound_tick_start(tic_mem* memory) |
| 547 | { |
| 548 | tic_core* core = (tic_core*)memory; |
| 549 | |
| 550 | for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i) |
| 551 | memset(&memory->ram->registers[i], 0, sizeof(tic_sound_register)); |
| 552 | |
| 553 | memory->ram->stereo.data = -1; |
| 554 | |
| 555 | processMusic(memory); |
| 556 | |
| 557 | for (s32 i = 0; i < TIC_SOUND_CHANNELS; ++i) |
| 558 | { |
| 559 | tic_channel_data* c = &core->state.sfx.channels[i]; |
| 560 | |
| 561 | if (c->index >= 0) |
| 562 | sfx(memory, c->index, c->note, 0, c, &memory->ram->registers[i], i); |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | void tic_core_sound_tick_end(tic_mem* memory) |
| 567 | { |
| 568 | tic_core* core = (tic_core*)memory; |
| 569 | |
| 570 | // instead of synthesizing the sound right away, push the sound registers to the head of a ring buffer |
| 571 | core->state.sound_ringbuf[core->state.sound_ringbuf_head].stereo = memory->ram->stereo; |
| 572 | memcpy(&core->state.sound_ringbuf[core->state.sound_ringbuf_head], &memory->ram->registers, sizeof(tic_sound_register[4])); |
| 573 | |
| 574 | if (core->state.sound_ringbuf_head != (core->state.sound_ringbuf_tail + TIC_SOUND_RINGBUF_LEN - 2) % TIC_SOUND_RINGBUF_LEN) { |
| 575 | // note: we assume storing a 32 bit integer is atomic, that should hold on pretty much any modern processor |
| 576 | // assuming it is aligned in memory (which it should be) |
| 577 | core->state.sound_ringbuf_head = (core->state.sound_ringbuf_head + 1) % TIC_SOUND_RINGBUF_LEN; |
| 578 | } |
| 579 | } |
| 580 | |