169 lines
5.0 KiB
Plaintext
169 lines
5.0 KiB
Plaintext
#if OS != .WASM {
|
|
Thread :: #import "Thread";
|
|
}
|
|
|
|
Mixer_Bus :: enum {
|
|
MUSIC;
|
|
SOUND_EFFECT;
|
|
DIALOGUE;
|
|
}
|
|
|
|
Play_Mode :: enum {
|
|
ONESHOT;
|
|
REPEAT;
|
|
}
|
|
|
|
Mixer_Play_Task :: struct {
|
|
audio : *Audio_Data;
|
|
bus : Mixer_Bus;
|
|
mode : Play_Mode;
|
|
unique : bool = true;
|
|
curSample : s64 = 0; // output frames consumed
|
|
|
|
startTime : float64 = 0;
|
|
delay : float64 = 0;
|
|
silenceBeforeRepeat : float64 = 0;
|
|
}
|
|
|
|
Mixer_Config :: struct {
|
|
musicVolume : float = 0.5; @Slider,0,1,0.1
|
|
masterVolume : float = 0.5; @Slider,0,1,0.1
|
|
dialogueVolume : float = 0.5; @Slider,0,1,0.1
|
|
soundEffectVolume : float = 0.3; @Slider,0,1,0.1
|
|
}
|
|
|
|
g_mixer : Mixer;
|
|
|
|
Mixer :: struct {
|
|
paused : bool = false;
|
|
#if OS != .WASM {
|
|
// If we are on WASM, the Thread module mutex does not work.
|
|
// Fortunately we don't need a mutex for this, since sokol does
|
|
// not do multithreading for audio on WASM.
|
|
//
|
|
// Let's just have the mutex as a part of the struct on all platforms
|
|
// except WASM. It would probably be a good idea to have a mutex wrapper
|
|
// thing that would use the Thread module Mutex or a mutex that works with
|
|
// WASM. :NoMutexForWasm
|
|
mutex : Thread.Mutex;
|
|
}
|
|
|
|
config : Mixer_Config;
|
|
tasks : [..]Mixer_Play_Task;
|
|
}
|
|
|
|
mixer_add_task :: (task: Mixer_Play_Task) {
|
|
mixer_lock(*g_mixer);
|
|
defer mixer_unlock(*g_mixer);
|
|
if task.unique {
|
|
for g_mixer.tasks {
|
|
if it.audio == task.audio then return;
|
|
}
|
|
}
|
|
array_add(*g_mixer.tasks, task);
|
|
}
|
|
|
|
mixer_add_task :: (audio: *Audio_Data, bus: Mixer_Bus, mode: Play_Mode) {
|
|
mixer_add_task(.{
|
|
audio = audio,
|
|
bus = bus,
|
|
mode = mode
|
|
});
|
|
}
|
|
|
|
mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) {
|
|
mixer_lock(*g_mixer);
|
|
defer mixer_unlock(*g_mixer);
|
|
|
|
total_samples := frame_count * channel_count;
|
|
memset(buffer, 0, total_samples * size_of(float));
|
|
|
|
if g_mixer.paused then return;
|
|
if g_mixer.tasks.count < 1 then return;
|
|
|
|
for < i : 0..g_mixer.tasks.count-1 {
|
|
task := *g_mixer.tasks[i];
|
|
|
|
if !task.audio {
|
|
array_unordered_remove_by_index(*g_mixer.tasks, i);
|
|
continue;
|
|
}
|
|
|
|
bus_vol := 1.0;
|
|
if task.bus == {
|
|
case .MUSIC; bus_vol = g_mixer.config.musicVolume;
|
|
case .SOUND_EFFECT; bus_vol = g_mixer.config.soundEffectVolume;
|
|
case .DIALOGUE; bus_vol = g_mixer.config.dialogueVolume;
|
|
}
|
|
|
|
vol := bus_vol * g_mixer.config.masterVolume;
|
|
|
|
source_data := task.audio.data;
|
|
source_channels := cast(s64) task.audio.channels;
|
|
src_rate := cast(s64) task.audio.sample_rate;
|
|
out_rate := cast(s64) saudio_sample_rate();
|
|
num_src_frames := source_data.count / source_channels;
|
|
|
|
task_finished := false;
|
|
|
|
for f : 0..frame_count-1 {
|
|
src_pos := cast(float64)(task.curSample + f) * cast(float64)src_rate / cast(float64)out_rate;
|
|
src_frame0 := cast(s64) src_pos;
|
|
src_frame1 := src_frame0 + 1;
|
|
t := cast(float)(src_pos - cast(float64)src_frame0);
|
|
|
|
if src_frame0 >= num_src_frames {
|
|
if task.mode == .ONESHOT {
|
|
task_finished = true;
|
|
break;
|
|
} else {
|
|
src_frame0 = src_frame0 % num_src_frames;
|
|
src_frame1 = src_frame1 % num_src_frames;
|
|
}
|
|
} else if src_frame1 >= num_src_frames {
|
|
// At the very last source frame: clamp for ONESHOT, wrap for REPEAT.
|
|
src_frame1 = ifx task.mode == .ONESHOT then src_frame0 else 0;
|
|
}
|
|
|
|
for ch : 0..channel_count-1 {
|
|
out_index := (f * channel_count) + ch;
|
|
src_ch := ifx source_channels == 1 then 0 else ch;
|
|
|
|
s0 := source_data[src_frame0 * source_channels + src_ch];
|
|
s1 := source_data[src_frame1 * source_channels + src_ch];
|
|
|
|
buffer[out_index] += (s0 + (s1 - s0) * t) * vol;
|
|
}
|
|
}
|
|
|
|
task.curSample += frame_count;
|
|
|
|
// Keep curSample bounded for REPEAT tracks so it never overflows.
|
|
if task.mode == .REPEAT && out_rate > 0 && src_rate > 0 {
|
|
loop_period := num_src_frames * out_rate / src_rate;
|
|
if loop_period > 0 then task.curSample = task.curSample % loop_period;
|
|
}
|
|
|
|
if task_finished {
|
|
array_unordered_remove_by_index(*g_mixer.tasks, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
mixer_lock :: (mixer: *Mixer) {
|
|
#if OS != .WASM {
|
|
Thread.lock(*mixer.mutex);
|
|
} else {
|
|
return; // On WASM this is a no-op. See :NoMutexForWasm
|
|
}
|
|
}
|
|
|
|
mixer_unlock :: (mixer: *Mixer) {
|
|
#if OS != .WASM {
|
|
Thread.unlock(*mixer.mutex);
|
|
} else {
|
|
return; // On WASM this is a no-op. See :NoMutexForWasm
|
|
}
|
|
|
|
}
|