mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-24 01:04:02 +02:00
Add Dreamcast backend.
This commit is contained in:
+596
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user