Skip to content

Instantly share code, notes, and snippets.

@madgarden
Last active May 27, 2016 17:59
Show Gist options
  • Save madgarden/6251565 to your computer and use it in GitHub Desktop.
Save madgarden/6251565 to your computer and use it in GitHub Desktop.
wav-play: A WAV loader/player I use for all of my projects, created initially for Saucelifter. Just needs a (sound) buffer for output. Only implemented what I currently need for my games, and for that it works just fine. You can find the unfinished stubs in the code.
// The Madgarden games WAV player
// LICENSE
//
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
/*
TODO: Post-process clipping (need 32-bit mix buffer)
- allow to create a blank WAV
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "wav-play.h"
#define DEFAULT_RATE 44100
#define DEFAULT_WIDTH 16
#ifndef MIN
#define MIN(a, b) ((b) < (a) ? (b) : (a))
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#define FIX_POINT 8
#define FIX_SCALE (1 << FIX_POINT)
typedef unsigned char uchar;
typedef struct
{
int taken:1;
int playing:1;
int loop:1;
int oneshot:1;
float pan;
int left;
int right;
float vol;
float pitch;
unsigned int step;
unsigned int pos;
WAV *wav;
}VOICE;
static int initialized = 0;
static float sample_rate = DEFAULT_RATE;
static unsigned int sample_bits = 16;
static VOICE voices[WAV_PLAYER_NUM_VOICES];
void wave_init(unsigned int rate, unsigned int bits)
{
sample_rate = rate;
sample_bits = bits;
memset(voices, 0, sizeof(voices));
initialized = 1;
}
/* Read a 32-bit number stored on disk as 4 little-endian bytes. */
static int read_uint32_le(uint32_t *i, FILE *fp)
{
unsigned char b[4];
int ret = fread(b, 4, 1, fp);
*i = b[0];
*i += b[1] << 8;
*i += b[2] << 16;
*i += b[3] << 24;
return ret;
}
static int read_uint16_le(uint16_t *i, FILE *fp)
{
unsigned char b[2];
int ret = fread(b, 2, 1, fp);
*i = b[0];
*i += b[1] << 8;
return ret;
}
static int convert_sample(WAV *wav)
{
unsigned int i;
int shift = sample_bits - wav->width;
if(!shift) return 1;
switch(shift)
{
// Convert 8 -> 16 bits
case 8:
{
unsigned int newsize = wav->frames * 2;
int16_t *newdata = malloc(newsize);
printf("Converting 8 -> 16 bits (%d bytes -> %d bytes)\n",
wav->frames, newsize);
for(i = 0; i < wav->frames; i++)
{
// Unsigned 8-bit to signed 16-bit
newdata[i] = (((int16_t)wav->data.buffer8[i]) << 8) ^ 0x8000;
}
free(wav->data.buffer8);
wav->data.buffer16 = newdata;
wav->width = 16;
}
return 1;
// Convert 16 -> 8 bits
case -8:
{
int8_t *newdata = malloc(wav->frames);
printf("Converting 16 -> 8 bits (%d bytes -> %d bytes)\n",
wav->frames * 2, wav->frames);
for(i = 0; i < wav->frames; i++)
{
newdata[i] = (int8_t)(wav->data.buffer16[i] >> 8);
}
free(wav->data.buffer16);
wav->data.buffer8 = (int8_t*)newdata;
wav->width = 8;
}
return 1;
}
return 0;
}
// TODO: bit/rate sample conversion to init'd settings
WAV *wave_load(const char *fname)
{
FILE *fp;
WAV *wav = NULL;
if(!initialized)
{
return NULL;
}
fp = fopen(fname, "rb");
if(fp)
{
char id[5];
uchar *sound_buffer;
uint32_t size;
uint16_t format_tag, channels, block_align, bits_per_sample;
uint32_t format_length, rate, avg_bytes_sec, data_size;
int ret;
id[4] = 0;
ret = fread(id, sizeof(uchar), 4, fp);
if (!strcmp(id, "RIFF"))
{
ret = fread(&size, sizeof(uint32_t), 1, fp);
ret = fread(id, sizeof(uchar), 4, fp);
if (!strcmp(id,"WAVE"))
{
ret = fread(id, sizeof(uchar), 4, fp);
ret = read_uint32_le(&format_length, fp);
ret = read_uint16_le(&format_tag, fp);
ret = read_uint16_le(&channels, fp);
ret = read_uint32_le(&rate, fp);
ret = read_uint32_le(&avg_bytes_sec, fp);
ret = read_uint16_le(&block_align, fp);
ret = read_uint16_le(&bits_per_sample, fp);
ret = fread(id, sizeof(uchar), 4, fp);
ret = read_uint32_le(&data_size, fp);
sound_buffer = (uchar *) malloc (sizeof(uchar) * data_size);
ret = fread(sound_buffer, sizeof(uchar), data_size, fp);
fclose(fp);
wav = (WAV*)malloc(sizeof(WAV));
wav->data.buffer = sound_buffer;
wav->width = bits_per_sample;
wav->channels = channels;
wav->rate = rate;
wav->frames = (data_size * 8) / (bits_per_sample * channels);
if(!convert_sample(wav))
{
printf("'%s':\n", fname);
printf("channels: %u\n", wav->channels);
printf("rate: %u\n", wav->rate);
printf("width: %u\n", wav->width);
free(wav->data.buffer);
free(wav);
printf("Error: conversion problem\n");
return 0;
}
//printf("frames: %u\n\n", wav->frames);
}
else
{
printf("Error: RIFF file but not a wave file\n");
}
}
else
{
printf("Error: not a RIFF file\n");
}
}
else
{
printf("Error: no file '%s'?\n", fname);
}
return wav;
}
void wave_free(WAV *wav)
{
if(!wav) return;
wave_stop_wav(wav);
if(wav->data.buffer) free(wav->data.buffer);
free(wav);
}
// TODO: use queue
int wave_take_voice(void)
{
int i;
for(i = 0; i < WAV_PLAYER_NUM_VOICES; i++)
{
if(voices[i].taken) continue;
voices[i].taken = 1;
return i;
}
return -1;
}
void wave_drop_voice(int voice)
{
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES) return;
voices[voice].taken = 0;
// Guess we'll let it still play if that's what they want
}
void wave_start_voice(int voice)
{
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES) return;
if(!voices[voice].wav) return;
voices[voice].playing = 1;
}
void wave_stop_voice(int voice)
{
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES)
{
return;
}
voices[voice].playing = 0;
}
void wave_set_voice_pan(int voice, float pan)
{
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES) return;
if(pan > 1)
{
pan = 1;
}
else if(pan < -1)
{
pan = -1;
}
voices[voice].pan = pan;
voices[voice].right = (pan >=0 ? 1 : (1 + pan)) * FIX_SCALE *
voices[voice].vol;
voices[voice].left = (pan <=0 ? 1 : (1 - pan)) * FIX_SCALE *
voices[voice].vol;
}
void wave_set_voice_vol(int voice, float vol)
{
int pan;
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES) return;
if(vol > 1)
{
vol = 1;
}
else if(vol < 0)
{
vol = 0;
}
pan = voices[voice].pan;
voices[voice].vol = vol;
voices[voice].right = (pan >=0 ? 1 : (1 + pan)) * FIX_SCALE *
voices[voice].vol;
voices[voice].left = (pan <=0 ? 1 : (1 - pan)) * FIX_SCALE *
voices[voice].vol;
}
void wave_set_voice_pitch(int voice, float pitch)
{
float resample;
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES) return;
if(!voices[voice].wav) return;
if(pitch < 0)
{
pitch = 0;
}
resample = voices[voice].wav->rate;
resample /= sample_rate;
voices[voice].pitch = pitch;
voices[voice].step = pitch * FIX_SCALE * resample;
}
void wave_set_voice_loop(int voice, int loop)
{
if(voice < 0 || voice > WAV_PLAYER_NUM_VOICES) return;
voices[voice].loop = (loop != 0);
}
int wave_play(WAV *wav, float vol, float pitch, float pan, int loop)
{
int voice;
if(!initialized) return -1;
if(!wav)
{
return -2;
}
voice = wave_take_voice();
if(voice < 0) return voice;
// TEMP:
// return -1;
voices[voice].loop = (loop != 0);
voices[voice].wav = wav;
voices[voice].playing = 1;
voices[voice].oneshot = 1;
voices[voice].pos = 0;
wave_set_voice_vol(voice, vol);
wave_set_voice_pitch(voice, pitch);
wave_set_voice_pan(voice, pan);
return voice;
}
int wave_just_play(WAV *wav)
{
return wave_play(wav, 1, 1, 0, 0);
}
static int wave_output_mono_s16(VOICE *voice, void *buffer, int frames)
{
int mix;
int16_t *ptr = buffer;
int i;
WAV *wav = voice->wav;
int pos = voice->pos << FIX_POINT;
int step = voice->step;
for(i = 0; i < frames; i++)
{
mix = *ptr;
mix += ((wav->data.buffer16[pos >> FIX_POINT]) * voice->left) >> FIX_POINT;
if(mix > 32767)
{
mix = 32767;
}
else if(mix < -32768)
{
mix = -32768;
}
*ptr++ = (int16_t)mix;
pos += step;
if((unsigned int)pos >= (wav->frames << FIX_POINT))
{
if(voice->loop)
{
pos -= (wav->frames << FIX_POINT);
}
else
{
pos = (wav->frames << FIX_POINT);
voice->playing = 0;
if(voice->oneshot)
{
voice->taken = 0;
voice->oneshot = 0;
pos = 0;
}
break;
}
}
}
voice->pos = (pos >> FIX_POINT);
return 0;
}
static int wave_output_stereo_s16(VOICE *voice, void *buffer, int frames)
{
int mix;
int16_t *ptr = buffer;
int i;
WAV *wav = voice->wav;
int pos = voice->pos << FIX_POINT;
int step = voice->step;
for(i = 0; i < frames; i++)
{
mix = *ptr;
mix += ((wav->data.buffer16[pos >> FIX_POINT]) * voice->left) >>
FIX_POINT;
if(mix > 0x7fff)
{
mix = 0x7fff;
}
else if(mix < -0x8000)
{
mix = -0x8000;
}
*ptr++ = (int16_t)mix;
mix = *ptr;
mix += ((wav->data.buffer16[pos >> FIX_POINT]) * voice->right) >>
FIX_POINT;
if(mix > 0x7fff)
{
mix = 0x7fff;
}
else if(mix < -0x8000)
{
mix = -0x8000;
}
*ptr++ = (int16_t)mix;
pos += step;
if((unsigned int)pos >= (wav->frames << FIX_POINT))
{
if(voice->loop)
{
pos -= (wav->frames << FIX_POINT);
}
else
{
pos = (wav->frames << FIX_POINT);
voice->playing = 0;
if(voice->oneshot)
{
voice->taken = 0;
voice->oneshot = 0;
pos = 0;
}
break;
}
}
}
voice->pos = (pos >> FIX_POINT);
return 0;
}
// Mono signed buffer
int wave_output(void *buffer, unsigned int frames)
{
int voice;
int playing = 0;
if(!buffer) return 0;
for(voice = 0; voice < WAV_PLAYER_NUM_VOICES; voice++)
{
if(!voices[voice].playing) continue;
// TODO: call appropriate mixing function
wave_output_mono_s16(&voices[voice], buffer, frames);
playing++;
}
return playing;
}
// Interleaved signed stereo buffer
int wave_output_stereo(void *interleaved_buffer, unsigned int frames)
{
int voice;
int playing = 0;
if(!interleaved_buffer) return 0;
for(voice = 0; voice < WAV_PLAYER_NUM_VOICES; voice++)
{
if(!voices[voice].playing) continue;
wave_output_stereo_s16(&voices[voice], interleaved_buffer, frames);
playing++;
}
return playing;
}
void wave_drop_all(void)
{
int i;
for(i = 0; i < WAV_PLAYER_NUM_VOICES; i++)
{
wave_stop_voice(i);
wave_drop_voice(i);
}
}
// Stop all voices that are playing specified wav
void wave_stop_wav(WAV *wav)
{
int i;
for(i = 0; i < WAV_PLAYER_NUM_VOICES; i++)
{
if(voices[i].wav == wav)
{
wave_stop_voice(i);
wave_drop_voice(i);
}
}
}
int wave_is_wav_playing(WAV *wav)
{
int i;
for(i = 0; i < WAV_PLAYER_NUM_VOICES; i++)
{
if(voices[i].wav == wav)
{
if(voices[i].playing) return 1;
}
}
return 0;
}
int wave_count_playing(void)
{
int count = 0;
int voice;
if(!initialized) return 0;
for(voice = 0; voice < WAV_PLAYER_NUM_VOICES; voice++)
{
if(voices[voice].playing) count++;
}
return count;
}
#ifndef __WAV_PLAYER_H__
#define __WAV_PLAYER_H__
#include <stdint.h>
#ifndef UINT32_MAX
typedef unsigned char uint8_t;
typedef char int8_t;
typedef unsigned short uint16_t;
typedef short int16_t;
typedef int int32_t;
typedef unsigned int uint32_t;
#endif
#ifndef WAV_PLAYER_NUM_VOICES
#define WAV_PLAYER_NUM_VOICES 32
#endif
typedef struct
{
unsigned int channels;
unsigned int rate;
unsigned int width;
unsigned int frames;
union
{
void *buffer;
int16_t *buffer16;
int8_t *buffer8;
}data;
}WAV;
void wave_init(unsigned int rate, unsigned int bits);
WAV *wave_load(const char *fname);
void wave_free(WAV *wav);
int wave_take_voice(void);
void wave_start_voice(int voice);
void wave_stop_voice(int voice);
void wave_drop_voice(int voice);
void wave_drop_all(void);
int wave_count_playing(void);
void wave_stop_wav(WAV *wav);
int wave_is_wav_playing(WAV *wav);
void wave_set_voice_vol(int voice, float vol);
void wave_set_voice_pan(int voice, float pan);
void wave_set_voice_pitch(int voice, float pitch);
void wave_set_voice_loop(int voice, int loop);
int wave_just_play(WAV *wav);
int wave_play(WAV *wav, float vol, float pitch, float pan, int loop);
int wave_output(void *buffer, unsigned int frames);
int wave_output_stereo(void *interleaved_buffer, unsigned int frames);
#endif
@losinggeneration
Copy link

Any specific license for this code?

@madgarden
Copy link
Author

Oops I didn't know there would be comments on this. No license, just PD. GO NUTS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment