@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
97
src/mixer.c
Normal file
97
src/mixer.c
Normal file
@@ -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 <string.h>
|
||||
#include <dos.h>
|
||||
#include <stdbool.h>
|
||||
#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;
|
||||
}
|
||||
18
src/mixer.h
Normal file
18
src/mixer.h
Normal file
@@ -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 <stdint.h>
|
||||
#include "source.h"
|
||||
|
||||
int16_t const* mixer_getNextBlock(void);
|
||||
void mixer_play(source_t const *source);
|
||||
void mixer_mix(void);
|
||||
|
||||
#endif
|
||||
@@ -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++) {
|
||||
|
||||
30
src/modules/l_sound.c
Normal file
30
src/modules/l_sound.c
Normal file
@@ -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;
|
||||
}
|
||||
52
src/modules/l_source.c
Normal file
52
src/modules/l_source.c
Normal file
@@ -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;
|
||||
}
|
||||
408
src/soundblaster.c
Normal file
408
src/soundblaster.c
Normal file
@@ -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 <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <dos.h>
|
||||
#include <dpmi.h>
|
||||
#include <go32.h>
|
||||
#include <sys/nearptr.h>
|
||||
#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<<irq));
|
||||
} else {
|
||||
uint8_t irqmask = inportb(PIC2_DATA);
|
||||
outportb(PIC2_DATA, irqmask & ~(1<<(irq-8)));
|
||||
}
|
||||
|
||||
isrInstalled = true;
|
||||
}
|
||||
|
||||
|
||||
static int parseBlasterSettings(void) {
|
||||
char const* blasterEnv = getenv("BLASTER");
|
||||
if(blasterEnv == 0) {
|
||||
return SOUNDBLASTER_ENV_NOT_SET;
|
||||
}
|
||||
|
||||
int res = sscanf(blasterEnv, "A%hx I%hu", &baseAddress, &irq);
|
||||
if(res < 2) {
|
||||
return SOUNDBLASTER_ENV_INVALID;
|
||||
}
|
||||
|
||||
// "H" field may be preceeded by any number of other fields, so let's just search it
|
||||
char const *dmaLoc = strchr(blasterEnv, 'H');
|
||||
if(dmaLoc == NULL) {
|
||||
return SOUNDBLASTER_ENV_INVALID;
|
||||
}
|
||||
|
||||
dmaChannel = atoi(dmaLoc+1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int allocSampleBuffer(void) {
|
||||
static int maxRetries = 10;
|
||||
int selectors[maxRetries];
|
||||
int current;
|
||||
|
||||
for(current = 0; current < maxRetries; ++current) {
|
||||
int segment = __dpmi_allocate_dos_memory((SAMPLE_BUFFER_SIZE+15)>>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;
|
||||
}
|
||||
29
src/soundblaster.h
Normal file
29
src/soundblaster.h
Normal file
@@ -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 <stdint.h>
|
||||
|
||||
// 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
|
||||
60
src/source.c
Normal file
60
src/source.c
Normal file
@@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
22
src/source.h
Normal file
22
src/source.h
Normal file
@@ -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 <stdint.h>
|
||||
|
||||
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
|
||||
84
src/wav.c
Normal file
84
src/wav.c
Normal file
@@ -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 <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#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";
|
||||
}
|
||||
36
src/wav.h
Normal file
36
src/wav.h
Normal file
@@ -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 <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user