|
| 1 | +package flowtimer |
| 2 | + |
| 3 | +import "core:mem" |
| 4 | +import "core:slice" |
| 5 | +import "core:time" |
| 6 | +import ma "vendor:miniaudio" |
| 7 | + |
| 8 | +AUDIO_FORMAT :: ma.format.f32 |
| 9 | +AUDIO_SAMPLE_RATE :: 48000 |
| 10 | +AUDIO_CHANNELS :: 2 |
| 11 | +AUDIO_PERIOD_IN_FRAMES :: 16 |
| 12 | +AUDIO_BYTES_PER_FRAME := ma.get_bytes_per_frame(AUDIO_FORMAT, AUDIO_CHANNELS) |
| 13 | + |
| 14 | +Audio :: struct { |
| 15 | + pcm: []u8, |
| 16 | + frame_count: u32, |
| 17 | +} |
| 18 | + |
| 19 | +Metronome :: struct { |
| 20 | + device: ma.device, |
| 21 | + |
| 22 | + queued: ^Audio, |
| 23 | + frame_position: u32, |
| 24 | + |
| 25 | + buffer: Audio, |
| 26 | + beep: Audio, |
| 27 | +} |
| 28 | + |
| 29 | +metronome_data_proc :: proc "c" (device: ^ma.device, sink, _: rawptr, requested_frames: u32) { |
| 30 | + mem.zero(sink, int(audio_frames_to_bytes(requested_frames))) |
| 31 | + |
| 32 | + metronome := transmute(^Metronome)device.pUserData |
| 33 | + if metronome.queued == nil do return |
| 34 | + |
| 35 | + remaining := metronome.queued.frame_count - metronome.frame_position |
| 36 | + frames_to_copy := min(requested_frames, remaining) |
| 37 | + |
| 38 | + if frames_to_copy > 0 { |
| 39 | + src_offset := audio_frames_to_bytes(metronome.frame_position) |
| 40 | + mem.copy(sink, &metronome.queued.pcm[src_offset], int(audio_frames_to_bytes(frames_to_copy))) |
| 41 | + metronome.frame_position += frames_to_copy |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +audio_frames_to_bytes :: proc "contextless" (frame_count: u32) -> u32 { |
| 46 | + return frame_count * AUDIO_BYTES_PER_FRAME |
| 47 | +} |
| 48 | + |
| 49 | +duration_to_audio_frames :: proc "contextless" (duration: time.Duration) -> u32 { |
| 50 | + return u32(time.duration_seconds(duration) * f64(AUDIO_SAMPLE_RATE)) |
| 51 | +} |
| 52 | + |
| 53 | +resize_audio :: proc(audio: ^Audio, frame_count: u32) { |
| 54 | + delete(audio.pcm) |
| 55 | + audio.pcm = make([]u8, audio_frames_to_bytes(frame_count)) |
| 56 | + audio.frame_count = frame_count |
| 57 | +} |
| 58 | + |
| 59 | +create_metronome :: proc(metronome: ^Metronome) -> bool { |
| 60 | + config := ma.device_config_init(.playback) |
| 61 | + config.periodSizeInFrames = AUDIO_PERIOD_IN_FRAMES |
| 62 | + config.playback.format = AUDIO_FORMAT |
| 63 | + config.playback.channels = AUDIO_CHANNELS |
| 64 | + config.sampleRate = AUDIO_SAMPLE_RATE |
| 65 | + config.dataCallback = metronome_data_proc |
| 66 | + config.pUserData = metronome |
| 67 | + |
| 68 | + if ma.device_init(nil, &config, &metronome.device) != .SUCCESS do return false |
| 69 | + if ma.device_start(&metronome.device) != .SUCCESS do return false |
| 70 | + |
| 71 | + return true |
| 72 | +} |
| 73 | + |
| 74 | +delete_metronome :: proc(metronome: ^Metronome) -> bool { |
| 75 | + delete(metronome.buffer.pcm) |
| 76 | + delete(metronome.beep.pcm) |
| 77 | + |
| 78 | + if ma.device_stop(&metronome.device) != .SUCCESS do return false |
| 79 | + ma.device_uninit(&metronome.device) |
| 80 | + |
| 81 | + return true |
| 82 | +} |
| 83 | + |
| 84 | +set_beep :: proc(metronome: ^Metronome, filepath: cstring) -> bool { |
| 85 | + config := ma.decoder_config_init(AUDIO_FORMAT, AUDIO_CHANNELS, AUDIO_SAMPLE_RATE) |
| 86 | + |
| 87 | + decoder: ma.decoder |
| 88 | + if ma.decoder_init_file(filepath, &config, &decoder) != .SUCCESS do return false |
| 89 | + defer ma.decoder_uninit(&decoder) |
| 90 | + |
| 91 | + frame_count: u64 |
| 92 | + if ma.decoder_get_length_in_pcm_frames(&decoder, &frame_count) != .SUCCESS do return false |
| 93 | + resize_audio(&metronome.beep, u32(frame_count)) |
| 94 | + |
| 95 | + frames_read: u64 |
| 96 | + if ma.decoder_read_pcm_frames(&decoder, raw_data(metronome.beep.pcm), u64(metronome.beep.frame_count), &frames_read) != .SUCCESS do return false |
| 97 | + if frames_read != frame_count do return false |
| 98 | + |
| 99 | + return true |
| 100 | +} |
| 101 | + |
| 102 | +prepare_metronome :: proc(metronome: ^Metronome, offsets: []time.Duration, interval: time.Duration, beeps: int) { |
| 103 | + max_offset := slice.max(offsets) |
| 104 | + |
| 105 | + frame_count := duration_to_audio_frames(max_offset) + metronome.beep.frame_count |
| 106 | + resize_audio(&metronome.buffer, frame_count) |
| 107 | + |
| 108 | + for offset in offsets { |
| 109 | + for i in 0..<beeps { |
| 110 | + offset_duration := offset - time.Duration(i) * interval |
| 111 | + offset_frames := duration_to_audio_frames(offset_duration) |
| 112 | + offset_byte := audio_frames_to_bytes(offset_frames) |
| 113 | + copy(metronome.buffer.pcm[offset_byte:], metronome.beep.pcm) |
| 114 | + } |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +play_audio :: proc(metronome: ^Metronome, audio: ^Audio) { |
| 119 | + metronome.queued = audio |
| 120 | + metronome.frame_position = 0 |
| 121 | +} |
0 commit comments