|
|
|
@@ -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;
|
|
|
|
|
}
|
|
|
|
|