Skip to content

Commit 74d5908

Browse files
author
GH Action
committed
1 parent d5dd2f9 commit 74d5908

File tree

2 files changed

+219
-1
lines changed

2 files changed

+219
-1
lines changed

sokol.c3l/c/sokol_audio.h

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
- on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib
4444
- on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32
4545
- on Vita: SceAudio
46+
- on 3DS: NDSP (libctru)
4647
4748
FEATURE OVERVIEW
4849
================
@@ -57,6 +58,7 @@
5758
- emscripten: WebAudio with ScriptProcessorNode
5859
- Android: AAudio
5960
- Vita: SceAudio
61+
- 3DS: NDSP (libctru)
6062
6163
Sokol Audio will not do any buffer mixing or volume control, if you have
6264
multiple independent input streams of sample data you need to perform the
@@ -400,6 +402,33 @@
400402
You need to link with the 'SceAudio' library, and the <psp2/audioout.h>
401403
header must be present (usually both are installed with the vitasdk).
402404
405+
THE 3DS BACKEND
406+
================
407+
The 3DS backend is automatically selected when compiling with libctru
408+
('__3DS__' is defined).
409+
410+
Running a separate thread on the older 3ds is not a good idea and I
411+
was not able to get it working without slowing down the main thread
412+
too much (it has a single core available with cooperative threads).
413+
414+
The NDSP seems to work better by using its ndspSetCallback method.
415+
416+
You may use any supported sample rate you wish, but all audio MUST
417+
match the same sample rate you choose or it will sound slowed down
418+
or sped up.
419+
420+
The queue size and other NDSP specific parameters can be chosen by
421+
the provided 'saudio_n3ds_desc' type. Defaults will be used if
422+
nothing is provided.
423+
424+
There is a known issue of a noticeable delay when starting a new
425+
sound on emulators. I was not able to improve this to my liking
426+
and ~300ms can be expected. This can be improved by using a lower
427+
buffer size than the 2048 default but I would not suggest under
428+
1536. It may crash under 1408, and they must be in multiples of 128.
429+
Note: I was NOT able to reproduce this issue on a real device and
430+
the audio worked perfectly.
431+
403432
404433
MEMORY ALLOCATION OVERRIDE
405434
==========================
@@ -560,6 +589,7 @@ extern "C" {
560589
_SAUDIO_LOGITEM_XMACRO(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE, "backend buffer size isn't multiple of packet size") \
561590
_SAUDIO_LOGITEM_XMACRO(VITA_SCEAUDIO_OPEN_FAILED, "sceAudioOutOpenPort() failed") \
562591
_SAUDIO_LOGITEM_XMACRO(VITA_PTHREAD_CREATE_FAILED, "pthread_create() failed") \
592+
_SAUDIO_LOGITEM_XMACRO(N3DS_NDSP_OPEN_FAILED, "ndspInit() failed") \
563593

564594
#define _SAUDIO_LOGITEM_XMACRO(item,msg) SAUDIO_LOGITEM_##item,
565595
typedef enum saudio_log_item {
@@ -599,6 +629,31 @@ typedef struct saudio_allocator {
599629
void* user_data;
600630
} saudio_allocator;
601631

632+
typedef enum saudio_n3ds_ndspinterptype {
633+
SAUDIO_N3DS_DSP_INTERP_POLYPHASE = 0,
634+
SAUDIO_N3DS_DSP_INTERP_LINEAR = 1,
635+
SAUDIO_N3DS_DSP_INTERP_NONE = 2,
636+
} saudio_n3ds_ndspinterptype;
637+
638+
typedef struct saudio_n3ds_desc {
639+
/* the 3DS requires multiple queues that it alternates between. */
640+
/* a single buffer will "work" but is choppy due to a slight */
641+
/* delay when it changes queues. */
642+
int queue_count; /* default value = 2 */
643+
644+
/* NDSP_INTERP_POLYPHASE = 0 (high quality, slower) */
645+
/* NDSP_INTERP_LINEAR = 1 (med quality, medium) */
646+
/* NDSP_INTERP_NONE = 2 (low quality, fast) */
647+
saudio_n3ds_ndspinterptype interpolation_type; /* default value = 0 */
648+
649+
/* 3DS supports different audio channels. they can be used */
650+
/* in a variety of ways as independent streams etc. */
651+
/* this implementation in sokol does NOT allow multiple */
652+
/* due to calling the global ndspInit/ndspExit functions. */
653+
/* valid range 0-23 */
654+
int channel_id; /* default value = 0 */
655+
} saudio_n3ds_desc;
656+
602657
typedef struct saudio_desc {
603658
int sample_rate; // requested sample rate
604659
int num_channels; // number of channels, default: 1 (mono)
@@ -608,6 +663,7 @@ typedef struct saudio_desc {
608663
void (*stream_cb)(float* buffer, int num_frames, int num_channels); // optional streaming callback (no user data)
609664
void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); //... and with user data
610665
void* user_data; // optional user data argument for stream_userdata_cb
666+
saudio_n3ds_desc n3ds; // optional data for use on n3ds
611667
saudio_allocator allocator; // optional allocation override functions
612668
saudio_logger logger; // optional logging function (default: NO LOGGING!)
613669
} saudio_desc;
@@ -713,6 +769,9 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
713769
#elif defined(PSP2_SDK_VERSION)
714770
#define _SAUDIO_VITA (1)
715771
#include <psp2/audioout.h>
772+
#elif defined(__3DS__)
773+
#define _SAUDIO_N3DS (1)
774+
#include <3ds.h>
716775
#else
717776
#error "sokol_audio.h: Unknown platform"
718777
#endif
@@ -811,6 +870,8 @@ inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc);
811870
#elif defined(_SAUDIO_VITA)
812871
#define _SAUDIO_PTHREADS (1)
813872
#include <pthread.h>
873+
#elif defined(_SAUDIO_N3DS)
874+
#define _SAUDIO_NOTHREADS (1)
814875
#endif
815876

816877
#define _saudio_def(val, def) (((val) == 0) ? (def) : (val))
@@ -1023,6 +1084,18 @@ typedef struct {
10231084
bool thread_stop;
10241085
} _saudio_vita_backend_t;
10251086

1087+
#elif defined(_SAUDIO_N3DS)
1088+
1089+
typedef struct {
1090+
saudio_n3ds_desc n3ds_desc; /* n3ds specific data */
1091+
float* buffer; /* used by sokol as floats */
1092+
int16_t* buffer_n3ds; /* sokol buffer converted to int16 */
1093+
ndspWaveBuf* queue_n3ds; /* device queues on 3DS */
1094+
int samples_per_buffer; /* frames * channel count */
1095+
int buffer_byte_size;
1096+
bool thread_stop;
1097+
} _saudio_n3ds_backend_t;
1098+
10261099
#else
10271100
#error "unknown platform"
10281101
#endif
@@ -1041,6 +1114,8 @@ typedef _saudio_aaudio_backend_t _saudio_backend_t;
10411114
typedef _saudio_alsa_backend_t _saudio_backend_t;
10421115
#elif defined(_SAUDIO_VITA)
10431116
typedef _saudio_vita_backend_t _saudio_backend_t;
1117+
#elif defined(_SAUDIO_N3DS)
1118+
typedef _saudio_n3ds_backend_t _saudio_backend_t;
10441119
#endif
10451120

10461121
/* a ringbuffer structure */
@@ -2282,7 +2357,130 @@ _SOKOL_PRIVATE void _saudio_vita_backend_shutdown(void) {
22822357
_saudio_free(_saudio.backend.buffer_vita);
22832358
_saudio_free(_saudio.backend.buffer);
22842359
}
2285-
2360+
2361+
// ███████ ██████ ███████
2362+
// ██ ██ ██ ██
2363+
// ██████ ██ ██ ███████
2364+
// ██ ██ ██ ██
2365+
// ███████ ██████ ███████
2366+
//
2367+
// >>3ds
2368+
#elif defined(_SAUDIO_N3DS)
2369+
2370+
/* NDSP triggers a callback for _saudio_n3ds_cb on the main thread */
2371+
_SOKOL_PRIVATE void _saudio_n3ds_cb(void*) {
2372+
if(_saudio.backend.thread_stop) {
2373+
return;
2374+
}
2375+
2376+
const float scale = 32767.0f;
2377+
2378+
ndspWaveBuf* bufferPtr = 0;
2379+
bufferPtr = 0;
2380+
int i = 0;
2381+
2382+
/* pick an available queue */
2383+
for (i = 0; i < _saudio.backend.n3ds_desc.queue_count; ++i) {
2384+
if (_saudio.backend.queue_n3ds[i].status == NDSP_WBUF_DONE) {
2385+
bufferPtr = &_saudio.backend.queue_n3ds[i];
2386+
break;
2387+
}
2388+
}
2389+
2390+
if (!bufferPtr) {
2391+
/* no buffers are available. we don't want to play */
2392+
/* anything, but we also don't want to drain the queue */
2393+
return;
2394+
}
2395+
2396+
int16_t* target_buffer = bufferPtr->data_pcm16;
2397+
const float* source_buffer = _saudio.backend.buffer;
2398+
for (i = 0; i < _saudio.backend.samples_per_buffer; i++) {
2399+
/* data_pcm16 points to a region in the linear alloc _saudio.backend.buffer_n3ds */
2400+
target_buffer[i] = (int16_t)(source_buffer[i] * scale);
2401+
}
2402+
2403+
bufferPtr->nsamples = _saudio.buffer_frames; /* nsamples is actually frames */
2404+
ndspChnWaveBufAdd(_saudio.backend.n3ds_desc.channel_id, bufferPtr);
2405+
DSP_FlushDataCache(target_buffer, bufferPtr->nsamples * sizeof(int16_t));
2406+
2407+
/* fill the streaming buffer with new data */
2408+
if (_saudio_has_callback()) {
2409+
_saudio_stream_callback(_saudio.backend.buffer, _saudio.buffer_frames, _saudio.num_channels);
2410+
}
2411+
else {
2412+
if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) {
2413+
/* not enough read data available, fill the entire buffer with silence */
2414+
_saudio_clear(_saudio.backend.buffer, (size_t)_saudio.backend.buffer_byte_size);
2415+
}
2416+
}
2417+
}
2418+
2419+
_SOKOL_PRIVATE bool _saudio_n3ds_backend_init(void) {
2420+
int rc = ndspInit();
2421+
if (rc != 0) {
2422+
_SAUDIO_ERROR(N3DS_NDSP_OPEN_FAILED);
2423+
return false;
2424+
}
2425+
2426+
/* set defaults if not provided */
2427+
_saudio.backend.n3ds_desc.queue_count = _saudio_def(_saudio.desc.n3ds.queue_count, 2);
2428+
_saudio.backend.n3ds_desc.interpolation_type = _saudio_def(_saudio.desc.n3ds.interpolation_type, SAUDIO_N3DS_DSP_INTERP_POLYPHASE);
2429+
_saudio.backend.n3ds_desc.channel_id = _saudio_def(_saudio.desc.n3ds.channel_id, 0);
2430+
2431+
/* clamp to 2 channels max */
2432+
if (_saudio.num_channels > 2) {
2433+
_saudio.num_channels = 2;
2434+
}
2435+
2436+
ndspChnReset(_saudio.backend.n3ds_desc.channel_id);
2437+
ndspChnWaveBufClear(_saudio.backend.n3ds_desc.channel_id);
2438+
ndspChnSetInterp(_saudio.backend.n3ds_desc.channel_id, (ndspInterpType)_saudio.backend.n3ds_desc.interpolation_type); /* cast to n3ds enum */
2439+
ndspChnSetRate(_saudio.backend.n3ds_desc.channel_id, _saudio.sample_rate);
2440+
ndspChnSetFormat(_saudio.backend.n3ds_desc.channel_id, _saudio.num_channels == 1 ? NDSP_FORMAT_MONO_PCM16 : NDSP_FORMAT_STEREO_PCM16);
2441+
ndspSetOutputMode(_saudio.num_channels == 1 ? NDSP_OUTPUT_MONO : NDSP_OUTPUT_STEREO);
2442+
2443+
/* read back actual sample rate and channels */
2444+
_saudio.sample_rate = (int)_saudio.sample_rate;
2445+
_saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
2446+
2447+
/* allocate the streaming buffer */
2448+
_saudio.backend.samples_per_buffer = _saudio.buffer_frames * _saudio.num_channels;
2449+
_saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
2450+
_saudio.backend.buffer = (float*) _saudio_malloc_clear((size_t)_saudio.backend.buffer_byte_size);
2451+
_saudio.backend.buffer_n3ds = (int16_t*)linearAlloc(_saudio.backend.n3ds_desc.queue_count * _saudio.backend.samples_per_buffer * sizeof(int16_t));
2452+
_saudio.backend.queue_n3ds = (ndspWaveBuf*)_saudio_malloc(_saudio.backend.n3ds_desc.queue_count * sizeof(ndspWaveBuf));
2453+
2454+
/* prepare the 3ds audio queues */
2455+
int16_t* bufferPtrCopy = _saudio.backend.buffer_n3ds;
2456+
for (int i = 0; i < _saudio.backend.n3ds_desc.queue_count; ++i) {
2457+
_saudio.backend.queue_n3ds[i].data_vaddr = bufferPtrCopy; /* point the queue at the section of the linear buffer */
2458+
_saudio.backend.queue_n3ds[i].looping = false; /* the user should handle looping on their end */
2459+
_saudio.backend.queue_n3ds[i].status = NDSP_WBUF_DONE; /* default to done status for buffering logic */
2460+
2461+
bufferPtrCopy += _saudio.backend.samples_per_buffer;
2462+
}
2463+
2464+
/* instead of a thread, ndsp will trigger a callback */
2465+
/* when it needs more data. */
2466+
ndspSetCallback(_saudio_n3ds_cb, 0);
2467+
2468+
return true;
2469+
}
2470+
2471+
_SOKOL_PRIVATE void _saudio_n3ds_backend_shutdown(void) {
2472+
_saudio.backend.thread_stop = true;
2473+
2474+
if (_saudio.backend.n3ds_desc.channel_id >= 0) {
2475+
ndspChnWaveBufClear(_saudio.backend.n3ds_desc.channel_id);
2476+
ndspExit();
2477+
_saudio.backend.n3ds_desc.channel_id = -1;
2478+
}
2479+
2480+
_saudio_free(_saudio.backend.queue_n3ds);
2481+
_saudio_free(_saudio.backend.buffer_n3ds);
2482+
_saudio_free(_saudio.backend.buffer);
2483+
}
22862484
#else
22872485
#error "unsupported platform"
22882486
#endif
@@ -2302,6 +2500,8 @@ bool _saudio_backend_init(void) {
23022500
return _saudio_coreaudio_backend_init();
23032501
#elif defined(_SAUDIO_VITA)
23042502
return _saudio_vita_backend_init();
2503+
#elif defined(_SAUDIO_N3DS)
2504+
return _saudio_n3ds_backend_init();
23052505
#else
23062506
#error "unknown platform"
23072507
#endif
@@ -2322,6 +2522,8 @@ void _saudio_backend_shutdown(void) {
23222522
_saudio_coreaudio_backend_shutdown();
23232523
#elif defined(_SAUDIO_VITA)
23242524
_saudio_vita_backend_shutdown();
2525+
#elif defined(_SAUDIO_N3DS)
2526+
_saudio_n3ds_backend_shutdown();
23252527
#else
23262528
#error "unknown platform"
23272529
#endif

sokol.c3l/saudio.c3

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ enum SaudioLogItem : const int
4646
BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE = 26,
4747
VITA_SCEAUDIO_OPEN_FAILED = 27,
4848
VITA_PTHREAD_CREATE_FAILED = 28,
49+
N3DS_NDSP_OPEN_FAILED = 29,
4950
}
5051

5152
struct SaudioLogger
@@ -61,6 +62,20 @@ struct SaudioAllocator
6162
void* user_data;
6263
}
6364

65+
enum SaudioN3dsNdspinterptype : const int
66+
{
67+
DSP_INTERP_POLYPHASE = 0,
68+
DSP_INTERP_LINEAR = 1,
69+
DSP_INTERP_NONE = 2,
70+
}
71+
72+
struct SaudioN3dsDesc
73+
{
74+
CInt queue_count;
75+
SaudioN3dsNdspinterptype interpolation_type;
76+
CInt channel_id;
77+
}
78+
6479
struct SaudioDesc
6580
{
6681
CInt sample_rate;
@@ -71,6 +86,7 @@ struct SaudioDesc
7186
StreamCb stream_cb;
7287
StreamDataCb stream_userdata_cb;
7388
void* user_data;
89+
SaudioN3dsDesc n3ds;
7490
SaudioAllocator allocator;
7591
SaudioLogger logger;
7692
}

0 commit comments

Comments
 (0)