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 | |