Repository: rxi/cmixer Branch: master Commit: 3592e0e26b66 Files: 8 Total size: 28.3 KB Directory structure: gitextract_9hoyoljj/ ├── LICENSE ├── README.md ├── demo/ │ └── sdl2/ │ ├── build.bat │ ├── build.sh │ └── src/ │ └── main.c ├── doc/ │ └── api.md └── src/ ├── cmixer.c └── cmixer.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ Copyright (c) 2017 rxi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 OR COPYRIGHT HOLDERS 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. ================================================ FILE: README.md ================================================ # cmixer A lightweight, portable ANSI C audio mixer for games. #### Features * Easy to use * Tiny: two files, around 600sloc * No dependencies (`stb_vorbis` is optional) * Support for `wav` and `ogg` files if `stb_vorbis` is used * Simple API for adding custom formats * Resampling and seamless looping * Per-source gain, pan and pitch control * Fast, fixed-point internal processing ## Getting Started The library's .c and .h files can be dropped into a project and compiled along with it. If ogg vorbis support is desired `stb_vorbis.c` should also be added to the project and the `-DCM_USE_STB_VORBIS` flag passed to the compiler. Before use, the library should be initialized with the desired samplerate: ```c cm_init(44100); ``` A sound can be played by first creating a source, then calling `cm_play()` on that source. ```c cm_Source *src = cm_new_source_from_file("sound.wav"); cm_play(src); ``` When the `cm_process()` function is called, all the currently playing streams will be processed and mixed, the master output will be written to the provided 16bit buffer. This output is always stereo interlaced, thus the buffer length must always be an even number: ```c cm_Int16 buffer[512]; cm_process(buffer, 512); ``` `cm_process()` can safely be called from a separate thread; if more than one thread is used a lock event handler must be set: ```c static void lock_handler(cm_Event *e) { if (e->type == CM_EVENT_LOCK) { printf("acquired lock\n"); } if (e->type == CM_EVENT_UNLOCK) { printf("released lock\n"); } } cm_set_lock(lock_handler); ``` See [doc/api.md](doc/api.md) for an overview of the API or [demo](demo) for usage demos. ## License This library is free software; you can redistribute it and/or modify it under the terms of the MIT license. See [LICENSE](LICENSE) for details. ================================================ FILE: demo/sdl2/build.bat ================================================ gcc -I../../src/ ../../src/cmixer.c src/main.c -lmingw32 -lSDL2main -lSDL2 -Wall -O3 ================================================ FILE: demo/sdl2/build.sh ================================================ #!/bin/bash gcc -I../../src/ ../../src/cmixer.c src/main.c -lSDL2 -Wall -O3 ================================================ FILE: demo/sdl2/src/main.c ================================================ #include #include #include #include #include "cmixer.h" static SDL_mutex* audio_mutex; static void lock_handler(cm_Event *e) { if (e->type == CM_EVENT_LOCK) { SDL_LockMutex(audio_mutex); } if (e->type == CM_EVENT_UNLOCK) { SDL_UnlockMutex(audio_mutex); } } static void audio_callback(void *udata, Uint8 *stream, int size) { cm_process((void*) stream, size / 2); } int main(int argc, char **argv) { SDL_AudioDeviceID dev; SDL_AudioSpec fmt, got; cm_Source *src; /* Init SDL */ SDL_Init(SDL_INIT_AUDIO); audio_mutex = SDL_CreateMutex(); /* Init SDL audio */ memset(&fmt, 0, sizeof(fmt)); fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 2; fmt.samples = 1024; fmt.callback = audio_callback; dev = SDL_OpenAudioDevice(NULL, 0, &fmt, &got, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if (dev == 0) { fprintf(stderr, "Error: failed to open audio device '%s'\n", SDL_GetError()); exit(EXIT_FAILURE); } /* Init library */ cm_init(got.freq); cm_set_lock(lock_handler); cm_set_master_gain(0.5); /* Start audio */ SDL_PauseAudioDevice(dev, 0); /* Create source and play */ src = cm_new_source_from_file("loop.wav"); if (!src) { fprintf(stderr, "Error: failed to create source '%s'\n", cm_get_error()); exit(EXIT_FAILURE); } cm_set_loop(src, 1); cm_play(src); /* Wait for [return] */ printf("Press [return] to exit\n"); getchar(); /* Clean up */ cm_destroy_source(src); SDL_CloseAudioDevice(dev); SDL_Quit(); return EXIT_SUCCESS; } ================================================ FILE: doc/api.md ================================================ # cmixer API * [Functions](#functions) * [Events](#events) ## Functions ##### const char\* cm_get_error(void) Returns and clears the last error. Returns NULL if there is no error. ##### void cm_init(int samplerate) Initializes the library — this must be called before any other function is used. `samplerate` should be the samplerate used by your output device. ##### void cm_set_lock(cm_EventHandler lock) Sets a lock event handler. A lock handler must be set if the library will be used from multiple threads. See *[events](#events)*. ##### void cm_set_master_gain(double gain) Sets the master gain — this is the overall volume of the resultant audio outputted by `cm_process()`. This is `1` by default. ##### void cm_process(cm_Int16 \*dst, int len) Processes and mixes all the current streams and outputs the result to `dst`. This function always outputs signed 16bit stereo interlaced. `len` is the number of samples to process, this should always be an even number. ##### cm_Source\* cm_new_source(const cm_SourceInfo \*info) Creates a new source using the information stored in `info`. All fields of the `info` struct should be set: Type | Field | Description -------------------|------------|----------------------------------------------- cm_EventHandler | handler | Event handler for stream void\* | udata | User value passed to event handler in event int | samplerate | Native samplerate of stream int | length | Length in frames NULL is returned if an error occurs. ##### cm_Source\* cm_new_source_from_file(const char \*filename) Loads the given file into memory and creates a source from it; NULL is returned if an error occurs. If you want to create several sources using the same audio data, the file should be loaded into memory and `cm_new_source_from_mem()` should be used instead. ##### cm_Source\* cm_new_source_from_mem(void \*data, int size) Creates a source using the file data stored in memory, the library does not own the `data` and will not free it when it is destroyed. NULL is returned if an error occurs. ##### void cm_destroy_source(cm_Source \*src) Frees all resources a source was using, if the source is playing it is stopped immediately. This should be called on a source when you are done with it; the source is no longer valid once it is destroyed. ##### double cm_get_length(cm_Source \*src) Returns the length in seconds of the source's audio data. ##### double cm_get_position(cm_Source \*src) Returns the current playhead position of the source in seconds. ##### int cm_get_state(cm_Source \*src) Returns the current play state of the source, this can be one of the following: * `CM_STATE_PLAYING` * `CM_STATE_PAUSED` * `CM_STATE_STOPPED` ##### void cm_set_gain(cm_Source \*src, double gain) Sets the gain (volume) of the source. For example: `0.5` will make it quieter, `2` will make it louder. `1` is the default. ##### void cm_set_pan(cm_Source \*src, double pan) Sets the left-right panning of the source — `-1` is fully-left, `1` is fully-right, `0` is centered (default). ##### void cm_set_pitch(cm_Source \*src, double pitch) Sets the playback pitch (speed) of the source; by default this is `1`. `2` is one octave higher, `0.5` is one octave lower. ##### void cm_set_loop(cm_Source \*src, int loop) Enables looping playback if `loop` is non-zero. By default looping is disabled. ##### void cm_play(cm_Source \*src) Plays the source. If the source is already playing this function has no effect; call `cm_stop()` before calling this function to play it from the beginning. ##### void cm_pause(cm_Source \*src) Pauses the source's playback. This stops playback without losing the current position, calling `cm_play()` will continue playing where it left off. ##### void cm_stop(cm_Source \*src) Stops playing and rewinds the stream's play position back to the beginning. ## Events All event handlers are passed a pointer to a `cm_Event`, the `type` field of the event is set to one of the following: #### CM_EVENT_LOCK Passed to the lock handler set by `cm_set_lock()` when the lock should be acquired. #### CM_EVENT_UNLOCK Passed to the lock handler set by `cm_set_lock()` when the lock should be released. #### CM_EVENT_DESTROY Passed to a source's event handler when that source is going to be destroyed. Any additional resources the source is using should be cleaned up when this event is received. #### CM_EVENT_SAMPLES Passed to a source's event handler when more audio samples are required. The `buffer` field should be filled with the number of audio samples specified by the `length` field. The audio written should always be signed 16bit stereo interlaced. If the stream reaches the end before it has filled the buffer it should loop back to the beginning and continue writing to the buffer until it is filled. The audio written to the buffer will automatically be resampled by the library on playback if the samplerate differs from the one passed to `cm_init()`. #### CM_EVENT_REWIND Passed to a source's event handler when the stream should rewind to the beginning. ================================================ FILE: src/cmixer.c ================================================ /* ** Copyright (c) 2017 rxi ** ** Permission is hereby granted, free of charge, to any person obtaining a copy ** of this software and associated documentation files (the "Software"), to ** deal in the Software without restriction, including without limitation the ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ** sell copies of the Software, and to permit persons to whom the Software is ** furnished to do so, subject to the following conditions: ** ** The above copyright notice and this permission notice shall be included in ** all copies or substantial portions of the Software. ** ** 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 OR COPYRIGHT HOLDERS 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. **/ #include #include #include #include "cmixer.h" #define UNUSED(x) ((void) (x)) #define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define FX_BITS (12) #define FX_UNIT (1 << FX_BITS) #define FX_MASK (FX_UNIT - 1) #define FX_FROM_FLOAT(f) ((f) * FX_UNIT) #define FX_LERP(a, b, p) ((a) + ((((b) - (a)) * (p)) >> FX_BITS)) #define BUFFER_SIZE (512) #define BUFFER_MASK (BUFFER_SIZE - 1) struct cm_Source { cm_Source *next; /* Next source in list */ cm_Int16 buffer[BUFFER_SIZE]; /* Internal buffer with raw stereo PCM */ cm_EventHandler handler; /* Event handler */ void *udata; /* Stream's udata (from cm_SourceInfo) */ int samplerate; /* Stream's native samplerate */ int length; /* Stream's length in frames */ int end; /* End index for the current play-through */ int state; /* Current state (playing|paused|stopped) */ cm_Int64 position; /* Current playhead position (fixed point) */ int lgain, rgain; /* Left and right gain (fixed point) */ int rate; /* Playback rate (fixed point) */ int nextfill; /* Next frame idx where the buffer needs to be filled */ int loop; /* Whether the source will loop when `end` is reached */ int rewind; /* Whether the source will rewind before playing */ int active; /* Whether the source is part of `sources` list */ double gain; /* Gain set by `cm_set_gain()` */ double pan; /* Pan set by `cm_set_pan()` */ }; static struct { const char *lasterror; /* Last error message */ cm_EventHandler lock; /* Event handler for lock/unlock events */ cm_Source *sources; /* Linked list of active (playing) sources */ cm_Int32 buffer[BUFFER_SIZE]; /* Internal master buffer */ int samplerate; /* Master samplerate */ int gain; /* Master gain (fixed point) */ } cmixer; static void dummy_handler(cm_Event *e) { UNUSED(e); } static void lock(void) { cm_Event e; e.type = CM_EVENT_LOCK; cmixer.lock(&e); } static void unlock(void) { cm_Event e; e.type = CM_EVENT_UNLOCK; cmixer.lock(&e); } const char* cm_get_error(void) { const char *res = cmixer.lasterror; cmixer.lasterror = NULL; return res; } static const char* error(const char *msg) { cmixer.lasterror = msg; return msg; } void cm_init(int samplerate) { cmixer.samplerate = samplerate; cmixer.lock = dummy_handler; cmixer.sources = NULL; cmixer.gain = FX_UNIT; } void cm_set_lock(cm_EventHandler lock) { cmixer.lock = lock; } void cm_set_master_gain(double gain) { cmixer.gain = FX_FROM_FLOAT(gain); } static void rewind_source(cm_Source *src) { cm_Event e; e.type = CM_EVENT_REWIND; e.udata = src->udata; src->handler(&e); src->position = 0; src->rewind = 0; src->end = src->length; src->nextfill = 0; } static void fill_source_buffer(cm_Source *src, int offset, int length) { cm_Event e; e.type = CM_EVENT_SAMPLES; e.udata = src->udata; e.buffer = src->buffer + offset; e.length = length; src->handler(&e); } static void process_source(cm_Source *src, int len) { int i, n, a, b, p; int frame, count; cm_Int32 *dst = cmixer.buffer; /* Do rewind if flag is set */ if (src->rewind) { rewind_source(src); } /* Don't process if not playing */ if (src->state != CM_STATE_PLAYING) { return; } /* Process audio */ while (len > 0) { /* Get current position frame */ frame = src->position >> FX_BITS; /* Fill buffer if required */ if (frame + 3 >= src->nextfill) { fill_source_buffer(src, (src->nextfill*2) & BUFFER_MASK, BUFFER_SIZE/2); src->nextfill += BUFFER_SIZE / 4; } /* Handle reaching the end of the playthrough */ if (frame >= src->end) { /* As streams continiously fill the raw buffer in a loop we simply ** increment the end idx by one length and continue reading from it for ** another play-through */ src->end = frame + src->length; /* Set state and stop processing if we're not set to loop */ if (!src->loop) { src->state = CM_STATE_STOPPED; break; } } /* Work out how many frames we should process in the loop */ n = MIN(src->nextfill - 2, src->end) - frame; count = (n << FX_BITS) / src->rate; count = MAX(count, 1); count = MIN(count, len / 2); len -= count * 2; /* Add audio to master buffer */ if (src->rate == FX_UNIT) { /* Add audio to buffer -- basic */ n = frame * 2; for (i = 0; i < count; i++) { dst[0] += (src->buffer[(n ) & BUFFER_MASK] * src->lgain) >> FX_BITS; dst[1] += (src->buffer[(n + 1) & BUFFER_MASK] * src->rgain) >> FX_BITS; n += 2; dst += 2; } src->position += count * FX_UNIT; } else { /* Add audio to buffer -- interpolated */ for (i = 0; i < count; i++) { n = (src->position >> FX_BITS) * 2; p = src->position & FX_MASK; a = src->buffer[(n ) & BUFFER_MASK]; b = src->buffer[(n + 2) & BUFFER_MASK]; dst[0] += (FX_LERP(a, b, p) * src->lgain) >> FX_BITS; n++; a = src->buffer[(n ) & BUFFER_MASK]; b = src->buffer[(n + 2) & BUFFER_MASK]; dst[1] += (FX_LERP(a, b, p) * src->rgain) >> FX_BITS; src->position += src->rate; dst += 2; } } } } void cm_process(cm_Int16 *dst, int len) { int i; cm_Source **s; /* Process in chunks of BUFFER_SIZE if `len` is larger than BUFFER_SIZE */ while (len > BUFFER_SIZE) { cm_process(dst, BUFFER_SIZE); dst += BUFFER_SIZE; len -= BUFFER_SIZE; } /* Zeroset internal buffer */ memset(cmixer.buffer, 0, len * sizeof(cmixer.buffer[0])); /* Process active sources */ lock(); s = &cmixer.sources; while (*s) { process_source(*s, len); /* Remove source from list if it is no longer playing */ if ((*s)->state != CM_STATE_PLAYING) { (*s)->active = 0; *s = (*s)->next; } else { s = &(*s)->next; } } unlock(); /* Copy internal buffer to destination and clip */ for (i = 0; i < len; i++) { int x = (cmixer.buffer[i] * cmixer.gain) >> FX_BITS; dst[i] = CLAMP(x, -32768, 32767); } } cm_Source* cm_new_source(const cm_SourceInfo *info) { cm_Source *src = calloc(1, sizeof(*src)); if (!src) { error("allocation failed"); return NULL; } src->handler = info->handler; src->length = info->length; src->samplerate = info->samplerate; src->udata = info->udata; cm_set_gain(src, 1); cm_set_pan(src, 0); cm_set_pitch(src, 1); cm_set_loop(src, 0); cm_stop(src); return src; } static const char* wav_init(cm_SourceInfo *info, void *data, int len, int ownsdata); #ifdef CM_USE_STB_VORBIS static const char* ogg_init(cm_SourceInfo *info, void *data, int len, int ownsdata); #endif static int check_header(void *data, int size, char *str, int offset) { int len = strlen(str); return (size >= offset + len) && !memcmp((char*) data + offset, str, len); } static cm_Source* new_source_from_mem(void *data, int size, int ownsdata) { const char *err; cm_SourceInfo info; if (check_header(data, size, "WAVE", 8)) { err = wav_init(&info, data, size, ownsdata); if (err) { return NULL; } return cm_new_source(&info); } #ifdef CM_USE_STB_VORBIS if (check_header(data, size, "OggS", 0)) { err = ogg_init(&info, data, size, ownsdata); if (err) { return NULL; } return cm_new_source(&info); } #endif error("unknown format or invalid data"); return NULL; } static void* load_file(const char *filename, int *size) { FILE *fp; void *data; int n; fp = fopen(filename, "rb"); if (!fp) { return NULL; } /* Get size */ fseek(fp, 0, SEEK_END); *size = ftell(fp); rewind(fp); /* Malloc, read and return data */ data = malloc(*size); if (!data) { fclose(fp); return NULL; } n = fread(data, 1, *size, fp); fclose(fp); if (n != *size) { free(data); return NULL; } return data; } cm_Source* cm_new_source_from_file(const char *filename) { int size; cm_Source *src; void *data; /* Load file into memory */ data = load_file(filename, &size); if (!data) { error("could not load file"); return NULL; } /* Try to load and return */ src = new_source_from_mem(data, size, 1); if (!src) { free(data); return NULL; } return src; } cm_Source* cm_new_source_from_mem(void *data, int size) { return new_source_from_mem(data, size, 0); } void cm_destroy_source(cm_Source *src) { cm_Event e; lock(); if (src->active) { cm_Source **s = &cmixer.sources; while (*s) { if (*s == src) { *s = src->next; break; } } } unlock(); e.type = CM_EVENT_DESTROY; e.udata = src->udata; src->handler(&e); free(src); } double cm_get_length(cm_Source *src) { return src->length / (double) src->samplerate; } double cm_get_position(cm_Source *src) { return ((src->position >> FX_BITS) % src->length) / (double) src->samplerate; } int cm_get_state(cm_Source *src) { return src->state; } static void recalc_source_gains(cm_Source *src) { double l, r; double pan = src->pan; l = src->gain * (pan <= 0. ? 1. : 1. - pan); r = src->gain * (pan >= 0. ? 1. : 1. + pan); src->lgain = FX_FROM_FLOAT(l); src->rgain = FX_FROM_FLOAT(r); } void cm_set_gain(cm_Source *src, double gain) { src->gain = gain; recalc_source_gains(src); } void cm_set_pan(cm_Source *src, double pan) { src->pan = CLAMP(pan, -1.0, 1.0); recalc_source_gains(src); } void cm_set_pitch(cm_Source *src, double pitch) { double rate; if (pitch > 0.) { rate = src->samplerate / (double) cmixer.samplerate * pitch; } else { rate = 0.001; } src->rate = FX_FROM_FLOAT(rate); } void cm_set_loop(cm_Source *src, int loop) { src->loop = loop; } void cm_play(cm_Source *src) { lock(); src->state = CM_STATE_PLAYING; if (!src->active) { src->active = 1; src->next = cmixer.sources; cmixer.sources = src; } unlock(); } void cm_pause(cm_Source *src) { src->state = CM_STATE_PAUSED; } void cm_stop(cm_Source *src) { src->state = CM_STATE_STOPPED; src->rewind = 1; } /*============================================================================ ** Wav stream **============================================================================*/ typedef struct { void *data; int bitdepth; int samplerate; int channels; int length; } Wav; typedef struct { Wav wav; void *data; int idx; } WavStream; static char* find_subchunk(char *data, int len, char *id, int *size) { /* TODO : Error handling on malformed wav file */ int idlen = strlen(id); char *p = data + 12; next: *size = *((cm_UInt32*) (p + 4)); if (memcmp(p, id, idlen)) { p += 8 + *size; if (p > data + len) return NULL; goto next; } return p + 8; } static const char* read_wav(Wav *w, void *data, int len) { int bitdepth, channels, samplerate, format; int sz; char *p = data; memset(w, 0, sizeof(*w)); /* Check header */ if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4)) { return error("bad wav header"); } /* Find fmt subchunk */ p = find_subchunk(data, len, "fmt", &sz); if (!p) { return error("no fmt subchunk"); } /* Load fmt info */ format = *((cm_UInt16*) (p)); channels = *((cm_UInt16*) (p + 2)); samplerate = *((cm_UInt32*) (p + 4)); bitdepth = *((cm_UInt16*) (p + 14)); if (format != 1) { return error("unsupported format"); } if (channels == 0 || samplerate == 0 || bitdepth == 0) { return error("bad format"); } /* Find data subchunk */ p = find_subchunk(data, len, "data", &sz); if (!p) { return error("no data subchunk"); } /* Init struct */ w->data = (void*) p; w->samplerate = samplerate; w->channels = channels; w->length = (sz / (bitdepth / 8)) / channels; w->bitdepth = bitdepth; /* Done */ return NULL; } #define WAV_PROCESS_LOOP(X) \ while (n--) { \ X \ dst += 2; \ s->idx++; \ } static void wav_handler(cm_Event *e) { int x, n; cm_Int16 *dst; WavStream *s = e->udata; int len; switch (e->type) { case CM_EVENT_DESTROY: free(s->data); free(s); break; case CM_EVENT_SAMPLES: dst = e->buffer; len = e->length / 2; fill: n = MIN(len, s->wav.length - s->idx); len -= n; if (s->wav.bitdepth == 16 && s->wav.channels == 1) { WAV_PROCESS_LOOP({ dst[0] = dst[1] = ((cm_Int16*) s->wav.data)[s->idx]; }); } else if (s->wav.bitdepth == 16 && s->wav.channels == 2) { WAV_PROCESS_LOOP({ x = s->idx * 2; dst[0] = ((cm_Int16*) s->wav.data)[x ]; dst[1] = ((cm_Int16*) s->wav.data)[x + 1]; }); } else if (s->wav.bitdepth == 8 && s->wav.channels == 1) { WAV_PROCESS_LOOP({ dst[0] = dst[1] = (((cm_UInt8*) s->wav.data)[s->idx] - 128) << 8; }); } else if (s->wav.bitdepth == 8 && s->wav.channels == 2) { WAV_PROCESS_LOOP({ x = s->idx * 2; dst[0] = (((cm_UInt8*) s->wav.data)[x ] - 128) << 8; dst[1] = (((cm_UInt8*) s->wav.data)[x + 1] - 128) << 8; }); } /* Loop back and continue filling buffer if we didn't fill the buffer */ if (len > 0) { s->idx = 0; goto fill; } break; case CM_EVENT_REWIND: s->idx = 0; break; } } static const char* wav_init(cm_SourceInfo *info, void *data, int len, int ownsdata) { WavStream *stream; Wav wav; const char *err = read_wav(&wav, data, len); if (err != NULL) { return err; } if (wav.channels > 2 || (wav.bitdepth != 16 && wav.bitdepth != 8)) { return error("unsupported wav format"); } stream = calloc(1, sizeof(*stream)); if (!stream) { return error("allocation failed"); } stream->wav = wav; if (ownsdata) { stream->data = data; } stream->idx = 0; info->udata = stream; info->handler = wav_handler; info->samplerate = wav.samplerate; info->length = wav.length; /* Return NULL (no error) for success */ return NULL; } /*============================================================================ ** Ogg stream **============================================================================*/ #ifdef CM_USE_STB_VORBIS #define STB_VORBIS_HEADER_ONLY #include "stb_vorbis.c" typedef struct { stb_vorbis *ogg; void *data; } OggStream; static void ogg_handler(cm_Event *e) { int n, len; OggStream *s = e->udata; cm_Int16 *buf; switch (e->type) { case CM_EVENT_DESTROY: stb_vorbis_close(s->ogg); free(s->data); free(s); break; case CM_EVENT_SAMPLES: len = e->length; buf = e->buffer; fill: n = stb_vorbis_get_samples_short_interleaved(s->ogg, 2, buf, len); n *= 2; /* rewind and fill remaining buffer if we reached the end of the ogg ** before filling it */ if (len != n) { stb_vorbis_seek_start(s->ogg); buf += n; len -= n; goto fill; } break; case CM_EVENT_REWIND: stb_vorbis_seek_start(s->ogg); break; } } static const char* ogg_init(cm_SourceInfo *info, void *data, int len, int ownsdata) { OggStream *stream; stb_vorbis *ogg; stb_vorbis_info ogginfo; int err; ogg = stb_vorbis_open_memory(data, len, &err, NULL); if (!ogg) { return error("invalid ogg data"); } stream = calloc(1, sizeof(*stream)); if (!stream) { stb_vorbis_close(ogg); return error("allocation failed"); } stream->ogg = ogg; if (ownsdata) { stream->data = data; } ogginfo = stb_vorbis_get_info(ogg); info->udata = stream; info->handler = ogg_handler; info->samplerate = ogginfo.sample_rate; info->length = stb_vorbis_stream_length_in_samples(ogg); /* Return NULL (no error) for success */ return NULL; } #endif ================================================ FILE: src/cmixer.h ================================================ /* ** Copyright (c) 2017 rxi ** ** This library is free software; you can redistribute it and/or modify it ** under the terms of the MIT license. See `cmixer.c` for details. **/ #ifndef CMIXER_H #define CMIXER_H #define CM_VERSION "0.1.1" typedef short cm_Int16; typedef int cm_Int32; typedef long long cm_Int64; typedef unsigned char cm_UInt8; typedef unsigned short cm_UInt16; typedef unsigned cm_UInt32; typedef struct cm_Source cm_Source; typedef struct { int type; void *udata; const char *msg; cm_Int16 *buffer; int length; } cm_Event; typedef void (*cm_EventHandler)(cm_Event *e); typedef struct { cm_EventHandler handler; void *udata; int samplerate; int length; } cm_SourceInfo; enum { CM_STATE_STOPPED, CM_STATE_PLAYING, CM_STATE_PAUSED }; enum { CM_EVENT_LOCK, CM_EVENT_UNLOCK, CM_EVENT_DESTROY, CM_EVENT_SAMPLES, CM_EVENT_REWIND }; const char* cm_get_error(void); void cm_init(int samplerate); void cm_set_lock(cm_EventHandler lock); void cm_set_master_gain(double gain); void cm_process(cm_Int16 *dst, int len); cm_Source* cm_new_source(const cm_SourceInfo *info); cm_Source* cm_new_source_from_file(const char *filename); cm_Source* cm_new_source_from_mem(void *data, int size); void cm_destroy_source(cm_Source *src); double cm_get_length(cm_Source *src); double cm_get_position(cm_Source *src); int cm_get_state(cm_Source *src); void cm_set_gain(cm_Source *src, double gain); void cm_set_pan(cm_Source *src, double pan); void cm_set_pitch(cm_Source *src, double pitch); void cm_set_loop(cm_Source *src, int loop); void cm_play(cm_Source *src); void cm_pause(cm_Source *src); void cm_stop(cm_Source *src); #endif