Add Dreamcast backend.

This commit is contained in:
David Reid
2026-02-16 19:07:35 +10:00
parent f6662fdb2e
commit 2324d5ad9e
6 changed files with 821 additions and 0 deletions
+5
View File
@@ -32,6 +32,11 @@
/tests/_build/a.out
/tests/_build/a.exe
/tests/debugging/archive/
/tests/dreamcast/miniaudio.elf
/tests/dreamcast/miniaudio_dreamcast_test.o
/tests/dreamcast/romdisk.img
/tests/dreamcast/romdisk.o
/tests/dreamcast/romdisk/
/tests/*.c
/tests/*.cpp
/website/docs/
+596
View File
@@ -3734,6 +3734,7 @@ example, ALSA, which is specific to Linux, will not be included in the Windows b
| AAudio | ma_device_backend_aaudio | Android 8+ |
| OpenSL|ES | ma_device_backend_opensl | Android (API level 16+) |
| Web Audio | ma_device_backend_webaudio | Web (via Emscripten) |
| Dreamcast | ma_device_backend_dreamcast | KallistiOS |
| Null | ma_device_backend_null | Cross Platform (not used on Web) |
+-------------+------------------------------+--------------------------------------------------------+
@@ -3800,6 +3801,62 @@ Some backends have some nuance details you may want to be aware of.
https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device
may fail if you try to start playback without first handling some kind of user input.
15.6. Dreamcast
---------------
The Dreamcast backend uses KallistiOS.
miniaudio will call `snd_init()` automatically unless you disable it via the context config:
```c
contextConfig.dreamcast.noInit = MA_TRUE;
```
The following device configuration is recommended.
Format: ma_format_u8 or ma_format_s16
Channels: 1 or 2
Sample Rate: 44100, 22050 or 11025
Period Size: 1024 to 16384, multiple of 32. Recommended you keep it a power of two.
noFixedSizedCallback: True (This will tell miniaudio to bypass one of its internal buffers.)
If you follow the above rules, miniaudio will skip it's data conversion pipeline and will pass
through a direct pointer to an internal buffer to the data callback without any extra buffering or
data conversion. You are still free to use other formats, but it'll just have a bit more overhead.
The miniaudio resampler will have enough overhead that it's strongly recommended you keep the
sample rate set to one of the recommended values and pre-process your audio assets to match.
You can initialize multiple devices and specify which voice channel you want to use. There is a
maximum of 64 voices. If you request two channels when initializing the device it will use two of
them. You can specify the index of the voice channel you want to use in the dreamcast device
config. You can use multiple devices to have the hardware do mixing for you, but if you do this
make sure you manage your voice channel properly. To configure the voice channel, set it in the
`dreamcast` config:
```c
deviceConfig.dreamcast.voiceChannel = 2; // 0 if the default. The left channel will use `voiceChannel + 0`, the right channel will use `voiceChannel + 1`.
```
If you initialize more than one `ma_device` object, self manage a single `ma_context` object. It is
a single context to many devices:
```c
ma_context context;
ma_context_init(...);
deviceConfig.dreamcast.voiceChannel = 0;
ma_device_init(&context, &deviceConfig, &device0);
deviceConfig.dreamcast.voiceChannel = 2; // <-- Make sure the voice channel is different between devices. Remember that a stereo device consumes two channels.
ma_device_init(&context, &deviceConfig, &device1);
```
Note that the `ma_device` struct itself has memory overhead just from its own members.
By default audio will be processed on a background thread. You can disable threading and run your
device in single threaded mode by disabling it at compiling with the `MA_NO_THREADING` define. By
disabling threading at compile time you will also reduce memory usage of the `ma_device` struct.
When running in single threaded mode you need to manually step the device periodically with
`ma_device_step()`, which you would normally do each frame.
16. Optimization Tips
@@ -7343,6 +7400,28 @@ MA_API ma_device_backend_vtable* ma_webaudio_get_vtable(void);
/* END WEBAUDIO */
/* BEG miniaudio_dreamcast.h */
typedef struct
{
ma_bool32 noInit; /* When set to true, will *not* call snd_init() when the context is initialized. */
} ma_context_config_dreamcast;
MA_API ma_context_config_dreamcast ma_context_config_dreamcast_init(void);
typedef struct
{
int voiceChannel; /* For a stereo stream, the left channel will be voiceChannel and the right channel will be voiceChannel+1. */
} ma_device_config_dreamcast;
MA_API ma_device_config_dreamcast ma_device_config_dreamcast_init(int voiceChannel);
extern ma_device_backend_vtable* ma_device_backend_dreamcast;
MA_API ma_device_backend_vtable* ma_dreamcast_get_vtable(void);
/* END miniaudio_dreamcast.h */
/* BEG NULL */
typedef struct ma_context_config_null
{
@@ -7773,6 +7852,7 @@ struct ma_device_config
ma_device_config_aaudio aaudio;
ma_device_config_opensl opensl;
ma_device_config_webaudio webaudio;
ma_device_config_dreamcast dreamcast;
ma_device_config_null null_backend;
};
@@ -7912,6 +7992,7 @@ struct ma_context_config
ma_context_config_aaudio aaudio;
ma_context_config_opensl opensl;
ma_context_config_webaudio webaudio;
ma_context_config_dreamcast dreamcast;
ma_context_config_null null_backend;
};
@@ -19861,6 +19942,9 @@ BACKENDS
#if defined(MA_EMSCRIPTEN)
#define MA_SUPPORT_WEBAUDIO
#endif
#if defined(MA_DREAMCAST)
#define MA_SUPPORT_DREAMCAST
#endif
/* All platforms should support custom backends. */
#define MA_SUPPORT_CUSTOM
@@ -19913,6 +19997,9 @@ BACKENDS
#if defined(MA_SUPPORT_WEBAUDIO) && !defined(MA_NO_WEBAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WEBAUDIO))
#define MA_HAS_WEBAUDIO
#endif
#if defined(MA_SUPPORT_DREAMCAST) && !defined(MA_NO_DREAMCAST) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_DREAMCAST))
#define MA_HAS_DREAMCAST
#endif
#if defined(MA_SUPPORT_NULL) && !defined(MA_NO_NULL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NULL))
#define MA_HAS_NULL
#endif
@@ -47405,6 +47492,508 @@ MA_API ma_device_backend_vtable* ma_webaudio_get_vtable(void)
/* BEG miniaudio_dreamcast.c */
#if defined(MA_HAS_DREAMCAST)
#include <kos.h>
#include <dc/sound/sound.h>
#include <dc/sound/aica_comm.h>
typedef struct
{
int _unused;
} ma_context_state_dreamcast;
typedef struct
{
ma_int8 voiceChannel;
ma_int8 subBufferIndex; /* Flip-flops between 0 and 1 and is used to determine which half of the buffer needs to be updated with fresh data. */
ma_int8 padding[2];
ma_uint32 buffer; /* Memory allocation on the SPU. Returned by snd_mem_malloc(). */
void* pIntermediaryBuffer; /* Big enough for one period (one half of the buffer). Used for transferring to the SPU buffer. */
} ma_device_state_dreamcast;
static ma_context_state_dreamcast* ma_context_get_backend_state__dreamcast(ma_context* pContext)
{
return (ma_context_state_dreamcast*)ma_context_get_backend_state(pContext);
}
static ma_device_state_dreamcast* ma_device_get_backend_state__dreamcast(ma_device* pDevice)
{
return (ma_device_state_dreamcast*)ma_device_get_backend_state(pDevice);
}
static ma_uint32 ma_format_to_dreamcast(ma_format format)
{
if (format == ma_format_u8) {
return AICA_SM_8BIT;
} else {
return AICA_SM_16BIT;
}
}
static void ma_backend_info__dreamcast(ma_device_backend_info* pBackendInfo)
{
MA_ASSERT(pBackendInfo != NULL);
pBackendInfo->pName = "Dreamcast";
}
static ma_result ma_context_init__dreamcast(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState)
{
ma_context_state_dreamcast* pContextStateDreamcast;
const ma_context_config_dreamcast* pContextConfigDreamcast = (ma_context_config_dreamcast*)pContextBackendConfig;
ma_log* pLog = ma_context_get_log(pContext);
/* The context config is not currently being used for this backend. */
(void)pContextConfigDreamcast;
(void)pLog;
if (!pContextConfigDreamcast->noInit) {
snd_init();
}
/* We don't actually do anything with context state so there's no need wasting time and memory allocating anything. */
#if 0
{
pContextStateDreamcast = (ma_context_state_dreamcast*)ma_calloc(sizeof(*pContextStateDreamcast), ma_context_get_allocation_callbacks(pContext));
if (pContextStateDreamcast == NULL) {
return MA_OUT_OF_MEMORY;
}
}
#else
{
pContextStateDreamcast = NULL;
}
#endif
*ppContextState = pContextStateDreamcast;
return MA_SUCCESS;
}
static void ma_context_uninit__dreamcast(ma_context* pContext)
{
ma_context_state_dreamcast* pContextStateDreamcast = ma_context_get_backend_state__dreamcast(pContext);
#if 0
ma_free(pContextStateDreamcast, ma_context_get_allocation_callbacks(pContext));
#else
(void)pContextStateDreamcast;
#endif
}
static ma_result ma_context_enumerate_devices__dreamcast(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData)
{
ma_context_state_dreamcast* pContextStateDreamcast = ma_context_get_backend_state__dreamcast(pContext);
ma_device_info deviceInfo;
(void)pContextStateDreamcast;
/* We're only outputting a single default playback device. */
MA_ZERO_OBJECT(&deviceInfo);
deviceInfo.isDefault = MA_TRUE;
deviceInfo.id.custom.i = 0;
ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1);
/* We can do u8 and s16, mono or stereo, and 11025, 22050 or 44100. */
ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 2, 11025, 44100);
ma_device_info_add_native_data_format(&deviceInfo, ma_format_u8, 1, 2, 11025, 44100);
return MA_SUCCESS;
}
static ma_result ma_device_init__dreamcast(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState)
{
ma_device_state_dreamcast* pDeviceStateDreamcast;
ma_device_config_dreamcast* pDeviceConfigDreamcast = (ma_device_config_dreamcast*)pDeviceBackendConfig;
ma_context_state_dreamcast* pContextStateDreamcast = ma_context_get_backend_state__dreamcast(ma_device_get_context(pDevice));
ma_device_config_dreamcast defaultConfig;
ma_device_type deviceType = ma_device_get_type(pDevice);
ma_uint32 bufferSizeInBytes;
ma_uint32 bpf;
(void)pContextStateDreamcast;
(void)pDescriptorCapture;
if (pDeviceConfigDreamcast == NULL) {
defaultConfig = ma_device_config_dreamcast_init(0);
pDeviceConfigDreamcast = &defaultConfig;
}
/* Can only support playback. */
if (deviceType != ma_device_type_playback) {
return MA_DEVICE_TYPE_NOT_SUPPORTED;
}
pDeviceStateDreamcast = (ma_device_state_dreamcast*)ma_calloc(sizeof(*pDeviceStateDreamcast), ma_device_get_allocation_callbacks(pDevice));
if (pDeviceStateDreamcast == NULL) {
return MA_OUT_OF_MEMORY;
}
pDeviceStateDreamcast->subBufferIndex = 1; /* Should start at one. */
/* Default to s16 for the format. Can support u8 and s16. */
if (pDescriptorPlayback->format != ma_format_u8 /*&& pDescriptorPlayback->format != ma_format_s16*/) {
pDescriptorPlayback->format = ma_format_s16;
}
/* The channels needs to be mono or stereo. */
if (pDescriptorPlayback->channels == 0 || pDescriptorPlayback->channels > 2) {
pDescriptorPlayback->channels = 2;
}
/* Now that we know our channel count we need to verify our voice channel index. There is a maximum of 64. Stereo uses two of them. */
if (pDeviceConfigDreamcast->voiceChannel + pDescriptorPlayback->channels > 64) {
return MA_INVALID_ARGS; /* Voice channel exceeds limit. */
}
pDeviceStateDreamcast->voiceChannel = (ma_uint8)pDeviceConfigDreamcast->voiceChannel;
/* The sample rate is always 44100, 22050 or 11025. */
if (pDescriptorPlayback->sampleRate != 44100 && pDescriptorPlayback->sampleRate != 22050 && pDescriptorPlayback->sampleRate != 11025) {
pDescriptorPlayback->sampleRate = 44100;
}
/* Channel map. */
ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels);
/* The period count is always 2 (double buffered). */
pDescriptorPlayback->periodCount = 2;
/*
The buffer cannot exceed 65534. Since we'll always be using 2 periods (double buffering), the maximum period
size in frames is 32767 = 65534/2. We're also going to align this to 32 bytes.
*/
pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate);
if (pDescriptorPlayback->periodSizeInFrames > 32767) {
pDescriptorPlayback->periodSizeInFrames = 32767;
}
if (pDescriptorPlayback->periodSizeInFrames < 1024) {
pDescriptorPlayback->periodSizeInFrames = 1024;
}
bpf = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels);
bufferSizeInBytes = (pDescriptorPlayback->periodSizeInFrames & ~31) * bpf * pDescriptorPlayback->periodCount;
pDescriptorPlayback->periodSizeInFrames = bufferSizeInBytes / bpf / pDescriptorPlayback->periodCount;
/* We need an intermediary buffer for transferring to the SPU buffer. */
pDeviceStateDreamcast->pIntermediaryBuffer = ma_aligned_malloc(pDescriptorPlayback->periodSizeInFrames * bpf, 32, ma_device_get_allocation_callbacks(pDevice));
if (pDeviceStateDreamcast->pIntermediaryBuffer == NULL) {
ma_free(pDeviceStateDreamcast, ma_device_get_allocation_callbacks(pDevice));
return MA_OUT_OF_MEMORY;
}
MA_ZERO_MEMORY(pDeviceStateDreamcast->pIntermediaryBuffer, pDescriptorPlayback->periodSizeInFrames * bpf);
/* Now we can allocate our memory for the buffer on the SPU side. */
pDeviceStateDreamcast->buffer = snd_mem_malloc(bufferSizeInBytes);
if (pDeviceStateDreamcast->buffer == 0) {
ma_free(pDeviceStateDreamcast->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice));
ma_free(pDeviceStateDreamcast, ma_device_get_allocation_callbacks(pDevice));
return MA_OUT_OF_MEMORY;
}
/* We will fill the buffer with initial silence. */
spu_memset_sq(pDeviceStateDreamcast->buffer, 0, bufferSizeInBytes); /* Can we safely assume the returned value from snd_mem_malloc() is aligned to 32 bytes? */
/* Done. */
*ppDeviceState = pDeviceStateDreamcast;
return MA_SUCCESS;
}
static void ma_device_uninit__dreamcast(ma_device* pDevice)
{
ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice);
snd_mem_free(pDeviceStateDreamcast->buffer);
ma_aligned_free(pDeviceStateDreamcast->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice));
ma_free(pDeviceStateDreamcast, ma_device_get_allocation_callbacks(pDevice));
}
static void ma_device_fill_sub_buffer__dreamcast(ma_device* pDevice, int subBufferIndex)
{
ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice);
ma_uint32 offsetInSamples = 0;
ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
/* The offset is in samples, not frames. */
offsetInSamples = subBufferIndex * pDevice->playback.internalPeriodSizeInFrames;
if (pDevice->playback.internalFormat == ma_format_s16) {
offsetInSamples *= sizeof(ma_int16);
}
/* Do the data callback. */
ma_device_handle_backend_data_callback(pDevice, pDeviceStateDreamcast->pIntermediaryBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames);
/* If our format is u8 we want to convert this to signed. */
if (pDevice->playback.internalFormat == ma_format_u8) {
ma_uint32 sampleCount = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalChannels;
ma_uint32 iSample;
for (iSample = 0; iSample < sampleCount; iSample += 1) {
((ma_int8*)pDeviceStateDreamcast->pIntermediaryBuffer)[iSample] = (ma_int8)128 - ((ma_uint8*)pDeviceStateDreamcast->pIntermediaryBuffer)[iSample];
}
if (pDevice->playback.internalChannels == 1) {
spu_memload_sq(pDeviceStateDreamcast->buffer + offsetInSamples, pDeviceStateDreamcast->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames * bpf);
} else {
/* There is no snd_pcm8_split_sq() from what I can tell. Might need to figure that out for myself. */
ma_uint32 l = pDeviceStateDreamcast->buffer + offsetInSamples;
ma_uint32 r = pDeviceStateDreamcast->buffer + (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * bpf) + offsetInSamples;
ma_uint32 framesWritten = 0;
while (framesWritten < pDevice->playback.internalPeriodSizeInFrames) {
ma_uint32 framesToWrite;
ma_int8 tmpl[256] __attribute__((aligned(32)));
ma_int8 tmpr[256] __attribute__((aligned(32)));
void* tmp[2];
framesToWrite = pDevice->playback.internalPeriodSizeInFrames - framesWritten;
if (framesToWrite > ma_countof(tmpl)) {
framesToWrite = ma_countof(tmpl);
}
tmp[0] = tmpl;
tmp[1] = tmpr;
ma_deinterleave_pcm_frames(ma_format_u8, 2, framesToWrite, ma_offset_ptr(pDeviceStateDreamcast->pIntermediaryBuffer, framesWritten * bpf), tmp);
spu_memload_sq(l + (framesWritten * sizeof(ma_int8)), tmpl, framesToWrite * sizeof(ma_int8));
spu_memload_sq(r + (framesWritten * sizeof(ma_int8)), tmpr, framesToWrite * sizeof(ma_int8));
framesWritten += framesToWrite;
}
}
} else {
if (pDevice->playback.internalChannels == 1) {
spu_memload_sq(pDeviceStateDreamcast->buffer + offsetInSamples, pDeviceStateDreamcast->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames * bpf);
} else {
ma_uint32 l = pDeviceStateDreamcast->buffer + offsetInSamples;
ma_uint32 r = pDeviceStateDreamcast->buffer + (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * bpf) + offsetInSamples;
/*
There is an endian bug in snd_pcm16_split_sq(). It merges two signed 16-bit samples into a single 32-bit variable in preparation
for transfer, but the ordering is the wrong way around. This results in a low quality sound. I'm working around this by splitting
this in one pass and then doing the transfer in a second pass. Later on I would like to update this to do the splitting and copy
in a single pass to save on some data movement.
*/
#if 0
snd_pcm16_split_sq((uint32_t*)pDeviceStateDreamcast->pIntermediaryBuffer, l, r, pDevice->playback.internalPeriodSizeInFrames * bpf);
#else
ma_uint32 framesWritten = 0;
while (framesWritten < pDevice->playback.internalPeriodSizeInFrames) {
ma_uint32 framesToWrite;
ma_int16 tmpl[256] __attribute__((aligned(32)));
ma_int16 tmpr[256] __attribute__((aligned(32)));
void* tmp[2];
framesToWrite = pDevice->playback.internalPeriodSizeInFrames - framesWritten;
if (framesToWrite > ma_countof(tmpl)) {
framesToWrite = ma_countof(tmpl);
}
tmp[0] = tmpl;
tmp[1] = tmpr;
ma_deinterleave_pcm_frames(ma_format_s16, 2, framesToWrite, ma_offset_ptr(pDeviceStateDreamcast->pIntermediaryBuffer, framesWritten * bpf), tmp);
spu_memload_sq(l + (framesWritten * sizeof(ma_int16)), tmpl, framesToWrite * sizeof(ma_int16));
spu_memload_sq(r + (framesWritten * sizeof(ma_int16)), tmpr, framesToWrite * sizeof(ma_int16));
framesWritten += framesToWrite;
}
#endif
}
}
}
static ma_result ma_device_start__dreamcast(ma_device* pDevice)
{
ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice);
AICA_CMDSTR_CHANNEL(tmp, cmd, chan);
/* Make sure our sub-buffer index is reset or else we'll get a glitch when starting. */
pDeviceStateDreamcast->subBufferIndex = 1;
/*
We'll want to fire the data callback and fill our buffer with an initial chunk of data. If we don't do this we'll end
up with one period of silence before hearing any audio.
*/
ma_device_fill_sub_buffer__dreamcast(pDevice, 0);
/* With the buffer pre-filled we can now start the voices. */
memset(tmp, 0, sizeof(tmp));
cmd->cmd = AICA_CMD_CHAN;
cmd->timestamp = 0;
cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel;
cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
chan->cmd = AICA_CH_CMD_START;
chan->base = pDeviceStateDreamcast->buffer;
chan->type = ma_format_to_dreamcast(pDevice->playback.internalFormat);
chan->length = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods;
chan->loop = 1;
chan->loopstart = 0;
chan->loopend = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods;
chan->freq = pDevice->playback.internalSampleRate;
chan->vol = 255; /* 0..255 */
chan->pan = 128; /* 0..255 (0=left, 128=center, 255=right) */
chan->pos = 0;
if (pDevice->playback.internalChannels == 1) {
snd_sh4_to_aica(tmp, cmd->size);
} else {
snd_sh4_to_aica_stop();
{
/* Left. Hard pan to the left. */
cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 0;
chan->base = pDeviceStateDreamcast->buffer + 0;
chan->pan = 0;
snd_sh4_to_aica(tmp, cmd->size);
/* Right. Hard pan to the right. */
cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 1;
chan->base = pDeviceStateDreamcast->buffer + (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels));
chan->pan = 255;
snd_sh4_to_aica(tmp, cmd->size);
}
snd_sh4_to_aica_start();
}
return MA_SUCCESS;
}
static ma_result ma_device_stop__dreamcast(ma_device* pDevice)
{
ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice);
AICA_CMDSTR_CHANNEL(tmp, cmd, chan);
memset(tmp, 0, sizeof(tmp));
cmd->cmd = AICA_CMD_CHAN;
cmd->timestamp = 0;
cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel;
cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
chan->cmd = AICA_CH_CMD_STOP;
if (pDevice->playback.internalChannels == 1) {
snd_sh4_to_aica(tmp, cmd->size);
} else {
snd_sh4_to_aica_stop();
{
/* Left. */
cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 0;
snd_sh4_to_aica(tmp, cmd->size);
/* Right. */
cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 1;
snd_sh4_to_aica(tmp, cmd->size);
}
snd_sh4_to_aica_start();
}
return MA_SUCCESS;
}
static ma_result ma_device_step__dreamcast(ma_device* pDevice, ma_blocking_mode blockingMode)
{
ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice);
/* Keep looping until we have processed some data, or we're in non-blocking mode. */
for (;;) {
int pos;
ma_int8 currentSubBufferIndex;
ma_bool32 wasDataProcessed = MA_FALSE;
if (!ma_device_is_started(pDevice)) {
return MA_DEVICE_NOT_STARTED;
}
/* We're currently using a poll/sleep pattern. Should try figuring out a better way to do this later. */
pos = (int)snd_get_pos(pDeviceStateDreamcast->voiceChannel);
if (pos < pDevice->playback.internalPeriodSizeInFrames) {
currentSubBufferIndex = 0;
} else {
currentSubBufferIndex = 1;
}
if (pDeviceStateDreamcast->subBufferIndex != currentSubBufferIndex) {
ma_device_fill_sub_buffer__dreamcast(pDevice, pDeviceStateDreamcast->subBufferIndex);
pDeviceStateDreamcast->subBufferIndex = currentSubBufferIndex;
wasDataProcessed = MA_TRUE;
}
if (wasDataProcessed) {
break;
}
/* Getting here means there is no data to process. In non blocking mode we just bomb out. In blocking mode we wait a bit. */
if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) {
break;
}
/* Getting here means we're in blocking mode. */
ma_sleep(1);
}
return MA_SUCCESS;
}
static void ma_device_wakeup__dreamcast(ma_device* pDevice)
{
ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice);
/* Nothing to do. */
(void)pDeviceStateDreamcast;
}
static ma_device_backend_vtable ma_gDeviceBackendVTable_Dreamcast =
{
ma_backend_info__dreamcast,
ma_context_init__dreamcast,
ma_context_uninit__dreamcast,
ma_context_enumerate_devices__dreamcast,
ma_device_init__dreamcast,
ma_device_uninit__dreamcast,
ma_device_start__dreamcast,
ma_device_stop__dreamcast,
ma_device_step__dreamcast,
ma_device_wakeup__dreamcast
};
ma_device_backend_vtable* ma_device_backend_dreamcast = &ma_gDeviceBackendVTable_Dreamcast;
#else
ma_device_backend_vtable* ma_device_backend_dreamcast = NULL;
#endif /* MA_HAS_DREAMCAST */
MA_API ma_device_backend_vtable* ma_dreamcast_get_vtable(void)
{
return ma_device_backend_dreamcast;
}
MA_API ma_context_config_dreamcast ma_context_config_dreamcast_init(void)
{
ma_context_config_dreamcast config;
MA_ZERO_OBJECT(&config);
return config;
}
MA_API ma_device_config_dreamcast ma_device_config_dreamcast_init(int voiceChannel)
{
ma_device_config_dreamcast config;
MA_ZERO_OBJECT(&config);
config.voiceChannel = voiceChannel;
return config;
}
/* END miniaudio_dreamcast.c */
MA_API void ma_get_device_backend_info(ma_device_backend_vtable* pBackendVTable, ma_device_backend_info* pBackendInfo)
{
if (pBackendVTable == NULL || pBackendVTable->onBackendInfo == NULL || pBackendInfo == NULL) {
@@ -48432,6 +49021,9 @@ static const void* ma_context_config_find_backend_config(const ma_context_config
if (pVTable == ma_device_backend_webaudio) {
return &pConfig->webaudio;
}
if (pVTable == ma_device_backend_dreamcast) {
return &pConfig->dreamcast;
}
if (pVTable == ma_device_backend_null) {
return &pConfig->null_backend;
}
@@ -48461,6 +49053,7 @@ MA_API ma_uint32 ma_get_stock_device_backends(ma_device_backend_config* pBackend
if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_aaudio, NULL); }
if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_opensl, NULL); }
if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_webaudio, NULL); }
if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_dreamcast, NULL); }
if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_null, NULL); }
return count;
@@ -48926,6 +49519,9 @@ static const void* ma_device_config_find_backend_config(const ma_device_config*
if (pVTable == ma_device_backend_webaudio) {
return &pConfig->webaudio;
}
if (pVTable == ma_device_backend_dreamcast) {
return &pConfig->dreamcast;
}
if (pVTable == ma_device_backend_null) {
return &pConfig->null_backend;
}
+22
View File
@@ -0,0 +1,22 @@
TARGET = miniaudio.elf
OBJS = miniaudio_dreamcast_test.o romdisk.o
KOS_ROMDISK_DIR = romdisk
all: clean $(TARGET)
include $(KOS_BASE)/Makefile.rules
clean:
-rm -f $(TARGET) $(OBJS)
-rm -f romdisk.o romdisk.img
$(TARGET): $(OBJS)
kos-cc -o $(TARGET) $(OBJS) -lpthread
run: $(TARGET)
$(KOS_LOADER) $(TARGET)
dist: $(TARGET)
rm -f $(OBJS) romdisk.o romdisk.img
$(KOS_STRIP) $(TARGET)
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
#set the KOS environtment variables
source /opt/toolchains/dc/kos/environ.sh
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
#set the KOS environtment variables
source /opt/toolchains/dc/kos/environ.sh
make
exit
+186
View File
@@ -0,0 +1,186 @@
//#define MA_NO_THREADING
#include "../../miniaudio.c"
ma_context context;
ma_device device;
ma_waveform waveform;
ma_decoder decoder;
typedef struct allocation_callbacks_data
{
ma_uint32 allocationCount;
ma_uint32 totalAllocationSizeInBytes;
} allocation_callbacks_data;
static void* test_malloc(size_t sz, void* pUserData)
{
allocation_callbacks_data* pData = (allocation_callbacks_data*)pUserData;
printf("malloc(%d)\n", (int)sz);
pData->allocationCount += 1;
pData->totalAllocationSizeInBytes += sz;
return malloc(sz);
}
static void* test_realloc(void* p, size_t sz, void* pUserData)
{
allocation_callbacks_data* pData = (allocation_callbacks_data*)pUserData;
printf("realloc(%d)\n", (int)sz);
pData->totalAllocationSizeInBytes += sz; /* Don't actually know how to properly track this. Might need to keep a list of allocations. */
return realloc(p, sz);
}
static void test_free(void* p, void* pUserData)
{
allocation_callbacks_data* pData = (allocation_callbacks_data*)pUserData;
printf("free()\n");
pData->allocationCount -= 1;
free(p);
}
allocation_callbacks_data allocationCallbacksData;
static void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{
(void)pDevice;
(void)pFramesOut;
(void)pFramesIn;
(void)frameCount;
//printf("data_callback(%d)\n", (int)frameCount);
//printf("internalePeriodSizeInFrames = %d\n", device.playback.internalPeriodSizeInFrames);
//ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount, NULL);
ma_decoder_read_pcm_frames(&decoder, pFramesOut, frameCount, NULL);
// Just applying a volume factor to make testing a bit more bearable...
ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, ma_device_get_format(pDevice, ma_device_type_playback), ma_device_get_channels(pDevice, ma_device_type_playback), 0.2f);
}
int main()
{
ma_result result;
ma_context_config contextConfig;
ma_device_config deviceConfig;
uint32 prevButtons = 0;
dbgio_init();
dbgio_dev_select("fb");
vid_init(DEFAULT_VID_MODE, PM_RGB565);
vid_clear(64, 192, 64);
bfont_draw_str(vram_s + 20 * 640 + 20, 640, 0, "MINIAUDIO");
printf("sizeof(ma_context) = %d\n", (int)sizeof(ma_context));
printf("sizeof(ma_device) = %d\n", (int)sizeof(ma_device));
printf("sizeof(ma_device.playback) = %d\n", (int)sizeof(device.playback));
printf("sizeof(ma_data_converter) = %d\n", (int)sizeof(ma_data_converter));
memset(&allocationCallbacksData, 0, sizeof(allocationCallbacksData));
contextConfig = ma_context_config_init();
contextConfig.allocationCallbacks.onMalloc = test_malloc;
contextConfig.allocationCallbacks.onRealloc = test_realloc;
contextConfig.allocationCallbacks.onFree = test_free;
contextConfig.allocationCallbacks.pUserData = &allocationCallbacksData;
result = ma_context_init(NULL, 0, &contextConfig, &context);
if (result != MA_SUCCESS) {
printf("ma_context_init() failed.");
}
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.threadingMode = MA_THREADING_MODE_SINGLE_THREADED;
deviceConfig.playback.format = ma_format_s16;
deviceConfig.playback.channels = 2;
deviceConfig.sampleRate = 44100;
deviceConfig.periodSizeInFrames = 2048;
deviceConfig.noFixedSizedCallback = MA_TRUE; /* With the period size between 2048 and 16384 we can be guaranteed a fixed sized callback. */
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = NULL;
result = ma_device_init(&context, &deviceConfig, &device);
if (result != MA_SUCCESS) {
printf("ma_device_init() failed.\n");
}
printf("internalPeriodSizeInFrames = %d\n", device.playback.internalPeriodSizeInFrames);
printf("format: %d %d\n", device.playback.format, device.playback.internalFormat);
printf("channels: %d %d\n", device.playback.channels, device.playback.internalChannels);
printf("rate: %d %d\n", device.sampleRate, device.playback.internalSampleRate);
/* Waveform. */
{
ma_waveform_config waveformConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.1f, 400);
ma_waveform_init(&waveformConfig, &waveform);
}
/* Decoder. */
{
ma_decoder_config decoderConfig = ma_decoder_config_init(device.playback.format, device.playback.channels, 0);
ma_decoder_init_file("/rd/test.mp3", &decoderConfig, &decoder);
}
printf("Allocation Count = %d\n", (int)allocationCallbacksData.allocationCount);
printf("Allocation Size = %d\n", (int)allocationCallbacksData.totalAllocationSizeInBytes);
result = ma_device_start(&device);
if (result != MA_SUCCESS) {
printf("ma_device_start() failed.\n");
}
thd_sleep(1);
for (;;) {
maple_device_t* dev;
cont_state_t* st;
dev = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); /* 0 = first controller found */
if (dev) {
st = (cont_state_t*)maple_dev_status(dev);
if (st) {
uint32 buttons;
uint32 pressed;
uint32 released;
buttons = st->buttons; /* bitmask of CONT_* */
pressed = (buttons ^ prevButtons) & buttons;
released = (buttons ^ prevButtons) & prevButtons;
prevButtons = buttons;
(void)released;
if (pressed & CONT_START) {
break;
}
if (buttons & CONT_DPAD_LEFT) {
/* Held */
}
}
}
ma_device_step(&device, MA_BLOCKING_MODE_NON_BLOCKING);
thd_pass(); /* yield */
//thd_sleep(1);
}
result = ma_device_stop(&device);
if (result != MA_SUCCESS) {
printf("ma_device_start() failed.\n");
}
ma_device_uninit(&device);
ma_context_uninit(&context);
return 0;
}