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
35static 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 };
36static_assert(COUNT_OF(NoteFreqs) == NOTES * OCTAVES + PIANO_START, "count_of_freqs");
37static_assert(sizeof(tic_sound_register) == 16 + 2, "tic_sound_register");
38static_assert(sizeof(tic_sample) == 66, "tic_sample");
39static_assert(sizeof(tic_track_pattern) == 3 * MUSIC_PATTERN_ROWS, "tic_track_pattern");
40static_assert(sizeof(tic_track) == 3 * MUSIC_FRAMES + 3, "tic_track");
41static_assert(tic_music_cmd_count == 1 << MUSIC_CMD_BITS, "tic_music_cmd_count");
42static_assert(sizeof(tic_music_state) == 4, "tic_music_state_size");
43
44static 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
51static 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
58static 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
67static 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
75static inline s32 param2val(const tic_track_row* row)
76{
77 return (row->param1 << 4) | row->param2;
78}
79
80static 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
87static 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
101static 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
107static 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
119static 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
135static 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
153static void resetSfxPos(tic_channel_data* channel)
154{
155 memset(channel->pos->data, -1, sizeof(tic_sfx_pos));
156 channel->tick = -1;
157}
158
159static 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
199static 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
220static 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
226static 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
236static void stopMusic(tic_mem* memory)
237{
238 tic_api_music(memory, -1, 0, 0, false, false, -1, -1);
239}
240
241static 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
453static 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
459static 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
489void 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
499void 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
505static 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(&reg->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
526void 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
546void 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
566void 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