Last active
May 27, 2016 17:59
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
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
Any specific license for this code?