audio: add a few enhancements
parent
427c957e49
commit
4c71f9899e
5
MAKE.bat
5
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
28
engine/v4k
28
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);
|
||||
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);
|
||||
|
|
29
engine/v4k.c
29
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;
|
||||
|
|
|
@ -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
|
||||
|
|
4
hello.c
4
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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash 2>nul
|
||||
|
||||
python tools/join.py --template engine/split/v4k.x.inl --path ./engine/split/ --output ./engine/v4k
|
Loading…
Reference in New Issue