[
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2017 rxi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n# cmixer\nA lightweight, portable ANSI C audio mixer for games.\n\n#### Features\n* Easy to use\n* Tiny: two files, around 600sloc\n* No dependencies (`stb_vorbis` is optional)\n* Support for `wav` and `ogg` files if `stb_vorbis` is used\n* Simple API for adding custom formats\n* Resampling and seamless looping\n* Per-source gain, pan and pitch control\n* Fast, fixed-point internal processing\n\n\n## Getting Started\nThe library's .c and .h files can be dropped into a project and compiled along\nwith it. If ogg vorbis support is desired `stb_vorbis.c` should also be added to\nthe project and the `-DCM_USE_STB_VORBIS` flag passed to the compiler.\n\nBefore use, the library should be initialized with the desired samplerate:\n```c\ncm_init(44100);\n```\n\nA sound can be played by first creating a source, then calling `cm_play()` on\nthat source.\n```c\ncm_Source *src = cm_new_source_from_file(\"sound.wav\");\ncm_play(src);\n```\n\nWhen the `cm_process()` function is called, all the currently playing streams\nwill be processed and mixed, the master output will be written to the provided\n16bit buffer. This output is always stereo interlaced, thus the buffer length\nmust always be an even number:\n```c\ncm_Int16 buffer[512];\ncm_process(buffer, 512);\n```\n\n`cm_process()` can safely be called from a separate thread; if more than one\nthread is used a lock event handler must be set:\n```c\nstatic void lock_handler(cm_Event *e) {\n  if (e->type == CM_EVENT_LOCK) {\n    printf(\"acquired lock\\n\");\n  }\n  if (e->type == CM_EVENT_UNLOCK) {\n    printf(\"released lock\\n\");\n  }\n}\n\ncm_set_lock(lock_handler);\n```\n\nSee [doc/api.md](doc/api.md) for an overview of the API or [demo](demo) for\nusage demos.\n\n## License\nThis library is free software; you can redistribute it and/or modify it under\nthe terms of the MIT license. See [LICENSE](LICENSE) for details.\n"
  },
  {
    "path": "demo/sdl2/build.bat",
    "content": "gcc -I../../src/ ../../src/cmixer.c src/main.c -lmingw32 -lSDL2main -lSDL2 -Wall -O3\n"
  },
  {
    "path": "demo/sdl2/build.sh",
    "content": "#!/bin/bash\ngcc -I../../src/ ../../src/cmixer.c src/main.c -lSDL2 -Wall -O3\n"
  },
  {
    "path": "demo/sdl2/src/main.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <SDL2/SDL.h>\n\n#include \"cmixer.h\"\n\n\nstatic SDL_mutex* audio_mutex;\n\nstatic void lock_handler(cm_Event *e) {\n  if (e->type == CM_EVENT_LOCK) {\n    SDL_LockMutex(audio_mutex);\n  }\n  if (e->type == CM_EVENT_UNLOCK) {\n    SDL_UnlockMutex(audio_mutex);\n  }\n}\n\n\nstatic void audio_callback(void *udata, Uint8 *stream, int size) {\n  cm_process((void*) stream, size / 2);\n}\n\n\nint main(int argc, char **argv) {\n  SDL_AudioDeviceID dev;\n  SDL_AudioSpec fmt, got;\n  cm_Source *src;\n\n  /* Init SDL */\n  SDL_Init(SDL_INIT_AUDIO);\n  audio_mutex = SDL_CreateMutex();\n\n  /* Init SDL audio */\n  memset(&fmt, 0, sizeof(fmt));\n  fmt.freq      = 44100;\n  fmt.format    = AUDIO_S16;\n  fmt.channels  = 2;\n  fmt.samples   = 1024;\n  fmt.callback  = audio_callback;\n\n  dev = SDL_OpenAudioDevice(NULL, 0, &fmt, &got, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);\n  if (dev == 0) {\n    fprintf(stderr, \"Error: failed to open audio device '%s'\\n\", SDL_GetError());\n    exit(EXIT_FAILURE);\n  }\n\n  /* Init library */\n  cm_init(got.freq);\n  cm_set_lock(lock_handler);\n  cm_set_master_gain(0.5);\n\n  /* Start audio */\n  SDL_PauseAudioDevice(dev, 0);\n\n  /* Create source and play */\n  src = cm_new_source_from_file(\"loop.wav\");\n  if (!src) {\n    fprintf(stderr, \"Error: failed to create source '%s'\\n\", cm_get_error());\n    exit(EXIT_FAILURE);\n  }\n  cm_set_loop(src, 1);\n  cm_play(src);\n\n  /* Wait for [return] */\n  printf(\"Press [return] to exit\\n\");\n  getchar();\n\n  /* Clean up */\n  cm_destroy_source(src);\n  SDL_CloseAudioDevice(dev);\n  SDL_Quit();\n\n  return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "doc/api.md",
    "content": "\n# cmixer API\n* [Functions](#functions)\n* [Events](#events)\n\n\n## Functions\n\n##### const char\\* cm_get_error(void)\nReturns and clears the last error. Returns NULL if there is no error.\n\n##### void cm_init(int samplerate)\nInitializes the library — this must be called before any other function is used.\n`samplerate` should be the samplerate used by your output device.\n\n##### void cm_set_lock(cm_EventHandler lock)\nSets a lock event handler. A lock handler must be set if the library will be\nused from multiple threads. See *[events](#events)*.\n\n##### void cm_set_master_gain(double gain)\nSets the master gain — this is the overall volume of the resultant audio\noutputted by `cm_process()`. This is `1` by default.\n\n##### void cm_process(cm_Int16 \\*dst, int len)\nProcesses and mixes all the current streams and outputs the result to `dst`.\nThis function always outputs signed 16bit stereo interlaced. `len` is the number\nof samples to process, this should always be an even number.\n\n##### cm_Source\\* cm_new_source(const cm_SourceInfo \\*info)\nCreates a new source using the information stored in `info`. All fields of the\n`info` struct should be set:\n\nType               | Field      | Description\n-------------------|------------|-----------------------------------------------\ncm_EventHandler    | handler    | Event handler for stream\nvoid\\*             | udata      | User value passed to event handler in event\nint                | samplerate | Native samplerate of stream\nint                | length     | Length in frames\n\nNULL is returned if an error occurs.\n\n##### cm_Source\\* cm_new_source_from_file(const char \\*filename)\nLoads the given file into memory and creates a source from it; NULL is returned\nif an error occurs. If you want to create several sources using the same audio\ndata, the file should be loaded into memory and `cm_new_source_from_mem()`\nshould be used instead.\n\n##### cm_Source\\* cm_new_source_from_mem(void \\*data, int size)\nCreates a source using the file data stored in memory, the library does not own\nthe `data` and will not free it when it is destroyed. NULL is returned if an\nerror occurs.\n\n##### void cm_destroy_source(cm_Source \\*src)\nFrees all resources a source was using, if the source is playing it is stopped\nimmediately. This should be called on a source when  you are done with it; the\nsource is no longer valid once it is destroyed.\n\n##### double cm_get_length(cm_Source \\*src)\nReturns the length in seconds of the source's audio data.\n\n##### double cm_get_position(cm_Source \\*src)\nReturns the current playhead position of the source in seconds.\n\n##### int cm_get_state(cm_Source \\*src)\nReturns the current play state of the source, this can be one of the following:\n* `CM_STATE_PLAYING`\n* `CM_STATE_PAUSED`\n* `CM_STATE_STOPPED`\n\n##### void cm_set_gain(cm_Source \\*src, double gain)\nSets the gain (volume) of the source. For example: `0.5` will make it quieter,\n`2` will make it louder. `1` is the default.\n\n##### void cm_set_pan(cm_Source \\*src, double pan)\nSets the left-right panning of the source — `-1` is fully-left, `1` is\nfully-right, `0` is centered (default).\n\n##### void cm_set_pitch(cm_Source \\*src, double pitch)\nSets the playback pitch (speed) of the source; by default this is `1`. `2` is\none octave higher, `0.5` is one octave lower.\n\n##### void cm_set_loop(cm_Source \\*src, int loop)\nEnables looping playback if `loop` is non-zero. By default looping is disabled.\n\n##### void cm_play(cm_Source \\*src)\nPlays the source. If the source is already playing this function has no effect;\ncall `cm_stop()` before calling this function to play it from the beginning.\n\n##### void cm_pause(cm_Source \\*src)\nPauses the source's playback. This stops playback without losing the current\nposition, calling `cm_play()` will continue playing where it left off.\n\n##### void cm_stop(cm_Source \\*src)\nStops playing and rewinds the stream's play position back to the beginning.\n\n\n## Events\nAll event handlers are passed a pointer to a `cm_Event`, the `type` field of\nthe event is set to one of the following:\n\n#### CM_EVENT_LOCK\nPassed to the lock handler set by `cm_set_lock()` when the lock should be\nacquired.\n\n#### CM_EVENT_UNLOCK\nPassed to the lock handler set by `cm_set_lock()` when the lock should be\nreleased.\n\n#### CM_EVENT_DESTROY\nPassed to a source's event handler when that source is going to be destroyed.\nAny additional resources the source is using should be cleaned up when this\nevent is received.\n\n#### CM_EVENT_SAMPLES\nPassed to a source's event handler when more audio samples are required. The\n`buffer` field should be filled with the number of audio samples specified by\nthe `length` field. The audio written should always be signed 16bit stereo\ninterlaced. If the stream reaches the end before it has filled the buffer it\nshould loop back to the beginning and continue writing to the buffer until it is\nfilled.\n\nThe audio written to the buffer will automatically be resampled by the library\non playback if the samplerate differs from the one passed to `cm_init()`.\n\n#### CM_EVENT_REWIND\nPassed to a source's event handler when the stream should rewind to the\nbeginning.\n"
  },
  {
    "path": "src/cmixer.c",
    "content": "/*\n** Copyright (c) 2017 rxi\n**\n** Permission is hereby granted, free of charge, to any person obtaining a copy\n** of this software and associated documentation files (the \"Software\"), to\n** deal in the Software without restriction, including without limitation the\n** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n** sell copies of the Software, and to permit persons to whom the Software is\n** furnished to do so, subject to the following conditions:\n**\n** The above copyright notice and this permission notice shall be included in\n** all copies or substantial portions of the Software.\n**\n** THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n** IN THE SOFTWARE.\n**/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"cmixer.h\"\n\n#define UNUSED(x)         ((void) (x))\n#define CLAMP(x, a, b)    ((x) < (a) ? (a) : (x) > (b) ? (b) : (x))\n#define MIN(a, b)         ((a) < (b) ? (a) : (b))\n#define MAX(a, b)         ((a) > (b) ? (a) : (b))\n\n#define FX_BITS           (12)\n#define FX_UNIT           (1 << FX_BITS)\n#define FX_MASK           (FX_UNIT - 1)\n#define FX_FROM_FLOAT(f)  ((f) * FX_UNIT)\n#define FX_LERP(a, b, p)  ((a) + ((((b) - (a)) * (p)) >> FX_BITS))\n\n#define BUFFER_SIZE       (512)\n#define BUFFER_MASK       (BUFFER_SIZE - 1)\n\n\nstruct cm_Source {\n  cm_Source *next;              /* Next source in list */\n  cm_Int16 buffer[BUFFER_SIZE]; /* Internal buffer with raw stereo PCM */\n  cm_EventHandler handler;      /* Event handler */\n  void *udata;          /* Stream's udata (from cm_SourceInfo) */\n  int samplerate;       /* Stream's native samplerate */\n  int length;           /* Stream's length in frames */\n  int end;              /* End index for the current play-through */\n  int state;            /* Current state (playing|paused|stopped) */\n  cm_Int64 position;    /* Current playhead position (fixed point) */\n  int lgain, rgain;     /* Left and right gain (fixed point) */\n  int rate;             /* Playback rate (fixed point) */\n  int nextfill;         /* Next frame idx where the buffer needs to be filled */\n  int loop;             /* Whether the source will loop when `end` is reached */\n  int rewind;           /* Whether the source will rewind before playing */\n  int active;           /* Whether the source is part of `sources` list */\n  double gain;          /* Gain set by `cm_set_gain()` */\n  double pan;           /* Pan set by `cm_set_pan()` */\n};\n\n\nstatic struct {\n  const char *lasterror;        /* Last error message */\n  cm_EventHandler lock;         /* Event handler for lock/unlock events */\n  cm_Source *sources;           /* Linked list of active (playing) sources */\n  cm_Int32 buffer[BUFFER_SIZE]; /* Internal master buffer */\n  int samplerate;               /* Master samplerate */\n  int gain;                     /* Master gain (fixed point) */\n} cmixer;\n\n\nstatic void dummy_handler(cm_Event *e) {\n  UNUSED(e);\n}\n\n\nstatic void lock(void) {\n  cm_Event e;\n  e.type = CM_EVENT_LOCK;\n  cmixer.lock(&e);\n}\n\n\nstatic void unlock(void) {\n  cm_Event e;\n  e.type = CM_EVENT_UNLOCK;\n  cmixer.lock(&e);\n}\n\n\nconst char* cm_get_error(void) {\n  const char *res = cmixer.lasterror;\n  cmixer.lasterror = NULL;\n  return res;\n}\n\n\nstatic const char* error(const char *msg) {\n  cmixer.lasterror = msg;\n  return msg;\n}\n\n\nvoid cm_init(int samplerate) {\n  cmixer.samplerate = samplerate;\n  cmixer.lock = dummy_handler;\n  cmixer.sources = NULL;\n  cmixer.gain = FX_UNIT;\n}\n\n\nvoid cm_set_lock(cm_EventHandler lock) {\n  cmixer.lock = lock;\n}\n\n\nvoid cm_set_master_gain(double gain) {\n  cmixer.gain = FX_FROM_FLOAT(gain);\n}\n\n\nstatic void rewind_source(cm_Source *src) {\n  cm_Event e;\n  e.type = CM_EVENT_REWIND;\n  e.udata = src->udata;\n  src->handler(&e);\n  src->position = 0;\n  src->rewind = 0;\n  src->end = src->length;\n  src->nextfill = 0;\n}\n\n\nstatic void fill_source_buffer(cm_Source *src, int offset, int length) {\n  cm_Event e;\n  e.type = CM_EVENT_SAMPLES;\n  e.udata = src->udata;\n  e.buffer = src->buffer + offset;\n  e.length = length;\n  src->handler(&e);\n}\n\n\nstatic void process_source(cm_Source *src, int len) {\n  int i, n, a, b, p;\n  int frame, count;\n  cm_Int32 *dst = cmixer.buffer;\n\n  /* Do rewind if flag is set */\n  if (src->rewind) {\n    rewind_source(src);\n  }\n\n  /* Don't process if not playing */\n  if (src->state != CM_STATE_PLAYING) {\n    return;\n  }\n\n  /* Process audio */\n  while (len > 0) {\n    /* Get current position frame */\n    frame = src->position >> FX_BITS;\n\n    /* Fill buffer if required */\n    if (frame + 3 >= src->nextfill) {\n      fill_source_buffer(src, (src->nextfill*2) & BUFFER_MASK, BUFFER_SIZE/2);\n      src->nextfill += BUFFER_SIZE / 4;\n    }\n\n    /* Handle reaching the end of the playthrough */\n    if (frame >= src->end) {\n      /* As streams continiously fill the raw buffer in a loop we simply\n      ** increment the end idx by one length and continue reading from it for\n      ** another play-through */\n      src->end = frame + src->length;\n      /* Set state and stop processing if we're not set to loop */\n      if (!src->loop) {\n        src->state = CM_STATE_STOPPED;\n        break;\n      }\n    }\n\n    /* Work out how many frames we should process in the loop */\n    n = MIN(src->nextfill - 2, src->end) - frame;\n    count = (n << FX_BITS) / src->rate;\n    count = MAX(count, 1);\n    count = MIN(count, len / 2);\n    len -= count * 2;\n\n    /* Add audio to master buffer */\n    if (src->rate == FX_UNIT) {\n      /* Add audio to buffer -- basic */\n      n = frame * 2;\n      for (i = 0; i < count; i++) {\n        dst[0] += (src->buffer[(n    ) & BUFFER_MASK] * src->lgain) >> FX_BITS;\n        dst[1] += (src->buffer[(n + 1) & BUFFER_MASK] * src->rgain) >> FX_BITS;\n        n += 2;\n        dst += 2;\n      }\n      src->position += count * FX_UNIT;\n\n    } else {\n      /* Add audio to buffer -- interpolated */\n      for (i = 0; i < count; i++) {\n        n = (src->position >> FX_BITS) * 2;\n        p = src->position & FX_MASK;\n        a = src->buffer[(n    ) & BUFFER_MASK];\n        b = src->buffer[(n + 2) & BUFFER_MASK];\n        dst[0] += (FX_LERP(a, b, p) * src->lgain) >> FX_BITS;\n        n++;\n        a = src->buffer[(n    ) & BUFFER_MASK];\n        b = src->buffer[(n + 2) & BUFFER_MASK];\n        dst[1] += (FX_LERP(a, b, p) * src->rgain) >> FX_BITS;\n        src->position += src->rate;\n        dst += 2;\n      }\n    }\n\n  }\n}\n\n\nvoid cm_process(cm_Int16 *dst, int len) {\n  int i;\n  cm_Source **s;\n\n  /* Process in chunks of BUFFER_SIZE if `len` is larger than BUFFER_SIZE */\n  while (len > BUFFER_SIZE) {\n    cm_process(dst, BUFFER_SIZE);\n    dst += BUFFER_SIZE;\n    len -= BUFFER_SIZE;\n  }\n\n  /* Zeroset internal buffer */\n  memset(cmixer.buffer, 0, len * sizeof(cmixer.buffer[0]));\n\n  /* Process active sources */\n  lock();\n  s = &cmixer.sources;\n  while (*s) {\n    process_source(*s, len);\n    /* Remove source from list if it is no longer playing */\n    if ((*s)->state != CM_STATE_PLAYING) {\n      (*s)->active = 0;\n      *s = (*s)->next;\n    } else {\n      s = &(*s)->next;\n    }\n  }\n  unlock();\n\n  /* Copy internal buffer to destination and clip */\n  for (i = 0; i < len; i++) {\n    int x = (cmixer.buffer[i] * cmixer.gain) >> FX_BITS;\n    dst[i] = CLAMP(x, -32768, 32767);\n  }\n}\n\n\ncm_Source* cm_new_source(const cm_SourceInfo *info) {\n  cm_Source *src = calloc(1, sizeof(*src));\n  if (!src) {\n    error(\"allocation failed\");\n    return NULL;\n  }\n  src->handler = info->handler;\n  src->length = info->length;\n  src->samplerate = info->samplerate;\n  src->udata = info->udata;\n  cm_set_gain(src, 1);\n  cm_set_pan(src, 0);\n  cm_set_pitch(src, 1);\n  cm_set_loop(src, 0);\n  cm_stop(src);\n  return src;\n}\n\n\nstatic const char* wav_init(cm_SourceInfo *info, void *data, int len, int ownsdata);\n\n#ifdef CM_USE_STB_VORBIS\nstatic const char* ogg_init(cm_SourceInfo *info, void *data, int len, int ownsdata);\n#endif\n\n\nstatic int check_header(void *data, int size, char *str, int offset) {\n  int len = strlen(str);\n  return (size >= offset + len) && !memcmp((char*) data + offset, str, len);\n}\n\n\nstatic cm_Source* new_source_from_mem(void *data, int size, int ownsdata) {\n  const char *err;\n  cm_SourceInfo info;\n\n  if (check_header(data, size, \"WAVE\", 8)) {\n    err = wav_init(&info, data, size, ownsdata);\n    if (err) {\n      return NULL;\n    }\n    return cm_new_source(&info);\n  }\n\n#ifdef CM_USE_STB_VORBIS\n  if (check_header(data, size, \"OggS\", 0)) {\n    err = ogg_init(&info, data, size, ownsdata);\n    if (err) {\n      return NULL;\n    }\n    return cm_new_source(&info);\n  }\n#endif\n\n  error(\"unknown format or invalid data\");\n  return NULL;\n}\n\n\nstatic void* load_file(const char *filename, int *size) {\n  FILE *fp;\n  void *data;\n  int n;\n\n  fp = fopen(filename, \"rb\");\n  if (!fp) {\n    return NULL;\n  }\n\n  /* Get size */\n  fseek(fp, 0, SEEK_END);\n  *size = ftell(fp);\n  rewind(fp);\n\n  /* Malloc, read and return data */\n  data = malloc(*size);\n  if (!data) {\n    fclose(fp);\n    return NULL;\n  }\n  n = fread(data, 1, *size, fp);\n  fclose(fp);\n  if (n != *size) {\n    free(data);\n    return NULL;\n  }\n\n  return data;\n}\n\n\ncm_Source* cm_new_source_from_file(const char *filename) {\n  int size;\n  cm_Source *src;\n  void *data;\n\n  /* Load file into memory */\n  data = load_file(filename, &size);\n  if (!data) {\n    error(\"could not load file\");\n    return NULL;\n  }\n\n  /* Try to load and return */\n  src = new_source_from_mem(data, size, 1);\n  if (!src) {\n    free(data);\n    return NULL;\n  }\n\n  return src;\n}\n\n\ncm_Source* cm_new_source_from_mem(void *data, int size) {\n  return new_source_from_mem(data, size, 0);\n}\n\n\nvoid cm_destroy_source(cm_Source *src) {\n  cm_Event e;\n  lock();\n  if (src->active) {\n    cm_Source **s = &cmixer.sources;\n    while (*s) {\n      if (*s == src) {\n        *s = src->next;\n        break;\n      }\n    }\n  }\n  unlock();\n  e.type = CM_EVENT_DESTROY;\n  e.udata = src->udata;\n  src->handler(&e);\n  free(src);\n}\n\n\ndouble cm_get_length(cm_Source *src) {\n  return src->length / (double) src->samplerate;\n}\n\n\ndouble cm_get_position(cm_Source *src) {\n  return ((src->position >> FX_BITS) % src->length) / (double) src->samplerate;\n}\n\n\nint cm_get_state(cm_Source *src) {\n  return src->state;\n}\n\n\nstatic void recalc_source_gains(cm_Source *src) {\n  double l, r;\n  double pan = src->pan;\n  l = src->gain * (pan <= 0. ? 1. : 1. - pan);\n  r = src->gain * (pan >= 0. ? 1. : 1. + pan);\n  src->lgain = FX_FROM_FLOAT(l);\n  src->rgain = FX_FROM_FLOAT(r);\n}\n\n\nvoid cm_set_gain(cm_Source *src, double gain) {\n  src->gain = gain;\n  recalc_source_gains(src);\n}\n\n\nvoid cm_set_pan(cm_Source *src, double pan) {\n  src->pan = CLAMP(pan, -1.0, 1.0);\n  recalc_source_gains(src);\n}\n\n\nvoid cm_set_pitch(cm_Source *src, double pitch) {\n  double rate;\n  if (pitch > 0.) {\n    rate = src->samplerate / (double) cmixer.samplerate * pitch;\n  } else {\n    rate = 0.001;\n  }\n  src->rate = FX_FROM_FLOAT(rate);\n}\n\n\nvoid cm_set_loop(cm_Source *src, int loop) {\n  src->loop = loop;\n}\n\n\nvoid cm_play(cm_Source *src) {\n  lock();\n  src->state = CM_STATE_PLAYING;\n  if (!src->active) {\n    src->active = 1;\n    src->next = cmixer.sources;\n    cmixer.sources = src;\n  }\n  unlock();\n}\n\n\nvoid cm_pause(cm_Source *src) {\n  src->state = CM_STATE_PAUSED;\n}\n\n\nvoid cm_stop(cm_Source *src) {\n  src->state = CM_STATE_STOPPED;\n  src->rewind = 1;\n}\n\n\n/*============================================================================\n** Wav stream\n**============================================================================*/\n\ntypedef struct {\n  void *data;\n  int bitdepth;\n  int samplerate;\n  int channels;\n  int length;\n} Wav;\n\ntypedef struct {\n  Wav wav;\n  void *data;\n  int idx;\n} WavStream;\n\n\nstatic char* find_subchunk(char *data, int len, char *id, int *size) {\n  /* TODO : Error handling on malformed wav file */\n  int idlen = strlen(id);\n  char *p = data + 12;\nnext:\n  *size = *((cm_UInt32*) (p + 4));\n  if (memcmp(p, id, idlen)) {\n    p += 8 + *size;\n    if (p > data + len) return NULL;\n    goto next;\n  }\n  return p + 8;\n}\n\n\nstatic const char* read_wav(Wav *w, void *data, int len) {\n  int bitdepth, channels, samplerate, format;\n  int sz;\n  char *p = data;\n  memset(w, 0, sizeof(*w));\n\n  /* Check header */\n  if (memcmp(p, \"RIFF\", 4) || memcmp(p + 8, \"WAVE\", 4)) {\n    return error(\"bad wav header\");\n  }\n  /* Find fmt subchunk */\n  p = find_subchunk(data, len, \"fmt\", &sz);\n  if (!p) {\n    return error(\"no fmt subchunk\");\n  }\n\n  /* Load fmt info */\n  format      = *((cm_UInt16*) (p));\n  channels    = *((cm_UInt16*) (p + 2));\n  samplerate  = *((cm_UInt32*) (p + 4));\n  bitdepth    = *((cm_UInt16*) (p + 14));\n  if (format != 1) {\n    return error(\"unsupported format\");\n  }\n  if (channels == 0 || samplerate == 0 || bitdepth == 0) {\n    return error(\"bad format\");\n  }\n\n  /* Find data subchunk */\n  p = find_subchunk(data, len, \"data\", &sz);\n  if (!p) {\n    return error(\"no data subchunk\");\n  }\n\n  /* Init struct */\n  w->data = (void*) p;\n  w->samplerate = samplerate;\n  w->channels = channels;\n  w->length = (sz / (bitdepth / 8)) / channels;\n  w->bitdepth = bitdepth;\n  /* Done */\n  return NULL;\n}\n\n\n#define WAV_PROCESS_LOOP(X) \\\n  while (n--) {             \\\n    X                       \\\n    dst += 2;               \\\n    s->idx++;               \\\n  }\n\nstatic void wav_handler(cm_Event *e) {\n  int x, n;\n  cm_Int16 *dst;\n  WavStream *s = e->udata;\n  int len;\n\n  switch (e->type) {\n\n    case CM_EVENT_DESTROY:\n      free(s->data);\n      free(s);\n      break;\n\n    case CM_EVENT_SAMPLES:\n      dst = e->buffer;\n      len = e->length / 2;\nfill:\n      n = MIN(len, s->wav.length - s->idx);\n      len -= n;\n      if (s->wav.bitdepth == 16 && s->wav.channels == 1) {\n        WAV_PROCESS_LOOP({\n          dst[0] = dst[1] = ((cm_Int16*) s->wav.data)[s->idx];\n        });\n      } else if (s->wav.bitdepth == 16 && s->wav.channels == 2) {\n        WAV_PROCESS_LOOP({\n          x = s->idx * 2;\n          dst[0] = ((cm_Int16*) s->wav.data)[x    ];\n          dst[1] = ((cm_Int16*) s->wav.data)[x + 1];\n        });\n      } else if (s->wav.bitdepth == 8 && s->wav.channels == 1) {\n        WAV_PROCESS_LOOP({\n          dst[0] = dst[1] = (((cm_UInt8*) s->wav.data)[s->idx] - 128) << 8;\n        });\n      } else if (s->wav.bitdepth == 8 && s->wav.channels == 2) {\n        WAV_PROCESS_LOOP({\n          x = s->idx * 2;\n          dst[0] = (((cm_UInt8*) s->wav.data)[x    ] - 128) << 8;\n          dst[1] = (((cm_UInt8*) s->wav.data)[x + 1] - 128) << 8;\n        });\n      }\n      /* Loop back and continue filling buffer if we didn't fill the buffer */\n      if (len > 0) {\n        s->idx = 0;\n        goto fill;\n      }\n      break;\n\n    case CM_EVENT_REWIND:\n      s->idx = 0;\n      break;\n  }\n}\n\n\nstatic const char* wav_init(cm_SourceInfo *info, void *data, int len, int ownsdata) {\n  WavStream *stream;\n  Wav wav;\n\n  const char *err = read_wav(&wav, data, len);\n  if (err != NULL) {\n    return err;\n  }\n\n  if (wav.channels > 2 || (wav.bitdepth != 16 && wav.bitdepth != 8)) {\n    return error(\"unsupported wav format\");\n  }\n\n  stream = calloc(1, sizeof(*stream));\n  if (!stream) {\n    return error(\"allocation failed\");\n  }\n  stream->wav = wav;\n\n  if (ownsdata) {\n    stream->data = data;\n  }\n  stream->idx = 0;\n\n  info->udata = stream;\n  info->handler = wav_handler;\n  info->samplerate = wav.samplerate;\n  info->length = wav.length;\n\n  /* Return NULL (no error) for success */\n  return NULL;\n}\n\n\n/*============================================================================\n** Ogg stream\n**============================================================================*/\n\n#ifdef CM_USE_STB_VORBIS\n\n#define STB_VORBIS_HEADER_ONLY\n#include \"stb_vorbis.c\"\n\ntypedef struct {\n  stb_vorbis *ogg;\n  void *data;\n} OggStream;\n\n\nstatic void ogg_handler(cm_Event *e) {\n  int n, len;\n  OggStream *s = e->udata;\n  cm_Int16 *buf;\n\n  switch (e->type) {\n\n    case CM_EVENT_DESTROY:\n      stb_vorbis_close(s->ogg);\n      free(s->data);\n      free(s);\n      break;\n\n    case CM_EVENT_SAMPLES:\n      len = e->length;\n      buf = e->buffer;\nfill:\n      n = stb_vorbis_get_samples_short_interleaved(s->ogg, 2, buf, len);\n      n *= 2;\n      /* rewind and fill remaining buffer if we reached the end of the ogg\n      ** before filling it */\n      if (len != n) {\n        stb_vorbis_seek_start(s->ogg);\n        buf += n;\n        len -= n;\n        goto fill;\n      }\n      break;\n\n    case CM_EVENT_REWIND:\n      stb_vorbis_seek_start(s->ogg);\n      break;\n  }\n}\n\n\nstatic const char* ogg_init(cm_SourceInfo *info, void *data, int len, int ownsdata) {\n  OggStream *stream;\n  stb_vorbis *ogg;\n  stb_vorbis_info ogginfo;\n  int err;\n\n  ogg = stb_vorbis_open_memory(data, len, &err, NULL);\n  if (!ogg) {\n    return error(\"invalid ogg data\");\n  }\n\n  stream = calloc(1, sizeof(*stream));\n  if (!stream) {\n    stb_vorbis_close(ogg);\n    return error(\"allocation failed\");\n  }\n\n  stream->ogg = ogg;\n  if (ownsdata) {\n    stream->data = data;\n  }\n\n  ogginfo = stb_vorbis_get_info(ogg);\n\n  info->udata = stream;\n  info->handler = ogg_handler;\n  info->samplerate = ogginfo.sample_rate;\n  info->length = stb_vorbis_stream_length_in_samples(ogg);\n\n  /* Return NULL (no error) for success */\n  return NULL;\n}\n\n\n#endif\n"
  },
  {
    "path": "src/cmixer.h",
    "content": "/*\n** Copyright (c) 2017 rxi\n**\n** This library is free software; you can redistribute it and/or modify it\n** under the terms of the MIT license. See `cmixer.c` for details.\n**/\n\n#ifndef CMIXER_H\n#define CMIXER_H\n\n#define CM_VERSION \"0.1.1\"\n\ntypedef short           cm_Int16;\ntypedef int             cm_Int32;\ntypedef long long       cm_Int64;\ntypedef unsigned char   cm_UInt8;\ntypedef unsigned short  cm_UInt16;\ntypedef unsigned        cm_UInt32;\n\ntypedef struct cm_Source cm_Source;\n\ntypedef struct {\n  int type;\n  void *udata;\n  const char *msg;\n  cm_Int16 *buffer;\n  int length;\n} cm_Event;\n\ntypedef void (*cm_EventHandler)(cm_Event *e);\n\ntypedef struct {\n  cm_EventHandler handler;\n  void *udata;\n  int samplerate;\n  int length;\n} cm_SourceInfo;\n\n\nenum {\n  CM_STATE_STOPPED,\n  CM_STATE_PLAYING,\n  CM_STATE_PAUSED\n};\n\nenum {\n  CM_EVENT_LOCK,\n  CM_EVENT_UNLOCK,\n  CM_EVENT_DESTROY,\n  CM_EVENT_SAMPLES,\n  CM_EVENT_REWIND\n};\n\n\nconst char* cm_get_error(void);\nvoid cm_init(int samplerate);\nvoid cm_set_lock(cm_EventHandler lock);\nvoid cm_set_master_gain(double gain);\nvoid cm_process(cm_Int16 *dst, int len);\n\ncm_Source* cm_new_source(const cm_SourceInfo *info);\ncm_Source* cm_new_source_from_file(const char *filename);\ncm_Source* cm_new_source_from_mem(void *data, int size);\nvoid cm_destroy_source(cm_Source *src);\ndouble cm_get_length(cm_Source *src);\ndouble cm_get_position(cm_Source *src);\nint cm_get_state(cm_Source *src);\nvoid cm_set_gain(cm_Source *src, double gain);\nvoid cm_set_pan(cm_Source *src, double pan);\nvoid cm_set_pitch(cm_Source *src, double pitch);\nvoid cm_set_loop(cm_Source *src, int loop);\nvoid cm_play(cm_Source *src);\nvoid cm_pause(cm_Source *src);\nvoid cm_stop(cm_Source *src);\n\n#endif\n"
  }
]