From 4c71f9899e546fcbccdb8409c08cc08cbab4fb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Tue, 12 Dec 2023 11:56:44 +0100 Subject: [PATCH] audio: add a few enhancements --- MAKE.bat | 5 +++ bind/v4k.lua | 4 ++- engine/joint/v4k.h | 63 ++++++++++++++++++++++++++++++++---- engine/split/3rd_sts_mixer.h | 32 +++++++++++++++--- engine/split/v4k_audio.c | 29 +++++++++++++++-- engine/split/v4k_audio.h | 2 ++ engine/v4k | 32 +++++++++++++++--- engine/v4k.c | 29 +++++++++++++++-- engine/v4k.h | 2 ++ hello.c | 4 +-- tools/join_3rd.bat | 3 ++ 11 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 tools/join_3rd.bat diff --git a/MAKE.bat b/MAKE.bat index 1925894..4bbbb21 100644 --- a/MAKE.bat +++ b/MAKE.bat @@ -33,6 +33,7 @@ if "%1"=="help" ( echo %0 [v4web] ; sync v4 website echo %0 [swap] ; toggle #line directives on/off echo %0 [split^|join] ; engine/v4k* ^>split^> engine/split/* or engine/split/* ^>join^> engine/v4k* + echo %0 [3rd] ; join 3rd parties together echo %0 [lua] ; execute lua script with v4k echo %0 [amalgamation] ; combine engine/v4k* into a single-header file echo %0 [prep] ; combine split files into a single-header file, ready for use @@ -253,6 +254,10 @@ if "%1"=="join" ( call tools\join exit /b ) +if "%1"=="3rd" ( + call tools\join_3rd + exit /b +) if "%1"=="swap" ( echo Swapping #line on v4k.h call tools\linswap engine\v4k.h diff --git a/bind/v4k.lua b/bind/v4k.lua index 50cc508..f4b7a65 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -2083,6 +2083,8 @@ typedef struct audio_handle* audio_t; int audio_play_gain_pitch( audio_t a, int flags, float gain, float pitch ); int audio_play_gain_pitch_pan( audio_t a, int flags, float gain, float pitch, float pan ); int audio_stop( audio_t a ); + void audio_loop( audio_t a, bool loop ); + bool audio_playing( audio_t a ); float audio_volume_clip(float gain); float audio_volume_stream(float gain); float audio_volume_master(float gain); @@ -3349,7 +3351,7 @@ unsigned play; bool paused; struct atlas_t *a; } sprite_t; -enum { OBJTYPE_sprite_t = 10 }; typedef struct { unsigned static_assert_on_L__LINE__ : !!(10 <= 255); } static_assert_on_Lconcat(_L,3973)___COUNTER__; typedef struct { unsigned static_assert_on_L__LINE__ : !!(sizeof(sprite_t)); } static_assert_on_Lconcat(_L,3973)___COUNTER__;; +enum { OBJTYPE_sprite_t = 10 }; typedef struct { unsigned static_assert_on_L__LINE__ : !!(10 <= 255); } static_assert_on_Lconcat(_L,3975)___COUNTER__; typedef struct { unsigned static_assert_on_L__LINE__ : !!(sizeof(sprite_t)); } static_assert_on_Lconcat(_L,3975)___COUNTER__;; void sprite_ctor(sprite_t *s); void sprite_dtor(sprite_t *s); void sprite_tick(sprite_t *s); diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 109d48b..da002e9 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -15636,6 +15636,8 @@ API int audio_play_gain( audio_t a, int flags, float gain/*0*/ ); API int audio_play_gain_pitch( audio_t a, int flags, float gain, float pitch/*1*/ ); API int audio_play_gain_pitch_pan( audio_t a, int flags, float gain, float pitch, float pan/*0*/ ); API int audio_stop( audio_t a ); +API void audio_loop( audio_t a, bool loop ); +API bool audio_playing( audio_t a ); API float audio_volume_clip(float gain); // set fx volume if gain is in [0..1] range. returns current fx volume in any case API float audio_volume_stream(float gain); // set bgm volume if gain is in [0..1] range. returns current bgm volume in any case @@ -78392,6 +78394,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // See the example at the end of the file. // // VERSION HISTORY +// 0.03 (2022-12-12) add an ability to stop audio stream via callback, add a method to check if voice is already stopped // 0.02 (2022-05-10) allow voice queueing in same channel. ie, chain another sample on same voice channel after current sample playback is done (@r-lyeh) // 0.01 (2016-05-01) initial version // @@ -78440,7 +78443,7 @@ typedef struct { // // The callback which will be called when the stream needs more data. -typedef void (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); +typedef bool (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); typedef struct { void* userdata; // a userdata pointer which will passed to the callback @@ -78508,6 +78511,12 @@ int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float // Stops voice with the given voice no. You can pass the returned number of sts_mixer_play_sample / sts_mixer_play_stream here. void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice); +// Returns whether the given sample has already stopped playing. +bool sts_mixer_sample_stopped(sts_mixer_t* mixer, sts_mixer_sample_t* sample); + +// Returns whether the given stream has already stopped playing. +bool sts_mixer_stream_stopped(sts_mixer_t* mixer, sts_mixer_stream_t* stream); + // Stops all voices playing the given sample. Useful when you want to delete the sample and make sure it is not used anymore. void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample); @@ -78650,6 +78659,19 @@ void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice) { if (voice >= 0 && voice < STS_MIXER_VOICES) sts_mixer__reset_voice(mixer, voice); } +bool sts_mixer_sample_stopped(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { + for (int i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].sample == sample && mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) return false; + } + return true; +} + +bool sts_mixer_stream_stopped(sts_mixer_t* mixer, sts_mixer_stream_t* stream) { + for (int i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].stream == stream && mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) return false; + } + return true; +} void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { int i; @@ -78699,9 +78721,13 @@ void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples) position = ((int)voice->position) * 2; if (position >= voice->stream->sample.length) { // buffer empty...refill - voice->stream->callback(&voice->stream->sample, voice->stream->userdata); - voice->position = 0.0f; - position = 0; + if (voice->stream->callback(&voice->stream->sample, voice->stream->userdata)) { + voice->position = 0.0f; + position = 0; + } else { + sts_mixer__reset_voice(mixer, i); + continue; + } } left += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position) * voice->gain); right += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position + 1) * voice->gain); @@ -353847,6 +353873,7 @@ typedef struct { float dataf[4096*2]; }; bool rewind; + bool loop; } mystream_t; static void downsample_to_mono_flt( int channels, float *buffer, int samples ) { @@ -353871,7 +353898,7 @@ static void downsample_to_mono_s16( int channels, short *buffer, int samples ) { } // the callback to refill the (stereo) stream data -static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { +static bool refill_stream(sts_mixer_sample_t* sample, void* userdata) { mystream_t* stream = (mystream_t*)userdata; switch( stream->type ) { default: @@ -353880,6 +353907,7 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0); if (ma_dr_wav_read_pcm_frames_s16(&stream->wav, sl, (short*)stream->data) < sl) { ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0); + if (!stream->loop) return false; } } break; case MP3: { @@ -353887,6 +353915,7 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0); if (ma_dr_mp3_read_pcm_frames_f32(&stream->mp3_, sl, stream->dataf) < sl) { ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0); + if (!stream->loop) return false; } } break; case OGG: { @@ -353894,9 +353923,12 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, stb_vorbis_seek(stream->ogg, 0); if( stb_vorbis_get_samples_short_interleaved(ogg, 2, (short*)stream->data, sample->length) == 0 ) { stb_vorbis_seek(stream->ogg, 0); + if (!stream->loop) return false; } } } + + return true; } static void reset_stream(mystream_t* stream) { if( stream ) memset( stream->data, 0, sizeof(stream->data) ), stream->rewind = 1; @@ -353910,6 +353942,7 @@ static bool load_stream(mystream_t* stream, const char *filename) { int error; int HZ = 44100; stream->type = UNK; + stream->loop = true; if( stream->type == UNK && (stream->ogg = stb_vorbis_open_memory((const unsigned char *)data, datalen, &error, NULL)) ) { stb_vorbis_info info = stb_vorbis_get_info(stream->ogg); if( info.channels != 2 ) { puts("cannot stream ogg file. stereo required."); goto end; } // @fixme: upsample @@ -354221,6 +354254,22 @@ int audio_stop( audio_t a ) { return 1; } +void audio_loop( audio_t a, bool loop ) { + if ( a->is_stream ) { + a->stream.loop = loop; + } +} + +bool audio_playing( audio_t a ) { + if( a->is_clip ) { + return !sts_mixer_sample_stopped(&mixer, &a->clip); + } + if( a->is_stream ) { + return !sts_mixer_stream_stopped(&mixer, &a->stream.stream); + } + return false; +} + // ----------------------------------------------------------------------------- // audio queue @@ -354248,7 +354297,7 @@ static void audio_queue_init() { do_once thread_queue_init(&queue_mutex, countof(audio_queues), audio_queues, 0); } -static void audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { +static bool audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { (void)userdata; int sl = sample->length / 2; // 2 ch @@ -354272,6 +354321,8 @@ static void audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { aq = 0; } } while( bytes > 0 ); + + return 1; } static int audio_queue_voice = -1; diff --git a/engine/split/3rd_sts_mixer.h b/engine/split/3rd_sts_mixer.h index 0dc5cb4..375bcab 100644 --- a/engine/split/3rd_sts_mixer.h +++ b/engine/split/3rd_sts_mixer.h @@ -18,6 +18,7 @@ // See the example at the end of the file. // // VERSION HISTORY +// 0.03 (2022-12-12) add an ability to stop audio stream via callback, add a method to check if voice is already stopped // 0.02 (2022-05-10) allow voice queueing in same channel. ie, chain another sample on same voice channel after current sample playback is done (@r-lyeh) // 0.01 (2016-05-01) initial version // @@ -66,7 +67,7 @@ typedef struct { // // The callback which will be called when the stream needs more data. -typedef void (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); +typedef bool (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); typedef struct { void* userdata; // a userdata pointer which will passed to the callback @@ -134,6 +135,12 @@ int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float // Stops voice with the given voice no. You can pass the returned number of sts_mixer_play_sample / sts_mixer_play_stream here. void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice); +// Returns whether the given sample has already stopped playing. +bool sts_mixer_sample_stopped(sts_mixer_t* mixer, sts_mixer_sample_t* sample); + +// Returns whether the given stream has already stopped playing. +bool sts_mixer_stream_stopped(sts_mixer_t* mixer, sts_mixer_stream_t* stream); + // Stops all voices playing the given sample. Useful when you want to delete the sample and make sure it is not used anymore. void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample); @@ -276,6 +283,19 @@ void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice) { if (voice >= 0 && voice < STS_MIXER_VOICES) sts_mixer__reset_voice(mixer, voice); } +bool sts_mixer_sample_stopped(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { + for (int i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].sample == sample && mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) return false; + } + return true; +} + +bool sts_mixer_stream_stopped(sts_mixer_t* mixer, sts_mixer_stream_t* stream) { + for (int i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].stream == stream && mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) return false; + } + return true; +} void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { int i; @@ -325,9 +345,13 @@ void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples) position = ((int)voice->position) * 2; if (position >= voice->stream->sample.length) { // buffer empty...refill - voice->stream->callback(&voice->stream->sample, voice->stream->userdata); - voice->position = 0.0f; - position = 0; + if (voice->stream->callback(&voice->stream->sample, voice->stream->userdata)) { + voice->position = 0.0f; + position = 0; + } else { + sts_mixer__reset_voice(mixer, i); + continue; + } } left += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position) * voice->gain); right += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position + 1) * voice->gain); diff --git a/engine/split/v4k_audio.c b/engine/split/v4k_audio.c index 4d1462a..3530415 100644 --- a/engine/split/v4k_audio.c +++ b/engine/split/v4k_audio.c @@ -70,6 +70,7 @@ typedef struct { float dataf[4096*2]; }; bool rewind; + bool loop; } mystream_t; static void downsample_to_mono_flt( int channels, float *buffer, int samples ) { @@ -94,7 +95,7 @@ static void downsample_to_mono_s16( int channels, short *buffer, int samples ) { } // the callback to refill the (stereo) stream data -static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { +static bool refill_stream(sts_mixer_sample_t* sample, void* userdata) { mystream_t* stream = (mystream_t*)userdata; switch( stream->type ) { default: @@ -103,6 +104,7 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0); if (ma_dr_wav_read_pcm_frames_s16(&stream->wav, sl, (short*)stream->data) < sl) { ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0); + if (!stream->loop) return false; } } break; case MP3: { @@ -110,6 +112,7 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0); if (ma_dr_mp3_read_pcm_frames_f32(&stream->mp3_, sl, stream->dataf) < sl) { ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0); + if (!stream->loop) return false; } } break; case OGG: { @@ -117,9 +120,12 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, stb_vorbis_seek(stream->ogg, 0); if( stb_vorbis_get_samples_short_interleaved(ogg, 2, (short*)stream->data, sample->length) == 0 ) { stb_vorbis_seek(stream->ogg, 0); + if (!stream->loop) return false; } } } + + return true; } static void reset_stream(mystream_t* stream) { if( stream ) memset( stream->data, 0, sizeof(stream->data) ), stream->rewind = 1; @@ -133,6 +139,7 @@ static bool load_stream(mystream_t* stream, const char *filename) { int error; int HZ = 44100; stream->type = UNK; + stream->loop = true; if( stream->type == UNK && (stream->ogg = stb_vorbis_open_memory((const unsigned char *)data, datalen, &error, NULL)) ) { stb_vorbis_info info = stb_vorbis_get_info(stream->ogg); if( info.channels != 2 ) { puts("cannot stream ogg file. stereo required."); goto end; } // @fixme: upsample @@ -444,6 +451,22 @@ int audio_stop( audio_t a ) { return 1; } +void audio_loop( audio_t a, bool loop ) { + if ( a->is_stream ) { + a->stream.loop = loop; + } +} + +bool audio_playing( audio_t a ) { + if( a->is_clip ) { + return !sts_mixer_sample_stopped(&mixer, &a->clip); + } + if( a->is_stream ) { + return !sts_mixer_stream_stopped(&mixer, &a->stream.stream); + } + return false; +} + // ----------------------------------------------------------------------------- // audio queue @@ -471,7 +494,7 @@ static void audio_queue_init() { do_once thread_queue_init(&queue_mutex, countof(audio_queues), audio_queues, 0); } -static void audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { +static bool audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { (void)userdata; int sl = sample->length / 2; // 2 ch @@ -495,6 +518,8 @@ static void audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { aq = 0; } } while( bytes > 0 ); + + return 1; } static int audio_queue_voice = -1; diff --git a/engine/split/v4k_audio.h b/engine/split/v4k_audio.h index 3e3dd5f..cdcae90 100644 --- a/engine/split/v4k_audio.h +++ b/engine/split/v4k_audio.h @@ -23,6 +23,8 @@ API int audio_play_gain( audio_t a, int flags, float gain/*0*/ ); API int audio_play_gain_pitch( audio_t a, int flags, float gain, float pitch/*1*/ ); API int audio_play_gain_pitch_pan( audio_t a, int flags, float gain, float pitch, float pan/*0*/ ); API int audio_stop( audio_t a ); +API void audio_loop( audio_t a, bool loop ); +API bool audio_playing( audio_t a ); API float audio_volume_clip(float gain); // set fx volume if gain is in [0..1] range. returns current fx volume in any case API float audio_volume_stream(float gain); // set bgm volume if gain is in [0..1] range. returns current bgm volume in any case diff --git a/engine/v4k b/engine/v4k index 01b1ebb..01df043 100644 --- a/engine/v4k +++ b/engine/v4k @@ -59542,6 +59542,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // See the example at the end of the file. // // VERSION HISTORY +// 0.03 (2022-12-12) add an ability to stop audio stream via callback, add a method to check if voice is already stopped // 0.02 (2022-05-10) allow voice queueing in same channel. ie, chain another sample on same voice channel after current sample playback is done (@r-lyeh) // 0.01 (2016-05-01) initial version // @@ -59590,7 +59591,7 @@ typedef struct { // // The callback which will be called when the stream needs more data. -typedef void (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); +typedef bool (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); typedef struct { void* userdata; // a userdata pointer which will passed to the callback @@ -59658,6 +59659,12 @@ int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float // Stops voice with the given voice no. You can pass the returned number of sts_mixer_play_sample / sts_mixer_play_stream here. void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice); +// Returns whether the given sample has already stopped playing. +bool sts_mixer_sample_stopped(sts_mixer_t* mixer, sts_mixer_sample_t* sample); + +// Returns whether the given stream has already stopped playing. +bool sts_mixer_stream_stopped(sts_mixer_t* mixer, sts_mixer_stream_t* stream); + // Stops all voices playing the given sample. Useful when you want to delete the sample and make sure it is not used anymore. void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample); @@ -59800,6 +59807,19 @@ void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice) { if (voice >= 0 && voice < STS_MIXER_VOICES) sts_mixer__reset_voice(mixer, voice); } +bool sts_mixer_sample_stopped(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { + for (int i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].sample == sample && mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) return false; + } + return true; +} + +bool sts_mixer_stream_stopped(sts_mixer_t* mixer, sts_mixer_stream_t* stream) { + for (int i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].stream == stream && mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) return false; + } + return true; +} void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { int i; @@ -59849,9 +59869,13 @@ void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples) position = ((int)voice->position) * 2; if (position >= voice->stream->sample.length) { // buffer empty...refill - voice->stream->callback(&voice->stream->sample, voice->stream->userdata); - voice->position = 0.0f; - position = 0; + if (voice->stream->callback(&voice->stream->sample, voice->stream->userdata)) { + voice->position = 0.0f; + position = 0; + } else { + sts_mixer__reset_voice(mixer, i); + continue; + } } left += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position) * voice->gain); right += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position + 1) * voice->gain); diff --git a/engine/v4k.c b/engine/v4k.c index ee16443..b504739 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -4165,6 +4165,7 @@ typedef struct { float dataf[4096*2]; }; bool rewind; + bool loop; } mystream_t; static void downsample_to_mono_flt( int channels, float *buffer, int samples ) { @@ -4189,7 +4190,7 @@ static void downsample_to_mono_s16( int channels, short *buffer, int samples ) { } // the callback to refill the (stereo) stream data -static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { +static bool refill_stream(sts_mixer_sample_t* sample, void* userdata) { mystream_t* stream = (mystream_t*)userdata; switch( stream->type ) { default: @@ -4198,6 +4199,7 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0); if (ma_dr_wav_read_pcm_frames_s16(&stream->wav, sl, (short*)stream->data) < sl) { ma_dr_wav_seek_to_pcm_frame(&stream->wav, 0); + if (!stream->loop) return false; } } break; case MP3: { @@ -4205,6 +4207,7 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0); if (ma_dr_mp3_read_pcm_frames_f32(&stream->mp3_, sl, stream->dataf) < sl) { ma_dr_mp3_seek_to_pcm_frame(&stream->mp3_, 0); + if (!stream->loop) return false; } } break; case OGG: { @@ -4212,9 +4215,12 @@ static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { if( stream->rewind ) stream->rewind = 0, stb_vorbis_seek(stream->ogg, 0); if( stb_vorbis_get_samples_short_interleaved(ogg, 2, (short*)stream->data, sample->length) == 0 ) { stb_vorbis_seek(stream->ogg, 0); + if (!stream->loop) return false; } } } + + return true; } static void reset_stream(mystream_t* stream) { if( stream ) memset( stream->data, 0, sizeof(stream->data) ), stream->rewind = 1; @@ -4228,6 +4234,7 @@ static bool load_stream(mystream_t* stream, const char *filename) { int error; int HZ = 44100; stream->type = UNK; + stream->loop = true; if( stream->type == UNK && (stream->ogg = stb_vorbis_open_memory((const unsigned char *)data, datalen, &error, NULL)) ) { stb_vorbis_info info = stb_vorbis_get_info(stream->ogg); if( info.channels != 2 ) { puts("cannot stream ogg file. stereo required."); goto end; } // @fixme: upsample @@ -4539,6 +4546,22 @@ int audio_stop( audio_t a ) { return 1; } +void audio_loop( audio_t a, bool loop ) { + if ( a->is_stream ) { + a->stream.loop = loop; + } +} + +bool audio_playing( audio_t a ) { + if( a->is_clip ) { + return !sts_mixer_sample_stopped(&mixer, &a->clip); + } + if( a->is_stream ) { + return !sts_mixer_stream_stopped(&mixer, &a->stream.stream); + } + return false; +} + // ----------------------------------------------------------------------------- // audio queue @@ -4566,7 +4589,7 @@ static void audio_queue_init() { do_once thread_queue_init(&queue_mutex, countof(audio_queues), audio_queues, 0); } -static void audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { +static bool audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { (void)userdata; int sl = sample->length / 2; // 2 ch @@ -4590,6 +4613,8 @@ static void audio_queue_callback(sts_mixer_sample_t* sample, void* userdata) { aq = 0; } } while( bytes > 0 ); + + return 1; } static int audio_queue_voice = -1; diff --git a/engine/v4k.h b/engine/v4k.h index 4662a42..b46c6a6 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -1703,6 +1703,8 @@ API int audio_play_gain( audio_t a, int flags, float gain/*0*/ ); API int audio_play_gain_pitch( audio_t a, int flags, float gain, float pitch/*1*/ ); API int audio_play_gain_pitch_pan( audio_t a, int flags, float gain, float pitch, float pan/*0*/ ); API int audio_stop( audio_t a ); +API void audio_loop( audio_t a, bool loop ); +API bool audio_playing( audio_t a ); API float audio_volume_clip(float gain); // set fx volume if gain is in [0..1] range. returns current fx volume in any case API float audio_volume_stream(float gain); // set bgm volume if gain is in [0..1] range. returns current bgm volume in any case diff --git a/hello.c b/hello.c index 2c8e04d..afaf706 100644 --- a/hello.c +++ b/hello.c @@ -11,8 +11,8 @@ // - linux/tcc : tcc hello.c -lm -ldl -lpthread -lX11 -D__STDC_NO_VLA__ // - osx : cc -ObjC hello.c -framework cocoa -framework iokit -framework audiotoolbox -#define V4K_IMPLEMENTATION // unrolls single-header implementation -#include "engine/joint/v4k.h" // single-header file +// #define V4K_IMPLEMENTATION // unrolls single-header implementation +#include "engine/v4k.c" // single-header file int main() { // options diff --git a/tools/join_3rd.bat b/tools/join_3rd.bat new file mode 100644 index 0000000..e106102 --- /dev/null +++ b/tools/join_3rd.bat @@ -0,0 +1,3 @@ +#!/bin/bash 2>nul + +python tools/join.py --template engine/split/v4k.x.inl --path ./engine/split/ --output ./engine/v4k