diff --git a/.github/workflows/build_nsfplay3.yml b/.github/workflows/build_nsfplay3.yml index 73e33d2..4fb7c91 100644 --- a/.github/workflows/build_nsfplay3.yml +++ b/.github/workflows/build_nsfplay3.yml @@ -26,9 +26,6 @@ jobs: run: make core_minimal - name: Build No-Sound Cmd run: make cmd_nosound - # Not testing this, to avoid these tests depending on wxWidgets - #- name: Build No-Sound NSFPlay - # run: make nsfplay_nosound build-msvc: name: Windows MSVC diff --git a/cmd/cmd.cpp b/cmd/cmd.cpp index d0fe989..01036ce 100644 --- a/cmd/cmd.cpp +++ b/cmd/cmd.cpp @@ -5,13 +5,14 @@ #define DEFAULT_INI_ENV "NSFPLAY_INI" #include -#include // std::fprintf -#include // std::exit, std::atexit, std::strtol -#include // std::strlen, std::memset, std::snprintf +#include // std::vfprintf, std::snprintf +#include // std::exit, std::atexit, std::strtol, std::malloc, std::free +#include // std::strlen, std::memset #include // NULL #include // va_list, va_start #include // cerrno #include // std::this_thread::sleep_for +#include "../shared/sound.h" // platform specific abstractions (platform.cpp) void platform_setup(int argc, char** argv); @@ -20,12 +21,7 @@ int platform_argc(); const char* platform_argv(int index); // note: return only valid until next argv/getenv const char* platform_getenv(const char* name); // note: return only valid until next argv/getenv, name limit of 63 chars FILE* platform_fopen(const char* path, const char* mode); -int platform_nonblock_getc(); - -// sound output (sound.cpp) -const char* sound_error_text(); -bool sound_setup(); -void sound_shutdown(); +int platform_nonblock_getc(); // get a keypress, 0 if none waiting // unit testing (unit_test.cpp) int unit_test(const char* path); @@ -40,20 +36,34 @@ NSFCore* core = NULL; // logging functions // +void err_printf(const char* fmt, ...) +{ + va_list args; + va_start(args,fmt); + std::vfprintf(stderr,fmt,args); +} + +void out_printf(const char* fmt, ...) +{ + va_list args; + va_start(args,fmt); + std::vfprintf(stdout,fmt,args); +} + void error_log(const NSFCore* core_, int32_t code, const char* msg) { (void)core_; - std::fprintf(stderr,"Error(%d): %s\n",code,msg); + err_printf("Error(%d): %s\n",code,msg); } void debug_print(const char* msg) { - std::fprintf(stdout,"Debug: %s\n",msg); + out_printf("Debug: %s\n",msg); } void fatal_log(const char* msg) { - std::fprintf(stderr,"Fatal: %s\n",msg); + err_printf("Fatal: %s\n",msg); platform_shutdown(); std::exit(-1); } @@ -69,7 +79,7 @@ static void* load_file(const char* path, const char* mode, size_t& filesize, boo FILE* f = platform_fopen(path,mode); if (f == NULL) { - if (!silent_notfound) std::fprintf(stderr,"Could not open: %s\n",path); + if (!silent_notfound) err_printf("Could not open: %s\n",path); return NULL; } // get size @@ -80,7 +90,7 @@ static void* load_file(const char* path, const char* mode, size_t& filesize, boo uint8_t* fd = reinterpret_cast(std::malloc(fs+1)); if (fd == NULL) { - std::fprintf(stderr,"Out of memory loading: %s\n",path); + err_printf("Out of memory loading: %s\n",path); std::fclose(f); return NULL; } @@ -89,7 +99,7 @@ static void* load_file(const char* path, const char* mode, size_t& filesize, boo size_t frs = std::fread(fd,1,fs,f); if (frs != fs) { - std::fprintf(stderr,"Read error in: %s\n",path); + err_printf("Read error in: %s\n",path); std::fclose(f); std::free(fd); return NULL; @@ -105,7 +115,7 @@ static bool load_ini(const char* path, bool silent_notfound=false) char* ini = reinterpret_cast(load_file(path,"rt",filesize,silent_notfound)); if (!ini) return false; nsfplay_set_ini(core,ini); - std::printf("Loaded INI: %s\n",path); + out_printf("Loaded INI: %s\n",path); std::free(ini); return true; } @@ -188,7 +198,7 @@ int parse_commandline() // returns -1 on success, otherwise is index of bad argu std::memset(arg.mute,0,sizeof(arg.mute)); #ifdef DEBUG - for (int i=0; i= 0) { - std::fprintf(stderr,"Invalid argument %d: %s\n",bad_arg,platform_argv(bad_arg)); - std::printf("Try -h for command line usage help.\n"); + err_printf("Invalid argument %d: %s\n",bad_arg,platform_argv(bad_arg)); + out_printf("Try -h for command line usage help.\n"); return -1; } } @@ -359,7 +369,7 @@ int run() // print help if (arg.help) { - std::printf(HELP_TEXT); + out_printf(HELP_TEXT); help_chans(); return 0; } @@ -368,11 +378,11 @@ int run() if (arg.save_default_ini >= 0) { const char* path = platform_argv(arg.save_default_ini); - std::printf("Generate default INI file: %s\n",path); + out_printf("Generate default INI file: %s\n",path); FILE* f = platform_fopen(path,"wt"); if (f == NULL) { - std::fprintf(stderr,"Could not open: %s\n",path); + err_printf("Could not open: %s\n",path); return -1; } nsfplay_set_default(core); @@ -380,10 +390,10 @@ int run() std::fclose(f); if (!result) { - std::fprintf(stderr,"Error writitng to file: %s\n",path); + err_printf("Error writitng to file: %s\n",path); return -1; } - std::printf("Success.\n"); + out_printf("Success.\n"); return 0; } @@ -410,14 +420,14 @@ int run() if (arg.waveout >= 0) { if (!waveout(platform_argv(arg.waveout))) return -1; - std::printf("Success.\n"); + out_printf("Success.\n"); return 0; } if (arg.waveout_multi >= 0) { if (nsfplay_song_count(core) < 1) { - std::fprintf(stderr,"No songs in input file.\n"); + err_printf("No songs in input file.\n"); return -1; } for (int32_t i=0; i"); if (info.list) { - printf("List:"); + out_printf("List:"); const char* e = info.list; for (int j=info.min_int; j<=info.max_int; ++j) { - printf(" %d=[%s]",j,e); + out_printf(" %d=[%s]",j,e); e += std::strlen(e)+1; } - printf("\n"); + out_printf("\n"); } } - */ // test props - printf("PROPS:\n"); + out_printf("PROPS:\n"); int32_t last_group = -1; for (int i=0;i(nsfplay_prop_blob(core,&blob_size,i)); - printf("%s: %d bytes\n",info.key,blob_size); + out_printf("%s: %d bytes\n",info.key,blob_size); const int COLS = 16; for (uint32_t j=0; j 0x20 && c < 0x7F) printf("%c",c); - else printf("."); // unprintable + if (c > 0x20 && c < 0x7F) out_printf("%c",c); + else out_printf("."); // unprintable } - else printf(" "); + else out_printf(" "); } - printf("\n"); + out_printf("\n"); } } else if (info.type == NSF_PROP_TYPE_LINES) { int32_t lines = nsfplay_prop_lines(core,i); - printf("%s: %d lines\n",info.key,lines); + out_printf("%s: %d lines\n",info.key,lines); const char* line; while ((line = nsfplay_prop_line(core)) != NULL) - printf(" %s\n",line); + out_printf(" %s\n",line); } else - printf("%s (Type: %d)\n",info.key,info.type); + out_printf("%s (Type: %d)\n",info.key,info.type); } } // test ini generation for (int i=0;i + - + + + + + diff --git a/cmd/cmd.vcxproj.filters b/cmd/cmd.vcxproj.filters index 2aa31b0..4cdfdc1 100644 --- a/cmd/cmd.vcxproj.filters +++ b/cmd/cmd.vcxproj.filters @@ -4,6 +4,11 @@ - + + + + + + \ No newline at end of file diff --git a/cmd/makefile b/cmd/makefile index d88da00..bff5966 100644 --- a/cmd/makefile +++ b/cmd/makefile @@ -17,6 +17,10 @@ CXXFLAGS_ALL = $(CXXFLAGS) $(CXXFLAGS_EXTRA) $(INC_COMMON) LDFLAGS_ALL = $(LDFLAGS) $(LDFLAGS_EXTRA) $(LDFLAGS_CMD) LIBS = +SHARED_SRCS = ../shared/sound.cpp +SHARED_OBJS = $(addprefix $(CMD_INTDIR)/shared/,$(notdir $(SHARED_SRCS:.cpp=.o))) +SHARED_DEPS = $(addprefix $(CMD_INTDIR)/shared/,$(notdir $(SHARED_SRCS:.cpp=.d))) + # NOSOUND=1 builds with no audio output (WAV out only) NOSOUND ?= 0 @@ -30,20 +34,21 @@ endif # target build -$(TARGET): $(OBJS) $(CORE) | $(dir $(TARGET)) - $(CXX) -o $(TARGET) $(LDFLAGS_ALL) $(OBJS) $(CORE) $(LIBS) +$(TARGET): $(OBJS) $(SHARED_OBJS) $(CORE) | $(dir $(TARGET)) + $(CXX) -o $(TARGET) $(LDFLAGS_ALL) $(OBJS) $(SHARED_OBJS) $(CORE) $(LIBS) $(STRIP_DEBUG) $(CMD_INTDIR)/%.d: %.cpp | $(CMD_INTDIR)/ - $(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/$(basename $<).o $(CXXFLAGS_ALL) -c $< + $(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/$(notdir $<).o $(CXXFLAGS_ALL) -c $< +$(CMD_INTDIR)/shared/%.d: ../shared/%.cpp | $(CMD_INTDIR)/shared/ + $(CXX) -M -MM -MF $@ -MT $(CMD_INTDIR)/shared/$(notdir $<).o $(CXXFLAGS_ALL) -c $< $(CMD_INTDIR)/%.o: %.cpp $(CMD_INTDIR)/%.d | $(CMD_INTDIR)/ $(CXX) -o $@ $(CXXFLAGS_ALL) -c $< +$(CMD_INTDIR)/shared/%.o: ../shared/%.cpp $(CMD_INTDIR)/shared/%.d | $(CMD_INTDIR)/shared/ + $(CXX) -o $@ $(CXXFLAGS_ALL) -c $< -$(CMD_INTDIR)/: - $(MKDIR) $@ - -$(dir $(TARGET)): +$(dir $(TARGET)) $(CMD_INTDIR)/ $(CMD_INTDIR)/shared/: $(MKDIR) $@ $(CORE): @@ -60,4 +65,4 @@ clean: rm -rf $(CMD_INTDIR) rm -rf $(TARGET) --include $(DEPS) +-include $(DEPS) $(SHARED_DEPS) diff --git a/cmd/sound.cpp b/cmd/sound.cpp deleted file mode 100644 index 134152d..0000000 --- a/cmd/sound.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include -#include // std::printf -#include // NULL - -#ifndef NSF_NOSOUND - // define NSF_NOSOUND=1 to remove audio output, allow WAV output only. - #define NSF_NOSOUND 0 -#endif - -#if !NSF_NOSOUND - -#include - -#include // TODO test - -static bool pa_initialized = false; -static PaError pa_last_error = 0; -static PaStream* pa_stream = NULL; - -static int pa_callback( - const void* input, void* output, unsigned long frame_count, - const PaStreamCallbackTimeInfo* time_info, - PaStreamCallbackFlags status_flags, - void* user_data ) -{ - // TODO test - (void)input; - (void)time_info; - (void)status_flags; - (void)user_data; - int16_t* data = reinterpret_cast(output); - for (unsigned int i=0; i +#include // std::vfprintf +#include // va_list, va_start + +// TODO these should go to logs (needed for ../shared/sound.cpp) + +void err_printf(const char* fmt, ...) +{ + va_list args; + va_start(args,fmt); + std::vfprintf(stderr,fmt,args); +} + +void out_printf(const char* fmt, ...) +{ + va_list args; + va_start(args,fmt); + std::vfprintf(stdout,fmt,args); +} class MainApp : public wxApp { diff --git a/nsfplay/nsfplay.vcxproj b/nsfplay/nsfplay.vcxproj index 3378c5e..ff71f92 100644 --- a/nsfplay/nsfplay.vcxproj +++ b/nsfplay/nsfplay.vcxproj @@ -172,9 +172,14 @@ + + + + + diff --git a/nsfplay/nsfplay.vcxproj.filters b/nsfplay/nsfplay.vcxproj.filters index 091fa09..0a1589d 100644 --- a/nsfplay/nsfplay.vcxproj.filters +++ b/nsfplay/nsfplay.vcxproj.filters @@ -18,11 +18,26 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + Source Files + + Source Files + diff --git a/portaudiolib.bat b/portaudiolib.bat index 2bdcb13..1b006e0 100644 --- a/portaudiolib.bat +++ b/portaudiolib.bat @@ -24,6 +24,8 @@ @set PACONF=%PACONF% -DPA_BUILD_TESTS=OFF @set PACONF=%PACONF% -DPA_BUILD_EXAMPLES=OFF @set PACONF=%PACONF% -DPA_LIBNAME_ADD_SUFFIX=OFF +@set PACONF=%PACONF% -DPA_USE_WDMKS=OFF +REM hopefully restore WDMKS in the next version of PortAudio @set PACONFD=%PACONF% @set PACONF=%PACONF% -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded @set PACONFD=%PACONFD% -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug diff --git a/readme.md b/readme.md index 02afc45..87beed9 100644 --- a/readme.md +++ b/readme.md @@ -24,6 +24,7 @@ Support: * `include` - Public interfaces for the `core` and `gui` libraries. * `icons` - Icons used for the GUI, edit the PNG files, and rebuild the ICO copies using the makefile ([imagemagick](https://imagemagick.org) required). * `enum` - Definitions of all settings and text used by NSFPlay, allowing localized text in multiple languages ([python](https://www.python.org/) required). +* `shared` - Miscellaneous shared internal code. * `wx` - [wxWidgets v3.2.4](https://github.com/wxWidgets/wxWidgets/tree/v3.2.4) cross platform GUI library. * `portaudio` - [PortAudio v19.7.0](https://github.com/PortAudio/portaudio/tree/v19.7.0) cross platform audio library. @@ -121,6 +122,8 @@ On other platforms, you can use `PAL_DIR` to tell the make build to use a differ Like with the wxWidgets library build, you can delete the cmake directories in `portaudiolib` after building the libraries if you wish to save space. +Note: the MSVC build currently does not support WDM/KS drivers, because there is some conflict involving a multiple definition of `_GUID_NULL` which I could not find a resolution for. Hopefully this will be fixed in the next release of PortAudio. + ### Make Targets * `make` - builds `cmd`, `gui` @@ -133,7 +136,6 @@ Like with the wxWidgets library build, you can delete the cmake directories in ` * `make cmd_nosound` - (build test) rebuild cmd without PortAudio. * `make gui` - build gui library `nsfgui` to `output/make/`. * `make nsfplay` - build `nsfplay` to `output/make/`. -* `make nsfplay_nosound` - (build test) rebuild nsfplay without PortAudio. * `make winamp` - (windows 32-bit only) build `nsfplay.dll` winamp plugin to `output/make/`. * `make install` - copy `nsfplay`/`nsfplac` to `/usr/local/bin/`. * `make uninstall` - delete install from `/usr/local/bin/`. diff --git a/shared/sound.cpp b/shared/sound.cpp new file mode 100644 index 0000000..cf82e8a --- /dev/null +++ b/shared/sound.cpp @@ -0,0 +1,412 @@ +// sound.cpp +// audio interface abstraction + +#include +#include "sound.h" + +#include // std::malloc, std::free +#include // NULL +#include // std::snprintf + +#ifndef NSF_NOSOUND + // define NSF_NOSOUND=1 to remove audio output, allow WAV output only. + #define NSF_NOSOUND 0 +#endif + +extern void out_printf(const char* fmt, ...); // print to stdout +extern void err_printf(const char* fmt, ...); // print to stderr + +#if !NSF_NOSOUND + +#include + +#include // TODO test + +static bool pa_initialized = false; +static PaError pa_last_error = 0; +static PaStream* pa_stream = NULL; +static int pa_device = -1; +static SoundStreamInfo pa_stream_info = { 0 }; + +void* pa_buffer = NULL; +size_t pa_buffer_size = 0; // total size in bytes +size_t pa_buffer_play_pos = 0; // next play position for callback +size_t pa_buffer_send_pos = 0; // next send position for write +// This has to be longer than PortAudio's possible callback buffer size, +// but the size of that is unspecified. We also shouldn't make it huge, +// to prevent CPU spikes from overgeneration. +#define PA_BUFFER_MS 2000 + +// +// string utilities (because we've never solved a few cross-platform string problems...) +// + +static void stringcpy(char* dst, size_t size, const char* src) +{ + while (*src && size>1) { *dst = *src; ++dst; ++src; } + *dst = 0; +} + +static void stringcat(char* dst, size_t size, const char* src) +{ + while (*dst && size) { ++dst; --size; } + if (size) stringcpy(dst, size, src); +} + +static void stringcatint(char* dst, size_t size, int i) +{ + char num[16]; + std::snprintf(num,16,"%d",i); + stringcat(dst,size,num); +} + +static bool stringicmp(const char* a, const char* b) +{ + while (*a && *b) + { + char ca = *a; ++a; + char cb = *b; ++b; + if (ca >= 'a' || ca <= 'z') ca = (ca - 'a') + 'A'; + if (cb >= 'a' || cb <= 'z') ca = (cb - 'a') + 'A'; + if (ca != cb) return true; + } + return *a != *b; +} + +// +// static sound utilities +// + +static int pa_callback( + const void* input, void* output, unsigned long frame_count, + const PaStreamCallbackTimeInfo* time_info, + PaStreamCallbackFlags status_flags, + void* user_data ) +{ + // TODO test + (void)input; + (void)time_info; + (void)status_flags; + (void)user_data; + int16_t* data = reinterpret_cast(output); + for (unsigned int i=0; i", 0, 0 }; + if (!pa_initialized) return info; + const PaDeviceInfo* pa_info = Pa_GetDeviceInfo(device); + if (!pa_info) return info; + const PaHostApiInfo* pa_host = Pa_GetHostApiInfo(pa_info->hostApi); + + // create combined name + { + info.name[0] = 0; + if (pa_host && pa_host->name) + { + stringcat(info.name,SoundDeviceInfo::NAME_LEN,pa_host->name); + stringcat(info.name,SoundDeviceInfo::NAME_LEN,":"); + } + stringcat(info.name,SoundDeviceInfo::NAME_LEN,pa_info->name ? pa_info->name : ""); + // remove characters that would cause trouble for the INI settings + int i; + for (i=0; info.name[i]; ++i) + { + char c = info.name[i]; + if (c == 10 || c == 13 || c == '\t') info.name[i] = ' '; + } + // remove trailing spaces + for (;i > 1 && info.name[i-1] == ' ';--i) info.name[i-1] = 0; + // remove leading spaces + for (i=0; info.name[i] == ' '; ++i) {} + if (i > 0) stringcpy(info.name,SoundDeviceInfo::NAME_LEN,info.name+i); + } + + // get other info + info.max_channels = pa_info->maxOutputChannels; + info.samplerate = int(pa_info->defaultSampleRate); + + // debug info, but only if it's an output device + #ifdef DEBUG + if (pa_info->maxOutputChannels > 0) + { + out_printf("Pa_GetDeviceInfo(%d)\n",device); + out_printf("\tname: [%s]\n",info.name); + out_printf("\tmaxInputChannels: %2d maxOutputChannels: %2d\n",pa_info->maxInputChannels,pa_info->maxOutputChannels); + out_printf("\tdefaultLowInputLatency: %8f (%6d) defaultLowOutputLatency: %8f (%6d)\n", + pa_info->defaultLowInputLatency, int(pa_info->defaultLowInputLatency * pa_info->defaultSampleRate), + pa_info->defaultLowOutputLatency, int(pa_info->defaultLowOutputLatency * pa_info->defaultSampleRate)); + out_printf("\tdefaultHighInputLatency: %8f (%6d) defaultHighOutputLatency: %8f (%6d)\n", + pa_info->defaultHighInputLatency, int(pa_info->defaultHighInputLatency * pa_info->defaultSampleRate), + pa_info->defaultHighOutputLatency, int(pa_info->defaultHighOutputLatency * pa_info->defaultSampleRate)); + out_printf("\tdefaultSampleRate: %f\n",pa_info->defaultSampleRate); + } + #endif + + return info; +} + +SoundStreamInfo sound_stream_info() +{ + return pa_stream_info; +} + +bool sound_setup(const NSFCore* core) +{ + pa_device = -1; + pa_stream_info = {0}; + + if (!pa_initialized) + { + pa_last_error = Pa_Initialize(); + pa_result_check("Pa_Initialize"); + if (pa_last_error != paNoError) return false; + pa_initialized = true; + } + + out_printf("Sound Library Version: %s\n",Pa_GetVersionText()); + #ifdef DEBUG + for (int i=0; i= 0) // no name, use the number + { + pa_device = device_int; + } + if (pa_device < 0) // if no device requested, use default + { + pa_device = Pa_GetDefaultOutputDevice(); + } + + // fill PortAudio parameters + pa_params.device = pa_device; + pa_params.channelCount = channels; + switch (bits) + { + case 8: pa_params.sampleFormat = paUInt8; break; + case 16: pa_params.sampleFormat = paInt16; break; + case 24: pa_params.sampleFormat = paInt24; break; + case 32: pa_params.sampleFormat = paInt32; break; + } + int latency = 64; // desired latency in samples + for (; sound_latency > 0; --sound_latency) latency <<= 1; + pa_params.suggestedLatency = double(latency) / double(samplerate); + pa_params.hostApiSpecificStreamInfo = NULL; + + pa_last_error = Pa_OpenStream( + &pa_stream, + NULL, // no input + &pa_params, // output parameters + double(samplerate), + paFramesPerBufferUnspecified, // callback buffer size may vary + paNoFlag, // stream flags + pa_callback, + NULL ); // userdata + pa_result_check("Pa_OpenStream"); + if (pa_last_error != paNoError) return false; + + pa_last_error = Pa_StartStream(pa_stream); + pa_result_check("Pa_StartStream"); + if (pa_last_error != paNoError) return false; + + pa_stream_info.bits = bits; + pa_stream_info.channels = channels; + const PaStreamInfo* pa_info = Pa_GetStreamInfo(pa_stream); + if (pa_info) + { + pa_stream_info.samplerate = int(pa_info->sampleRate); + pa_stream_info.latency = int(pa_info->outputLatency * pa_info->sampleRate); + } + + // TODO allocate pa_buffer (if too small) + // is there a convenient pa error for out of memory? + + return true; +} + +void sound_shutdown() +{ + if (pa_stream) + { + pa_last_error = Pa_StopStream(pa_stream); + pa_result_check("Pa_StopStream"); + pa_last_error = Pa_CloseStream(pa_stream); + pa_result_check("Pa_CloseStream"); + pa_stream = NULL; + } + + if (pa_initialized) + { + pa_last_error = Pa_Terminate(); + pa_result_check("Pa_Terminate"); + pa_initialized = false; + } +} + +#else // NSF_NOSOUND + +const char* sound_error_text() +{ + return "No sound library included."; +} + +const char* sound_debug_text() +{ + return "No sound library included.\n"; +} + +int sound_device_count() +{ + return 0; +} + +SoundDeviceInfo sound_device_info(int device) +{ + SoundDeviceInfo info = { "", 0, 0 }; + return info; +} + +bool sound_setup(const NSFCore* core) +{ + return false; +} + +void sound_shutdown() +{ +} + +#endif diff --git a/shared/sound.h b/shared/sound.h new file mode 100644 index 0000000..1ae9f19 --- /dev/null +++ b/shared/sound.h @@ -0,0 +1,43 @@ +#pragma once +// shared/sound.h +// common hardware audio interface abstraction +// +// Expects external functions for debug output and error logging: +// extern void out_printf(const char* fmt, ...); +// extern void err_printf(const char* fmt, ...); +// (Calls to these will always end with \n) + +struct NSFCore_; +typedef struct NSFCore_ NSFCore; + +typedef struct +{ + enum { NAME_LEN = 256 }; + char name[NAME_LEN]; + int max_channels; + int samplerate; // default samplerate +} SoundDeviceInfo; + +typedef struct +{ + int samplerate; + int latency; // approximate latency in samples + int bits; + int channels; +} SoundStreamInfo; + +const char* sound_error_text(); // describes the last error (no newline) +const char* sound_debug_text(); // describes the current audio stream (multiple lines, ends with \n) + +int sound_device_count(); +SoundDeviceInfo sound_device_info(int device); +SoundStreamInfo sound_stream_info(); + +bool sound_setup(const NSFCore* core); +void sound_shutdown(); + +// TODO +// uint32_t sound_buffer_get(void** buffer) returns number of samples to fill (ring buffer can be split with this) +// sound_buffer_send(); // sends the buffer just requested by sound_buffer_get +// sound_buffer_flush(); // redact unplayed buffers (leaving 1x latency extra) +// (setup should allocate at least 10 buffers, maybe minimum 1 second worth)