Full Code of rxi/cmixer for AI

master 3592e0e26b66 cached
8 files
28.3 KB
8.2k tokens
45 symbols
1 requests
Download .txt
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL2/SDL.h>

#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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#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
Download .txt
gitextract_9hoyoljj/

├── LICENSE
├── README.md
├── demo/
│   └── sdl2/
│       ├── build.bat
│       ├── build.sh
│       └── src/
│           └── main.c
├── doc/
│   └── api.md
└── src/
    ├── cmixer.c
    └── cmixer.h
Download .txt
SYMBOL INDEX (45 symbols across 3 files)

FILE: demo/sdl2/src/main.c
  function lock_handler (line 11) | static void lock_handler(cm_Event *e) {
  function audio_callback (line 21) | static void audio_callback(void *udata, Uint8 *stream, int size) {
  function main (line 26) | int main(int argc, char **argv) {

FILE: src/cmixer.c
  type cm_Source (line 44) | struct cm_Source {
  function dummy_handler (line 75) | static void dummy_handler(cm_Event *e) {
  function lock (line 80) | static void lock(void) {
  function unlock (line 87) | static void unlock(void) {
  function cm_init (line 107) | void cm_init(int samplerate) {
  function cm_set_lock (line 115) | void cm_set_lock(cm_EventHandler lock) {
  function cm_set_master_gain (line 120) | void cm_set_master_gain(double gain) {
  function rewind_source (line 125) | static void rewind_source(cm_Source *src) {
  function fill_source_buffer (line 137) | static void fill_source_buffer(cm_Source *src, int offset, int length) {
  function process_source (line 147) | static void process_source(cm_Source *src, int len) {
  function cm_process (line 226) | void cm_process(cm_Int16 *dst, int len) {
  function cm_Source (line 263) | cm_Source* cm_new_source(const cm_SourceInfo *info) {
  function check_header (line 289) | static int check_header(void *data, int size, char *str, int offset) {
  function cm_Source (line 295) | static cm_Source* new_source_from_mem(void *data, int size, int ownsdata) {
  function cm_Source (line 354) | cm_Source* cm_new_source_from_file(const char *filename) {
  function cm_Source (line 377) | cm_Source* cm_new_source_from_mem(void *data, int size) {
  function cm_destroy_source (line 382) | void cm_destroy_source(cm_Source *src) {
  function cm_get_length (line 402) | double cm_get_length(cm_Source *src) {
  function cm_get_position (line 407) | double cm_get_position(cm_Source *src) {
  function cm_get_state (line 412) | int cm_get_state(cm_Source *src) {
  function recalc_source_gains (line 417) | static void recalc_source_gains(cm_Source *src) {
  function cm_set_gain (line 427) | void cm_set_gain(cm_Source *src, double gain) {
  function cm_set_pan (line 433) | void cm_set_pan(cm_Source *src, double pan) {
  function cm_set_pitch (line 439) | void cm_set_pitch(cm_Source *src, double pitch) {
  function cm_set_loop (line 450) | void cm_set_loop(cm_Source *src, int loop) {
  function cm_play (line 455) | void cm_play(cm_Source *src) {
  function cm_pause (line 467) | void cm_pause(cm_Source *src) {
  function cm_stop (line 472) | void cm_stop(cm_Source *src) {
  type Wav (line 482) | typedef struct {
  type WavStream (line 490) | typedef struct {
  function wav_handler (line 564) | static void wav_handler(cm_Event *e) {
  type OggStream (line 661) | typedef struct {
  function ogg_handler (line 667) | static void ogg_handler(cm_Event *e) {

FILE: src/cmixer.h
  type cm_Int16 (line 13) | typedef short           cm_Int16;
  type cm_Int32 (line 14) | typedef int             cm_Int32;
  type cm_Int64 (line 15) | typedef long long       cm_Int64;
  type cm_UInt8 (line 16) | typedef unsigned char   cm_UInt8;
  type cm_UInt16 (line 17) | typedef unsigned short  cm_UInt16;
  type cm_UInt32 (line 18) | typedef unsigned        cm_UInt32;
  type cm_Source (line 20) | typedef struct cm_Source cm_Source;
  type cm_Event (line 22) | typedef struct {
  type cm_SourceInfo (line 32) | typedef struct {
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
  {
    "path": "LICENSE",
    "chars": 1047,
    "preview": "Copyright (c) 2017 rxi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software an"
  },
  {
    "path": "README.md",
    "chars": 1824,
    "preview": "\n# cmixer\nA lightweight, portable ANSI C audio mixer for games.\n\n#### Features\n* Easy to use\n* Tiny: two files, around 6"
  },
  {
    "path": "demo/sdl2/build.bat",
    "chars": 85,
    "preview": "gcc -I../../src/ ../../src/cmixer.c src/main.c -lmingw32 -lSDL2main -lSDL2 -Wall -O3\n"
  },
  {
    "path": "demo/sdl2/build.sh",
    "chars": 76,
    "preview": "#!/bin/bash\ngcc -I../../src/ ../../src/cmixer.c src/main.c -lSDL2 -Wall -O3\n"
  },
  {
    "path": "demo/sdl2/src/main.c",
    "chars": 1600,
    "preview": "#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"
  },
  {
    "path": "doc/api.md",
    "chars": 5135,
    "preview": "\n# cmixer API\n* [Functions](#functions)\n* [Events](#events)\n\n\n## Functions\n\n##### const char\\* cm_get_error(void)\nReturn"
  },
  {
    "path": "src/cmixer.c",
    "chars": 17479,
    "preview": "/*\n** Copyright (c) 2017 rxi\n**\n** Permission is hereby granted, free of charge, to any person obtaining a copy\n** of th"
  },
  {
    "path": "src/cmixer.h",
    "chars": 1714,
    "preview": "/*\n** Copyright (c) 2017 rxi\n**\n** This library is free software; you can redistribute it and/or modify it\n** under the "
  }
]

About this extraction

This page contains the full source code of the rxi/cmixer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (28.3 KB), approximately 8.2k tokens, and a symbol index with 45 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!