diff --git a/src/embed/boot.lua b/src/embed/boot.lua index d93a122..4b31bd4 100644 --- a/src/embed/boot.lua +++ b/src/embed/boot.lua @@ -104,6 +104,7 @@ function love.run() love.graphics.clear() if love.draw then love.draw() end love.graphics.present() + love.sound.mix() end end diff --git a/src/luaobj.h b/src/luaobj.h index 1566760..aade433 100644 --- a/src/luaobj.h +++ b/src/luaobj.h @@ -22,9 +22,10 @@ typedef struct { /* Each mask should consist of its unique bit and the unique bit of all its * super classes */ -#define LUAOBJ_TYPE_IMAGE (1 << 0) -#define LUAOBJ_TYPE_QUAD (1 << 1) -#define LUAOBJ_TYPE_FONT (1 << 2) +#define LUAOBJ_TYPE_IMAGE (1 << 0) +#define LUAOBJ_TYPE_QUAD (1 << 1) +#define LUAOBJ_TYPE_FONT (1 << 2) +#define LUAOBJ_TYPE_SOURCE (1 << 3) int luaobj_newclass(lua_State *L, const char *name, const char *extends, diff --git a/src/main.c b/src/main.c index 918b370..3d48184 100644 --- a/src/main.c +++ b/src/main.c @@ -20,12 +20,15 @@ #include "image.h" #include "palette.h" #include "package.h" +#include "soundblaster.h" +#include "mixer.h" static lua_State *L; static void deinit(void) { /* Deinit and clear up everything. Called at exit */ + soundblaster_deinit(); vga_deinit(); keyboard_deinit(); lua_close(L); @@ -55,6 +58,7 @@ int main(int argc, char **argv) { /* Init everything */ atexit(deinit); + soundblaster_init(mixer_getNextBlock); vga_init(); palette_init(); keyboard_init(); diff --git a/src/mixer.c b/src/mixer.c new file mode 100644 index 0000000..891b359 --- /dev/null +++ b/src/mixer.c @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include "mixer.h" +#include "soundblaster.h" + +// Configure me! +#define MIXER_MAX_SOURCES 8 + + +typedef struct { + int offset; + source_t const *source; +} mixed_sound_t; + + +static mixed_sound_t sources[MIXER_MAX_SOURCES]; +static int activeSources = 0; +static int16_t data[SOUNDBLASTER_SAMPLES_PER_BUFFER] = {0}; +static bool canmix = true; + + +int16_t const * mixer_getNextBlock(void) { + canmix = true; + return data; +} + + +void mixer_play(source_t const *source) { + if(activeSources == MIXER_MAX_SOURCES) { + // TODO Replace older source with new one instead? + return; + } + + for(int i = 0; i < activeSources; ++i) { + if(sources[i].source == source) { + sources[i].offset = 0; + return; + } + } + + sources[activeSources].offset = 0; + sources[activeSources].source = source; + ++activeSources; +} + + +static inline int16_t mix(int32_t a, int32_t b) { + int32_t res = a + b; + if(res > INT16_MAX) + res = INT16_MAX; + if(res < INT16_MIN) + res = INT16_MIN; + return res; +} + + +void mixer_mix(void) { + if(!canmix) { + return; + } + + memset(data, 0, SOUNDBLASTER_SAMPLES_PER_BUFFER * sizeof(int16_t)); + + for(int i = 0; i < activeSources; ++i) { + mixed_sound_t *snd = sources + i; + int len = snd->source->sampleCount - snd->offset; + int16_t const* sourceBuf = snd->source->samples + snd->offset; + + if(len > SOUNDBLASTER_SAMPLES_PER_BUFFER) { + len = SOUNDBLASTER_SAMPLES_PER_BUFFER; + } + + for(int offset = 0; offset < len; ++offset) { + data[offset] = mix(data[offset], sourceBuf[offset]); + } + + snd->offset += len; + } + + for(int i = activeSources-1; i >= 0; --i) { + mixed_sound_t *snd = sources + i; + if(snd->offset == snd->source->sampleCount) { + *snd = sources[activeSources-1]; + --activeSources; + } + } + + canmix = false; +} diff --git a/src/mixer.h b/src/mixer.h new file mode 100644 index 0000000..e11c9bd --- /dev/null +++ b/src/mixer.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef MIXER_H +#define MIXER_H + +#include +#include "source.h" + +int16_t const* mixer_getNextBlock(void); +void mixer_play(source_t const *source); +void mixer_mix(void); + +#endif diff --git a/src/modules/l_love.c b/src/modules/l_love.c index 9a978de..eff034c 100644 --- a/src/modules/l_love.c +++ b/src/modules/l_love.c @@ -26,6 +26,8 @@ int luaopen_graphics(lua_State *L); int luaopen_timer(lua_State *L); int luaopen_keyboard(lua_State *L); int luaopen_mouse(lua_State *L); +int luaopen_sound(lua_State *L); +int luaopen_source(lua_State *L); int luaopen_love(lua_State *L) { int i; @@ -36,6 +38,7 @@ int luaopen_love(lua_State *L) { luaopen_image, luaopen_quad, luaopen_font, + luaopen_source, NULL, }; for (i = 0; classes[i]; i++) { @@ -59,6 +62,7 @@ int luaopen_love(lua_State *L) { { "timer", luaopen_timer }, { "keyboard", luaopen_keyboard }, { "mouse", luaopen_mouse }, + { "sound", luaopen_sound }, { 0 }, }; for (i = 0; mods[i].name; i++) { diff --git a/src/modules/l_sound.c b/src/modules/l_sound.c new file mode 100644 index 0000000..eee6994 --- /dev/null +++ b/src/modules/l_sound.c @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include "mixer.h" +#include "luaobj.h" + + +int l_sound_mix(lua_State *L) { + mixer_mix(); + return 0; +} + + +int l_source_new(lua_State *L); + + +int luaopen_sound(lua_State *L) { + luaL_Reg reg[] = { + { "mix", l_sound_mix }, + { "newSource", l_source_new }, + { 0, 0 } + }; + + luaL_newlib(L, reg); + return 1; +} diff --git a/src/modules/l_source.c b/src/modules/l_source.c new file mode 100644 index 0000000..a043da1 --- /dev/null +++ b/src/modules/l_source.c @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include "luaobj.h" +#include "source.h" +#include "mixer.h" + + +#define CLASS_TYPE LUAOBJ_TYPE_SOURCE +#define CLASS_NAME "Source" + + +int l_source_new(lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + + source_t *self = luaobj_newudata(L, sizeof(*self)); + luaobj_setclass(L, CLASS_TYPE, CLASS_NAME); + const char *err = source_init(self, filename); + if (err) luaL_error(L, err); + return 1; +} + + +int l_source_gc(lua_State *L) { + source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE); + source_deinit(self); + return 0; +} + + +int l_source_play(lua_State *L) { + source_t *self = luaobj_checkudata(L, 1, CLASS_TYPE); + mixer_play(self); + return 0; +} + + +int luaopen_source(lua_State *L) { + luaL_Reg reg[] = { + { "new", l_source_new }, + { "__gc", l_source_gc }, + { "play", l_source_play }, + { 0, 0 } + }; + + luaobj_newclass(L, CLASS_NAME, NULL, l_source_new, reg); + return 1; +} diff --git a/src/soundblaster.c b/src/soundblaster.c new file mode 100644 index 0000000..918135d --- /dev/null +++ b/src/soundblaster.c @@ -0,0 +1,408 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "soundblaster.h" + +#define BYTE(val, byte) (((val) >> ((byte) * 8)) & 0xFF) + +#define SOUNDBLASTER_SAMPLES_PER_BUFFER 2048 +#define SAMPLE_BUFFER_SIZE (SOUNDBLASTER_SAMPLES_PER_BUFFER * sizeof(uint16_t) * 2) +#define SAMPLE_RATE 22050 + + +// SB16 +#define BLASTER_RESET_PORT 0x6 +#define BLASTER_READ_PORT 0xA +#define BLASTER_WRITE_PORT 0xC +#define BLASTER_READ_BUFFER_STATUS_PORT 0xE +#define BLASTER_INTERRUPT_ACKNOWLEDGE_8BIT 0xE +#define BLASTER_INTERRUPT_ACKNOWLEDGE_16BIT 0xF +#define BLASTER_MIXER_OUT_PORT 0x4 +#define BLASTER_MIXER_IN_PORT 0x5 +#define BLASTER_MIXER_INTERRUPT_STATUS 0x82 +#define BLASTER_16BIT_INTERRUPT 0x02 +#define BLASTER_READ_BUFFER_STATUS_AVAIL 0x80 +#define BLASTER_WRITE_BUFFER_STATUS_UNAVAIL 0x80 +#define BLASTER_READY_BYTE 0xAA +#define BLASTER_SET_OUTPUT_SAMPLING_RATE 0x41 +#define BLASTER_PROGRAM_16BIT_IO_CMD 0xB0 +#define BLASTER_PROGRAM_FLAG_FIFO 0x02 +#define BLASTER_PROGRAM_FLAG_AUTO_INIT 0x04 +#define BLASTER_PROGRAM_FLAG_INPUT 0x08 +#define BLASTER_PROGRAM_8BIT_IO_CMD 0xC0 +#define BLASTER_PROGRAM_STEREO 0x20 +#define BLASTER_PROGRAM_SIGNED 0x10 +#define BLASTER_SPEAKER_ON_CMD 0xD1 +#define BLASTER_SPEAKER_OFF_CMD 0xD3 +#define BLASTER_EXIT_AUTO_DMA 0xD9 + + +// PIC +#define PIC1_COMMAND 0x20 +#define PIC2_COMMAND 0xA0 +#define PIC1_DATA 0x21 +#define PIC2_DATA 0xA1 +#define PIC_EOI 0x20 +#define PIC_IRQ07_MAP 0x08 +#define PIC_IRQ8F_MAP 0x70 + + +// DMA +#define DMA_DIRECTION_READ_FROM_MEMORY 0x08 +#define DMA_TRANSFER_MODE_BLOCK 0x80 + + +static const struct { + uint8_t startAddressRegister; + uint8_t countRegister; + uint8_t singleChannelMaskRegister; + uint8_t modeRegister; + uint8_t flipFlopResetRegister; + uint8_t pageRegister; +} dmaRegisters[] = { + { 0x00, 0x01, 0x0A, 0x0B, 0x0C, 0x87 }, + { 0x02, 0x03, 0x0A, 0x0B, 0x0C, 0x83 }, + { 0x04, 0x05, 0x0A, 0x0B, 0x0C, 0x81 }, + { 0x06, 0x07, 0x0A, 0x0B, 0x0C, 0x82 }, + { 0xC0, 0xC2, 0xD4, 0xD6, 0xD8, 0x8F }, + { 0xC4, 0xC6, 0xD4, 0xD6, 0xD8, 0x8B }, + { 0xC8, 0xCA, 0xD4, 0xD6, 0xD8, 0x89 }, + { 0xCC, 0xCE, 0xD4, 0xD6, 0xD8, 0x8A } +}; + +static volatile int stopDma = 0; +static uint16_t *sampleBuffer; +static int sampleBufferSelector; +static uint16_t baseAddress; +static uint16_t irq; +static uint16_t dmaChannel; +static bool isrInstalled = false; +static int writePage = 0; +static bool blasterInitialized = false; +static _go32_dpmi_seginfo oldBlasterHandler, newBlasterHandler; +static soundblaster_getSampleProc getSamples; + + +static void writeDSP(uint8_t value) { + while((inportb(baseAddress + BLASTER_WRITE_PORT) & + BLASTER_WRITE_BUFFER_STATUS_UNAVAIL) != 0) {} + + outportb(baseAddress + BLASTER_WRITE_PORT, value); +} + + +static uint8_t readDSP() { + uint8_t status; + while(((status = inportb(baseAddress + BLASTER_READ_BUFFER_STATUS_PORT)) + & BLASTER_READ_BUFFER_STATUS_AVAIL) == 0) { + } + + return inportb(baseAddress + BLASTER_READ_PORT); +} + + +static inline void delay3us() { + uint64_t waited = 0; + uclock_t lastTime = uclock(); + while(waited < (3*UCLOCKS_PER_SEC) / 1000000) { + uclock_t nowTime = uclock(); + + // Just ignore timer wraps. In the worst case we get a slightly + // longer delay, but who cares? + if(nowTime > lastTime) { + waited += nowTime - lastTime; + } + + lastTime = nowTime; + } +} + + +static int resetBlaster(void) { + for(int j = 0; j < 1000; ++j) { + outportb(baseAddress + BLASTER_RESET_PORT, 1); + delay3us(); + outportb(baseAddress + BLASTER_RESET_PORT, 0); + + if(readDSP() == BLASTER_READY_BYTE) { + return 0; + } + } + return SOUNDBLASTER_RESET_ERROR; +} + + +static void soundblasterISR(void) { + outportb(baseAddress + BLASTER_MIXER_OUT_PORT, + BLASTER_MIXER_INTERRUPT_STATUS); + uint8_t status = inportb(baseAddress + BLASTER_MIXER_IN_PORT); + + if(status & BLASTER_16BIT_INTERRUPT) { + if(stopDma == 1) { + writeDSP(BLASTER_EXIT_AUTO_DMA); + stopDma = 2; + } else { + uint8_t* dst = (uint8_t*)(sampleBuffer) + + writePage * SAMPLE_BUFFER_SIZE / 2; + + memcpy(dst, getSamples(), SAMPLE_BUFFER_SIZE / 2); + + writePage = 1 - writePage; + inportb(baseAddress + BLASTER_INTERRUPT_ACKNOWLEDGE_16BIT); + } + } + + if(irq >= 8) { + outportb(PIC2_COMMAND, PIC_EOI); + } + outportb(PIC1_COMMAND, PIC_EOI); +} + + +static void setBlasterISR(void) { + // Map IRQ to interrupt number on the CPU + uint16_t interruptVector = irq + irq + (irq < 8) + ? PIC_IRQ07_MAP + : PIC_IRQ8F_MAP; + + _go32_dpmi_get_protected_mode_interrupt_vector(interruptVector, + &oldBlasterHandler); + + newBlasterHandler.pm_offset = (int)soundblasterISR; + newBlasterHandler.pm_selector = _go32_my_cs(); + _go32_dpmi_chain_protected_mode_interrupt_vector(interruptVector, &newBlasterHandler); + + // PIC: unmask SB IRQ + if(irq < 8) { + uint8_t irqmask = inportb(PIC1_DATA); + outportb(PIC1_DATA, irqmask & ~(1<>4, &selectors[current]); + if(segment == -1) { + break; + } + + uint32_t bufferPhys = __djgpp_conventional_base + segment * 16; + + // The DMA buffer must not cross a 64k boundary + if(bufferPhys % 0x10000 < 0x10000 - SAMPLE_BUFFER_SIZE) { + sampleBuffer = (uint16_t*)bufferPhys; + memset(sampleBuffer, 0, SAMPLE_BUFFER_SIZE); + sampleBufferSelector = selectors[current]; + --current; + break; + } + } + + // Free misaligned buffers + for(; current > 0; --current) { + __dpmi_free_dos_memory(selectors[current]); + } + + if(sampleBuffer == NULL) { + return SOUNDBLASTER_ALLOC_ERROR; + } + + return 0; +} + + +static void turnSpeakerOn(void) { + writeDSP(BLASTER_SPEAKER_ON_CMD); +} + + +static void turnSpeakerOff(void) { + writeDSP(BLASTER_SPEAKER_OFF_CMD); +} + + +static void dmaSetupTransfer(int channel, + uint8_t direction, + bool autoReload, + bool down, + uint8_t mode, + uint32_t startAddress, + uint32_t count) { + + uint8_t modeByte = direction + | mode + | ((uint8_t)autoReload << 4) + | ((uint8_t)down << 5) + | (channel & 0x03); + + uint8_t maskEnable = (channel & 0x03) | 0x04; + + uint32_t offset = startAddress; + + // Special handling of 16 bit DMA channels: + // The DMA controller needs offset and count to be half the actual value and + // internally doubles it again + if(channel > 3) { + offset >>= 1; + count >>= 1; + } + + uint8_t page = BYTE(startAddress, 2); + + outportb(dmaRegisters[channel].singleChannelMaskRegister, maskEnable); + outportb(dmaRegisters[channel].flipFlopResetRegister, 0x00); + outportb(dmaRegisters[channel].modeRegister, modeByte); + outportb(dmaRegisters[channel].startAddressRegister, BYTE(offset, 0)); + outportb(dmaRegisters[channel].startAddressRegister, BYTE(offset, 1)); + outportb(dmaRegisters[channel].countRegister, BYTE(count-1, 0)); + outportb(dmaRegisters[channel].countRegister, BYTE(count-1, 1)); + outportb(dmaRegisters[channel].pageRegister, page); + outportb(dmaRegisters[channel].singleChannelMaskRegister, maskEnable & 0x03); +} + + +static void startDMAOutput(void) { + uint32_t offset = ((uint32_t)sampleBuffer) - __djgpp_conventional_base; + + uint32_t samples = SAMPLE_BUFFER_SIZE / sizeof(int16_t); + + dmaSetupTransfer(dmaChannel, DMA_DIRECTION_READ_FROM_MEMORY, true, false, + DMA_TRANSFER_MODE_BLOCK, offset, SAMPLE_BUFFER_SIZE); + + // SB16 setup + writeDSP(BLASTER_SET_OUTPUT_SAMPLING_RATE); + writeDSP(BYTE(SAMPLE_RATE, 1)); + writeDSP(BYTE(SAMPLE_RATE, 0)); + writeDSP(BLASTER_PROGRAM_16BIT_IO_CMD + | BLASTER_PROGRAM_FLAG_AUTO_INIT + | BLASTER_PROGRAM_FLAG_FIFO); + writeDSP(BLASTER_PROGRAM_SIGNED); + writeDSP(BYTE(samples/2-1, 0)); + writeDSP(BYTE(samples/2-1, 1)); +} + + +int soundblaster_init(soundblaster_getSampleProc getsamplesproc) { + if(!__djgpp_nearptr_enable()) { + return SOUNDBLASTER_DOS_ERROR; + } + + int err = parseBlasterSettings(); + if(err != 0) { + fprintf(stderr, "BLASTER environment variable not set or invalid\n"); + return err; + } + + err = resetBlaster(); + if(err != 0) { + fprintf(stderr, "Could not reset Soundblaster\n"); + return err; + } + + err = allocSampleBuffer(); + if(err != 0) { + fprintf(stderr, "Could not allocate sample buffer in conventional memory\n"); + return err; + } + + getSamples = getsamplesproc; + + setBlasterISR(); + turnSpeakerOn(); + startDMAOutput(); + + blasterInitialized = true; + + return 0; +} + + +static void deallocSampleBuffer(void) { + __dpmi_free_dos_memory(sampleBufferSelector); +} + + +static void resetBlasterISR(void) { + if(isrInstalled) { + uint16_t interruptVector = irq + irq + (irq < 8) + ? PIC_IRQ07_MAP + : PIC_IRQ8F_MAP; + + _go32_dpmi_set_protected_mode_interrupt_vector(interruptVector, &oldBlasterHandler); + isrInstalled = false; + } +} + + +static void stopDMAOutput(void) { + stopDma = 1; + while(stopDma == 1) {} +} + + +void soundblaster_deinit(void) { + if(blasterInitialized) { + turnSpeakerOff(); + stopDMAOutput(); + resetBlaster(); + resetBlasterISR(); + deallocSampleBuffer(); + blasterInitialized = false; + } +} + + +int soundblaster_getSampleRate(void) { + return SAMPLE_RATE; +} + + +int soundblaster_getSampleBufferSize(void) { + return SOUNDBLASTER_SAMPLES_PER_BUFFER; +} diff --git a/src/soundblaster.h b/src/soundblaster.h new file mode 100644 index 0000000..0a9d1e2 --- /dev/null +++ b/src/soundblaster.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef SOUNDBLASTER_H +#define SOUNDBLASTER_H + +#include + +// Error codes +#define SOUNDBLASTER_ENV_NOT_SET 1 +#define SOUNDBLASTER_ENV_INVALID 2 +#define SOUNDBLASTER_DOS_ERROR 3 +#define SOUNDBLASTER_RESET_ERROR 4 +#define SOUNDBLASTER_ALLOC_ERROR 5 + +#define SOUNDBLASTER_SAMPLES_PER_BUFFER 2048 + +typedef int16_t const* (*soundblaster_getSampleProc)(void); + +int soundblaster_init(soundblaster_getSampleProc sampleproc); +void soundblaster_deinit(void); +int soundblaster_getSampleRate(void); +int soundblaster_getSampleBufferSize(void); + +#endif diff --git a/src/source.c b/src/source.c new file mode 100644 index 0000000..eff79c9 --- /dev/null +++ b/src/source.c @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include "source.h" +#include "filesystem.h" +#include "lib/dmt/dmt.h" +#include "wav.h" + + +char const* source_init(source_t *self, char const* filename) { + int fileSize; + void *waveData = filesystem_read(filename, &fileSize); + + char const *err = NULL; + wav_t wav; + int res = wav_read(&wav, waveData, fileSize); + + if(res != WAV_ESUCCESS) { + err = wav_strerror(res); + goto fail; + } + + if(wav.bitdepth != 16) { + err = "Invalid Audio Format (only 16 Bit supported)"; + goto fail; + } + + if(wav.samplerate != 22050) { + err = "Invalid Audio Format (only 22050Hz supported)"; + goto fail; + } + + if(wav.channels != 1) { + err = "Invalid Audio Format (only mono supported)"; + goto fail; + } + + self->sampleCount = wav.length; + self->data = waveData; + self->samples = (int16_t*)wav.data; + + return NULL; + +fail: + filesystem_free(waveData); + return err; +} + + +void source_deinit(source_t *self) { + filesystem_free(self->data); +} + diff --git a/src/source.h b/src/source.h new file mode 100644 index 0000000..6176013 --- /dev/null +++ b/src/source.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2017 Florian Kesseler + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef SOURCE_H +#define SOURCE_H + +#include + +typedef struct { + int sampleCount; + int16_t *samples; + void *data; +} source_t; + +char const* source_init(source_t *self, char const* filename); +void source_deinit(source_t *self); + +#endif diff --git a/src/wav.c b/src/wav.c new file mode 100644 index 0000000..030d856 --- /dev/null +++ b/src/wav.c @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2015 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + + +#include +#include +#include +#include "wav.h" + +typedef unsigned short Uint16; +typedef unsigned int Uint32; + + +static const char *findSubChunk( + const char *data, size_t len, const char *id, size_t *size +) { + /* TODO : Error handling on malformed wav file */ + size_t idLen = strlen(id); + const char *p = data + 12; +next: + *size = *((Uint32*) (p + 4)); + if (memcmp(p, id, idLen)) { + p += 8 + *size; + if (p > data + len) return NULL; + goto next; + } + return p + 8; +} + + +int wav_read(wav_t *w, const void *data, size_t len) { + int bitdepth, channels, samplerate, format; + size_t sz; + const char *p = data; + memset(w, 0, sizeof(*w)); + /* Check header */ + if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4)) { + return WAV_EBADHEADER; + } + /* Find fmt subchunk */ + p = findSubChunk(data, len, "fmt", &sz); + if (!p) return WAV_ENOFMT; + /* Load fmt info */ + format = *((Uint16*) (p)); + channels = *((Uint16*) (p + 2)); + samplerate = *((Uint32*) (p + 4)); + bitdepth = *((Uint16*) (p + 14)); + if (format != 1) { + return WAV_ENOSUPPORT; + } + if (channels == 0 || samplerate == 0 || bitdepth == 0) { + return WAV_EBADFMT; + } + /* Find data subchunk */ + p = findSubChunk(data, len, "data", &sz); + if (!p) return WAV_ENODATA; + /* Init wav_t struct */ + w->data = p; + w->samplerate = samplerate; + w->channels = channels; + w->length = (sz / (bitdepth / 8)) / channels; + w->bitdepth = bitdepth; + /* Done! */ + return WAV_ESUCCESS; +} + + +const char *wav_strerror(int err) { + switch (err) { + case WAV_ESUCCESS : return "success"; + case WAV_EFAILURE : return "failure"; + case WAV_EBADHEADER : return "bad header data"; + case WAV_EBADFMT : return "bad fmt data"; + case WAV_ENOFMT : return "missing 'fmt' subchunk"; + case WAV_ENODATA : return "missing 'data' subchunk"; + case WAV_ENOSUPPORT : return "unsupported format; " + "expected uncompressed PCM"; + } + return "unknown error"; +} diff --git a/src/wav.h b/src/wav.h new file mode 100644 index 0000000..86dc202 --- /dev/null +++ b/src/wav.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2015 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + + +#ifndef WAV_H +#define WAV_H + +#include +#include + +typedef struct { + const void *data; + int bitdepth; + int samplerate; + int channels; + size_t length; +} wav_t; + +enum { + WAV_ESUCCESS = 0, + WAV_EFAILURE = -1, + WAV_EBADHEADER = -2, + WAV_EBADFMT = -3, + WAV_ENOFMT = -4, + WAV_ENODATA = -5, + WAV_ENOSUPPORT = -6 +}; + +int wav_read(wav_t *w, const void *data, size_t len); +const char *wav_strerror(int err); + +#endif