v4k-git-backup/engine/split/3rd_sts_mixer.h

536 lines
20 KiB
C
Raw Normal View History

2024-03-01 09:12:47 +00:00
///////////////////////////////////////////////////////////////////////////////
// sts_mixer.h - v0.02
// written 2016 by Sebastian Steinhauer
//
// LICENSE
// Public domain. See "unlicense" statement at the end of this file.
//
// ABOUT
// A simple stereo audio mixer which is capable of mixing samples and audio streams.
// Samples can be played with different gain, pitch and panning.
// Streams can be played with different gain.
// This library has no malloc/free. All structs have to be "prepared" by the user. So you can enroll your own memory management.
// You have to implement/provide a real audio-backend to hear something from the speakers.
// A good starting point would be SDL2 where you can use an audio callback to feed the audio device.
//
// USAGE
// Please note that most audio systems will run in a separate thread. So you have to take care about locking before modifying the sts_mixer_t state.
// 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
//
#ifndef __INCLUDED__STS_MIXER_H__
#define __INCLUDED__STS_MIXER_H__
// The number of concurrent voices (channels) which are used to mix the audio.
// If you need more, use a higher number by setting #define STS_MIXER_VOICE n before including this header.
#ifndef STS_MIXER_VOICES
#define STS_MIXER_VOICES 32
#endif // STS_MIXER_VOICES
// Defines the various audio formats. Note that they are all on system endianess.
enum {
STS_MIXER_SAMPLE_FORMAT_NONE, // no format
STS_MIXER_SAMPLE_FORMAT_8, // signed 8-bit
STS_MIXER_SAMPLE_FORMAT_16, // signed 16-bit
STS_MIXER_SAMPLE_FORMAT_32, // signed 32-bit
STS_MIXER_SAMPLE_FORMAT_FLOAT // floats
};
////////////////////////////////////////////////////////////////////////////////
//
// SAMPLES
//
// A sample is a *MONO* piece of audio which is loaded fully to memory.
// It can be played with various gains, pitches and pannings.
//
typedef struct {
unsigned int length; // length in samples (so 1024 samples of STS_MIXER_SAMPLE_FORMAT_16 would be 2048 bytes)
unsigned int frequency; // frequency of this sample (e.g. 44100, 22000 ...)
int audio_format; // one of STS_MIXER_SAMPLE_FORMAT_*
void* data; // pointer to the sample data, sts_mixer makes no copy, so you have to keep them in memory
void* next; // next sample in chain (if any) //< @r-lyeh
} sts_mixer_sample_t;
////////////////////////////////////////////////////////////////////////////////
//
// STREAMS
//
// A stream is *STEREO* audio which will be decoded/loaded as needed.
// It can be played with various gains. No panning or pitching.
//
// The callback which will be called when the stream needs more data.
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
sts_mixer_stream_callback callback; // this callback will be called when the stream needs more data
sts_mixer_sample_t sample; // the current stream "sample" which holds the current piece of audio
} sts_mixer_stream_t;
////////////////////////////////////////////////////////////////////////////////
//
// VOICES
//
// A voice is an audio source which will be used during mixing.
// It can play nothing, a sample or a stream.
// Most of those fields are considered "private" and you should not play around with those.
//
typedef struct {
int state;
sts_mixer_sample_t* sample;
sts_mixer_stream_t* stream;
float position;
float gain;
float pitch;
float pan;
} sts_mixer_voice_t;
////////////////////////////////////////////////////////////////////////////////
//
// MIXER
//
// The mixer state.
//
typedef struct {
float gain; // the global gain (you can change it if you want to change to overall volume)
unsigned int frequency; // the frequency for the output of mixed audio data
int audio_format; // the audio format for the output of mixed audio data
sts_mixer_voice_t voices[STS_MIXER_VOICES]; // holding all audio voices for this state
} sts_mixer_t;
////////////////////////////////////////////////////////////////////////////////
//
// API
//
// "Initializes" a new sts_mixer state.
void sts_mixer_init(sts_mixer_t* mixer, unsigned int frequency, int audio_format);
// "Shutdown" the mixer state. It will simply reset all fields.
void sts_mixer_shutdown(sts_mixer_t* mixer);
// Return the number of active voices. Active voices are voices that play either a stream or a sample.
int sts_mixer_get_active_voices(sts_mixer_t* mixer);
// Play the given sample with the gain, pitch and panning.
// Panning can be something between -1.0f (fully left) ... +1.0f (fully right)
// Please note that pitch will be clamped so it cannot reach 0.0f (would be useless).
// Returns the number of the voice where this sample will be played or -1 if no voice was free.
int sts_mixer_play_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample, float gain, float pitch, float pan);
// Plays the given stream with the gain.
// Returns the number of the voice where this stream will be played or -1 if no voice was free.
int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float gain);
// 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);
// Stops all voices playing the given stream. Useful when you want to delete the stream and make sure it is not used anymore.
void sts_mixer_stop_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream);
// The mixing function. You should call the function if you need to pass more audio data to the audio device.
// Typically this function is called in a separate thread or something like that.
// It will write audio data in the specified format and frequency of the mixer state.
void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples);
#endif // __INCLUDED__STS_MIXER_H__
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
////
//// IMPLEMENTATION
////
////
#ifdef STS_MIXER_IMPLEMENTATION
enum {
STS_MIXER_VOICE_STOPPED,
STS_MIXER_VOICE_PLAYING,
STS_MIXER_VOICE_STREAMING
};
static float sts_mixer__clamp(const float value, const float min, const float max) {
if (value < min) return min;
else if (value > max) return max;
else return value;
}
static float sts_mixer__clamp_sample(const float sample) {
if (sample < -1.0f) return -1.0f;
else if (sample > 1.0f) return 1.0f;
else return sample;
}
static float sts_mixer__get_sample(sts_mixer_sample_t* sample, unsigned int position) {
switch (sample->audio_format) {
case STS_MIXER_SAMPLE_FORMAT_8:
return (float)((char*)sample->data)[position] / 127.0f;
case STS_MIXER_SAMPLE_FORMAT_16:
return (float)((short*)sample->data)[position] / 32767.0f;
case STS_MIXER_SAMPLE_FORMAT_32:
return (float)((int*)sample->data)[position] / 2147483647.0f;
case STS_MIXER_SAMPLE_FORMAT_FLOAT:
return ((float*)sample->data)[position];
default:
return 0.0f;
}
}
static void sts_mixer__reset_voice(sts_mixer_t* mixer, const int i) {
sts_mixer_voice_t* voice = &mixer->voices[i];
voice->state = STS_MIXER_VOICE_STOPPED;
voice->sample = 0;
voice->stream = 0;
voice->position = voice->gain = voice->pitch = voice->pan = 0.0f;
}
static int sts_mixer__find_free_voice(sts_mixer_t* mixer) {
int i;
for (i = 0; i < STS_MIXER_VOICES; ++i) {
if (mixer->voices[i].state == STS_MIXER_VOICE_STOPPED) return i;
}
return -1;
}
void sts_mixer_init(sts_mixer_t* mixer, unsigned int frequency, int audio_format) {
int i;
for (i = 0; i < STS_MIXER_VOICES; ++i) sts_mixer__reset_voice(mixer, i);
mixer->frequency = frequency;
mixer->gain = 1.0f;
mixer->audio_format = audio_format;
}
void sts_mixer_shutdown(sts_mixer_t* mixer) {
sts_mixer_init(mixer, 0, 0);
}
int sts_mixer_get_active_voices(sts_mixer_t* mixer) {
int i, active;
for (i = 0, active = 0; i < STS_MIXER_VOICES; ++i) {
if (mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) ++active;
}
return active;
}
int sts_mixer_play_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample, float gain, float pitch, float pan) {
int i;
sts_mixer_voice_t* voice;
i = sts_mixer__find_free_voice(mixer);
if (i >= 0) {
voice = &mixer->voices[i];
voice->gain = gain;
voice->pitch = sts_mixer__clamp(pitch, 0.1f, 10.0f);
voice->pan = sts_mixer__clamp(pan * 0.5f, -0.5f, 0.5f);
voice->position = 0.0f;
voice->sample = sample;
voice->stream = 0;
voice->state = STS_MIXER_VOICE_PLAYING;
}
return i;
}
int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float gain) {
int i;
sts_mixer_voice_t* voice;
i = sts_mixer__find_free_voice(mixer);
if (i >= 0) {
voice = &mixer->voices[i];
voice->gain = gain;
voice->position = 0.0f;
voice->sample = 0;
voice->stream = stream;
voice->state = STS_MIXER_VOICE_STREAMING;
}
return i;
}
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;
for (i = 0; i < STS_MIXER_VOICES; ++i) {
if (mixer->voices[i].sample == sample) sts_mixer__reset_voice(mixer, i);
}
}
void sts_mixer_stop_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream) {
int i;
for (i = 0; i < STS_MIXER_VOICES; ++i) {
if (mixer->voices[i].stream == stream) sts_mixer__reset_voice(mixer, i);
}
}
void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples) {
sts_mixer_voice_t* voice;
unsigned int i, position;
float left, right, advance, sample;
char* out_8 = (char*)output;
short* out_16 = (short*)output;
int* out_32 = (int*)output;
float* out_float = (float*)output;
// mix all voices
advance = 1.0f / (float)mixer->frequency;
for (; samples > 0; --samples) {
left = right = 0.0f;
for (i = 0; i < STS_MIXER_VOICES; ++i) {
voice = &mixer->voices[i];
if (voice->state == STS_MIXER_VOICE_PLAYING) {
position = (int)voice->position;
if (position < voice->sample->length) {
sample = sts_mixer__clamp_sample(sts_mixer__get_sample(voice->sample, position) * voice->gain);
left += sts_mixer__clamp_sample(sample * (0.5f - voice->pan));
right += sts_mixer__clamp_sample(sample * (0.5f + voice->pan));
voice->position += (float)voice->sample->frequency * advance * voice->pitch;
} else if( voice->sample->next ) { //< @r-lyeh
*voice->sample = *(sts_mixer_sample_t*)voice->sample->next; //< @r-lyeh
voice->position = 0; //< @r-lyeh
} else sts_mixer__reset_voice(mixer, i);
} else if (voice->state == STS_MIXER_VOICE_STREAMING) {
position = ((int)voice->position) * 2;
if (position >= voice->stream->sample.length) {
// buffer empty...refill
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);
voice->position += (float)voice->stream->sample.frequency * advance;
}
}
// write to buffer.
float _g = mixer->gain; //< @r-lyeh: added master gain
float _127 = 127.0f * _g; //< @r-lyeh: added master gain
float _32767 = 32767.0f * _g; //< @r-lyeh: added master gain
float _2147483647 = 2147483647.0f * _g; //< @r-lyeh: added master gain
left = sts_mixer__clamp_sample(left);
right = sts_mixer__clamp_sample(right);
switch (mixer->audio_format) {
case STS_MIXER_SAMPLE_FORMAT_8:
*out_8++ = (char)(left * _127); //< @r-lyeh: added master gain
*out_8++ = (char)(right * _127); //< @r-lyeh: added master gain
break;
case STS_MIXER_SAMPLE_FORMAT_16:
*out_16++ = (short)(left * _32767); //< @r-lyeh: added master gain
*out_16++ = (short)(right * _32767); //< @r-lyeh: added master gain
break;
case STS_MIXER_SAMPLE_FORMAT_32:
*out_32++ = (int)(left * _2147483647); //< @r-lyeh: added master gain
*out_32++ = (int)(right * _2147483647); //< @r-lyeh: added master gain
break;
case STS_MIXER_SAMPLE_FORMAT_FLOAT:
*out_float++ = left * _g; //< @r-lyeh: added master gain
*out_float++ = right * _g; //< @r-lyeh: added master gain
break;
}
}
}
#endif // STS_MIXER_IMPLEMENTATION
////////////////////////////////////////////////////////////////////////////////
// EXAMPLE
// This is a very simple example loading a stream and a sample using
// dr_flac.h (https://github.com/mackron/dr_libs) and SDL2. You can of course also use stb_vorbis or something similar :)
// Please note how the audio thread of SDL2 will be locked when the mixer state get's modified. This is important!
// Also there's no error checking in the entire example code, so beware.
//
#if 0
#include "SDL.h"
#define DR_FLAC_IMPLEMENTATION
#include "dr_flac.h"
#define STS_MIXER_IMPLEMENTATION
#include "sts_mixer.h"
SDL_AudioDeviceID audio_device = 0;
sts_mixer_t mixer;
// encapsulate drflac and some buffer with the sts_mixer_stream_t
typedef struct {
drflac* flac; // FLAC decoder state
sts_mixer_stream_t stream; // mixer stream
int32_t data[4096*2]; // static sample buffer
} mystream_t;
// SDL2 audio callback
static void audio_callback(void* userdata, Uint8* stream, int len) {
(void)(userdata);
sts_mixer_mix_audio(&mixer, stream, len / (sizeof(int) * 2));
}
// load a sample
static void load_sample(sts_mixer_sample_t* sample, const char *filename) {
drflac* flac = drflac_open_file(filename);
sample->frequency = flac->sampleRate;
sample->audio_format = STS_MIXER_SAMPLE_FORMAT_32;
sample->length = flac->totalSampleCount;
sample->data = malloc(sample->length * sizeof(int32_t));
drflac_read_s32(flac, sample->length, (int32_t*)sample->data);
drflac_close(flac);
}
// the callback to refill the stream data
static void refill_stream(sts_mixer_sample_t* sample, void* userdata) {
mystream_t* stream = (mystream_t*)userdata;
if (drflac_read_s32(stream->flac, sample->length, stream->data) < sample->length) drflac_seek_to_sample(stream->flac, 0);
}
// load a stream
static void load_stream(mystream_t* stream, const char *filename) {
stream->flac = drflac_open_file(filename);
stream->stream.userdata = stream;
stream->stream.callback = refill_stream;
stream->stream.sample.frequency = stream->flac->sampleRate;
stream->stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_32;
stream->stream.sample.length = 4096*2;
stream->stream.sample.data = stream->data;
refill_stream(&stream->stream.sample, stream);
}
// helper to get random [0.0f..1.0f values
static float randf() {
return (float)(rand()) / (float)RAND_MAX;
}
int main(int argc, char *argv[]) {
SDL_AudioSpec want, have;
sts_mixer_sample_t sample;
mystream_t stream;
(void)(argc); (void)(argv);
// init SDL2 + audio
want.format = AUDIO_S32SYS;
want.freq = 44100;
want.channels = 2;
want.userdata = NULL;
want.samples = 4096;
want.callback = audio_callback;
SDL_Init(SDL_INIT_AUDIO);
audio_device = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
// init sts_mixer and load things
sts_mixer_init(&mixer, 44100, STS_MIXER_SAMPLE_FORMAT_32);
load_sample(&sample, "effect.flac");
load_stream(&stream, "music.flac");
// play the stream
sts_mixer_play_stream(&mixer, &stream.stream, 0.7f);
// start audio processing and do a loop for audio effects
SDL_PauseAudioDevice(audio_device, 0);
for (;;) {
// !!!IMPORTANT!!! lock the audio thread before modifying data in the sts_mixer !!!
SDL_LockAudioDevice(audio_device);
// play a sample with random gain, pitch and panning
sts_mixer_play_sample(&mixer, &sample, randf(), 0.5f + randf(), -1.0f + randf() * 2.0f);
// unlock audio thread again
SDL_UnlockAudioDevice(audio_device);
// wait ...
SDL_Delay(76);
}
SDL_PauseAudioDevice(audio_device, 1);
SDL_CloseAudioDevice(audio_device);
SDL_Quit();
return 0;
}
#endif // 0
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/