Volume control
This commit is contained in:
parent
c6bf5bc8bb
commit
fa36beaef3
5 changed files with 88 additions and 32 deletions
|
@ -52,6 +52,7 @@ Other than examples, a great way to learn is to delve into the code of whatever
|
||||||
|
|
||||||
## Known bugs
|
## Known bugs
|
||||||
- Window positioning & sizing is fucky wucky
|
- Window positioning & sizing is fucky wucky
|
||||||
|
- Converting 24-bit audio files doesn't really work
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
By default, the repository has an educational license that makes the engine free to use for personal projects.
|
By default, the repository has an educational license that makes the engine free to use for personal projects.
|
||||||
|
|
11
TODO
11
TODO
|
@ -1,17 +1,16 @@
|
||||||
|
|
||||||
- Audio
|
- Audio
|
||||||
- Player volume control
|
|
||||||
- Optimize
|
|
||||||
- Spam simd
|
|
||||||
- Concurrent jobs for players?
|
|
||||||
- Mega buffer for contiguous intermediate buffers
|
|
||||||
We definitely also want a limit to how much memory we want allocated to intermediate buffers.
|
|
||||||
- Avoid playing identical audio at the same time
|
- Avoid playing identical audio at the same time
|
||||||
- Custom audio mixing
|
- Custom audio mixing
|
||||||
- Fix vorbis >FILE< streaming (#StbVorbisFileStream)
|
- Fix vorbis >FILE< streaming (#StbVorbisFileStream)
|
||||||
- Handle audio sources which had their format defaulted to update when default device changes
|
- Handle audio sources which had their format defaulted to update when default device changes
|
||||||
For streamed audio sources this should be easy enough, because the conversion happens from raw format to source format as we stream it.
|
For streamed audio sources this should be easy enough, because the conversion happens from raw format to source format as we stream it.
|
||||||
For loaded sources though, we would need to convert the source->pcm_frames.
|
For loaded sources though, we would need to convert the source->pcm_frames.
|
||||||
|
- Optimize
|
||||||
|
- Spam simd
|
||||||
|
- Concurrent jobs for players?
|
||||||
|
- Mega buffer for contiguous intermediate buffers
|
||||||
|
We definitely also want a limit to how much memory we want allocated to intermediate buffers.
|
||||||
- Bugs:
|
- Bugs:
|
||||||
- Small fadeout on pause is slightly noisy
|
- Small fadeout on pause is slightly noisy
|
||||||
- Setting time stamp/progression causes noise (need fade transition like on pause/play)
|
- Setting time stamp/progression causes noise (need fade transition like on pause/play)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
- Added position overloads for play_one_clip
|
- Added position overloads for play_one_clip
|
||||||
play_one_audio_clip_source_at_position(source, pos)
|
play_one_audio_clip_source_at_position(source, pos)
|
||||||
play_one_audio_clip_at_position(path, pos)
|
play_one_audio_clip_at_position(path, pos)
|
||||||
|
- Implemented volume control with player->volume
|
||||||
- Renderer
|
- Renderer
|
||||||
- Added draw_line(p0, p1, width, color)
|
- Added draw_line(p0, p1, width, color)
|
||||||
- Implemented culling of quads out of view
|
- Implemented culling of quads out of view
|
||||||
|
|
|
@ -390,8 +390,8 @@ wav_read_frames(Wav_Stream *wav, Audio_Format format, void *frames,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool raw_is_float = wav->format == 0x0003;
|
bool raw_is_float32 = wav->format == 0x0003;
|
||||||
bool raw_is_f32 = raw_is_float && wav->valid_bits_per_sample == 32;
|
bool raw_is_f32 = raw_is_float32 && wav->valid_bits_per_sample == 32;
|
||||||
bool raw_is_int = wav->format == 0x0001;
|
bool raw_is_int = wav->format == 0x0001;
|
||||||
bool raw_is_s16 = raw_is_int && wav->valid_bits_per_sample == 16;
|
bool raw_is_s16 = raw_is_int && wav->valid_bits_per_sample == 16;
|
||||||
|
|
||||||
|
@ -981,8 +981,8 @@ resample_frames(void *dst, Audio_Format dst_format,
|
||||||
void *dst_comp = (u8*)dst_frame + c * dst_comp_size;
|
void *dst_comp = (u8*)dst_frame + c * dst_comp_size;
|
||||||
|
|
||||||
if (src_format.bit_width == AUDIO_BITS_32) {
|
if (src_format.bit_width == AUDIO_BITS_32) {
|
||||||
float sample_1 = *((f32*)src_comp_1);
|
float32 sample_1 = *((f32*)src_comp_1);
|
||||||
float sample_2 = *((f32*)src_comp_2);
|
float32 sample_2 = *((f32*)src_comp_2);
|
||||||
f32 s = sample_1 + lerp_factor * (sample_2 - sample_1);
|
f32 s = sample_1 + lerp_factor * (sample_2 - sample_1);
|
||||||
memcpy(dst_comp, &s, sizeof(f32));
|
memcpy(dst_comp, &s, sizeof(f32));
|
||||||
} else if (src_format.bit_width == AUDIO_BITS_16) {
|
} else if (src_format.bit_width == AUDIO_BITS_16) {
|
||||||
|
@ -1164,6 +1164,7 @@ typedef struct Audio_Player {
|
||||||
// These can be set safely
|
// These can be set safely
|
||||||
Vector3 position; // ndc space -1 to 1
|
Vector3 position; // ndc space -1 to 1
|
||||||
bool disable_spacialization;
|
bool disable_spacialization;
|
||||||
|
float32 volume;
|
||||||
|
|
||||||
} Audio_Player;
|
} Audio_Player;
|
||||||
#define AUDIO_PLAYERS_PER_BLOCK 128
|
#define AUDIO_PLAYERS_PER_BLOCK 128
|
||||||
|
@ -1188,6 +1189,7 @@ audio_player_get_one() {
|
||||||
|
|
||||||
memset(&block->players[i], 0, sizeof(block->players[i]));
|
memset(&block->players[i], 0, sizeof(block->players[i]));
|
||||||
block->players[i].allocated = true;
|
block->players[i].allocated = true;
|
||||||
|
block->players[i].volume = 1.0;
|
||||||
|
|
||||||
return &block->players[i];
|
return &block->players[i];
|
||||||
}
|
}
|
||||||
|
@ -1208,6 +1210,7 @@ audio_player_get_one() {
|
||||||
last->next = new_block;
|
last->next = new_block;
|
||||||
|
|
||||||
new_block->players[0].allocated = true;
|
new_block->players[0].allocated = true;
|
||||||
|
new_block->players[0].volume = 1.0;
|
||||||
return &new_block->players[0];
|
return &new_block->players[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1424,22 +1427,22 @@ audio_apply_fade_out(void *frames, u64 number_of_frames, Audio_Format format,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void spacialize_audio_mono(void* frames, Audio_Format format, u64 number_of_frames, Vector3 pos) {
|
void apply_audio_spacialization_mono(void* frames, Audio_Format format, u64 number_of_frames, Vector3 pos) {
|
||||||
// No idea if this actually gives the perception of audio being positioned.
|
// No idea if this actually gives the perception of audio being positioned.
|
||||||
// I also don't have a mono audio device to test it.
|
// I also don't have a mono audio device to test it.
|
||||||
float* audio_data = (float*)frames;
|
float* audio_data = (float*)frames;
|
||||||
|
|
||||||
float distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
|
float32 distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
|
||||||
float attenuation = 1.0f / (1.0f + distance);
|
float32 attenuation = 1.0f / (1.0f + distance);
|
||||||
|
|
||||||
float alpha = 0.1f;
|
float32 alpha = 0.1f;
|
||||||
float prev_sample = 0.0f;
|
float32 prev_sample = 0.0f;
|
||||||
|
|
||||||
u64 comp_size = get_audio_bit_width_byte_size(format.bit_width);
|
u64 comp_size = get_audio_bit_width_byte_size(format.bit_width);
|
||||||
u64 frame_size = comp_size * format.channels;
|
u64 frame_size = comp_size * format.channels;
|
||||||
|
|
||||||
for (u64 i = 0; i < number_of_frames; ++i) {
|
for (u64 i = 0; i < number_of_frames; ++i) {
|
||||||
float sample = audio_data[i];
|
float32 sample = audio_data[i];
|
||||||
convert_one_component(
|
convert_one_component(
|
||||||
&sample,
|
&sample,
|
||||||
AUDIO_BITS_32,
|
AUDIO_BITS_32,
|
||||||
|
@ -1460,43 +1463,43 @@ void spacialize_audio_mono(void* frames, Audio_Format format, u64 number_of_fram
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void spacialize_audio(void* frames, Audio_Format format, u64 number_of_frames, Vector3 pos) {
|
void apply_audio_spacialization(void* frames, Audio_Format format, u64 number_of_frames, Vector3 pos) {
|
||||||
|
|
||||||
if (format.channels == 1) {
|
if (format.channels == 1) {
|
||||||
spacialize_audio_mono(frames, format, number_of_frames, pos);
|
apply_audio_spacialization_mono(frames, format, number_of_frames, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
float distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
|
float32 distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
|
||||||
float attenuation = 1.0f / (1.0f + distance);
|
float32 attenuation = 1.0f / (1.0f + distance);
|
||||||
|
|
||||||
float left_right_pan = (pos.x + 1.0f) * 0.5f;
|
float32 left_right_pan = (pos.x + 1.0f) * 0.5f;
|
||||||
float up_down_pan = (pos.y + 1.0f) * 0.5f;
|
float32 up_down_pan = (pos.y + 1.0f) * 0.5f;
|
||||||
float front_back_pan = (pos.z + 1.0f) * 0.5f;
|
float32 front_back_pan = (pos.z + 1.0f) * 0.5f;
|
||||||
|
|
||||||
u64 comp_size = get_audio_bit_width_byte_size(format.bit_width);
|
u64 comp_size = get_audio_bit_width_byte_size(format.bit_width);
|
||||||
u64 frame_size = comp_size * format.channels;
|
u64 frame_size = comp_size * format.channels;
|
||||||
|
|
||||||
float high_pass_coeff = 0.8f + 0.2f * up_down_pan;
|
float32 high_pass_coeff = 0.8f + 0.2f * up_down_pan;
|
||||||
float low_pass_coeff = 1.0f - high_pass_coeff;
|
float32 low_pass_coeff = 1.0f - high_pass_coeff;
|
||||||
|
|
||||||
// Apply gains to each frame
|
// Apply gains to each frame
|
||||||
for (u64 i = 0; i < number_of_frames; ++i) {
|
for (u64 i = 0; i < number_of_frames; ++i) {
|
||||||
for (u64 c = 0; c < format.channels; ++c) {
|
for (u64 c = 0; c < format.channels; ++c) {
|
||||||
// Convert whatever to float -1 to 1
|
// Convert whatever to float32 -1 to 1
|
||||||
float sample;
|
float32 sample;
|
||||||
convert_one_component(
|
convert_one_component(
|
||||||
&sample,
|
&sample,
|
||||||
AUDIO_BITS_32,
|
AUDIO_BITS_32,
|
||||||
(u8*)frames+i*frame_size+c*comp_size,
|
(u8*)frames+i*frame_size+c*comp_size,
|
||||||
format.bit_width
|
format.bit_width
|
||||||
);
|
);
|
||||||
float gain = 1.0f / format.channels;
|
float32 gain = 1.0f / format.channels;
|
||||||
|
|
||||||
if (format.channels == 2) {
|
if (format.channels == 2) {
|
||||||
|
|
||||||
// time delay and phase shift for vertical position
|
// time delay and phase shift for vertical position
|
||||||
float time_delay = (up_down_pan - 0.5f) * 0.0005f; // 0.5ms delay range
|
float32 time_delay = (up_down_pan - 0.5f) * 0.0005f; // 0.5ms delay range
|
||||||
float phase_shift = (up_down_pan - 0.5f) * 0.5f; // 0.5 radians phase shift range
|
float32 phase_shift = (up_down_pan - 0.5f) * 0.5f; // 0.5 radians phase shift range
|
||||||
|
|
||||||
// Stereo
|
// Stereo
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
|
@ -1549,6 +1552,39 @@ void spacialize_audio(void* frames, Audio_Format format, u64 number_of_frames, V
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void apply_audio_volume(void* frames, Audio_Format format, u64 number_of_frames, float32 vol) {
|
||||||
|
|
||||||
|
// #Speed
|
||||||
|
// This is lazy, also it can be combined with other passes.
|
||||||
|
|
||||||
|
u64 comp_size = get_audio_bit_width_byte_size(format.bit_width);
|
||||||
|
u64 frame_size = comp_size * format.channels;
|
||||||
|
if (vol <= 0.0) {
|
||||||
|
memset(frames, 0, frame_size*number_of_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u64 i = 0; i < number_of_frames; ++i) {
|
||||||
|
for (u64 c = 0; c < format.channels; ++c) {
|
||||||
|
float32 sample;
|
||||||
|
convert_one_component(
|
||||||
|
&sample,
|
||||||
|
AUDIO_BITS_32,
|
||||||
|
(u8*)frames+i*frame_size+c*comp_size,
|
||||||
|
format.bit_width
|
||||||
|
);
|
||||||
|
|
||||||
|
sample *= vol;
|
||||||
|
|
||||||
|
convert_one_component(
|
||||||
|
(u8*)frames+i*frame_size+c*comp_size,
|
||||||
|
format.bit_width,
|
||||||
|
&sample,
|
||||||
|
AUDIO_BITS_32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is supposed to be called by OS layer audio thread whenever it wants more audio samples
|
// This is supposed to be called by OS layer audio thread whenever it wants more audio samples
|
||||||
void
|
void
|
||||||
do_program_audio_sample(u64 number_of_output_frames, Audio_Format out_format,
|
do_program_audio_sample(u64 number_of_output_frames, Audio_Format out_format,
|
||||||
|
@ -1725,7 +1761,10 @@ do_program_audio_sample(u64 number_of_output_frames, Audio_Format out_format,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!p->disable_spacialization) {
|
if (!p->disable_spacialization) {
|
||||||
spacialize_audio(mix_buffer, out_format, number_of_output_frames, p->position);
|
apply_audio_spacialization(mix_buffer, out_format, number_of_output_frames, p->position);
|
||||||
|
}
|
||||||
|
if (p->volume != 0.0) {
|
||||||
|
apply_audio_volume(mix_buffer, out_format, number_of_output_frames, p->volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
mix_frames(output, mix_buffer, number_of_output_frames, out_format);
|
mix_frames(output, mix_buffer, number_of_output_frames, out_format);
|
||||||
|
|
|
@ -71,7 +71,7 @@ int entry(int argc, char **argv) {
|
||||||
Vector4 rect;
|
Vector4 rect;
|
||||||
rect.x = -window.width/2+40;
|
rect.x = -window.width/2+40;
|
||||||
rect.y = window.height/2-FONT_HEIGHT-40;
|
rect.y = window.height/2-FONT_HEIGHT-40;
|
||||||
rect.z = FONT_HEIGHT*5;
|
rect.z = FONT_HEIGHT*8;
|
||||||
rect.w = FONT_HEIGHT*1.5;
|
rect.w = FONT_HEIGHT*1.5;
|
||||||
|
|
||||||
bool clip_playing = clip_player->state == AUDIO_PLAYER_STATE_PLAYING;
|
bool clip_playing = clip_player->state == AUDIO_PLAYER_STATE_PLAYING;
|
||||||
|
@ -96,7 +96,23 @@ int entry(int argc, char **argv) {
|
||||||
audio_player_set_progression_factor(song_player, 0);
|
audio_player_set_progression_factor(song_player, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rect.y = window.height/2-FONT_HEIGHT-40;
|
||||||
|
rect.x += rect.z + FONT_HEIGHT;
|
||||||
|
if (button(STR("Song vol up"), rect.xy, rect.zw, false)) {
|
||||||
|
song_player->volume += 0.05;
|
||||||
|
}
|
||||||
|
rect.y -= FONT_HEIGHT*1.8;
|
||||||
|
if (button(STR("Song vol down"), rect.xy, rect.zw, false)) {
|
||||||
|
song_player->volume -= 0.05;
|
||||||
|
}
|
||||||
|
song_player->volume = clamp(song_player->volume, 0, 5);
|
||||||
|
rect.x += rect.z + FONT_HEIGHT;
|
||||||
|
draw_text(font, tprint("Song volume: %d%%", (s64)round(song_player->volume*100)), FONT_HEIGHT, v2_sub(rect.xy, v2(2, -2)), v2(1, 1), COLOR_BLACK);
|
||||||
|
draw_text(font, tprint("Song volume: %d%%", (s64)round(song_player->volume*100)), FONT_HEIGHT, rect.xy, v2(1, 1), COLOR_WHITE);
|
||||||
|
|
||||||
|
|
||||||
rect.y -= FONT_HEIGHT*3;
|
rect.y -= FONT_HEIGHT*3;
|
||||||
|
|
||||||
draw_text(font, STR("Right-click for thing"), FONT_HEIGHT, v2_sub(rect.xy, v2(2, -2)), v2(1, 1), COLOR_BLACK);
|
draw_text(font, STR("Right-click for thing"), FONT_HEIGHT, v2_sub(rect.xy, v2(2, -2)), v2(1, 1), COLOR_BLACK);
|
||||||
draw_text(font, STR("Right-click for thing"), FONT_HEIGHT, rect.xy, v2(1, 1), COLOR_WHITE);
|
draw_text(font, STR("Right-click for thing"), FONT_HEIGHT, rect.xy, v2(1, 1), COLOR_WHITE);
|
||||||
|
|
||||||
|
|
Reference in a new issue