diff --git a/audio/audren-mp3-simple/Makefile b/audio/audren-mp3-simple/Makefile new file mode 100644 index 0000000..a074bf5 --- /dev/null +++ b/audio/audren-mp3-simple/Makefile @@ -0,0 +1,222 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx -lmpg123 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/audio/audren-mp3-simple/romfs/test.mp3 b/audio/audren-mp3-simple/romfs/test.mp3 new file mode 100644 index 0000000..c96578f Binary files /dev/null and b/audio/audren-mp3-simple/romfs/test.mp3 differ diff --git a/audio/audren-mp3-simple/source/main.cpp b/audio/audren-mp3-simple/source/main.cpp new file mode 100644 index 0000000..688c689 --- /dev/null +++ b/audio/audren-mp3-simple/source/main.cpp @@ -0,0 +1,264 @@ +#include +#include +#include +#include + +// == + +#include +#include +#include + +// This sample requires switch-mpg123 installed. This can be done via the pacman install 'pacman -S switch-mpg123' +// Note the link in the Makefile 'LIBS := -lnx -lmpg123' + +// Sample comes from this website: +// https://www.soundjay.com/magic-sound-effect.html + +// Simple struct to store our mp3 file +struct Mp3File +{ + mpg123_handle* handle; + int channels; + long rate; + float length; + + AudioDriverWaveBuf WaveBuff; + int audrenMpid; +}; + +// == + +// Cleanup a given mpg123 handle + inline void CleanupMPGHandle(mpg123_handle* handle) +{ + mpg123_close(handle); + mpg123_delete(handle); +} + + // Load a given mp3 file - This will load, decode and assign the file to audren +Mp3File* LoadMP3File(AudioDriver* audioDriver, const char* filename) +{ + mpg123_handle* mpgHandle = NULL; + int channels = 0; + int encoding = 0; + long rate = 0; + int err = MPG123_OK; + + // Create a new mpg123 handle, Open the file, get the format of the file + if ((mpgHandle = mpg123_new(NULL, &err)) == NULL || + mpg123_open(mpgHandle, filename) != MPG123_OK || + mpg123_getformat(mpgHandle, &rate, &channels, &encoding) != MPG123_OK) + { + printf("Trouble with mpg123: %s with file: %s\n", mpgHandle == NULL ? mpg123_plain_strerror(err) : mpg123_strerror(mpgHandle), filename); + CleanupMPGHandle(mpgHandle); + return nullptr; + } + + // Ensure that this output format will not change (it could, when we allow it). + mpg123_format_none(mpgHandle); + mpg123_format(mpgHandle, rate, channels, encoding); + + // Get the length of the file + int length = mpg123_length(mpgHandle); + if (length == MPG123_ERR) + { + printf("Trouble with mpg123 getting length of file: %s \n", filename); + CleanupMPGHandle(mpgHandle); + return nullptr; + } + + // Create a buffer. Must be 4096 byte aligned. + int buffer_size = ((length * channels) + 0xFFF) & ~0xFFF; + unsigned char* buffer = (unsigned char*)memalign(0x1000, buffer_size); + armDCacheFlush(buffer, buffer_size); + + // Read and decode the mp3 into the buffer, here we are just reading the entire file into memory. You can do this in chunks too for streaming audio. + size_t done = 0; + err = mpg123_read(mpgHandle, buffer, buffer_size, &done); + if (err != MPG123_OK) + { + printf("file not read into buffer correctly\n"); + CleanupMPGHandle(mpgHandle); + free((void*)buffer); + return nullptr; + } + + // Setup the MP3File + Mp3File* mp3File = new Mp3File(); + + // Set the mpg123 handle - We need this to release later + mp3File->handle = mpgHandle; + + // Set the metadata - this is just for information, it's not required + mp3File->channels = channels; + mp3File->rate = rate; + mp3File->length = length / rate; + + // Setup the wave buffer that audren will use when playing this sound. It stores our allocated buffer and size + mp3File->WaveBuff = { 0 }; + mp3File->WaveBuff.data_raw = (const void*)buffer; + mp3File->WaveBuff.size = buffer_size; + mp3File->WaveBuff.start_sample_offset = 0; + mp3File->WaveBuff.end_sample_offset = done / 2; + + // Assin the memory to audren - this is important to let audren know what memory it's able to use. Store the Map ID to allow us to free later + mp3File->audrenMpid = audrvMemPoolAdd(audioDriver, (void*)buffer, buffer_size); + audrvMemPoolAttach(audioDriver, mp3File->audrenMpid); + + return mp3File; +} + +// Unloads the mp3 file - returning audren resources and mpg123 resources. mp3 will no longer be valid. +void UnloadMP3File(AudioDriver* audioDriver, Mp3File* mp3) +{ + if (audioDriver == nullptr) return; + if (mp3 == nullptr) return; + + // Detach and remove the memory from audren + audrvMemPoolDetach(audioDriver, mp3->audrenMpid); + audrvMemPoolRemove(audioDriver, mp3->audrenMpid); + + // Free all our memory and handles + free((void*)mp3->WaveBuff.data_raw); + CleanupMPGHandle(mp3->handle); + delete mp3; +} + +// == + +void PlayMp3File(AudioDriver* audioDriver, Mp3File* mp3, int id, bool loop) +{ + // Should the music loop + mp3->WaveBuff.is_looping = loop; + + audrvVoiceStop(audioDriver, id); // Stop the previous music playing on this id + audrvVoiceAddWaveBuf(audioDriver, id, &(mp3->WaveBuff)); // Set the WaveBuf to this id + audrvVoiceStart(audioDriver, id); // Play the music assigned to this id - which is now our mp3 file +} + +// == + +int main(void) +{ + // Initialize the default console + consoleInit(NULL); + + // Initialize and mount the rom file system 'romfs:/' - This will give you access to the files in the romfs folder + romfsInit(); + + // Initialize mpg123 + mpg123_init(); + + // Configure our supported input layout: a single player with standard controller styles + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + + // Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller) + PadState pad; + padInitializeDefault(&pad); + + // Initialize Audren + printf("Simple audren demonstration with mpg123 for loading mp3 files\n\n"); + + static const AudioRendererConfig arConfig = + { + .output_rate = AudioRendererOutputRate_48kHz, + .num_voices = 24, + .num_effects = 0, + .num_sinks = 1, + .num_mix_objs = 1, + .num_mix_buffers = 2, + }; + + AudioDriver audioDriver; + Result res = audrenInitialize(&arConfig); + bool initedDriver = false; + bool initedAudren = R_SUCCEEDED(res); + if (!initedAudren) + { + printf("audrenInitialize Failed: %08" PRIx32 "\n", res); + } + else + { + printf("Audren Initialized\n"); + res = audrvCreate(&audioDriver, &arConfig, 2); + initedDriver = R_SUCCEEDED(res); + if (!initedDriver) + { + printf("audrvCreate Failed: %08" PRIx32 "\n", res); + } + else + { + static const u8 sink_channels[] = { 0, 1 }; + audrvDeviceSinkAdd(&audioDriver, AUDREN_DEFAULT_DEVICE_NAME, 2, sink_channels); + + res = audrvUpdate(&audioDriver); + printf("audrvUpdate: %" PRIx32 "\n", res); + + res = audrenStartAudioRenderer(); + printf("audrenStartAudioRenderer: %" PRIx32 "\n", res); + + audrvVoiceInit(&audioDriver, 0, 1, PcmFormat_Int16, 48000); + audrvVoiceSetDestinationMix(&audioDriver, 0, AUDREN_FINAL_MIX_ID); + audrvVoiceSetMixFactor(&audioDriver, 0, 1.0f, 0, 0); + audrvVoiceSetMixFactor(&audioDriver, 0, 1.0f, 0, 1); + audrvVoiceStart(&audioDriver, 0); + } + } + + // Load a Mp3 File + Mp3File* music = LoadMP3File(&audioDriver, "romfs:/test.mp3"); + if (music == nullptr) + { + printf("Failed to load mp3 file\n"); + } + else + { + printf("\nLoaded MP3 File Successfully. Details:\n"); + printf("Channels: %d\nRate: %ld\nLength: %f\nbuffer_size: %ld\nleftSamples: %d\noffset: %d\n\n", + music->channels, music->rate, music->length, music->WaveBuff.size, music->WaveBuff.end_sample_offset, music->WaveBuff.start_sample_offset); + } + + printf("Press A to play the mp3 file.\n"); + + // Main loop + while (appletMainLoop()) + { + padUpdate(&pad); // Update the gamepad + u64 kDown = padGetButtonsDown(&pad); // Get a bitmask of the buttons pressed this frame + if (kDown & HidNpadButton_Plus) break; // If we pressed the + button - return to HB menu + + if (initedDriver && music != nullptr) + { + if (kDown & HidNpadButton_A) + { + PlayMp3File(&audioDriver, music, 0, false); + } + + // Update Audren + res = audrvUpdate(&audioDriver); + if (R_FAILED(res)) printf("audrvUpdate Failed: %" PRIx32 "\n", res); + if (music->WaveBuff.state == AudioDriverWaveBufState_Playing) printf("Played Samples = %" PRIu32 "\n", audrvVoiceGetPlayedSampleCount(&audioDriver, 0)); + } + + // Update the default console - sending a new frame + consoleUpdate(NULL); + } + + // Unload the MP3 file, releasing resources + UnloadMP3File(&audioDriver, music); + + // Audren Cleanup releasing resources + if (initedDriver) audrvClose(&audioDriver); + if (initedAudren) audrenExit(); + + // Close mpg123 + mpg123_exit(); + + // Close romfs mount + romfsExit(); + + // Close default console and release resources + consoleExit(NULL); + return 0; +}