libsdl3-mixer-3~git20250523~daf0503+ds/000077500000000000000000000000001501405355700172115ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/.clang-format000066400000000000000000000037651501405355700215770ustar00rootroot00000000000000--- AlignConsecutiveMacros: Consecutive AlignConsecutiveAssignments: None AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine # Custom brace breaking BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: Never AfterEnum: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: true AfterStruct: true AfterUnion: true AfterExternBlock: false BeforeElse: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true # Make the closing brace of container literals go to a new line Cpp11BracedListStyle: false # Never format includes IncludeBlocks: Preserve # clang-format version 4.0 through 12.0: #SortIncludes: false # clang-format version 13.0+: #SortIncludes: Never # No length limit, in case it breaks macros, you can # disable it with /* clang-format off/on */ comments ColumnLimit: 0 IndentWidth: 4 ContinuationIndentWidth: 4 IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: NoIndent PointerAlignment: Right SpaceAfterCStyleCast: false SpacesInCStyleCastParentheses: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeParens: ControlStatements SpaceAroundPointerQualifiers: Default SpaceInEmptyBlock: false SpaceInEmptyParentheses: false UseCRLF: false UseTab: Never --- libsdl3-mixer-3~git20250523~daf0503+ds/.editorconfig000066400000000000000000000016201501405355700216650ustar00rootroot00000000000000# For format see editorconfig.org # Copyright 2022 Collabora Ltd. # SPDX-License-Identifier: Zlib root = true [README.txt] end_of_line = crlf [*.{c,cc,cg,cpp,gradle,h,java,m,metal,pl,py,S,sh,txt}] indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.{html,js,json,m4,yml,yaml,vcxproj,vcxproj.filters}] indent_size = 2 indent_style = space trim_tailing_whitespace = true [{CMakeLists.txt,cmake/*.cmake}] indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [{cmake/*.cmake.in}] indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [{Makefile.*,*.mk,*.sln,*.pbxproj,*.plist}] indent_size = 8 indent_style = tab tab_width = 8 [*.{markdown,md}] indent_size = 4 indent_style = space # Markdown syntax treats tabs as 4 spaces tab_width = 4 [{*.bat,*.rc}] end_of_line = crlf libsdl3-mixer-3~git20250523~daf0503+ds/.gitignore000066400000000000000000000003561501405355700212050ustar00rootroot00000000000000build* !build-scripts/ aclocal.m4 autom4te* config.cache config.log config.status Makefile libtool .deps .libs *.lo *.o *.la *.lai Debug Release *.user *.ncb *.suo *.sdf .DS_Store xcuserdata *.xcworkspace .vs .vscode Xcode/build.xcconfig libsdl3-mixer-3~git20250523~daf0503+ds/.wikiheaders-options000066400000000000000000000021601501405355700232010ustar00rootroot00000000000000projectfullname = SDL_mixer projectshortname = SDL_mixer incsubdir = include/SDL3_mixer wikisubdir = SDL3_mixer apiprefixregex = (Mix_|MIX_) mainincludefname = SDL3_mixer/SDL_mixer.h versionfname = include/SDL3_mixer/SDL_mixer.h versionmajorregex = \A\#define\s+SDL_MIXER_MAJOR_VERSION\s+(\d+)\Z versionminorregex = \A\#define\s+SDL_MIXER_MINOR_VERSION\s+(\d+)\Z versionmicroregex = \A\#define\s+SDL_MIXER_MICRO_VERSION\s+(\d+)\Z selectheaderregex = \ASDL_mixer\.h\Z projecturl = https://libsdl.org/projects/SDL_mixer wikiurl = https://wiki.libsdl.org/SDL_mixer bugreporturl = https://github.com/libsdl-org/sdlwiki/issues/new warn_about_missing = 0 wikipreamble = (This function is part of SDL_mixer, a separate library from SDL.) wikiheaderfiletext = Defined in [](https://github.com/libsdl-org/SDL_mixer/blob/main/include/SDL3_mixer/%fname%) manpageheaderfiletext = Defined in SDL3_mixer/%fname% quickrefenabled = 1 quickreftitle = SDL3_mixer API Quick Reference quickrefurl = https://libsdl.org/ quickrefdesc = The latest version of this document can be found at https://wiki.libsdl.org/SDL3_mixer/QuickReference libsdl3-mixer-3~git20250523~daf0503+ds/Android.mk000066400000000000000000000120511501405355700211210ustar00rootroot00000000000000# Save the local path SDL_MIXER_LOCAL_PATH := $(call my-dir) # Enable this if you want to support loading WAV music SUPPORT_WAV ?= true # Enable this if you want to support loading FLAC music via dr_flac SUPPORT_FLAC_DRFLAC ?= true # Enable this if you want to support loading FLAC music with libFLAC SUPPORT_FLAC_LIBFLAC ?= false FLAC_LIBRARY_PATH := external/flac # Enable this if you want to support loading OGG Vorbis music via stb_vorbis SUPPORT_OGG_STB ?= true # Enable this if you want to support loading OGG Vorbis music via Tremor SUPPORT_OGG ?= false OGG_LIBRARY_PATH := external/ogg VORBIS_LIBRARY_PATH := external/tremor # Enable this if you want to support loading MP3 music via dr_mp3 SUPPORT_MP3_DRMP3 ?= true # Enable this if you want to support loading MP3 music via MPG123 SUPPORT_MP3_MPG123 ?= false MPG123_LIBRARY_PATH := external/mpg123 # Enable this if you want to support loading WavPack music via libwavpack SUPPORT_WAVPACK ?= true WAVPACK_LIBRARY_PATH := external/wavpack # Enable this if you want to support loading music via libgme SUPPORT_GME ?= true GME_LIBRARY_PATH := external/libgme # Enable this if you want to support loading MOD music via XMP-lite SUPPORT_MOD_XMP ?= false XMP_LIBRARY_PATH := external/libxmp # Enable this if you want to support TiMidity SUPPORT_MID_TIMIDITY ?= false TIMIDITY_LIBRARY_PATH := src/codecs/timidity # Build the library ifeq ($(SUPPORT_FLAC_LIBFLAC),true) include $(SDL_MIXER_LOCAL_PATH)/$(FLAC_LIBRARY_PATH)/Android.mk endif # Build the library ifeq ($(SUPPORT_OGG),true) include $(SDL_MIXER_LOCAL_PATH)/$(OGG_LIBRARY_PATH)/Android.mk include $(SDL_MIXER_LOCAL_PATH)/$(VORBIS_LIBRARY_PATH)/Android.mk endif # Build the library ifeq ($(SUPPORT_MP3_MPG123),true) include $(SDL_MIXER_LOCAL_PATH)/$(MPG123_LIBRARY_PATH)/Android.mk endif # Build the library ifeq ($(SUPPORT_WAVPACK),true) include $(SDL_MIXER_LOCAL_PATH)/$(WAVPACK_LIBRARY_PATH)/Android.mk endif # Build the library ifeq ($(SUPPORT_GME),true) include $(SDL_MIXER_LOCAL_PATH)/$(GME_LIBRARY_PATH)/Android.mk endif # Build the library ifeq ($(SUPPORT_MOD_XMP),true) include $(SDL_MIXER_LOCAL_PATH)/$(XMP_LIBRARY_PATH)/Android.mk endif # Build the library ifeq ($(SUPPORT_MID_TIMIDITY),true) include $(SDL_MIXER_LOCAL_PATH)/$(TIMIDITY_LIBRARY_PATH)/Android.mk endif # Restore local path LOCAL_PATH := $(SDL_MIXER_LOCAL_PATH) include $(CLEAR_VARS) LOCAL_MODULE := SDL3_mixer LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/include \ $(LOCAL_PATH)/src/ \ $(LOCAL_PATH)/src/codecs \ LOCAL_SRC_FILES := \ $(subst $(LOCAL_PATH)/,, \ $(wildcard $(LOCAL_PATH)/src/*.c) \ $(wildcard $(LOCAL_PATH)/src/codecs/*.c) \ ) LOCAL_CFLAGS := LOCAL_LDLIBS := LOCAL_LDFLAGS := -Wl,--no-undefined -Wl,--version-script=$(LOCAL_PATH)/src/SDL_mixer.sym LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := SDL3 ifeq ($(SUPPORT_WAV),true) LOCAL_CFLAGS += -DMUSIC_WAV endif ifeq ($(SUPPORT_FLAC_DRFLAC),true) LOCAL_CFLAGS += -DMUSIC_FLAC_DRFLAC endif ifeq ($(SUPPORT_FLAC_LIBFLAC),true) LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(FLAC_LIBRARY_PATH)/include LOCAL_CFLAGS += -DMUSIC_FLAC_LIBFLAC LOCAL_STATIC_LIBRARIES += libFLAC endif ifeq ($(SUPPORT_OGG_STB),true) LOCAL_CFLAGS += -DMUSIC_OGG -DOGG_USE_STB endif ifeq ($(SUPPORT_OGG),true) LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(OGG_LIBRARY_PATH)/include LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(VORBIS_LIBRARY_PATH) LOCAL_CFLAGS += -DMUSIC_OGG -DOGG_USE_TREMOR -DOGG_HEADER="" LOCAL_STATIC_LIBRARIES += ogg vorbisidec endif ifeq ($(SUPPORT_MP3_DRMP3),true) LOCAL_CFLAGS += -DMUSIC_MP3_DRMP3 endif # This needs to be a shared library to comply with the LGPL license ifeq ($(SUPPORT_MP3_MPG123),true) LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(MPG123_LIBRARY_PATH)/android LOCAL_CFLAGS += -DMUSIC_MP3_MPG123 LOCAL_SHARED_LIBRARIES += mpg123 endif ifeq ($(SUPPORT_WAVPACK),true) LOCAL_CFLAGS += -DMUSIC_WAVPACK -DMUSIC_WAVPACK_DSD -DWAVPACK_HEADER=\"../external/wavpack/include/wavpack.h\" LOCAL_STATIC_LIBRARIES += wavpack endif ifeq ($(SUPPORT_GME),true) LOCAL_CFLAGS += -DMUSIC_GME LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(GME_LIBRARY_PATH) LOCAL_STATIC_LIBRARIES += libgme endif ifeq ($(SUPPORT_MOD_XMP),true) LOCAL_CFLAGS += -DMUSIC_MOD_XMP -DLIBXMP_HEADER=\"../external/libxmp/include/xmp.h\" LOCAL_STATIC_LIBRARIES += xmp endif ifeq ($(SUPPORT_MID_TIMIDITY),true) LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(TIMIDITY_LIBRARY_PATH) LOCAL_CFLAGS += -DMUSIC_MID_TIMIDITY LOCAL_STATIC_LIBRARIES += timidity endif LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)/include include $(BUILD_SHARED_LIBRARY) ########################### # # SDL3_mixer static library # ########################### LOCAL_MODULE := SDL3_mixer_static LOCAL_MODULE_FILENAME := libSDL3_mixer LOCAL_LDLIBS := LOCAL_EXPORT_LDLIBS := include $(BUILD_STATIC_LIBRARY) libsdl3-mixer-3~git20250523~daf0503+ds/CHANGES.txt000066400000000000000000000002341501405355700210210ustar00rootroot000000000000003.0.0: * Removed support for libmodplug as a MOD music backend. * Go back to using dr_mp3 as the default backend for MP3 music * Updated for SDL 3.0 libsdl3-mixer-3~git20250523~daf0503+ds/CMakeLists.txt000066400000000000000000001467101501405355700217620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16...4.0) if(NOT DEFINED CMAKE_BUILD_TYPE) set(cmake_build_type_undefined 1) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # See docs/release_checklist.md set(MAJOR_VERSION 3) set(MINOR_VERSION 0) set(MICRO_VERSION 0) set(SDL_REQUIRED_VERSION 3.0.0) project(SDL3_mixer LANGUAGES C VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}" ) include("${SDL3_mixer_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake") include("${SDL3_mixer_SOURCE_DIR}/cmake/PrivateSdlFunctions.cmake") include("${SDL3_mixer_SOURCE_DIR}/cmake/sdlcpu.cmake") include("${SDL3_mixer_SOURCE_DIR}/cmake/sdlmanpages.cmake") include("${SDL3_mixer_SOURCE_DIR}/cmake/sdlplatform.cmake") sdl_calculate_derived_version_variables(${MAJOR_VERSION} ${MINOR_VERSION} ${MICRO_VERSION}) message(STATUS "Configuring ${PROJECT_NAME} ${PROJECT_VERSION}") if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(SDLMIXER_ROOTPROJECT ON) else() set(SDLMIXER_ROOTPROJECT OFF) endif() # By default, configure in RelWithDebInfo configuration if(SDLMIXER_ROOTPROJECT) get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(is_multi_config) # The first item in CMAKE_CONFIGURATION_TYPES is the default configuration if(DEFINED CMAKE_CONFIGURATION_TYPES AND "RelWithDebInfo" IN_LIST CMAKE_CONFIGURATION_TYPES) list(REMOVE_ITEM CMAKE_CONFIGURATION_TYPES "RelWithDebInfo") list(INSERT CMAKE_CONFIGURATION_TYPES 0 "RelWithDebInfo") set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING "CMake configuration types" FORCE) endif() else() if(cmake_build_type_undefined) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "CMake build type" FORCE) endif() endif() endif() set(SDLMIXER_SAMPLES_DEFAULT ${SDLMIXER_ROOTPROJECT}) if(ANDROID) set(SDLMIXER_SAMPLES_DEFAULT OFF) endif() # option() honors normal variables. cmake_policy(SET CMP0077 NEW) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) if(POLICY CMP0112) # Target file component generator expressions do not add target dependencies. cmake_policy(SET CMP0112 NEW) endif() if(POLICY CMP0099) # Make `INTERFACE_LINK_DIRECTORIES` a transitive usage requirement. # This is needed for static dependencies which have transitive dependencies # outside of compiler default search paths. cmake_policy(SET CMP0099 NEW) endif() # Assume MSVC projects don't have a package manager and need vendored dependencies (by default). # Most other platforms have some kind of package manager. # FIXME: consider a package manager such as conan/vcpkg instead of vendoring if(ANDROID OR MSVC) set(vendored_default ON) else() set(vendored_default OFF) endif() set(sdl3mixer_install_enableable ON) if((TARGET SDL3-shared OR TARGET SDL3-static) AND SDL_DISABLE_INSTALL) # Cannot install SDL3_mixer when SDL3 is built in same built, and is not installed. set(sdl3mixer_install_enableable OFF) endif() if(NOT DEFINED CMAKE_FIND_PACKAGE_PREFER_CONFIG) set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) endif() include(CheckIncludeFile) include(CheckSymbolExists) include(CMakeDependentOption) include(CMakePackageConfigHelpers) include(GNUInstallDirs) include(PkgConfigHelper) set(PLATFORM_SUPPORTS_SHARED ON) if(EMSCRIPTEN OR VITA OR PSP OR PS2 OR N3DS OR RISCOS) set(PLATFORM_SUPPORTS_SHARED OFF) endif() option(CMAKE_POSITION_INDEPENDENT_CODE "Build static libraries with -fPIC" ${PLATFORM_SUPPORTS_SHARED}) cmake_dependent_option(BUILD_SHARED_LIBS "Build the library as a shared library" ON PLATFORM_SUPPORTS_SHARED OFF) cmake_dependent_option(SDLMIXER_INSTALL "Enable SDL3mixer install target" ${SDLMIXER_ROOTPROJECT} "${sdl3mixer_install_enableable}" OFF) cmake_dependent_option(SDLMIXER_INSTALL_CPACK "Create binary SDL3_mixer archive using CPack" ${SDLMIXER_ROOTPROJECT} "SDLMIXER_INSTALL" OFF) cmake_dependent_option(SDLMIXER_INSTALL_MAN "Install man pages for SDL3_mixer" OFF "SDLMIXER_INSTALL" OFF) cmake_dependent_option(SDLMIXER_DEPS_SHARED "Load dependencies dynamically" ON PLATFORM_SUPPORTS_SHARED OFF) cmake_dependent_option(SDLMIXER_RELOCATABLE "Create relocatable SDL_mixer package" "${MSVC}" SDLMIXER_INSTALL OFF) option(SDLMIXER_VENDORED "Use vendored third-party libraries" ${vendored_default}) option(SDLMIXER_WERROR "Treat warnings as errors" OFF) option(SDLMIXER_STRICT "Fail when a dependency could not be found" OFF) set(required "") set(fatal_error "STATUS") if(SDLMIXER_STRICT) set(required "REQUIRED") set(fatal_error "FATAL_ERROR") endif() option(SDLMIXER_SAMPLES "Build the SDL3_mixer sample program(s)" ${SDLMIXER_SAMPLES_DEFAULT}) cmake_dependent_option(SDLMIXER_SAMPLES_INSTALL "Install the SDL3_mixer sample program(s)" OFF "SDLMIXER_SAMPLES;SDLMIXER_INSTALL" OFF) option(SDLMIXER_SNDFILE "Support loading sounds via libsndfile" OFF) option(SDLMIXER_SNDFILE_SHARED "Dynamically load libsndfile" "${SDLMIXER_DEPS_SHARED}") option(SDLMIXER_FLAC "Enable FLAC music" ON) cmake_dependent_option(SDLMIXER_FLAC_LIBFLAC "Enable FLAC music using libFLAC" OFF SDLMIXER_FLAC OFF) cmake_dependent_option(SDLMIXER_FLAC_LIBFLAC_SHARED "Dynamically load LIBFLAC" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_FLAC_LIBFLAC OFF) cmake_dependent_option(SDLMIXER_FLAC_DRFLAC "Enable FLAC music using drflac" ON SDLMIXER_FLAC OFF) option(SDLMIXER_GME "Support loading GME music via game-music-emu" ON) option(SDLMIXER_GME_SHARED "Dynamically load libgme" "${SDLMIXER_DEPS_SHARED}") option(SDLMIXER_MOD "Support loading MOD music" ON) cmake_dependent_option(SDLMIXER_MOD_XMP "Support loading MOD music via libxmp" ON SDLMIXER_MOD OFF) cmake_dependent_option(SDLMIXER_MOD_XMP_LITE "Use libxmp-lite instead of libxmp" OFF "SDLMIXER_MOD_XMP;NOT SDLMIXER_VENDORED" OFF) cmake_dependent_option(SDLMIXER_MOD_XMP_SHARED "Dynamically load libxmp(-lite)" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_MOD_XMP OFF) if(SDLMIXER_MOD AND NOT SDLMIXER_MOD_XMP) message(FATAL_ERROR "MOD support was enabled (SDLMIXER_MOD) but xmp (SDLMIXER_MOD_XMP) was disabled.") endif() option(SDLMIXER_MP3 "Enable MP3 music" ON) cmake_dependent_option(SDLMIXER_MP3_DRMP3 "Support loading MP3 music via dr_mp3" ON SDLMIXER_MP3 OFF) cmake_dependent_option(SDLMIXER_MP3_MPG123 "Support loading MP3 music via MPG123" OFF SDLMIXER_MP3 OFF) cmake_dependent_option(SDLMIXER_MP3_MPG123_SHARED "Dynamically load mpg123" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_MP3_MPG123 OFF) if(SDLMIXER_MP3 AND NOT (SDLMIXER_MP3_DRMP3 OR SDLMIXER_MP3_MPG123)) message(FATAL_ERROR "MP3 support was enabled (SDLMIXER_MP3) but neither drmp3 (SDLMIXER_MP3_DRMP3) or mpg123 (SDLMIXER_MP3_MPG123) were enabled.") endif() option(SDLMIXER_MIDI "Enable MIDI music" ON) cmake_dependent_option(SDLMIXER_MIDI_FLUIDSYNTH "Support FluidSynth MIDI output" ON "SDLMIXER_MIDI;NOT SDLMIXER_VENDORED" OFF) cmake_dependent_option(SDLMIXER_MIDI_FLUIDSYNTH_SHARED "Dynamically load libfluidsynth" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_MIDI_FLUIDSYNTH OFF) if(WIN32 OR APPLE OR HAIKU OR "${CMAKE_SYSTEM_NAME}" MATCHES "Linux") cmake_dependent_option(SDLMIXER_MIDI_NATIVE "Support native MIDI output" ON SDLMIXER_MIDI OFF) else() set(SDLMIXER_MIDI_NATIVE OFF) endif() cmake_dependent_option(SDLMIXER_MIDI_TIMIDITY "Support timidity MIDI output" ON SDLMIXER_MIDI OFF) if(SDLMIXER_MIDI AND NOT (SDLMIXER_MIDI_TIMIDITY OR SDLMIXER_MIDI_NATIVE OR SDLMIXER_MIDI_FLUIDSYNTH)) message(FATAL_ERROR "MIDI support was enabled (SDLMIXER_MIDI) but neither FluidSynth (SDLMIXER_MIDI_FLUIDSYNTH), native (SDLMIXER_MIDI_NATIVE) or timidity (SDLMIXER_MIDI_TIMIDITY) was enabled") endif() option(SDLMIXER_OPUS "Enable Opus music" ON) cmake_dependent_option(SDLMIXER_OPUS_SHARED "Dynamically load libopus" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_OPUS OFF) set(sdl3mixer_vorbis_strings STB TREMOR VORBISFILE) set(SDLMIXER_VORBIS "STB" CACHE STRING "Enable OGG Vorbis music") set_property(CACHE SDLMIXER_VORBIS PROPERTY STRINGS "${sdl3mixer_vorbis_strings}") if(SDLMIXER_VORBIS) if(NOT SDLMIXER_VORBIS IN_LIST sdl3mixer_vorbis_strings) message(FATAL_ERROR "SDLMIXER_VORBIS contains an invalid value (=${SDLMIXER_VORBIS}). It must be one of ${sdl3mixer_vorbis_strings}.") endif() endif() set(SDLMIXER_VORBIS_STB OFF) set(SDLMIXER_VORBIS_TREMOR OFF) set(SDLMIXER_VORBIS_VORBISFILE OFF) if(SDLMIXER_VORBIS STREQUAL "STB") set(SDLMIXER_VORBIS_STB ON) endif() if(SDLMIXER_VORBIS STREQUAL "TREMOR") set(SDLMIXER_VORBIS_TREMOR ON) endif() if(SDLMIXER_VORBIS STREQUAL "VORBISFILE") set(SDLMIXER_VORBIS_VORBISFILE ON) endif() cmake_dependent_option(SDLMIXER_VORBIS_TREMOR_SHARED "Dynamically load tremor library" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_VORBIS_TREMOR OFF) cmake_dependent_option(SDLMIXER_VORBIS_VORBISFILE_SHARED "Dynamically load vorbisfile library" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_VORBIS_VORBISFILE OFF) option(SDLMIXER_WAVE "Enable streaming WAVE music" ON) option(SDLMIXER_WAVPACK "Enable WavPack music" ON) cmake_dependent_option(SDLMIXER_WAVPACK_DSD "Enable WavPack DSD music support" OFF SDLMIXER_WAVPACK OFF) cmake_dependent_option(SDLMIXER_WAVPACK_SHARED "Dynamically load WavPack library" "${SDLMIXER_DEPS_SHARED}" SDLMIXER_WAVPACK OFF) if(SDLMIXER_VORBIS_TREMOR OR SDLMIXER_VORBIS_VORBISFILE OR SDLMIXER_FLAC_LIBFLAC OR SDLMIXER_OPUS) set(SDLMIXER_OGG TRUE) set(SDLMIXER_OGG_install FALSE) if(SDLMIXER_VORBIS_VORBISFILE_SHARED OR SDLMIXER_FLAC_SHARED OR SDLMIXER_OPUS_SHARED) set(SDLMIXER_OGG_SHARED TRUE) set(SDLMIXER_OGG_install TRUE) else() set(SDLMIXER_OGG_SHARED FALSE) if(NOT SDLMIXER_BUILD_SHARED_LIBS) set(SDLMIXER_OGG_install TRUE) endif() endif() else() set(SDLMIXER_OGG FALSE) endif() # Save BUILD_SHARED_LIBS variable set(SDLMIXER_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) set(sdl_required_components Headers) if(SDLMIXER_BUILD_SHARED_LIBS) set(sdl3_mixer_target_name SDL3_mixer-shared) set(sdl3_target_name SDL3::SDL3-shared) list(APPEND sdl_required_components SDL3-shared) else() set(sdl3_mixer_target_name SDL3_mixer-static) set(sdl3_target_name SDL3::SDL3) endif() if(NOT TARGET SDL3::Headers OR NOT TARGET ${sdl3_target_name}) find_package(SDL3 ${SDL_REQUIRED_VERSION} REQUIRED COMPONENTS ${sdl_required_components}) endif() SDL_DetectTargetCPUArchitectures(SDL_CPU_NAMES) SDL_DetectCMakePlatform() set(BUILD_SHARED_LIBS ${SDLMIXER_BUILD_SHARED_LIBS}) add_library(${sdl3_mixer_target_name} src/codecs/load_aiff.c src/codecs/load_voc.c src/codecs/load_sndfile.c src/codecs/mp3utils.c src/codecs/music_drflac.c src/codecs/music_drmp3.c src/codecs/music_flac.c src/codecs/music_fluidsynth.c src/codecs/music_gme.c src/codecs/music_mpg123.c src/codecs/music_nativemidi.c src/codecs/music_ogg.c src/codecs/music_ogg_stb.c src/codecs/music_opus.c src/codecs/music_timidity.c src/codecs/music_wav.c src/codecs/music_wavpack.c src/codecs/music_xmp.c src/effect_position.c src/effect_stereoreverse.c src/effects_internal.c src/mixer.c src/music.c src/utils.c ) add_library(SDL3_mixer::${sdl3_mixer_target_name} ALIAS ${sdl3_mixer_target_name}) if(NOT TARGET SDL3_mixer::SDL3_mixer) add_library(SDL3_mixer::SDL3_mixer ALIAS ${sdl3_mixer_target_name}) endif() target_include_directories(${sdl3_mixer_target_name} PUBLIC "$" "$" PRIVATE src src/codecs ) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE BUILD_SDL SDL_BUILD_MAJOR_VERSION=${MAJOR_VERSION} SDL_BUILD_MINOR_VERSION=${MINOR_VERSION} SDL_BUILD_MICRO_VERSION=${MICRO_VERSION} ) target_link_libraries(${sdl3_mixer_target_name} PUBLIC SDL3::Headers) if(SDLMIXER_BUILD_SHARED_LIBS) target_link_libraries(${sdl3_mixer_target_name} PRIVATE SDL3::SDL3-shared) endif() sdl_add_warning_options(${sdl3_mixer_target_name} WARNING_AS_ERROR ${SDLMIXER_WERROR}) if(WIN32 AND BUILD_SHARED_LIBS) target_sources(${sdl3_mixer_target_name} PRIVATE src/version.rc ) if(MINGW) target_link_options(${sdl3_mixer_target_name} PRIVATE -static-libgcc) endif() endif() set_target_properties(${sdl3_mixer_target_name} PROPERTIES OUTPUT_NAME "SDL3_mixer" DEFINE_SYMBOL DLL_EXPORT EXPORT_NAME ${sdl3_mixer_target_name} C_VISIBILITY_PRESET "hidden" ) sdl_target_link_option_version_file(${sdl3_mixer_target_name} "${CMAKE_CURRENT_SOURCE_DIR}/src/SDL_mixer.sym") if(NOT ANDROID) set_target_properties(${sdl3_mixer_target_name} PROPERTIES SOVERSION "${SO_VERSION_MAJOR}" VERSION "${SO_VERSION}" ) if(APPLE) cmake_minimum_required(VERSION 3.17...3.28) set_target_properties(${sdl3_mixer_target_name} PROPERTIES MACHO_COMPATIBILITY_VERSION "${DYLIB_COMPAT_VERSION}" MACHO_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}" ) endif() endif() if(SDLMIXER_BUILD_SHARED_LIBS) if(WIN32) set_target_properties(${sdl3_mixer_target_name} PROPERTIES PREFIX "" DLL_NAME_WITH_SOVERSION FALSE ) endif() else() if(MSVC) set_target_properties(${sdl3_mixer_target_name} PROPERTIES OUTPUT_NAME "SDL3_mixer-static" ) endif() endif() # Use `Compatible Interface Properties` to ensure a shared SDL3_mixer is linked to a shared SDL3 library if(SDLMIXER_BUILD_SHARED_LIBS) set_property(TARGET ${sdl3_mixer_target_name} PROPERTY INTERFACE_SDL3_SHARED ${SDLMIXER_BUILD_SHARED_LIBS}) set_property(TARGET ${sdl3_mixer_target_name} APPEND PROPERTY COMPATIBLE_INTERFACE_BOOL SDL3_SHARED) endif() if(SDLMIXER_BUILD_SHARED_LIBS) sdl_target_link_options_no_undefined(${sdl3_mixer_target_name}) endif() if(SDLMIXER_BUILD_SHARED_LIBS) # Make sure static library dependencies are built with -fPIC when building a shared SDL3_mixer set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() set(INSTALL_EXTRA_TARGETS) set(PC_LIBS) set(PC_REQUIRES) set(SDLMIXER_BACKENDS) list(APPEND SDLMIXER_BACKENDS SNDFILE) set(SDLMIXER_SNDFILE_ENABLED FALSE) if(SDLMIXER_SNDFILE) if(SDLMIXER_VENDORED) message(STATUS "Using vendored libsndfile") message(${fatal_error} "libsndfile is not vendored.") else() find_package(SndFile ${required}) if(SndFile_FOUND) set(SDLMIXER_SNDFILE_ENABLED TRUE) message(STATUS "Using system libsndfile") if(NOT SDLMIXER_SNDFILE_SHARED) list(APPEND PC_REQUIRES sndfile) endif() else() message(${fatal_error} "libsndfile NOT found") endif() endif() if(SDLMIXER_SNDFILE_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE LOAD_SNDFILE) if(SDLMIXER_SNDFILE_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_sndfile SndFile::sndfile) message(STATUS "Dynamic libsndfile: ${dynamic_sndfile}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "SNDFILE_DYNAMIC=\"${dynamic_sndfile}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} SndFile::sndfile) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE SndFile::sndfile) endif() endif() endif() if(SDLMIXER_OGG) # libogg is a requirement of libflac, libtremor and libvorbisfile, so only need this library when vendoring if(SDLMIXER_VENDORED) message(STATUS "Using vendored libogg") set(BUILD_SHARED_LIBS ${SDLMIXER_OGG_SHARED}) set(INSTALL_CMAKE_PACKAGE_MODULE FALSE) set(BUILD_TESTING OFF) sdl_check_project_in_subfolder(external/ogg ogg SDLMIXER_VENDORED) add_subdirectory(external/ogg external/ogg-build EXCLUDE_FROM_ALL) if(SDLMIXER_OGG_install) list(APPEND INSTALL_EXTRA_TARGETS ogg) endif() endif() endif() list(APPEND SDLMIXER_BACKENDS OPUS) set(SDLMIXER_OPUS_ENABLED FALSE) if(SDLMIXER_OPUS) if(SDLMIXER_VENDORED) set(SDLMIXER_OPUS_ENABLED TRUE) # vendored libogg already handled if(NOT TARGET ogg) message(FATAL_ERROR "ogg target not present") endif() message(STATUS "Using vendored opus") set(BUILD_SHARED_LIBS ${SDLMIXER_OPUS_SHARED}) set(BUILD_PROGRAMS OFF) sdl_check_project_in_subfolder(external/opus opus SDLMIXER_VENDORED) add_subdirectory(external/opus external/opus-build EXCLUDE_FROM_ALL) set(OP_DISABLE_DOCS TRUE) set(OP_DISABLE_EXAMPLES TRUE) set(OP_DISABLE_HTTP TRUE) message(STATUS "Using vendored opusfile") set(BUILD_SHARED_LIBS ${SDLMIXER_OPUS_SHARED}) sdl_check_project_in_subfolder(external/opusfile opusfile SDLMIXER_VENDORED) add_subdirectory(external/opusfile external/opusfile-build EXCLUDE_FROM_ALL) if(MSVC) set_property(TARGET opusfile PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/opusfile.h" "#include \"${CMAKE_CURRENT_SOURCE_DIR}/external/opusfile/include/opusfile.h\"\n") execute_process( COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/opus" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/opusfile.h" "${CMAKE_CURRENT_BINARY_DIR}/opus/opusfile.h" ) target_include_directories(${sdl3_mixer_target_name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") if(NOT TARGET OpusFile::opusfile) add_library(OpusFile::opusfile ALIAS opusfile) endif() if(SDLMIXER_OPUS_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS opus opusfile) endif() if(NOT SDLMIXER_OPUS_SHARED) list(APPEND PC_LIBS -l$ -l$ -l$) endif() else() find_package(OpusFile ${required}) if(OpusFile_FOUND AND Ogg_FOUND) set(SDLMIXER_OPUS_ENABLED TRUE) message(STATUS "Using system opusfile") if(NOT SDLMIXER_OPUS_SHARED) list(APPEND PC_REQUIRES opusfile) endif() else() message(${fatal_error} "opusfile NOT found") endif() endif() if(SDLMIXER_OPUS_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_OPUS) if(SDLMIXER_OPUS_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_opusfile OpusFile::opusfile) message(STATUS "Dynamic opus (opusfile): ${dynamic_opusfile}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "OPUS_DYNAMIC=\"${dynamic_opusfile}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} OpusFile::opusfile) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE OpusFile::opusfile) endif() endif() endif() list(APPEND SDLMIXER_BACKENDS VORBIS_STB) set(SDLMIXER_VORBIS_STB_ENABLED FALSE) if(SDLMIXER_VORBIS_STB) set(SDLMIXER_VORBIS_STB_ENABLED TRUE) message(STATUS "Enabled ogg music: using stb_vorbis") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_OGG) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE OGG_USE_STB) endif() list(APPEND SDLMIXER_BACKENDS VORBIS_TREMOR) set(SDLMIXER_VORBIS_TREMOR_ENABLED FALSE) if(SDLMIXER_VORBIS_TREMOR) if(SDLMIXER_VENDORED) set(SDLMIXER_VORBIS_TREMOR_ENABLED TRUE) # vendored libogg already handled if(NOT TARGET ogg) message(FATAL_ERROR "ogg target not present") endif() message(STATUS "Using vendored tremor") set(BUILD_SHARED_LIBS ${SDLMIXER_VORBIS_TREMOR_SHARED}) sdl_check_project_in_subfolder(external/tremor tremor SDLMIXER_VENDORED) add_subdirectory(external/tremor external/tremor-build EXCLUDE_FROM_ALL) if(NOT TARGET tremor::tremor) add_library(tremor::tremor ALIAS vorbisidec) endif() file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ivorbisfile.h" "#include \"${CMAKE_CURRENT_SOURCE_DIR}/external/tremor/ivorbisfile.h\"\n") execute_process( COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/tremor" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/ivorbisfile.h" "${CMAKE_CURRENT_BINARY_DIR}/tremor/ivorbisfile.h" ) target_include_directories(${sdl3_mixer_target_name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") if(SDLMIXER_VORBIS_TREMOR_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS vorbisidec) endif() if(NOT SDLMIXER_VORBIS_TREMOR_SHARED) list(APPEND PC_LIBS -l$ -l$) endif() else() message(STATUS "Using system tremor") find_package(tremor ${required}) if(tremor_FOUND) if(NOT SDLMIXER_VORBIS_TREMOR_SHARED) list(APPEND PC_REQUIRES tremor) endif() set(SDLMIXER_VORBIS_TREMOR_ENABLED TRUE) else() message(${fatal_error} "tremor NOT found") endif() endif() if(SDLMIXER_VORBIS_TREMOR_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_OGG OGG_USE_TREMOR) if(SDLMIXER_VORBIS_TREMOR_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_tremor tremor::tremor) message(STATUS "Dynamic vorbis (tremor): ${dynamic_tremor}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "OGG_DYNAMIC=\"${dynamic_tremor}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} tremor::tremor) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE tremor::tremor) endif() endif() endif() list(APPEND SDLMIXER_BACKENDS VORBIS_VORBISFILE) set(SDLMIXER_VORBIS_VORBISFILE_ENABLED FALSE) if(SDLMIXER_VORBIS_VORBISFILE) if(SDLMIXER_VENDORED) set(SDLMIXER_VORBIS_VORBISFILE_ENABLED TRUE) # vendored libogg already handled if(NOT TARGET ogg) message(FATAL_ERROR "ogg target not present") endif() message(STATUS "Using vendored vorbis + vorbisfile") set(BUILD_SHARED_LIBS ${SDLMIXER_VORBIS_VORBISFILE_SHARED}) sdl_check_project_in_subfolder(external/vorbis vorbisfile SDLMIXER_VENDORED) add_subdirectory(external/vorbis external/vorbis-build EXCLUDE_FROM_ALL) if(NOT TARGET Vorbis::vorbisfile) add_library(Vorbis::vorbisfile ALIAS vorbisfile) endif() if(SDLMIXER_VORBIS_VORBISFILE_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS vorbis vorbisfile) endif() if(NOT SDLMIXER_VORBIS_VORBISFILE_SHARED) list(APPEND PC_LIBS -l$) endif() else() find_package(Vorbis ${required}) if(Vorbis_FOUND) set(SDLMIXER_VORBIS_VORBISFILE_ENABLED TRUE) message(STATUS "Using system vorbisfile") if(NOT SDLMIXER_VORBIS_VORBISFILE_SHARED) list(APPEND PC_REQUIRES vorbisfile) endif() else() message(${fatal_error} "vorbisfile NOT found") endif() endif() if(SDLMIXER_VORBIS_VORBISFILE_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_OGG) if(SDLMIXER_VORBIS_VORBISFILE_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_vorbisfile Vorbis::vorbisfile) message(STATUS "Dynamic vorbisfile: ${dynamic_vorbisfile}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "OGG_DYNAMIC=\"${dynamic_vorbisfile}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} Vorbis::vorbisfile) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE Vorbis::vorbisfile) endif() endif() endif() set(SDLMIXER_VORBIS_ENABLED FALSE) if(SDLMIXER_VORBIS_STB_ENABLED OR SDLMIXER_VORBIS_TREMOR_ENABLED OR SDLMIXER_VORBIS_VORBISFILE_ENABLED) set(SDLMIXER_VORBIS_ENABLED TRUE) endif() list(APPEND SDLMIXER_BACKENDS FLAC_LIBFLAC) set(SDLMIXER_FLAC_LIBFLAC_ENABLED FALSE) if(SDLMIXER_FLAC_LIBFLAC) if(SDLMIXER_VENDORED) set(SDLMIXER_FLAC_LIBFLAC_ENABLED TRUE) # vendored libogg already handled if(NOT TARGET ogg) message(FATAL_ERROR "ogg target not present") endif() set(BUILD_SHARED_LIBS "${SDLMIXER_FLAC_LIBFLAC_SHARED}") set(INSTALL_CMAKE_CONFIG_MODULE OFF) set(WITH_OGG OFF) set(BUILD_CXXLIBS OFF) set(BUILD_EXAMPLES OFF) set(BUILD_PROGRAMS OFF) set(BUILD_TESTING OFF) set(INSTALL_MANPAGES OFF) message(STATUS "Using vendored libflac") sdl_check_project_in_subfolder(external/flac libflac SDLMIXER_VENDORED) add_subdirectory(external/flac external/flac-build EXCLUDE_FROM_ALL) if(SDLMIXER_FLAC_LIBFLAC_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS FLAC) endif() if(NOT SDLMIXER_FLAC_LIBFLAC_SHARED) list(APPEND PC_LIBS -l$ -l$) endif() else() find_package(FLAC ${required}) if(FLAC_FOUND) set(SDLMIXER_FLAC_LIBFLAC_ENABLED TRUE) message(STATUS "Using system libflac") if(NOT SDLMIXER_FLAC_LIBFLAC_SHARED) list(APPEND PC_REQUIRES flac) endif() else() message(${fatal_error} "libflac NOT found") endif() endif() if(SDLMIXER_FLAC_LIBFLAC_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_FLAC_LIBFLAC) if(SDLMIXER_FLAC_LIBFLAC_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_flac FLAC::FLAC) message(STATUS "Dynamic libflac: ${dynamic_flac}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "FLAC_DYNAMIC=\"${dynamic_flac}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} FLAC) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE FLAC::FLAC) endif() endif() endif() list(APPEND SDLMIXER_BACKENDS FLAC_DRFLAC) set(SDLMIXER_FLAC_DRFLAC_ENABLED FALSE) if(SDLMIXER_FLAC_DRFLAC) set(SDLMIXER_FLAC_DRFLAC_ENABLED TRUE) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_FLAC_DRFLAC) endif() set(SDLMIXER_FLAC_ENABLED FALSE) if(SDLMIXER_FLAC_DRFLAC_ENABLED OR SDLMIXER_FLAC_LIBFLAC_ENABLED) set(SDLMIXER_FLAC_ENABLED TRUE) endif() list(APPEND SDLMIXER_BACKENDS GME) set(SDLMIXER_GME_ENABLED FALSE) if(SDLMIXER_GME) if(SDLMIXER_VENDORED) set(SDLMIXER_GME_ENABLED TRUE) set(GME_BUILD_SHARED "${SDLMIXER_GME_SHARED}") if(SDLMIXER_GME_SHARED) set(GME_BUILD_STATIC OFF) set(tgt_gme gme_shared) else() set(GME_BUILD_STATIC ON) set(tgt_gme gme_static gme_deps) endif() set(GME_BUILD_FRAMEWORK OFF) set(GME_BUILD_TESTING OFF) set(GME_BUILD_EXAMPLES OFF) set(GME_ENABLE_UBSAN OFF) option(GME_ZLIB "Enable GME to support compressed sound formats" OFF) message(STATUS "Using vendored libgme") sdl_check_project_in_subfolder(external/libgme libgme SDLMIXER_VENDORED) enable_language(CXX) add_subdirectory(external/libgme external/libgme-build EXCLUDE_FROM_ALL) if(SDLMIXER_GME_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS ${tgt_gme}) endif() if(NOT SDLMIXER_GME_SHARED) list(APPEND PC_LIBS -l$) endif() else() find_package(gme ${required}) if(gme_FOUND) set(SDLMIXER_GME_ENABLED TRUE) message(STATUS "Using system libgme") if(NOT SDLMIXER_GME_SHARED) list(APPEND PC_REQUIRES libgme) endif() else() message(${fatal_error} "libgme NOT found") endif() endif() if(SDLMIXER_GME_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_GME) if(SDLMIXER_GME_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_gme gme::gme) message(STATUS "Dynamic libgme: ${dynamic_gme}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "GME_DYNAMIC=\"${dynamic_gme}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} gme::gme) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE gme::gme) endif() endif() endif() list(APPEND SDLMIXER_BACKENDS MOD_XMP) set(SDLMIXER_MOD_XMP_ENABLED FALSE) if(SDLMIXER_MOD_XMP) if(SDLMIXER_VENDORED) set(SDLMIXER_MOD_XMP_ENABLED TRUE) message(STATUS "Using vendored libxmp") sdl_check_project_in_subfolder(external/libxmp libxmp SDLMIXER_VENDORED) set(LIBXMP_DISABLE_DEPACKERS ON CACHE BOOL "Disable archive depackers") set(LIBXMP_DISABLE_PROWIZARD ON CACHE BOOL "Disable ProWizard format loaders") if(SDLMIXER_MOD_XMP_SHARED) set(BUILD_STATIC OFF) set(BUILD_SHARED ON) set(tgt_xmp xmp_shared) else() set(BUILD_STATIC ON) set(BUILD_SHARED OFF) set(tgt_xmp xmp_static) endif() set(xmp_name libxmp) add_subdirectory(external/libxmp external/libxmp-build EXCLUDE_FROM_ALL) if(SDLMIXER_MOD_XMP_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS ${tgt_xmp}) endif() if(NOT SDLMIXER_MOD_XMP_SHARED) list(APPEND PC_LIBS -l$) endif() else() if(SDLMIXER_MOD_XMP_LITE) find_package(libxmp-lite ${required}) if(libxmp-lite_FOUND) set(SDLMIXER_MOD_XMP_ENABLED TRUE) message(STATUS "Using system libxmp-lite") set(tgt_xmp libxmp-lite::libxmp-lite) set(xmp_name libxmp-lite) if(NOT SDLMIXER_MOD_XMP_SHARED) list(APPEND PC_REQUIRES libxmp-lite) endif() else() message(${fatal_error} "libxmp-lite NOT found") endif() else() find_package(libxmp ${required}) if(libxmp_FOUND) set(SDLMIXER_MOD_XMP_ENABLED TRUE) message(STATUS "Using system libxmp") if(TARGET libxmp::xmp_shared) set(tgt_xmp libxmp::xmp_shared) elseif(TARGET libxmp::xmp_static) set(tgt_xmp libxmp::xmp_static) else() set(tgt_xmp libxmp::libxmp) endif() set(xmp_name libxmp) if(NOT SDLMIXER_MOD_XMP_SHARED) list(APPEND PC_REQUIRES libxmp) endif() else() message(${fatal_error} "libxmp NOT found") endif() endif() endif() if(SDLMIXER_MOD_XMP_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_MOD_XMP) if(SDLMIXER_MOD_XMP_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_xmp ${tgt_xmp}) message(STATUS "Dynamic ${xmp_name}: ${dynamic_xmp}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "XMP_DYNAMIC=\"${dynamic_xmp}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} ${tgt_xmp}) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE ${tgt_xmp}) endif() endif() endif() set(SDLMIXER_MOD_ENABLED FALSE) if(SDLMIXER_MOD_XMP_ENABLED) set(SDLMIXER_MOD_ENABLED TRUE) endif() list(APPEND SDLMIXER_BACKENDS MP3_DRMP3) set(SDLMIXER_MP3_DRMP3_ENABLED FALSE) if(SDLMIXER_MP3_DRMP3) set(SDLMIXER_MP3_DRMP3_ENABLED TRUE) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_MP3_DRMP3) endif() list(APPEND SDLMIXER_BACKENDS MP3_MPG123) set(SDLMIXER_MP3_MPG123_ENABLED FALSE) if(SDLMIXER_MP3_MPG123) if(SDLMIXER_VENDORED) set(SDLMIXER_MP3_MPG123_ENABLED TRUE) message(STATUS "Using vendored mpg123") sdl_check_project_in_subfolder(external/mpg123/ports/cmake mpg123 SDLMIXER_VENDORED) set(BUILD_LIBOUT123 FALSE) set(BUILD_PROGRAMS OFF) set(BUILD_SHARED_LIBS "${SDLMIXER_MP3_MPG123_SHARED}") add_subdirectory(external/mpg123/ports/cmake external/libmpg123-build/ports/cmake EXCLUDE_FROM_ALL) if(NOT TARGET MPG123::libmpg123) add_library(MPG123::libmpg123 ALIAS libmpg123) endif() if(SDLMIXER_MP3_MPG123_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS libmpg123) endif() if(NOT SDLMIXER_MP3_MPG123_SHARED) list(APPEND PC_LIBS -l$) endif() else() find_package(mpg123 ${required}) if(mpg123_FOUND) set(SDLMIXER_MP3_MPG123_ENABLED TRUE) message(STATUS "Using system mpg123") if(NOT SDLMIXER_MP3_MPG123_SHARED) list(APPEND PC_REQUIRES libmpg123) endif() else() message(${fatal_error} "mpg123 NOT found") endif() endif() if(SDLMIXER_MP3_MPG123_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_MP3_MPG123) if(SDLMIXER_MP3_MPG123_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_mpg123 MPG123::libmpg123) message(STATUS "Dynamic mpg123}: ${dynamic_mpg123}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "MPG123_DYNAMIC=\"${dynamic_mpg123}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} MPG123::libmpg123) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE MPG123::libmpg123) endif() endif() endif() set(SDLMIXER_MP3_ENABLED FALSE) if(SDLMIXER_MP3_DRMP3_ENABLED OR SDLMIXER_MP3_MPG123_ENABLED) set(SDLMIXER_MP3_ENABLED TRUE) endif() list(APPEND SDLMIXER_BACKENDS MIDI_FLUIDSYNTH) set(SDLMIXER_MIDI_FLUIDSYNTH_ENABLED FALSE) if(SDLMIXER_MIDI_FLUIDSYNTH) if(SDLMIXER_VENDORED) message(STATUS "Using vendored FluidSynth") message(${fatal_error} "FluidSynth is not vendored.") set(SDLMIXER_MIDI_FLUIDSYNTH_ENABLED FALSE) else() find_package(FluidSynth ${required}) if(FluidSynth_FOUND) set(SDLMIXER_MIDI_FLUIDSYNTH_ENABLED TRUE) message(STATUS "Using system FluidSynth") if(NOT SDLMIXER_MIDI_FLUIDSYNTH_SHARED) list(APPEND PC_REQUIRES fluidsynth) endif() else() message(${fatal_error} "FluidSynth NOT found") endif() endif() if(SDLMIXER_MIDI_FLUIDSYNTH_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_MID_FLUIDSYNTH) if(SDLMIXER_MIDI_FLUIDSYNTH_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_fluidsynth FluidSynth::libfluidsynth) message(STATUS "Dynamic fluidsynth: ${dynamic_fluidsynth}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "FLUIDSYNTH_DYNAMIC=\"${dynamic_fluidsynth}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} FluidSynth::libfluidsynth) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE FluidSynth::libfluidsynth) endif() endif() endif() list(APPEND SDLMIXER_BACKENDS MIDI_NATIVE) list(APPEND SDLMIXER_MIDI_NATIVE_ENABLED FALSE) if(SDLMIXER_MIDI_NATIVE) set(midi_common_sources src/codecs/native_midi/native_midi_common.c src/codecs/native_midi/native_midi_common.h ) if(WIN32) list(APPEND SDLMIXER_MIDI_NATIVE_ENABLED TRUE) target_sources(${sdl3_mixer_target_name} PRIVATE src/codecs/native_midi/native_midi_win32.c ${midi_common_sources}) target_link_libraries(${sdl3_mixer_target_name} PRIVATE winmm) elseif(APPLE) list(APPEND SDLMIXER_MIDI_NATIVE_ENABLED TRUE) target_sources(${sdl3_mixer_target_name} PRIVATE src/codecs/native_midi/native_midi_macosx.c ${midi_common_sources}) target_link_libraries(${sdl3_mixer_target_name} PRIVATE -Wl,-framework,AudioToolbox -Wl,-framework,AudioUnit -Wl,-framework,CoreServices) elseif(HAIKU) list(APPEND SDLMIXER_MIDI_NATIVE_ENABLED TRUE) enable_language(CXX) target_sources(${sdl3_mixer_target_name} PRIVATE src/codecs/native_midi/native_midi_haiku.cpp ${midi_common_sources}) target_link_libraries(${sdl3_mixer_target_name} PRIVATE midi) elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") list(APPEND SDLMIXER_MIDI_NATIVE_ENABLED TRUE) target_sources(${sdl3_mixer_target_name} PRIVATE src/codecs/native_midi/native_midi_linux_alsa.c ${midi_common_sources}) target_link_libraries(${sdl3_mixer_target_name} PRIVATE asound) endif() if(SDLMIXER_MIDI_NATIVE_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_MID_NATIVE) else() message(${fatal_error} "native midi NOT available for current platform") endif() endif() list(APPEND SDLMIXER_BACKENDS MIDI_TIMIDITY) set(SDLMIXER_MIDI_TIMIDITY_ENABLED FALSE) if(SDLMIXER_MIDI_TIMIDITY) set(SDLMIXER_MIDI_TIMIDITY_ENABLED TRUE) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_MID_TIMIDITY) target_sources(${sdl3_mixer_target_name} PRIVATE src/codecs/timidity/common.c src/codecs/timidity/instrum.c src/codecs/timidity/mix.c src/codecs/timidity/output.c src/codecs/timidity/playmidi.c src/codecs/timidity/readmidi.c src/codecs/timidity/resample.c src/codecs/timidity/tables.c src/codecs/timidity/timidity.c ) endif() set(SDLMIXER_MIDI_ENABLED FALSE) if(SDLMIXER_MIDI_FLUIDSYNTH_ENABLED OR SDLMIXER_MIDI_NATIVE_ENABLED OR SDLMIXER_MIDI_TIMIDITY_ENABLED) set(SDLMIXER_MIDI_ENABLED TRUE) endif() list(APPEND SDLMIXER_BACKENDS WAVE) set(SDLMIXER_WAVE_ENABLED FALSE) if(SDLMIXER_WAVE) set(SDLMIXER_WAVE_ENABLED TRUE) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_WAV) endif() list(APPEND SDLMIXER_BACKENDS WAVPACK) set(SDLMIXER_WAVPACK_ENABLED FALSE) if(SDLMIXER_WAVPACK) if(SDLMIXER_VENDORED) set(SDLMIXER_WAVPACK_ENABLED TRUE) message(STATUS "Using vendored WavPack") sdl_check_project_in_subfolder(external/wavpack WavPack SDLMIXER_VENDORED) set(WAVPACK_ENABLE_THREADS FALSE) set(WAVPACK_BUILD_PROGRAMS FALSE) set(WAVPACK_BUILD_COOLEDIT_PLUGIN OFF) set(WAVPACK_BUILD_WINAMP_PLUGIN OFF) set(WAVPACK_BUILD_DOCS OFF) set(BUILD_SHARED_LIBS "${SDLMIXER_WAVPACK_SHARED}") add_subdirectory(external/wavpack external/wavpack-build EXCLUDE_FROM_ALL) if(SDLMIXER_WAVPACK_SHARED OR NOT SDLMIXER_BUILD_SHARED_LIBS) list(APPEND INSTALL_EXTRA_TARGETS wavpack) endif() if(NOT SDLMIXER_WAVPACK_SHARED) list(APPEND PC_LIBS -l$) endif() target_compile_definitions(${sdl3_mixer_target_name} PRIVATE HAVE_WAVPACK_H) else() find_package(wavpack ${required}) if(wavpack_FOUND) set(SDLMIXER_WAVPACK_ENABLED TRUE) message(STATUS "Using system WavPack") if(NOT SDLMIXER_WAVPACK_SHARED) list(APPEND PC_REQUIRES wavpack) endif() else() message(${fatal_error} "wavpack NOT found") endif() endif() if(SDLMIXER_WAVPACK_ENABLED) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_WAVPACK) if(SDLMIXER_WAVPACK_DSD) target_compile_definitions(${sdl3_mixer_target_name} PRIVATE MUSIC_WAVPACK_DSD) endif() if(SDLMIXER_WAVPACK_SHARED) target_include_directories(${sdl3_mixer_target_name} PRIVATE $ $ $ ) target_get_dynamic_library(dynamic_wavpack WavPack::WavPack) message(STATUS "Dynamic WavPack: ${dynamic_wavpack}") target_compile_definitions(${sdl3_mixer_target_name} PRIVATE "WAVPACK_DYNAMIC=\"${dynamic_wavpack}\"") if(SDLMIXER_VENDORED) add_dependencies(${sdl3_mixer_target_name} WavPack::WavPack) endif() else() target_link_libraries(${sdl3_mixer_target_name} PRIVATE WavPack::WavPack) endif() endif() endif() # Restore BUILD_SHARED_LIBS set(BUILD_SHARED_LIBS ${SDLMIXER_BUILD_SHARED_LIBS}) if(SDLMIXER_INSTALL) install( TARGETS ${sdl3_mixer_target_name} EXPORT SDL3MixerTargets ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT devel LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT library RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT library ) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/SDL3_mixer/SDL_mixer.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/SDL3_mixer" COMPONENT devel ) if(BUILD_SHARED_LIBS) set(pdbdir "${CMAKE_INSTALL_BINDIR}") else() set(pdbdir "${CMAKE_INSTALL_LIBDIR}") endif() if(MSVC) SDL_install_pdb("${sdl3_mixer_target_name}" "${pdbdir}") endif() if(INSTALL_EXTRA_TARGETS) install(TARGETS ${INSTALL_EXTRA_TARGETS} EXPORT SDL3MixerTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT library RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT library ) if(MSVC) foreach(tgt IN LISTS INSTALL_EXTRA_TARGETS) SDL_install_pdb("${tgt}" "${pdbdir}") endforeach() endif() endif() if(WIN32 AND NOT MINGW) set(SDLMIXER_INSTALL_CMAKEDIR_ROOT_DEFAULT "cmake") else() set(SDLMIXER_INSTALL_CMAKEDIR_ROOT_DEFAULT "${CMAKE_INSTALL_LIBDIR}/cmake") endif() set(SDLMIXER_INSTALL_CMAKEDIR_ROOT "${SDLMIXER_INSTALL_CMAKEDIR_ROOT_DEFAULT}" CACHE STRING "Root folder where to install SDL3_mixerConfig.cmake related files (SDL3_mixer subfolder for non-MSVC projects)") set(SDLMIXER_PKGCONFIG_INSTALLDIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") if(WIN32 AND NOT MINGW) set(SDLMIXER_INSTALL_CMAKEDIR "${SDLMIXER_INSTALL_CMAKEDIR_ROOT}") set(LICENSES_PREFIX "licenses/SDL3_mixer") else() set(SDLMIXER_INSTALL_CMAKEDIR "${SDLMIXER_INSTALL_CMAKEDIR_ROOT}/SDL3_mixer") set(LICENSES_PREFIX "${CMAKE_INSTALL_DATAROOTDIR}/licenses/SDL3_mixer") endif() configure_package_config_file(cmake/SDL3_mixerConfig.cmake.in SDL3_mixerConfig.cmake NO_SET_AND_CHECK_MACRO INSTALL_DESTINATION "${SDLMIXER_INSTALL_CMAKEDIR}" ) write_basic_package_version_file("${PROJECT_BINARY_DIR}/SDL3_mixerConfigVersion.cmake" COMPATIBILITY AnyNewerVersion ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/SDL3_mixerConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/SDL3_mixerConfigVersion.cmake" DESTINATION "${SDLMIXER_INSTALL_CMAKEDIR}" COMPONENT devel ) if(NOT SDLMIXER_VENDORED) install( FILES cmake/PkgConfigHelper.cmake cmake/FindFLAC.cmake cmake/FindFluidSynth.cmake cmake/Findgme.cmake cmake/Findlibxmp.cmake cmake/Findlibxmp-lite.cmake cmake/FindOgg.cmake cmake/FindOpus.cmake cmake/FindOpusFile.cmake cmake/Findmpg123.cmake cmake/FindVorbis.cmake cmake/Findtremor.cmake cmake/Findwavpack.cmake cmake/FindSndFile.cmake DESTINATION "${SDLMIXER_INSTALL_CMAKEDIR}" COMPONENT devel ) endif() install(EXPORT SDL3MixerTargets FILE ${sdl3_mixer_target_name}-targets.cmake NAMESPACE SDL3_mixer:: DESTINATION "${SDLMIXER_INSTALL_CMAKEDIR}" COMPONENT devel ) export(TARGETS ${sdl3_mixer_target_name} ${INSTALL_EXTRA_TARGETS} NAMESPACE "SDL3_mixer::" FILE "${sdl3_mixer_target_name}-targets.cmake") if(SDLMIXER_RELOCATABLE) file(RELATIVE_PATH SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG "${CMAKE_INSTALL_PREFIX}/${SDLMIXER_PKGCONFIG_INSTALLDIR}" "${CMAKE_INSTALL_PREFIX}") string(REGEX REPLACE "[/]+$" "" SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG "${SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG}") set(SDL_PKGCONFIG_PREFIX "\${pcfiledir}/${SDL_PATH_PREFIX_RELATIVE_TO_PKGCONFIG}") else() set(SDL_PKGCONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") endif() if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") set(INCLUDEDIR_FOR_PKG_CONFIG "${CMAKE_INSTALL_INCLUDEDIR}") else() set(INCLUDEDIR_FOR_PKG_CONFIG "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") endif() if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") set(LIBDIR_FOR_PKG_CONFIG "${CMAKE_INSTALL_LIBDIR}") else() set(LIBDIR_FOR_PKG_CONFIG "\${prefix}/${CMAKE_INSTALL_LIBDIR}") endif() string(JOIN " " PC_REQUIRES ${PC_REQUIRES}) string(JOIN " " PC_LIBS ${PC_LIBS}) configure_file(cmake/sdl3-mixer.pc.in sdl3-mixer.pc @ONLY) # Always install sdl3-mixer.pc file: libraries might be different between config modes install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sdl3-mixer.pc" DESTINATION "${SDLMIXER_PKGCONFIG_INSTALLDIR}" COMPONENT devel) install(FILES "LICENSE.txt" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/${PROJECT_NAME}" COMPONENT library ) if(SDLMIXER_INSTALL_CPACK) if(MSVC) set(CPACK_GENERATOR "ZIP") else() set(CPACK_GENERATOR "TGZ") endif() configure_file(cmake/CPackProjectConfig.cmake.in CPackProjectConfig.cmake @ONLY) set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CPackProjectConfig.cmake") # CPACK_SOURCE_PACKAGE_FILE_NAME must end with "-src" (so we can block creating a source archive) set(CPACK_SOURCE_PACKAGE_FILE_NAME "SDL${PROJECT_VERSION_MAJOR}-${PROJECT_VERSION}-src") set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/dist") include(CPack) endif() if(SDLMIXER_INSTALL_MAN) sdl_get_git_revision_hash(SDLMIXER_REVISION) SDL_generate_manpages( HEADERS_DIR "${PROJECT_SOURCE_DIR}/include/SDL3_mixer" SYMBOL "Mix_Init" WIKIHEADERS_PL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build-scripts/wikiheaders.pl" REVISION "${SDLMIXER_REVISION}" ) endif() endif() if(SDLMIXER_SAMPLES) find_package(SDL3 REQUIRED COMPONENTS SDL3_test) check_include_file("signal.h" HAVE_SIGNAL_H) check_symbol_exists("setbuf" "stdio.h" HAVE_SETBUF) function(add_sdl_mixer_example_executable TARGET) if(ANDROID) add_library(${TARGET} SHARED ${ARGN}) else() add_executable(${TARGET} ${ARGN}) endif() sdl_add_warning_options(${TARGET} WARNING_AS_ERROR ${SDLMIXER_WERROR}) sdl_target_link_options_no_undefined(${TARGET}) target_link_libraries(${TARGET} PRIVATE SDL3::SDL3_test) target_link_libraries(${TARGET} PRIVATE SDL3_mixer::${sdl3_mixer_target_name}) target_link_libraries(${TARGET} PRIVATE ${sdl3_target_name}) if(HAVE_SIGNAL_H) target_compile_definitions(${TARGET} PRIVATE HAVE_SIGNAL_H) endif() if(HAVE_SETBUF) target_compile_definitions(${TARGET} PRIVATE HAVE_SETBUF) endif() if(SDLMIXER_SAMPLES_INSTALL) install(TARGETS ${TARGET} RUNTIME DESTINATION "${CMAKE_INSTALL_LIBEXECDIR}/installed-tests/SDL3_mixer" ) endif() endfunction() add_sdl_mixer_example_executable(playmus examples/playmus.c) add_sdl_mixer_example_executable(playwave examples/playwave.c) endif() set(available_deps) set(unavailable_deps) foreach(dep IN LISTS SDLMIXER_BACKENDS) set(var SDLMIXER_${dep}_ENABLED) if(NOT DEFINED ${var}) message(AUTHOR_WARNING "${var} not defined") endif() if(${var}) list(APPEND available_deps ${dep}) else() list(APPEND unavailable_deps ${dep}) endif() endforeach() string(JOIN " " avail_str ${available_deps}) string(TOLOWER "${avail_str}" avail_str) string(JOIN " " unavail_str ${unavailable_deps}) string(TOLOWER "${unavail_str}" unavail_str) message(STATUS "SDL3_mixer backends:") message(STATUS "- enabled: ${avail_str}") message(STATUS "- disabled: ${unavail_str}") libsdl3-mixer-3~git20250523~daf0503+ds/LICENSE.txt000066400000000000000000000015561501405355700210430ustar00rootroot00000000000000Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. libsdl3-mixer-3~git20250523~daf0503+ds/README-versions.md000066400000000000000000000050101501405355700223320ustar00rootroot00000000000000# Versioning ## Since 2.5.0 `SDL_mixer` follows an "odd/even" versioning policy, similar to GLib, GTK, Flatpak and older versions of the Linux kernel: * The major version (first part) increases when backwards compatibility is broken, which will happen infrequently. * If the minor version (second part) is divisible by 2 (for example 2.6.x, 2.8.x), this indicates a version that is believed to be stable and suitable for production use. * In stable releases, the patchlevel or micro version (third part) indicates bugfix releases. Bugfix releases should not add or remove ABI, so the ".0" release (for example 2.6.0) should be forwards-compatible with all the bugfix releases from the same cycle (for example 2.6.1). * The minor version increases when new API or ABI is added, or when other significant changes are made. Newer minor versions are backwards-compatible, but not fully forwards-compatible. For example, programs built against `SDL_mixer` 2.6.x should work fine with 2.8.x, but programs built against 2.8.x will not necessarily work with 2.6.x. * If the minor version (second part) is not divisible by 2 (for example 2.5.x, 2.7.x), this indicates a development prerelease that is not suitable for stable software distributions. Use with caution. * The patchlevel or micro version (third part) increases with each prerelease. * Each prerelease might add new API and/or ABI. * Prereleases are backwards-compatible with older stable branches. For example, 2.7.x will be backwards-compatible with 2.6.x. * Prereleases are not guaranteed to be backwards-compatible with each other. For example, new API or ABI added in 2.5.1 might be removed or changed in 2.5.2. If this would be a problem for you, please do not use prereleases. * Only upgrade to a prerelease if you can guarantee that you will promptly upgrade to the stable release that follows it. For example, do not upgrade to 2.5.x unless you will be able to upgrade to 2.6.0 when it becomes available. * Software distributions that have a freeze policy (in particular Linux distributions with a release cycle, such as Debian and Fedora) should usually only package stable releases, and not prereleases. ## Before 2.5.0 Older versions of `SDL_mixer` used the patchlevel (micro version, third part) for feature releases, and did not distinguish between feature and bugfix releases. libsdl3-mixer-3~git20250523~daf0503+ds/README.txt000066400000000000000000000042461501405355700207150ustar00rootroot00000000000000 SDL_mixer 3.0 The latest version of this library is available from GitHub: https://github.com/libsdl-org/SDL_mixer/releases Due to popular demand, here is a simple multi-channel audio mixer. It supports 8 channels of 16 bit stereo audio, plus a single channel of music. It can load FLAC, MP3, Ogg, VOC, and WAV format audio. It can also load MIDI, MOD, and Opus audio, depending on build options (see the note below for details.) See the header file SDL_mixer.h and the examples playwave.c and playmus.c for documentation on this mixer library. This documentation is also available online at https://wiki.libsdl.org/SDL3_mixer The process of mixing MIDI files to wave output is very CPU intensive, so if playing regular WAVE files sound great, but playing MIDI files sound choppy, try using 8-bit audio, mono audio, or lower frequencies. If you have built with FluidSynth support, you'll need to set the SDL_SOUNDFONTS environment variable to a Sound Font 2 (.sf2) file containing the musical instruments you want to use for MIDI playback. (On some Linux distributions you can install the fluid-soundfont-gm package) To play MIDI files using Timidity, you'll need to get a complete set of GUS patches from: http://www.libsdl.org/projects/mixer/timidity/timidity.tar.gz and unpack them in /usr/local/lib under UNIX, and C:\ under Win32. This library is under the zlib license, see the file "LICENSE.txt" for details. Note: Support for software MIDI, MOD, and Opus are not included by default because of the size of the decode libraries, but you can get them by running external/download.sh - When building with CMake, you can enable the appropriate SDLMIXER_* options defined in CMakeLists.txt. SDLMIXER_VENDORED allows switching between system and vendored libraries. - When building with Visual Studio, you will need to build the libraries and then add the appropriate LOAD_* preprocessor define to the Visual Studio project. - When building with Xcode, you can edit the config at the top of the project to enable them, and you will need to include the appropriate framework in your application. - For Android, you can edit the config at the top of Android.mk to enable them. libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/000077500000000000000000000000001501405355700217755ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/android-prefab.sh000077500000000000000000000303431501405355700252140ustar00rootroot00000000000000#!/bin/bash set -e if ! [ "x${ANDROID_NDK_HOME}" != "x" -a -d "${ANDROID_NDK_HOME}" ]; then echo "ANDROID_NDK_HOME environment variable is not set" exit 1 fi if ! [ "x${ANDROID_HOME}" != "x" -a -d "${ANDROID_HOME}" ]; then echo "ANDROID_HOME environment variable is not set" exit 1 fi ANDROID_PLATFORM="${ANDROID_PLATFORM:-16}" if [ "x${android_platform}" = "x" ]; then ANDROID_API="$(ls "${ANDROID_HOME}/platforms" | grep -E "^android-[0-9]+$" | sed 's/android-//' | sort -n -r | head -1)" if [ "x${ANDROID_API}" = "x" ]; then echo "No Android platform found in $ANDROID_HOME/platforms" exit 1 fi else if ! [ -d "${ANDROID_HOME}/platforms/android-${ANDROID_API}" ]; then echo "Android api version ${ANDROID_API} is not available (${ANDROID_HOME}/platforms/android-${ANDROID_API} does not exist)" >2 exit 1 fi fi android_platformdir="${ANDROID_HOME}/platforms/android-${ANDROID_API}" echo "Building with ANDROID_PLATFORM=${ANDROID_PLATFORM}" echo "android_platformdir=${android_platformdir}" scriptdir=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)") sdlmixer_root=$(cd -P -- "$(dirname -- "$0")/.." && printf '%s\n' "$(pwd -P)") build_root="${sdlmixer_root}/build-android-prefab" android_abis="armeabi-v7a arm64-v8a x86 x86_64" android_api=19 android_ndk=21 android_stl="c++_shared" sdlmixer_major=$(sed -ne 's/^#define SDL_MIXER_MAJOR_VERSION *//p' "${sdlmixer_root}/include/SDL3_mixer/SDL_mixer.h") sdlmixer_minor=$(sed -ne 's/^#define SDL_MIXER_MINOR_VERSION *//p' "${sdlmixer_root}/include/SDL3_mixer/SDL_mixer.h") sdlmixer_micro=$(sed -ne 's/^#define SDL_MIXER_MICRO_VERSION *//p' "${sdlmixer_root}/include/SDL3_mixer/SDL_mixer.h") sdlmixer_version="${sdlmixer_major}.${sdlmixer_minor}.${sdlmixer_micro}" echo "Building Android prefab package for SDL_mixer version $sdlmixer_version" if test ! -d "${sdl_build_root}"; then echo "sdl_build_root is not defined or is not a directory." echo "Set this environment folder to the root of an android SDL${sdlmixer_major} prefab build" echo "This usually is SDL/build-android-prefab" exit 1 fi prefabhome="${build_root}/prefab-${sdlmixer_version}" rm -rf "$prefabhome" mkdir -p "${prefabhome}" build_cmake_projects() { for android_abi in $android_abis; do rm -rf "${build_root}/build_${android_abi}/prefix" for build_shared_libs in ON OFF; do echo "Configuring CMake project for $android_abi (shared=${build_shared_libs})" cmake -S "${sdlmixer_root}" -B "${build_root}/build_${android_abi}/shared_${build_shared_libs}" \ -DSDLMIXER_DEPS_SHARED=ON \ -DSDLMIXER_VENDORED=ON \ -DSDLMIXER_FLAC=ON \ -DWITH_ASM=OFF \ -DSDLMIXER_FLAC_LIBFLAC=ON \ -DSDLMIXER_MOD=ON \ -DSDLMIXER_MOD_XMP=ON \ -DSDLMIXER_MP3=ON \ -DSDLMIXER_MP3_MPG123=ON \ -DSDLMIXER_MIDI=ON \ -DSDLMIXER_MIDI_TIMIDITY=ON \ -DSDLMIXER_OPUS=ON \ -DSDLMIXER_VORBIS=STB \ -DSDLMIXER_WAVPACK=ON \ -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \ -DSDL${sdlmixer_major}_DIR="${sdl_build_root}/build_${android_abi}/prefix/lib/cmake/SDL${sdlmixer_major}" \ -DANDROID_PLATFORM=${ANDROID_PLATFORM} \ -DANDROID_ABI=${android_abi} \ -DBUILD_SHARED_LIBS=${build_shared_libs} \ -DCMAKE_INSTALL_PREFIX="${build_root}/build_${android_abi}/prefix" \ -DCMAKE_INSTALL_INCLUDEDIR=include \ -DCMAKE_INSTALL_LIBDIR=lib \ -DCMAKE_BUILD_TYPE=Release \ -DSDL${sdlmixer_major}MIXER_SAMPLES=OFF \ -GNinja echo "Building CMake project for $android_abi (shared=${build_shared_libs})" cmake --build "${build_root}/build_${android_abi}/shared_${build_shared_libs}" echo "Installing CMake project for $android_abi (shared=${build_shared_libs})" cmake --install "${build_root}/build_${android_abi}/shared_${build_shared_libs}" done done } pom_filename="SDL${sdlmixer_major}_mixer-${sdlmixer_version}.pom" pom_filepath="${prefabhome}/${pom_filename}" create_pom_xml() { echo "Creating ${pom_filename}" cat >"${pom_filepath}" < 4.0.0 org.libsdl.android SDL${sdlmixer_major}_mixer ${sdlmixer_version} aar SDL${sdlmixer_major}_mixer The AAR for SDL${sdlmixer_major}_mixer https://libsdl.org/ zlib License https://github.com/libsdl-org/SDL_mixer/blob/main/LICENSE.txt repo Sam Lantinga slouken@libsdl.org SDL https://www.libsdl.org scm:git:https://github.com/libsdl-org/SDL_mixer scm:git:ssh://github.com:libsdl-org/SDL_mixer.git https://github.com/libsdl-org/SDL_mixer ossrh https://s01.oss.sonatype.org/content/repositories/snapshots ossrh https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ com.simpligility.maven.plugins android-maven-plugin 4.6.0 true false EOF } create_aar_androidmanifest() { echo "Creating AndroidManifest.xml" cat >"${aar_root}/AndroidManifest.xml" < EOF } echo "Creating AAR root directory" aar_root="${prefabhome}/SDL${sdlmixer_major}_mixer-${sdlmixer_version}" mkdir -p "${aar_root}" aar_metainfdir_path=${aar_root}/META-INF mkdir -p "${aar_metainfdir_path}" cp "${sdlmixer_root}/LICENSE.txt" "${aar_metainfdir_path}" prefabworkdir="${aar_root}/prefab" mkdir -p "${prefabworkdir}" cat >"${prefabworkdir}/prefab.json" <"${sdl_moduleworkdir}/module.json" <"${abi_sdllibdir}/abi.json" <"${sdl_moduleworkdir}/module.json" <"${abi_sdllibdir}/abi.json" <"${sdl_moduleworkdir}/module.json" <"${abi_sdllibdir}/abi.json" <"${aar_metainfdir_path}/LICENSE.libxmp.txt" create_shared_module external_libwavpack libwavpack "" tail -n15 "${sdlmixer_root}/external/wavpack/COPYING" >"${aar_metainfdir_path}/LICENSE.wavpack.txt" pushd "${aar_root}" aar_filename="SDL${sdlmixer_major}_mixer-${sdlmixer_version}.aar" zip -r "${aar_filename}" AndroidManifest.xml prefab META-INF zip -Tv "${aar_filename}" 2>/dev/null ; mv "${aar_filename}" "${prefabhome}" popd maven_filename="SDL${sdlmixer_major}_mixer-${sdlmixer_version}.zip" pushd "${prefabhome}" zip_filename="SDL${sdlmixer_major}_mixer-${sdlmixer_version}.zip" zip "${maven_filename}" "${aar_filename}" "${pom_filename}" 2>/dev/null; zip -Tv "${zip_filename}" 2>/dev/null; popd echo "Prefab zip is ready at ${prefabhome}/${aar_filename}" echo "Maven archive is ready at ${prefabhome}/${zip_filename}" libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/build-release.py000077500000000000000000002356421501405355700251030ustar00rootroot00000000000000#!/usr/bin/env python3 """ This script is shared between SDL2, SDL3, and all satellite libraries. Don't specialize this script for doing project-specific modifications. Rather, modify release-info.json. """ import argparse import collections import dataclasses from collections.abc import Callable import contextlib import datetime import fnmatch import glob import io import json import logging import multiprocessing import os from pathlib import Path import platform import re import shlex import shutil import subprocess import sys import tarfile import tempfile import textwrap import typing import zipfile logger = logging.getLogger(__name__) GIT_HASH_FILENAME = ".git-hash" REVISION_TXT = "REVISION.txt" RE_ILLEGAL_MINGW_LIBRARIES = re.compile(r"(?:lib)?(?:gcc|(?:std)?c[+][+]|(?:win)?pthread).*", flags=re.I) def safe_isotime_to_datetime(str_isotime: str) -> datetime.datetime: try: return datetime.datetime.fromisoformat(str_isotime) except ValueError: pass logger.warning("Invalid iso time: %s", str_isotime) if str_isotime[-6:-5] in ("+", "-"): # Commits can have isotime with invalid timezone offset (e.g. "2021-07-04T20:01:40+32:00") modified_str_isotime = str_isotime[:-6] + "+00:00" try: return datetime.datetime.fromisoformat(modified_str_isotime) except ValueError: pass raise ValueError(f"Invalid isotime: {str_isotime}") def arc_join(*parts: list[str]) -> str: assert all(p[:1] != "/" and p[-1:] != "/" for p in parts), f"None of {parts} may start or end with '/'" return "/".join(p for p in parts if p) @dataclasses.dataclass(frozen=True) class VsArchPlatformConfig: arch: str configuration: str platform: str def extra_context(self): return { "ARCH": self.arch, "CONFIGURATION": self.configuration, "PLATFORM": self.platform, } @contextlib.contextmanager def chdir(path): original_cwd = os.getcwd() try: os.chdir(path) yield finally: os.chdir(original_cwd) class Executer: def __init__(self, root: Path, dry: bool=False): self.root = root self.dry = dry def run(self, cmd, cwd=None, env=None): logger.info("Executing args=%r", cmd) sys.stdout.flush() if not self.dry: subprocess.check_call(cmd, cwd=cwd or self.root, env=env, text=True) def check_output(self, cmd, cwd=None, dry_out=None, env=None, text=True): logger.info("Executing args=%r", cmd) sys.stdout.flush() if self.dry: return dry_out return subprocess.check_output(cmd, cwd=cwd or self.root, env=env, text=text) class SectionPrinter: @contextlib.contextmanager def group(self, title: str): print(f"{title}:") yield class GitHubSectionPrinter(SectionPrinter): def __init__(self): super().__init__() self.in_group = False @contextlib.contextmanager def group(self, title: str): print(f"::group::{title}") assert not self.in_group, "Can enter a group only once" self.in_group = True yield self.in_group = False print("::endgroup::") class VisualStudio: def __init__(self, executer: Executer, year: typing.Optional[str]=None): self.executer = executer self.vsdevcmd = self.find_vsdevcmd(year) self.msbuild = self.find_msbuild() @property def dry(self) -> bool: return self.executer.dry VS_YEAR_TO_VERSION = { "2022": 17, "2019": 16, "2017": 15, "2015": 14, "2013": 12, } def find_vsdevcmd(self, year: typing.Optional[str]=None) -> typing.Optional[Path]: vswhere_spec = ["-latest"] if year is not None: try: version = self.VS_YEAR_TO_VERSION[year] except KeyError: logger.error("Invalid Visual Studio year") return None vswhere_spec.extend(["-version", f"[{version},{version+1})"]) vswhere_cmd = ["vswhere"] + vswhere_spec + ["-property", "installationPath"] vs_install_path = Path(self.executer.check_output(vswhere_cmd, dry_out="/tmp").strip()) logger.info("VS install_path = %s", vs_install_path) assert vs_install_path.is_dir(), "VS installation path does not exist" vsdevcmd_path = vs_install_path / "Common7/Tools/vsdevcmd.bat" logger.info("vsdevcmd path = %s", vsdevcmd_path) if self.dry: vsdevcmd_path.parent.mkdir(parents=True, exist_ok=True) vsdevcmd_path.touch(exist_ok=True) assert vsdevcmd_path.is_file(), "vsdevcmd.bat batch file does not exist" return vsdevcmd_path def find_msbuild(self) -> typing.Optional[Path]: vswhere_cmd = ["vswhere", "-latest", "-requires", "Microsoft.Component.MSBuild", "-find", r"MSBuild\**\Bin\MSBuild.exe"] msbuild_path = Path(self.executer.check_output(vswhere_cmd, dry_out="/tmp/MSBuild.exe").strip()) logger.info("MSBuild path = %s", msbuild_path) if self.dry: msbuild_path.parent.mkdir(parents=True, exist_ok=True) msbuild_path.touch(exist_ok=True) assert msbuild_path.is_file(), "MSBuild.exe does not exist" return msbuild_path def build(self, arch_platform: VsArchPlatformConfig, projects: list[Path]): assert projects, "Need at least one project to build" vsdev_cmd_str = f"\"{self.vsdevcmd}\" -arch={arch_platform.arch}" msbuild_cmd_str = " && ".join([f"\"{self.msbuild}\" \"{project}\" /m /p:BuildInParallel=true /p:Platform={arch_platform.platform} /p:Configuration={arch_platform.configuration}" for project in projects]) bat_contents = f"{vsdev_cmd_str} && {msbuild_cmd_str}\n" bat_path = Path(tempfile.gettempdir()) / "cmd.bat" with bat_path.open("w") as f: f.write(bat_contents) logger.info("Running cmd.exe script (%s): %s", bat_path, bat_contents) cmd = ["cmd.exe", "/D", "/E:ON", "/V:OFF", "/S", "/C", f"CALL {str(bat_path)}"] self.executer.run(cmd) class Archiver: def __init__(self, zip_path: typing.Optional[Path]=None, tgz_path: typing.Optional[Path]=None, txz_path: typing.Optional[Path]=None): self._zip_files = [] self._tar_files = [] self._added_files = set() if zip_path: self._zip_files.append(zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED)) if tgz_path: self._tar_files.append(tarfile.open(tgz_path, "w:gz")) if txz_path: self._tar_files.append(tarfile.open(txz_path, "w:xz")) @property def added_files(self) -> set[str]: return self._added_files def add_file_data(self, arcpath: str, data: bytes, mode: int, time: datetime.datetime): for zf in self._zip_files: file_data_time = (time.year, time.month, time.day, time.hour, time.minute, time.second) zip_info = zipfile.ZipInfo(filename=arcpath, date_time=file_data_time) zip_info.external_attr = mode << 16 zip_info.compress_type = zipfile.ZIP_DEFLATED zf.writestr(zip_info, data=data) for tf in self._tar_files: tar_info = tarfile.TarInfo(arcpath) tar_info.type = tarfile.REGTYPE tar_info.mode = mode tar_info.size = len(data) tar_info.mtime = int(time.timestamp()) tf.addfile(tar_info, fileobj=io.BytesIO(data)) self._added_files.add(arcpath) def add_symlink(self, arcpath: str, target: str, time: datetime.datetime, files_for_zip): logger.debug("Adding symlink (target=%r) -> %s", target, arcpath) for zf in self._zip_files: file_data_time = (time.year, time.month, time.day, time.hour, time.minute, time.second) for f in files_for_zip: zip_info = zipfile.ZipInfo(filename=f["arcpath"], date_time=file_data_time) zip_info.external_attr = f["mode"] << 16 zip_info.compress_type = zipfile.ZIP_DEFLATED zf.writestr(zip_info, data=f["data"]) for tf in self._tar_files: tar_info = tarfile.TarInfo(arcpath) tar_info.type = tarfile.SYMTYPE tar_info.mode = 0o777 tar_info.mtime = int(time.timestamp()) tar_info.linkname = target tf.addfile(tar_info) self._added_files.update(f["arcpath"] for f in files_for_zip) def add_git_hash(self, arcdir: str, commit: str, time: datetime.datetime): arcpath = arc_join(arcdir, GIT_HASH_FILENAME) data = f"{commit}\n".encode() self.add_file_data(arcpath=arcpath, data=data, mode=0o100644, time=time) def add_file_path(self, arcpath: str, path: Path): assert path.is_file(), f"{path} should be a file" logger.debug("Adding %s -> %s", path, arcpath) for zf in self._zip_files: zf.write(path, arcname=arcpath) for tf in self._tar_files: tf.add(path, arcname=arcpath) def add_file_directory(self, arcdirpath: str, dirpath: Path): assert dirpath.is_dir() if arcdirpath and arcdirpath[-1:] != "/": arcdirpath += "/" for f in dirpath.iterdir(): if f.is_file(): arcpath = f"{arcdirpath}{f.name}" logger.debug("Adding %s to %s", f, arcpath) self.add_file_path(arcpath=arcpath, path=f) def close(self): # Archiver is intentionally made invalid after this function del self._zip_files self._zip_files = None del self._tar_files self._tar_files = None def __enter__(self): return self def __exit__(self, type, value, traceback): self.close() class NodeInArchive: def __init__(self, arcpath: str, path: typing.Optional[Path]=None, data: typing.Optional[bytes]=None, mode: typing.Optional[int]=None, symtarget: typing.Optional[str]=None, time: typing.Optional[datetime.datetime]=None, directory: bool=False): self.arcpath = arcpath self.path = path self.data = data self.mode = mode self.symtarget = symtarget self.time = time self.directory = directory @classmethod def from_fs(cls, arcpath: str, path: Path, mode: int=0o100644, time: typing.Optional[datetime.datetime]=None) -> "NodeInArchive": if time is None: time = datetime.datetime.fromtimestamp(os.stat(path).st_mtime) return cls(arcpath=arcpath, path=path, mode=mode) @classmethod def from_data(cls, arcpath: str, data: bytes, time: datetime.datetime) -> "NodeInArchive": return cls(arcpath=arcpath, data=data, time=time, mode=0o100644) @classmethod def from_text(cls, arcpath: str, text: str, time: datetime.datetime) -> "NodeInArchive": return cls.from_data(arcpath=arcpath, data=text.encode(), time=time) @classmethod def from_symlink(cls, arcpath: str, symtarget: str) -> "NodeInArchive": return cls(arcpath=arcpath, symtarget=symtarget) @classmethod def from_directory(cls, arcpath: str) -> "NodeInArchive": return cls(arcpath=arcpath, directory=True) def __repr__(self) -> str: return f"<{type(self).__name__}:arcpath={self.arcpath},path='{str(self.path)}',len(data)={len(self.data) if self.data else 'n/a'},directory={self.directory},symtarget={self.symtarget}>" def configure_file(path: Path, context: dict[str, str]) -> bytes: text = path.read_text() return configure_text(text, context=context).encode() def configure_text(text: str, context: dict[str, str]) -> str: original_text = text for txt, repl in context.items(): text = text.replace(f"@<@{txt}@>@", repl) success = all(thing not in text for thing in ("@<@", "@>@")) if not success: raise ValueError(f"Failed to configure {repr(original_text)}") return text def configure_text_list(text_list: list[str], context: dict[str, str]) -> list[str]: return [configure_text(text=e, context=context) for e in text_list] class ArchiveFileTree: def __init__(self): self._tree: dict[str, NodeInArchive] = {} def add_file(self, file: NodeInArchive): self._tree[file.arcpath] = file def __iter__(self) -> typing.Iterable[NodeInArchive]: yield from self._tree.values() def __contains__(self, value: str) -> bool: return value in self._tree def get_latest_mod_time(self) -> datetime.datetime: return max(item.time for item in self._tree.values() if item.time) def add_to_archiver(self, archive_base: str, archiver: Archiver): remaining_symlinks = set() added_files = dict() def calculate_symlink_target(s: NodeInArchive) -> str: dest_dir = os.path.dirname(s.arcpath) if dest_dir: dest_dir += "/" target = dest_dir + s.symtarget while True: new_target, n = re.subn(r"([^/]+/+[.]{2}/)", "", target) target = new_target if not n: break return target # Add files in first pass for arcpath, node in self._tree.items(): assert node is not None, f"{arcpath} -> node" if node.data is not None: archiver.add_file_data(arcpath=arc_join(archive_base, arcpath), data=node.data, time=node.time, mode=node.mode) assert node.arcpath is not None, f"{node=}" added_files[node.arcpath] = node elif node.path is not None: archiver.add_file_path(arcpath=arc_join(archive_base, arcpath), path=node.path) assert node.arcpath is not None, f"{node=}" added_files[node.arcpath] = node elif node.symtarget is not None: remaining_symlinks.add(node) elif node.directory: pass else: raise ValueError(f"Invalid Archive Node: {repr(node)}") assert None not in added_files # Resolve symlinks in second pass: zipfile does not support symlinks, so add files to zip archive while True: if not remaining_symlinks: break symlinks_this_time = set() extra_added_files = {} for symlink in remaining_symlinks: symlink_files_for_zip = {} symlink_target_path = calculate_symlink_target(symlink) if symlink_target_path in added_files: symlink_files_for_zip[symlink.arcpath] = added_files[symlink_target_path] else: symlink_target_path_slash = symlink_target_path + "/" for added_file in added_files: if added_file.startswith(symlink_target_path_slash): path_in_symlink = symlink.arcpath + "/" + added_file.removeprefix(symlink_target_path_slash) symlink_files_for_zip[path_in_symlink] = added_files[added_file] if symlink_files_for_zip: symlinks_this_time.add(symlink) extra_added_files.update(symlink_files_for_zip) files_for_zip = [{"arcpath": f"{archive_base}/{sym_path}", "data": sym_info.data, "mode": sym_info.mode} for sym_path, sym_info in symlink_files_for_zip.items()] archiver.add_symlink(arcpath=f"{archive_base}/{symlink.arcpath}", target=symlink.symtarget, time=symlink.time, files_for_zip=files_for_zip) # if not symlinks_this_time: # logger.info("files added: %r", set(path for path in added_files.keys())) assert symlinks_this_time, f"No targets found for symlinks: {remaining_symlinks}" remaining_symlinks.difference_update(symlinks_this_time) added_files.update(extra_added_files) def add_directory_tree(self, arc_dir: str, path: Path, time: datetime.datetime): assert path.is_dir() for files_dir, _, filenames in os.walk(path): files_dir_path = Path(files_dir) rel_files_path = files_dir_path.relative_to(path) for filename in filenames: self.add_file(NodeInArchive.from_fs(arcpath=arc_join(arc_dir, str(rel_files_path), filename), path=files_dir_path / filename, time=time)) def _add_files_recursively(self, arc_dir: str, paths: list[Path], time: datetime.datetime): logger.debug(f"_add_files_recursively({arc_dir=} {paths=})") for path in paths: arcpath = arc_join(arc_dir, path.name) if path.is_file(): logger.debug("Adding %s as %s", path, arcpath) self.add_file(NodeInArchive.from_fs(arcpath=arcpath, path=path, time=time)) elif path.is_dir(): self._add_files_recursively(arc_dir=arc_join(arc_dir, path.name), paths=list(path.iterdir()), time=time) else: raise ValueError(f"Unsupported file type to add recursively: {path}") def add_file_mapping(self, arc_dir: str, file_mapping: dict[str, list[str]], file_mapping_root: Path, context: dict[str, str], time: datetime.datetime): for meta_rel_destdir, meta_file_globs in file_mapping.items(): rel_destdir = configure_text(meta_rel_destdir, context=context) assert "@" not in rel_destdir, f"archive destination should not contain an @ after configuration ({repr(meta_rel_destdir)}->{repr(rel_destdir)})" for meta_file_glob in meta_file_globs: file_glob = configure_text(meta_file_glob, context=context) assert "@" not in rel_destdir, f"archive glob should not contain an @ after configuration ({repr(meta_file_glob)}->{repr(file_glob)})" if ":" in file_glob: original_path, new_filename = file_glob.rsplit(":", 1) assert ":" not in original_path, f"Too many ':' in {repr(file_glob)}" assert "/" not in new_filename, f"New filename cannot contain a '/' in {repr(file_glob)}" path = file_mapping_root / original_path arcpath = arc_join(arc_dir, rel_destdir, new_filename) if path.suffix == ".in": data = configure_file(path, context=context) logger.debug("Adding processed %s -> %s", path, arcpath) self.add_file(NodeInArchive.from_data(arcpath=arcpath, data=data, time=time)) else: logger.debug("Adding %s -> %s", path, arcpath) self.add_file(NodeInArchive.from_fs(arcpath=arcpath, path=path, time=time)) else: relative_file_paths = glob.glob(file_glob, root_dir=file_mapping_root) assert relative_file_paths, f"Glob '{file_glob}' does not match any file" self._add_files_recursively(arc_dir=arc_join(arc_dir, rel_destdir), paths=[file_mapping_root / p for p in relative_file_paths], time=time) class SourceCollector: # TreeItem = collections.namedtuple("TreeItem", ("path", "mode", "data", "symtarget", "directory", "time")) def __init__(self, root: Path, commit: str, filter: typing.Optional[Callable[[str], bool]], executer: Executer): self.root = root self.commit = commit self.filter = filter self.executer = executer def get_archive_file_tree(self) -> ArchiveFileTree: git_archive_args = ["git", "archive", "--format=tar.gz", self.commit, "-o", "/dev/stdout"] logger.info("Executing args=%r", git_archive_args) contents_tgz = subprocess.check_output(git_archive_args, cwd=self.root, text=False) tar_archive = tarfile.open(fileobj=io.BytesIO(contents_tgz), mode="r:gz") filenames = tuple(m.name for m in tar_archive if (m.isfile() or m.issym())) file_times = self._get_file_times(paths=filenames) git_contents = ArchiveFileTree() for ti in tar_archive: if self.filter and not self.filter(ti.name): continue data = None symtarget = None directory = False file_time = None if ti.isfile(): contents_file = tar_archive.extractfile(ti.name) data = contents_file.read() file_time = file_times[ti.name] elif ti.issym(): symtarget = ti.linkname file_time = file_times[ti.name] elif ti.isdir(): directory = True else: raise ValueError(f"{ti.name}: unknown type") node = NodeInArchive(arcpath=ti.name, data=data, mode=ti.mode, symtarget=symtarget, time=file_time, directory=directory) git_contents.add_file(node) return git_contents def _get_file_times(self, paths: tuple[str, ...]) -> dict[str, datetime.datetime]: dry_out = textwrap.dedent("""\ time=2024-03-14T15:40:25-07:00 M\tCMakeLists.txt """) git_log_out = self.executer.check_output(["git", "log", "--name-status", '--pretty=time=%cI', self.commit], dry_out=dry_out, cwd=self.root).splitlines(keepends=False) current_time = None set_paths = set(paths) path_times: dict[str, datetime.datetime] = {} for line in git_log_out: if not line: continue if line.startswith("time="): current_time = safe_isotime_to_datetime(line.removeprefix("time=")) continue mod_type, file_paths = line.split(maxsplit=1) assert current_time is not None for file_path in file_paths.split("\t"): if file_path in set_paths and file_path not in path_times: path_times[file_path] = current_time # FIXME: find out why some files are not shown in "git log" # assert set(path_times.keys()) == set_paths if set(path_times.keys()) != set_paths: found_times = set(path_times.keys()) paths_without_times = set_paths.difference(found_times) logger.warning("No times found for these paths: %s", paths_without_times) max_time = max(time for time in path_times.values()) for path in paths_without_times: path_times[path] = max_time return path_times class AndroidApiVersion: def __init__(self, name: str, ints: tuple[int, ...]): self.name = name self.ints = ints def __repr__(self) -> str: return f"<{self.name} ({'.'.join(str(v) for v in self.ints)})>" class Releaser: def __init__(self, release_info: dict, commit: str, revision: str, root: Path, dist_path: Path, section_printer: SectionPrinter, executer: Executer, cmake_generator: str, deps_path: Path, overwrite: bool, github: bool, fast: bool): self.release_info = release_info self.project = release_info["name"] self.version = self.extract_sdl_version(root=root, release_info=release_info) self.root = root self.commit = commit self.revision = revision self.dist_path = dist_path self.section_printer = section_printer self.executer = executer self.cmake_generator = cmake_generator self.cpu_count = multiprocessing.cpu_count() self.deps_path = deps_path self.overwrite = overwrite self.github = github self.fast = fast self.arc_time = datetime.datetime.now() self.artifacts: dict[str, Path] = {} def get_context(self, extra_context: typing.Optional[dict[str, str]]=None) -> dict[str, str]: ctx = { "PROJECT_NAME": self.project, "PROJECT_VERSION": self.version, "PROJECT_COMMIT": self.commit, "PROJECT_REVISION": self.revision, "PROJECT_ROOT": str(self.root), } if extra_context: ctx.update(extra_context) return ctx @property def dry(self) -> bool: return self.executer.dry def prepare(self): logger.debug("Creating dist folder") self.dist_path.mkdir(parents=True, exist_ok=True) @classmethod def _path_filter(cls, path: str) -> bool: if ".gitmodules" in path: return True if path.startswith(".git"): return False return True @classmethod def _external_repo_path_filter(cls, path: str) -> bool: if not cls._path_filter(path): return False if path.startswith("test/") or path.startswith("tests/"): return False return True def create_source_archives(self) -> None: source_collector = SourceCollector(root=self.root, commit=self.commit, executer=self.executer, filter=self._path_filter) print(f"Collecting sources of {self.project}...") archive_tree: ArchiveFileTree = source_collector.get_archive_file_tree() latest_mod_time = archive_tree.get_latest_mod_time() archive_tree.add_file(NodeInArchive.from_text(arcpath=REVISION_TXT, text=f"{self.revision}\n", time=latest_mod_time)) archive_tree.add_file(NodeInArchive.from_text(arcpath=f"{GIT_HASH_FILENAME}", text=f"{self.commit}\n", time=latest_mod_time)) archive_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["source"].get("files", {}), file_mapping_root=self.root, context=self.get_context(), time=latest_mod_time) if "Makefile.am" in archive_tree: patched_time = latest_mod_time + datetime.timedelta(minutes=1) print(f"Makefile.am detected -> touching aclocal.m4, */Makefile.in, configure") for node_data in archive_tree: arc_name = os.path.basename(node_data.arcpath) arc_name_we, arc_name_ext = os.path.splitext(arc_name) if arc_name in ("aclocal.m4", "configure", "Makefile.in"): print(f"Bumping time of {node_data.arcpath}") node_data.time = patched_time archive_base = f"{self.project}-{self.version}" zip_path = self.dist_path / f"{archive_base}.zip" tgz_path = self.dist_path / f"{archive_base}.tar.gz" txz_path = self.dist_path / f"{archive_base}.tar.xz" logger.info("Creating zip/tgz/txz source archives ...") if self.dry: zip_path.touch() tgz_path.touch() txz_path.touch() else: with Archiver(zip_path=zip_path, tgz_path=tgz_path, txz_path=txz_path) as archiver: print(f"Adding source files of {self.project}...") archive_tree.add_to_archiver(archive_base=archive_base, archiver=archiver) for extra_repo in self.release_info["source"].get("extra-repos", []): extra_repo_root = self.root / extra_repo assert (extra_repo_root / ".git").exists(), f"{extra_repo_root} must be a git repo" extra_repo_commit = self.executer.check_output(["git", "rev-parse", "HEAD"], dry_out=f"gitsha-extra-repo-{extra_repo}", cwd=extra_repo_root).strip() extra_repo_source_collector = SourceCollector(root=extra_repo_root, commit=extra_repo_commit, executer=self.executer, filter=self._external_repo_path_filter) print(f"Collecting sources of {extra_repo} ...") extra_repo_archive_tree = extra_repo_source_collector.get_archive_file_tree() print(f"Adding source files of {extra_repo} ...") extra_repo_archive_tree.add_to_archiver(archive_base=f"{archive_base}/{extra_repo}", archiver=archiver) for file in self.release_info["source"]["checks"]: assert f"{archive_base}/{file}" in archiver.added_files, f"'{archive_base}/{file}' must exist" logger.info("... done") self.artifacts["src-zip"] = zip_path self.artifacts["src-tar-gz"] = tgz_path self.artifacts["src-tar-xz"] = txz_path if not self.dry: with tgz_path.open("r+b") as f: # Zero the embedded timestamp in the gzip'ed tarball f.seek(4, 0) f.write(b"\x00\x00\x00\x00") def create_dmg(self, configuration: str="Release") -> None: dmg_in = self.root / self.release_info["dmg"]["path"] xcode_project = self.root / self.release_info["dmg"]["project"] assert xcode_project.is_dir(), f"{xcode_project} must be a directory" assert (xcode_project / "project.pbxproj").is_file, f"{xcode_project} must contain project.pbxproj" if not self.fast: dmg_in.unlink(missing_ok=True) build_xcconfig = self.release_info["dmg"].get("build-xcconfig") if build_xcconfig: shutil.copy(self.root / build_xcconfig, xcode_project.parent / "build.xcconfig") xcode_scheme = self.release_info["dmg"].get("scheme") xcode_target = self.release_info["dmg"].get("target") assert xcode_scheme or xcode_target, "dmg needs scheme or target" assert not (xcode_scheme and xcode_target), "dmg cannot have both scheme and target set" if xcode_scheme: scheme_or_target = "-scheme" target_like = xcode_scheme else: scheme_or_target = "-target" target_like = xcode_target self.executer.run(["xcodebuild", "ONLY_ACTIVE_ARCH=NO", "-project", xcode_project, scheme_or_target, target_like, "-configuration", configuration]) if self.dry: dmg_in.parent.mkdir(parents=True, exist_ok=True) dmg_in.touch() assert dmg_in.is_file(), f"{self.project}.dmg was not created by xcodebuild" dmg_out = self.dist_path / f"{self.project}-{self.version}.dmg" shutil.copy(dmg_in, dmg_out) self.artifacts["dmg"] = dmg_out @property def git_hash_data(self) -> bytes: return f"{self.commit}\n".encode() def verify_mingw_library(self, triplet: str, path: Path): objdump_output = self.executer.check_output([f"{triplet}-objdump", "-p", str(path)]) libraries = re.findall(r"DLL Name: ([^\n]+)", objdump_output) logger.info("%s (%s) libraries: %r", path, triplet, libraries) illegal_libraries = list(filter(RE_ILLEGAL_MINGW_LIBRARIES.match, libraries)) logger.error("Detected 'illegal' libraries: %r", illegal_libraries) if illegal_libraries: raise Exception(f"{path} links to illegal libraries: {illegal_libraries}") def create_mingw_archives(self) -> None: build_type = "Release" build_parent_dir = self.root / "build-mingw" ARCH_TO_GNU_ARCH = { # "arm64": "aarch64", "x86": "i686", "x64": "x86_64", } ARCH_TO_TRIPLET = { # "arm64": "aarch64-w64-mingw32", "x86": "i686-w64-mingw32", "x64": "x86_64-w64-mingw32", } new_env = dict(os.environ) cmake_prefix_paths = [] mingw_deps_path = self.deps_path / "mingw-deps" if "dependencies" in self.release_info["mingw"]: shutil.rmtree(mingw_deps_path, ignore_errors=True) mingw_deps_path.mkdir() for triplet in ARCH_TO_TRIPLET.values(): (mingw_deps_path / triplet).mkdir() def extract_filter(member: tarfile.TarInfo, path: str, /): if member.name.startswith("SDL"): member.name = "/".join(Path(member.name).parts[1:]) return member for dep in self.release_info.get("dependencies", {}): extract_path = mingw_deps_path / f"extract-{dep}" extract_path.mkdir() with chdir(extract_path): tar_path = self.deps_path / glob.glob(self.release_info["mingw"]["dependencies"][dep]["artifact"], root_dir=self.deps_path)[0] logger.info("Extracting %s to %s", tar_path, mingw_deps_path) assert tar_path.suffix in (".gz", ".xz") with tarfile.open(tar_path, mode=f"r:{tar_path.suffix.strip('.')}") as tarf: tarf.extractall(filter=extract_filter) for arch, triplet in ARCH_TO_TRIPLET.items(): install_cmd = self.release_info["mingw"]["dependencies"][dep]["install-command"] extra_configure_data = { "ARCH": ARCH_TO_GNU_ARCH[arch], "TRIPLET": triplet, "PREFIX": str(mingw_deps_path / triplet), } install_cmd = configure_text(install_cmd, context=self.get_context(extra_configure_data)) self.executer.run(shlex.split(install_cmd), cwd=str(extract_path)) dep_binpath = mingw_deps_path / triplet / "bin" assert dep_binpath.is_dir(), f"{dep_binpath} for PATH should exist" dep_pkgconfig = mingw_deps_path / triplet / "lib/pkgconfig" assert dep_pkgconfig.is_dir(), f"{dep_pkgconfig} for PKG_CONFIG_PATH should exist" new_env["PATH"] = os.pathsep.join([str(dep_binpath), new_env["PATH"]]) new_env["PKG_CONFIG_PATH"] = str(dep_pkgconfig) cmake_prefix_paths.append(mingw_deps_path) new_env["CFLAGS"] = f"-O2 -ffile-prefix-map={self.root}=/src/{self.project}" new_env["CXXFLAGS"] = f"-O2 -ffile-prefix-map={self.root}=/src/{self.project}" assert any(system in self.release_info["mingw"] for system in ("autotools", "cmake")) assert not all(system in self.release_info["mingw"] for system in ("autotools", "cmake")) mingw_archs = set() arc_root = f"{self.project}-{self.version}" archive_file_tree = ArchiveFileTree() if "autotools" in self.release_info["mingw"]: for arch in self.release_info["mingw"]["autotools"]["archs"]: triplet = ARCH_TO_TRIPLET[arch] new_env["CC"] = f"{triplet}-gcc" new_env["CXX"] = f"{triplet}-g++" new_env["RC"] = f"{triplet}-windres" assert arch not in mingw_archs mingw_archs.add(arch) build_path = build_parent_dir / f"build-{triplet}" install_path = build_parent_dir / f"install-{triplet}" shutil.rmtree(install_path, ignore_errors=True) build_path.mkdir(parents=True, exist_ok=True) context = self.get_context({ "ARCH": arch, "DEP_PREFIX": str(mingw_deps_path / triplet), }) extra_args = configure_text_list(text_list=self.release_info["mingw"]["autotools"]["args"], context=context) with self.section_printer.group(f"Configuring MinGW {triplet} (autotools)"): assert "@" not in " ".join(extra_args), f"@ should not be present in extra arguments ({extra_args})" self.executer.run([ self.root / "configure", f"--prefix={install_path}", f"--includedir=${{prefix}}/include", f"--libdir=${{prefix}}/lib", f"--bindir=${{prefix}}/bin", f"--host={triplet}", f"--build=x86_64-none-linux-gnu", "CFLAGS=-O2", "CXXFLAGS=-O2", "LDFLAGS=-Wl,-s", ] + extra_args, cwd=build_path, env=new_env) with self.section_printer.group(f"Build MinGW {triplet} (autotools)"): self.executer.run(["make", f"-j{self.cpu_count}"], cwd=build_path, env=new_env) with self.section_printer.group(f"Install MinGW {triplet} (autotools)"): self.executer.run(["make", "install"], cwd=build_path, env=new_env) self.verify_mingw_library(triplet=ARCH_TO_TRIPLET[arch], path=install_path / "bin" / f"{self.project}.dll") archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path, time=self.arc_time) print("Recording arch-dependent extra files for MinGW development archive ...") extra_context = { "TRIPLET": ARCH_TO_TRIPLET[arch], } archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["mingw"]["autotools"].get("files", {}), file_mapping_root=self.root, context=self.get_context(extra_context=extra_context), time=self.arc_time) if "cmake" in self.release_info["mingw"]: assert self.release_info["mingw"]["cmake"]["shared-static"] in ("args", "both") for arch in self.release_info["mingw"]["cmake"]["archs"]: triplet = ARCH_TO_TRIPLET[arch] new_env["CC"] = f"{triplet}-gcc" new_env["CXX"] = f"{triplet}-g++" new_env["RC"] = f"{triplet}-windres" assert arch not in mingw_archs mingw_archs.add(arch) context = self.get_context({ "ARCH": arch, "DEP_PREFIX": str(mingw_deps_path / triplet), }) extra_args = configure_text_list(text_list=self.release_info["mingw"]["cmake"]["args"], context=context) build_path = build_parent_dir / f"build-{triplet}" install_path = build_parent_dir / f"install-{triplet}" shutil.rmtree(install_path, ignore_errors=True) build_path.mkdir(parents=True, exist_ok=True) if self.release_info["mingw"]["cmake"]["shared-static"] == "args": args_for_shared_static = ([], ) elif self.release_info["mingw"]["cmake"]["shared-static"] == "both": args_for_shared_static = (["-DBUILD_SHARED_LIBS=ON"], ["-DBUILD_SHARED_LIBS=OFF"]) for arg_for_shared_static in args_for_shared_static: with self.section_printer.group(f"Configuring MinGW {triplet} (CMake)"): assert "@" not in " ".join(extra_args), f"@ should not be present in extra arguments ({extra_args})" self.executer.run([ f"cmake", f"-S", str(self.root), "-B", str(build_path), f"-DCMAKE_BUILD_TYPE={build_type}", f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', f"-DCMAKE_PREFIX_PATH={mingw_deps_path / triplet}", f"-DCMAKE_INSTALL_PREFIX={install_path}", f"-DCMAKE_INSTALL_INCLUDEDIR=include", f"-DCMAKE_INSTALL_LIBDIR=lib", f"-DCMAKE_INSTALL_BINDIR=bin", f"-DCMAKE_INSTALL_DATAROOTDIR=share", f"-DCMAKE_TOOLCHAIN_FILE={self.root}/build-scripts/cmake-toolchain-mingw64-{ARCH_TO_GNU_ARCH[arch]}.cmake", f"-G{self.cmake_generator}", ] + extra_args + ([] if self.fast else ["--fresh"]) + arg_for_shared_static, cwd=build_path, env=new_env) with self.section_printer.group(f"Build MinGW {triplet} (CMake)"): self.executer.run(["cmake", "--build", str(build_path), "--verbose", "--config", build_type], cwd=build_path, env=new_env) with self.section_printer.group(f"Install MinGW {triplet} (CMake)"): self.executer.run(["cmake", "--install", str(build_path)], cwd=build_path, env=new_env) self.verify_mingw_library(triplet=ARCH_TO_TRIPLET[arch], path=install_path / "bin" / f"{self.project}.dll") archive_file_tree.add_directory_tree(arc_dir=arc_join(arc_root, triplet), path=install_path, time=self.arc_time) print("Recording arch-dependent extra files for MinGW development archive ...") extra_context = { "TRIPLET": ARCH_TO_TRIPLET[arch], } archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["mingw"]["cmake"].get("files", {}), file_mapping_root=self.root, context=self.get_context(extra_context=extra_context), time=self.arc_time) print("... done") print("Recording extra files for MinGW development archive ...") archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["mingw"].get("files", {}), file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) print("... done") print("Creating zip/tgz/txz development archives ...") zip_path = self.dist_path / f"{self.project}-devel-{self.version}-mingw.zip" tgz_path = self.dist_path / f"{self.project}-devel-{self.version}-mingw.tar.gz" txz_path = self.dist_path / f"{self.project}-devel-{self.version}-mingw.tar.xz" with Archiver(zip_path=zip_path, tgz_path=tgz_path, txz_path=txz_path) as archiver: archive_file_tree.add_to_archiver(archive_base="", archiver=archiver) archiver.add_git_hash(arcdir=arc_root, commit=self.commit, time=self.arc_time) print("... done") self.artifacts["mingw-devel-zip"] = zip_path self.artifacts["mingw-devel-tar-gz"] = tgz_path self.artifacts["mingw-devel-tar-xz"] = txz_path def _detect_android_api(self, android_home: str) -> typing.Optional[AndroidApiVersion]: platform_dirs = list(Path(p) for p in glob.glob(f"{android_home}/platforms/android-*")) re_platform = re.compile("^android-([0-9]+)(?:-ext([0-9]+))?$") platform_versions: list[AndroidApiVersion] = [] for platform_dir in platform_dirs: logger.debug("Found Android Platform SDK: %s", platform_dir) if not (platform_dir / "android.jar").is_file(): logger.debug("Skipping SDK, missing android.jar") continue if m:= re_platform.match(platform_dir.name): platform_versions.append(AndroidApiVersion(name=platform_dir.name, ints=(int(m.group(1)), int(m.group(2) or 0)))) platform_versions.sort(key=lambda v: v.ints) logger.info("Available platform versions: %s", platform_versions) platform_versions = list(filter(lambda v: v.ints >= self._android_api_minimum.ints, platform_versions)) logger.info("Valid platform versions (>=%s): %s", self._android_api_minimum.ints, platform_versions) if not platform_versions: return None android_api = platform_versions[0] logger.info("Selected API version %s", android_api) return android_api def _get_prefab_json_text(self) -> str: return textwrap.dedent(f"""\ {{ "schema_version": 2, "name": "{self.project}", "version": "{self.version}", "dependencies": [] }} """) def _get_prefab_module_json_text(self, library_name: typing.Optional[str], export_libraries: list[str]) -> str: for lib in export_libraries: assert isinstance(lib, str), f"{lib} must be a string" module_json_dict = { "export_libraries": export_libraries, } if library_name: module_json_dict["library_name"] = f"lib{library_name}" return json.dumps(module_json_dict, indent=4) @property def _android_api_minimum(self) -> AndroidApiVersion: value = self.release_info["android"]["api-minimum"] if isinstance(value, int): ints = (value, ) elif isinstance(value, str): ints = tuple(split(".")) else: raise ValueError("Invalid android.api-minimum: must be X or X.Y") match len(ints): case 1: name = f"android-{ints[0]}" case 2: name = f"android-{ints[0]}-ext-{ints[1]}" case _: raise ValueError("Invalid android.api-minimum: must be X or X.Y") return AndroidApiVersion(name=name, ints=ints) @property def _android_api_target(self): return self.release_info["android"]["api-target"] @property def _android_ndk_minimum(self): return self.release_info["android"]["ndk-minimum"] def _get_prefab_abi_json_text(self, abi: str, cpp: bool, shared: bool) -> str: abi_json_dict = { "abi": abi, "api": self._android_api_minimum.ints[0], "ndk": self._android_ndk_minimum, "stl": "c++_shared" if cpp else "none", "static": not shared, } return json.dumps(abi_json_dict, indent=4) def _get_android_manifest_text(self) -> str: return textwrap.dedent(f"""\ """) def create_android_archives(self, android_api: int, android_home: Path, android_ndk_home: Path) -> None: cmake_toolchain_file = Path(android_ndk_home) / "build/cmake/android.toolchain.cmake" if not cmake_toolchain_file.exists(): logger.error("CMake toolchain file does not exist (%s)", cmake_toolchain_file) raise SystemExit(1) aar_path = self.root / "build-android" / f"{self.project}-{self.version}.aar" android_dist_path = self.dist_path / f"{self.project}-devel-{self.version}-android.zip" android_abis = self.release_info["android"]["abis"] java_jars_added = False module_data_added = False android_deps_path = self.deps_path / "android-deps" shutil.rmtree(android_deps_path, ignore_errors=True) for dep, depinfo in self.release_info["android"].get("dependencies", {}).items(): dep_devel_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0] dep_extract_path = self.deps_path / f"extract/android/{dep}" shutil.rmtree(dep_extract_path, ignore_errors=True) dep_extract_path.mkdir(parents=True, exist_ok=True) with self.section_printer.group(f"Extracting Android dependency {dep} ({dep_devel_zip})"): with zipfile.ZipFile(dep_devel_zip, "r") as zf: zf.extractall(dep_extract_path) dep_devel_aar = dep_extract_path / glob.glob("*.aar", root_dir=dep_extract_path)[0] self.executer.run([sys.executable, str(dep_devel_aar), "-o", str(android_deps_path)]) for module_name, module_info in self.release_info["android"]["modules"].items(): assert "type" in module_info and module_info["type"] in ("interface", "library"), f"module {module_name} must have a valid type" aar_file_tree = ArchiveFileTree() android_devel_file_tree = ArchiveFileTree() for android_abi in android_abis: with self.section_printer.group(f"Building for Android {android_api} {android_abi}"): build_dir = self.root / "build-android" / f"{android_abi}-build" install_dir = self.root / "install-android" / f"{android_abi}-install" shutil.rmtree(install_dir, ignore_errors=True) assert not install_dir.is_dir(), f"{install_dir} should not exist prior to build" build_type = "Release" cmake_args = [ "cmake", "-S", str(self.root), "-B", str(build_dir), f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', f"-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}", f"-DCMAKE_PREFIX_PATH={str(android_deps_path)}", f"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH", f"-DANDROID_HOME={android_home}", f"-DANDROID_PLATFORM={android_api}", f"-DANDROID_ABI={android_abi}", "-DCMAKE_POSITION_INDEPENDENT_CODE=ON", f"-DCMAKE_INSTALL_PREFIX={install_dir}", "-DCMAKE_INSTALL_INCLUDEDIR=include ", "-DCMAKE_INSTALL_LIBDIR=lib", "-DCMAKE_INSTALL_DATAROOTDIR=share", f"-DCMAKE_BUILD_TYPE={build_type}", f"-G{self.cmake_generator}", ] + self.release_info["android"]["cmake"]["args"] + ([] if self.fast else ["--fresh"]) build_args = [ "cmake", "--build", str(build_dir), "--verbose", "--config", build_type, ] install_args = [ "cmake", "--install", str(build_dir), "--config", build_type, ] self.executer.run(cmake_args) self.executer.run(build_args) self.executer.run(install_args) for module_name, module_info in self.release_info["android"]["modules"].items(): arcdir_prefab_module = f"prefab/modules/{module_name}" if module_info["type"] == "library": library = install_dir / module_info["library"] assert library.suffix in (".so", ".a") assert library.is_file(), f"CMake should have built library '{library}' for module {module_name}" arcdir_prefab_libs = f"{arcdir_prefab_module}/libs/android.{android_abi}" aar_file_tree.add_file(NodeInArchive.from_fs(arcpath=f"{arcdir_prefab_libs}/{library.name}", path=library, time=self.arc_time)) aar_file_tree.add_file(NodeInArchive.from_text(arcpath=f"{arcdir_prefab_libs}/abi.json", text=self._get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=library.suffix == ".so"), time=self.arc_time)) if not module_data_added: library_name = None if module_info["type"] == "library": library_name = Path(module_info["library"]).stem.removeprefix("lib") export_libraries = module_info.get("export-libraries", []) aar_file_tree.add_file(NodeInArchive.from_text(arcpath=arc_join(arcdir_prefab_module, "module.json"), text=self._get_prefab_module_json_text(library_name=library_name, export_libraries=export_libraries), time=self.arc_time)) arcdir_prefab_include = f"prefab/modules/{module_name}/include" if "includes" in module_info: aar_file_tree.add_file_mapping(arc_dir=arcdir_prefab_include, file_mapping=module_info["includes"], file_mapping_root=install_dir, context=self.get_context(), time=self.arc_time) else: aar_file_tree.add_file(NodeInArchive.from_text(arcpath=arc_join(arcdir_prefab_include, ".keep"), text="\n", time=self.arc_time)) module_data_added = True if not java_jars_added: java_jars_added = True if "jars" in self.release_info["android"]: classes_jar_path = install_dir / configure_text(text=self.release_info["android"]["jars"]["classes"], context=self.get_context()) sources_jar_path = install_dir / configure_text(text=self.release_info["android"]["jars"]["sources"], context=self.get_context()) doc_jar_path = install_dir / configure_text(text=self.release_info["android"]["jars"]["doc"], context=self.get_context()) assert classes_jar_path.is_file(), f"CMake should have compiled the java sources and archived them into a JAR ({classes_jar_path})" assert sources_jar_path.is_file(), f"CMake should have archived the java sources into a JAR ({sources_jar_path})" assert doc_jar_path.is_file(), f"CMake should have archived javadoc into a JAR ({doc_jar_path})" aar_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes.jar", path=classes_jar_path, time=self.arc_time)) aar_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes-sources.jar", path=sources_jar_path, time=self.arc_time)) aar_file_tree.add_file(NodeInArchive.from_fs(arcpath="classes-doc.jar", path=doc_jar_path, time=self.arc_time)) assert ("jars" in self.release_info["android"] and java_jars_added) or "jars" not in self.release_info["android"], "Must have archived java JAR archives" aar_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["android"]["aar-files"], file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) aar_file_tree.add_file(NodeInArchive.from_text(arcpath="prefab/prefab.json", text=self._get_prefab_json_text(), time=self.arc_time)) aar_file_tree.add_file(NodeInArchive.from_text(arcpath="AndroidManifest.xml", text=self._get_android_manifest_text(), time=self.arc_time)) with Archiver(zip_path=aar_path) as archiver: aar_file_tree.add_to_archiver(archive_base="", archiver=archiver) archiver.add_git_hash(arcdir="", commit=self.commit, time=self.arc_time) android_devel_file_tree.add_file(NodeInArchive.from_fs(arcpath=aar_path.name, path=aar_path)) android_devel_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["android"]["files"], file_mapping_root=self.root, context=self.get_context(), time=self.arc_time) with Archiver(zip_path=android_dist_path) as archiver: android_devel_file_tree.add_to_archiver(archive_base="", archiver=archiver) archiver.add_git_hash(arcdir="", commit=self.commit, time=self.arc_time) self.artifacts[f"android-aar"] = android_dist_path def download_dependencies(self): shutil.rmtree(self.deps_path, ignore_errors=True) self.deps_path.mkdir(parents=True) if self.github: with open(os.environ["GITHUB_OUTPUT"], "a") as f: f.write(f"dep-path={self.deps_path.absolute()}\n") for dep, depinfo in self.release_info.get("dependencies", {}).items(): startswith = depinfo["startswith"] dep_repo = depinfo["repo"] # FIXME: dropped "--exclude-pre-releases" dep_string_data = self.executer.check_output(["gh", "-R", dep_repo, "release", "list", "--exclude-drafts", "--json", "name,createdAt,tagName", "--jq", f'[.[]|select(.name|startswith("{startswith}"))]|max_by(.createdAt)']).strip() dep_data = json.loads(dep_string_data) dep_tag = dep_data["tagName"] dep_version = dep_data["name"] logger.info("Download dependency %s version %s (tag=%s) ", dep, dep_version, dep_tag) self.executer.run(["gh", "-R", dep_repo, "release", "download", dep_tag], cwd=self.deps_path) if self.github: with open(os.environ["GITHUB_OUTPUT"], "a") as f: f.write(f"dep-{dep.lower()}-version={dep_version}\n") def verify_dependencies(self): for dep, depinfo in self.release_info.get("dependencies", {}).items(): if "mingw" in self.release_info: mingw_matches = glob.glob(self.release_info["mingw"]["dependencies"][dep]["artifact"], root_dir=self.deps_path) assert len(mingw_matches) == 1, f"Exactly one archive matches mingw {dep} dependency: {mingw_matches}" if "dmg" in self.release_info: dmg_matches = glob.glob(self.release_info["dmg"]["dependencies"][dep]["artifact"], root_dir=self.deps_path) assert len(dmg_matches) == 1, f"Exactly one archive matches dmg {dep} dependency: {dmg_matches}" if "msvc" in self.release_info: msvc_matches = glob.glob(self.release_info["msvc"]["dependencies"][dep]["artifact"], root_dir=self.deps_path) assert len(msvc_matches) == 1, f"Exactly one archive matches msvc {dep} dependency: {msvc_matches}" if "android" in self.release_info: android_matches = glob.glob(self.release_info["android"]["dependencies"][dep]["artifact"], root_dir=self.deps_path) assert len(android_matches) == 1, f"Exactly one archive matches msvc {dep} dependency: {android_matches}" @staticmethod def _arch_to_vs_platform(arch: str, configuration: str="Release") -> VsArchPlatformConfig: ARCH_TO_VS_PLATFORM = { "x86": VsArchPlatformConfig(arch="x86", platform="Win32", configuration=configuration), "x64": VsArchPlatformConfig(arch="x64", platform="x64", configuration=configuration), "arm64": VsArchPlatformConfig(arch="arm64", platform="ARM64", configuration=configuration), } return ARCH_TO_VS_PLATFORM[arch] def build_msvc(self): with self.section_printer.group("Find Visual Studio"): vs = VisualStudio(executer=self.executer) for arch in self.release_info["msvc"].get("msbuild", {}).get("archs", []): self._build_msvc_msbuild(arch_platform=self._arch_to_vs_platform(arch=arch), vs=vs) if "cmake" in self.release_info["msvc"]: deps_path = self.root / "msvc-deps" shutil.rmtree(deps_path, ignore_errors=True) dep_roots = [] for dep, depinfo in self.release_info["msvc"].get("dependencies", {}).items(): dep_extract_path = deps_path / f"extract-{dep}" msvc_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0] with zipfile.ZipFile(msvc_zip, "r") as zf: zf.extractall(dep_extract_path) contents_msvc_zip = glob.glob(str(dep_extract_path / "*")) assert len(contents_msvc_zip) == 1, f"There must be exactly one root item in the root directory of {dep}" dep_roots.append(contents_msvc_zip[0]) for arch in self.release_info["msvc"].get("cmake", {}).get("archs", []): self._build_msvc_cmake(arch_platform=self._arch_to_vs_platform(arch=arch), dep_roots=dep_roots) with self.section_printer.group("Create SDL VC development zip"): self._build_msvc_devel() def _build_msvc_msbuild(self, arch_platform: VsArchPlatformConfig, vs: VisualStudio): platform_context = self.get_context(arch_platform.extra_context()) for dep, depinfo in self.release_info["msvc"].get("dependencies", {}).items(): msvc_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0] src_globs = [configure_text(instr["src"], context=platform_context) for instr in depinfo["copy"]] with zipfile.ZipFile(msvc_zip, "r") as zf: for member in zf.namelist(): member_path = "/".join(Path(member).parts[1:]) for src_i, src_glob in enumerate(src_globs): if fnmatch.fnmatch(member_path, src_glob): dst = (self.root / configure_text(depinfo["copy"][src_i]["dst"], context=platform_context)).resolve() / Path(member_path).name zip_data = zf.read(member) if dst.exists(): identical = False if dst.is_file(): orig_bytes = dst.read_bytes() if orig_bytes == zip_data: identical = True if not identical: logger.warning("Extracting dependency %s, will cause %s to be overwritten", dep, dst) if not self.overwrite: raise RuntimeError("Run with --overwrite to allow overwriting") logger.debug("Extracting %s -> %s", member, dst) dst.parent.mkdir(exist_ok=True, parents=True) dst.write_bytes(zip_data) prebuilt_paths = set(self.root / full_prebuilt_path for prebuilt_path in self.release_info["msvc"]["msbuild"].get("prebuilt", []) for full_prebuilt_path in glob.glob(configure_text(prebuilt_path, context=platform_context), root_dir=self.root)) msbuild_paths = set(self.root / configure_text(f, context=platform_context) for file_mapping in (self.release_info["msvc"]["msbuild"]["files-lib"], self.release_info["msvc"]["msbuild"]["files-devel"]) for files_list in file_mapping.values() for f in files_list) assert prebuilt_paths.issubset(msbuild_paths), f"msvc.msbuild.prebuilt must be a subset of (msvc.msbuild.files-lib, msvc.msbuild.files-devel)" built_paths = msbuild_paths.difference(prebuilt_paths) logger.info("MSbuild builds these files, to be included in the package: %s", built_paths) if not self.fast: for b in built_paths: b.unlink(missing_ok=True) rel_projects: list[str] = self.release_info["msvc"]["msbuild"]["projects"] projects = list(self.root / p for p in rel_projects) directory_build_props_src_relpath = self.release_info["msvc"]["msbuild"].get("directory-build-props") for project in projects: dir_b_props = project.parent / "Directory.Build.props" dir_b_props.unlink(missing_ok = True) if directory_build_props_src_relpath: src = self.root / directory_build_props_src_relpath logger.debug("Copying %s -> %s", src, dir_b_props) shutil.copy(src=src, dst=dir_b_props) with self.section_printer.group(f"Build {arch_platform.arch} VS binary"): vs.build(arch_platform=arch_platform, projects=projects) if self.dry: for b in built_paths: b.parent.mkdir(parents=True, exist_ok=True) b.touch() for b in built_paths: assert b.is_file(), f"{b} has not been created" b.parent.mkdir(parents=True, exist_ok=True) b.touch() zip_path = self.dist_path / f"{self.project}-{self.version}-win32-{arch_platform.arch}.zip" zip_path.unlink(missing_ok=True) logger.info("Collecting files...") archive_file_tree = ArchiveFileTree() archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["msbuild"]["files-lib"], file_mapping_root=self.root, context=platform_context, time=self.arc_time) archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["files-lib"], file_mapping_root=self.root, context=platform_context, time=self.arc_time) logger.info("Writing to %s", zip_path) with Archiver(zip_path=zip_path) as archiver: arc_root = f"" archive_file_tree.add_to_archiver(archive_base=arc_root, archiver=archiver) archiver.add_git_hash(arcdir=arc_root, commit=self.commit, time=self.arc_time) self.artifacts[f"VC-{arch_platform.arch}"] = zip_path for p in built_paths: assert p.is_file(), f"{p} should exist" def _arch_platform_to_build_path(self, arch_platform: VsArchPlatformConfig) -> Path: return self.root / f"build-vs-{arch_platform.arch}" def _arch_platform_to_install_path(self, arch_platform: VsArchPlatformConfig) -> Path: return self._arch_platform_to_build_path(arch_platform) / "prefix" def _build_msvc_cmake(self, arch_platform: VsArchPlatformConfig, dep_roots: list[Path]): build_path = self._arch_platform_to_build_path(arch_platform) install_path = self._arch_platform_to_install_path(arch_platform) platform_context = self.get_context(extra_context=arch_platform.extra_context()) build_type = "Release" extra_context = { "ARCH": arch_platform.arch, "PLATFORM": arch_platform.platform, } built_paths = set(install_path / configure_text(f, context=platform_context) for file_mapping in (self.release_info["msvc"]["cmake"]["files-lib"], self.release_info["msvc"]["cmake"]["files-devel"]) for files_list in file_mapping.values() for f in files_list) logger.info("CMake builds these files, to be included in the package: %s", built_paths) if not self.fast: for b in built_paths: b.unlink(missing_ok=True) shutil.rmtree(install_path, ignore_errors=True) build_path.mkdir(parents=True, exist_ok=True) with self.section_printer.group(f"Configure VC CMake project for {arch_platform.arch}"): self.executer.run([ "cmake", "-S", str(self.root), "-B", str(build_path), "-A", arch_platform.platform, "-DCMAKE_INSTALL_BINDIR=bin", "-DCMAKE_INSTALL_DATAROOTDIR=share", "-DCMAKE_INSTALL_INCLUDEDIR=include", "-DCMAKE_INSTALL_LIBDIR=lib", f"-DCMAKE_BUILD_TYPE={build_type}", f"-DCMAKE_INSTALL_PREFIX={install_path}", # MSVC debug information format flags are selected by an abstraction "-DCMAKE_POLICY_DEFAULT_CMP0141=NEW", # MSVC debug information format "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase", # Linker flags for executables "-DCMAKE_EXE_LINKER_FLAGS=-INCREMENTAL:NO -DEBUG -OPT:REF -OPT:ICF", # Linker flag for shared libraries "-DCMAKE_SHARED_LINKER_FLAGS=-INCREMENTAL:NO -DEBUG -OPT:REF -OPT:ICF", # MSVC runtime library flags are selected by an abstraction "-DCMAKE_POLICY_DEFAULT_CMP0091=NEW", # Use statically linked runtime (-MT) (ideally, should be "MultiThreaded$<$:Debug>") "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", f"-DCMAKE_PREFIX_PATH={';'.join(str(s) for s in dep_roots)}", ] + self.release_info["msvc"]["cmake"]["args"] + ([] if self.fast else ["--fresh"])) with self.section_printer.group(f"Build VC CMake project for {arch_platform.arch}"): self.executer.run(["cmake", "--build", str(build_path), "--verbose", "--config", build_type]) with self.section_printer.group(f"Install VC CMake project for {arch_platform.arch}"): self.executer.run(["cmake", "--install", str(build_path), "--config", build_type]) if self.dry: for b in built_paths: b.parent.mkdir(parents=True, exist_ok=True) b.touch() zip_path = self.dist_path / f"{self.project}-{self.version}-win32-{arch_platform.arch}.zip" zip_path.unlink(missing_ok=True) logger.info("Collecting files...") archive_file_tree = ArchiveFileTree() archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["cmake"]["files-lib"], file_mapping_root=install_path, context=platform_context, time=self.arc_time) archive_file_tree.add_file_mapping(arc_dir="", file_mapping=self.release_info["msvc"]["files-lib"], file_mapping_root=self.root, context=self.get_context(extra_context=extra_context), time=self.arc_time) logger.info("Creating %s", zip_path) with Archiver(zip_path=zip_path) as archiver: arc_root = f"" archive_file_tree.add_to_archiver(archive_base=arc_root, archiver=archiver) archiver.add_git_hash(arcdir=arc_root, commit=self.commit, time=self.arc_time) for p in built_paths: assert p.is_file(), f"{p} should exist" def _build_msvc_devel(self) -> None: zip_path = self.dist_path / f"{self.project}-devel-{self.version}-VC.zip" arc_root = f"{self.project}-{self.version}" def copy_files_devel(ctx): archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["msvc"]["files-devel"], file_mapping_root=self.root, context=ctx, time=self.arc_time) logger.info("Collecting files...") archive_file_tree = ArchiveFileTree() if "msbuild" in self.release_info["msvc"]: for arch in self.release_info["msvc"]["msbuild"]["archs"]: arch_platform = self._arch_to_vs_platform(arch=arch) platform_context = self.get_context(arch_platform.extra_context()) archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["msvc"]["msbuild"]["files-devel"], file_mapping_root=self.root, context=platform_context, time=self.arc_time) copy_files_devel(ctx=platform_context) if "cmake" in self.release_info["msvc"]: for arch in self.release_info["msvc"]["cmake"]["archs"]: arch_platform = self._arch_to_vs_platform(arch=arch) platform_context = self.get_context(arch_platform.extra_context()) archive_file_tree.add_file_mapping(arc_dir=arc_root, file_mapping=self.release_info["msvc"]["cmake"]["files-devel"], file_mapping_root=self._arch_platform_to_install_path(arch_platform), context=platform_context, time=self.arc_time) copy_files_devel(ctx=platform_context) with Archiver(zip_path=zip_path) as archiver: archive_file_tree.add_to_archiver(archive_base="", archiver=archiver) archiver.add_git_hash(arcdir=arc_root, commit=self.commit, time=self.arc_time) self.artifacts["VC-devel"] = zip_path @classmethod def extract_sdl_version(cls, root: Path, release_info: dict) -> str: with open(root / release_info["version"]["file"], "r") as f: text = f.read() major = next(re.finditer(release_info["version"]["re_major"], text, flags=re.M)).group(1) minor = next(re.finditer(release_info["version"]["re_minor"], text, flags=re.M)).group(1) micro = next(re.finditer(release_info["version"]["re_micro"], text, flags=re.M)).group(1) return f"{major}.{minor}.{micro}" def main(argv=None) -> int: if sys.version_info < (3, 11): logger.error("This script needs at least python 3.11") return 1 parser = argparse.ArgumentParser(allow_abbrev=False, description="Create SDL release artifacts") parser.add_argument("--root", metavar="DIR", type=Path, default=Path(__file__).absolute().parents[1], help="Root of project") parser.add_argument("--release-info", metavar="JSON", dest="path_release_info", type=Path, default=Path(__file__).absolute().parent / "release-info.json", help="Path of release-info.json") parser.add_argument("--dependency-folder", metavar="FOLDER", dest="deps_path", type=Path, default="deps", help="Directory containing pre-built archives of dependencies (will be removed when downloading archives)") parser.add_argument("--out", "-o", metavar="DIR", dest="dist_path", type=Path, default="dist", help="Output directory") parser.add_argument("--github", action="store_true", help="Script is running on a GitHub runner") parser.add_argument("--commit", default="HEAD", help="Git commit/tag of which a release should be created") parser.add_argument("--actions", choices=["download", "source", "android", "mingw", "msvc", "dmg"], required=True, nargs="+", dest="actions", help="What to do?") parser.set_defaults(loglevel=logging.INFO) parser.add_argument('--vs-year', dest="vs_year", help="Visual Studio year") parser.add_argument('--android-api', dest="android_api", help="Android API version") parser.add_argument('--android-home', dest="android_home", default=os.environ.get("ANDROID_HOME"), help="Android Home folder") parser.add_argument('--android-ndk-home', dest="android_ndk_home", default=os.environ.get("ANDROID_NDK_HOME"), help="Android NDK Home folder") parser.add_argument('--cmake-generator', dest="cmake_generator", default="Ninja", help="CMake Generator") parser.add_argument('--debug', action='store_const', const=logging.DEBUG, dest="loglevel", help="Print script debug information") parser.add_argument('--dry-run', action='store_true', dest="dry", help="Don't execute anything") parser.add_argument('--force', action='store_true', dest="force", help="Ignore a non-clean git tree") parser.add_argument('--overwrite', action='store_true', dest="overwrite", help="Allow potentially overwriting other projects") parser.add_argument('--fast', action='store_true', dest="fast", help="Don't do a rebuild") args = parser.parse_args(argv) logging.basicConfig(level=args.loglevel, format='[%(levelname)s] %(message)s') args.deps_path = args.deps_path.absolute() args.dist_path = args.dist_path.absolute() args.root = args.root.absolute() args.dist_path = args.dist_path.absolute() if args.dry: args.dist_path = args.dist_path / "dry" if args.github: section_printer: SectionPrinter = GitHubSectionPrinter() else: section_printer = SectionPrinter() if args.github and "GITHUB_OUTPUT" not in os.environ: os.environ["GITHUB_OUTPUT"] = "/tmp/github_output.txt" executer = Executer(root=args.root, dry=args.dry) root_git_hash_path = args.root / GIT_HASH_FILENAME root_is_maybe_archive = root_git_hash_path.is_file() if root_is_maybe_archive: logger.warning("%s detected: Building from archive", GIT_HASH_FILENAME) archive_commit = root_git_hash_path.read_text().strip() if args.commit != archive_commit: logger.warning("Commit argument is %s, but archive commit is %s. Using %s.", args.commit, archive_commit, archive_commit) args.commit = archive_commit revision = (args.root / REVISION_TXT).read_text().strip() else: args.commit = executer.check_output(["git", "rev-parse", args.commit], dry_out="e5812a9fd2cda317b503325a702ba3c1c37861d9").strip() revision = executer.check_output(["git", "describe", "--always", "--tags", "--long", args.commit], dry_out="preview-3.1.3-96-g9512f2144").strip() logger.info("Using commit %s", args.commit) try: with args.path_release_info.open() as f: release_info = json.load(f) except FileNotFoundError: logger.error(f"Could not find {args.path_release_info}") releaser = Releaser( release_info=release_info, commit=args.commit, revision=revision, root=args.root, dist_path=args.dist_path, executer=executer, section_printer=section_printer, cmake_generator=args.cmake_generator, deps_path=args.deps_path, overwrite=args.overwrite, github=args.github, fast=args.fast, ) if root_is_maybe_archive: logger.warning("Building from archive. Skipping clean git tree check.") else: porcelain_status = executer.check_output(["git", "status", "--ignored", "--porcelain"], dry_out="\n").strip() if porcelain_status: print(porcelain_status) logger.warning("The tree is dirty! Do not publish any generated artifacts!") if not args.force: raise Exception("The git repo contains modified and/or non-committed files. Run with --force to ignore.") if args.fast: logger.warning("Doing fast build! Do not publish generated artifacts!") with section_printer.group("Arguments"): print(f"project = {releaser.project}") print(f"version = {releaser.version}") print(f"revision = {revision}") print(f"commit = {args.commit}") print(f"out = {args.dist_path}") print(f"actions = {args.actions}") print(f"dry = {args.dry}") print(f"force = {args.force}") print(f"overwrite = {args.overwrite}") print(f"cmake_generator = {args.cmake_generator}") releaser.prepare() if "download" in args.actions: releaser.download_dependencies() if set(args.actions).intersection({"msvc", "mingw", "android"}): print("Verifying presence of dependencies (run 'download' action to download) ...") releaser.verify_dependencies() print("... done") if "source" in args.actions: if root_is_maybe_archive: raise Exception("Cannot build source archive from source archive") with section_printer.group("Create source archives"): releaser.create_source_archives() if "dmg" in args.actions: if platform.system() != "Darwin" and not args.dry: parser.error("framework artifact(s) can only be built on Darwin") releaser.create_dmg() if "msvc" in args.actions: if platform.system() != "Windows" and not args.dry: parser.error("msvc artifact(s) can only be built on Windows") releaser.build_msvc() if "mingw" in args.actions: releaser.create_mingw_archives() if "android" in args.actions: if args.android_home is None or not Path(args.android_home).is_dir(): parser.error("Invalid $ANDROID_HOME or --android-home: must be a directory containing the Android SDK") if args.android_ndk_home is None or not Path(args.android_ndk_home).is_dir(): parser.error("Invalid $ANDROID_NDK_HOME or --android_ndk_home: must be a directory containing the Android NDK") if args.android_api is None: with section_printer.group("Detect Android APIS"): args.android_api = releaser._detect_android_api(android_home=args.android_home) else: try: android_api_ints = tuple(int(v) for v in args.android_api.split(".")) match len(android_api_ints): case 1: android_api_name = f"android-{android_api_ints[0]}" case 2: android_api_name = f"android-{android_api_ints[0]}-ext-{android_api_ints[1]}" case _: raise ValueError except ValueError: logger.error("Invalid --android-api, must be a 'X' or 'X.Y' version") args.android_api = AndroidApiVersion(ints=android_api_ints, name=android_api_name) if args.android_api is None: parser.error("Invalid --android-api, and/or could not be detected") android_api_path = Path(args.android_home) / f"platforms/{args.android_api.name}" if not android_api_path.is_dir(): parser.error(f"Android API directory does not exist ({android_api_path})") with section_printer.group("Android arguments"): print(f"android_home = {args.android_home}") print(f"android_ndk_home = {args.android_ndk_home}") print(f"android_api = {args.android_api}") releaser.create_android_archives( android_api=args.android_api.ints[0], android_home=args.android_home, android_ndk_home=args.android_ndk_home, ) with section_printer.group("Summary"): print(f"artifacts = {releaser.artifacts}") if args.github: with open(os.environ["GITHUB_OUTPUT"], "a") as f: f.write(f"project={releaser.project}\n") f.write(f"version={releaser.version}\n") for k, v in releaser.artifacts.items(): f.write(f"{k}={v.name}\n") return 0 if __name__ == "__main__": raise SystemExit(main()) libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/cmake-toolchain-mingw64-i686.cmake000066400000000000000000000010331501405355700300150ustar00rootroot00000000000000set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86) find_program(CMAKE_C_COMPILER NAMES i686-w64-mingw32-gcc) find_program(CMAKE_CXX_COMPILER NAMES i686-w64-mingw32-g++) find_program(CMAKE_RC_COMPILER NAMES i686-w64-mingw32-windres windres) if(NOT CMAKE_C_COMPILER) message(FATAL_ERROR "Failed to find CMAKE_C_COMPILER.") endif() if(NOT CMAKE_CXX_COMPILER) message(FATAL_ERROR "Failed to find CMAKE_CXX_COMPILER.") endif() if(NOT CMAKE_RC_COMPILER) message(FATAL_ERROR "Failed to find CMAKE_RC_COMPILER.") endif() libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/cmake-toolchain-mingw64-x86_64.cmake000066400000000000000000000010441501405355700302610ustar00rootroot00000000000000set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) find_program(CMAKE_C_COMPILER NAMES x86_64-w64-mingw32-gcc) find_program(CMAKE_CXX_COMPILER NAMES x86_64-w64-mingw32-g++) find_program(CMAKE_RC_COMPILER NAMES x86_64-w64-mingw32-windres windres) if(NOT CMAKE_C_COMPILER) message(FATAL_ERROR "Failed to find CMAKE_C_COMPILER.") endif() if(NOT CMAKE_CXX_COMPILER) message(FATAL_ERROR "Failed to find CMAKE_CXX_COMPILER.") endif() if(NOT CMAKE_RC_COMPILER) message(FATAL_ERROR "Failed to find CMAKE_RC_COMPILER.") endif() libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/create-release.py000077500000000000000000000027211501405355700252350ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse from pathlib import Path import json import logging import re import subprocess ROOT = Path(__file__).resolve().parents[1] def determine_remote() -> str: text = (ROOT / "build-scripts/release-info.json").read_text() release_info = json.loads(text) if "remote" in release_info: return release_info["remote"] project_with_version = release_info["name"] project, _ = re.subn("([^a-zA-Z_])", "", project_with_version) return f"libsdl-org/{project}" def main(): default_remote = determine_remote() parser = argparse.ArgumentParser(allow_abbrev=False) parser.add_argument("--ref", required=True, help=f"Name of branch or tag containing release.yml") parser.add_argument("--remote", "-R", default=default_remote, help=f"Remote repo (default={default_remote})") parser.add_argument("--commit", help=f"Input 'commit' of release.yml (default is the hash of the ref)") args = parser.parse_args() if args.commit is None: args.commit = subprocess.check_output(["git", "rev-parse", args.ref], cwd=ROOT, text=True).strip() print(f"Running release.yml workflow:") print(f" remote = {args.remote}") print(f" ref = {args.ref}") print(f" commit = {args.commit}") subprocess.check_call(["gh", "-R", args.remote, "workflow", "run", "release.yml", "--ref", args.ref, "-f", f"commit={args.commit}"], cwd=ROOT) if __name__ == "__main__": raise SystemExit(main()) libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/000077500000000000000000000000001501405355700242705ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/000077500000000000000000000000001501405355700257105ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/README.md.in000066400000000000000000000044161501405355700276010ustar00rootroot00000000000000 The Simple DirectMedia Layer (SDL for short) is a cross-platform library designed to make it easy to write multi-media software, such as games and emulators. The Simple DirectMedia Layer library source code is available from: https://www.libsdl.org/ This library is distributed under the terms of the zlib license: http://www.zlib.net/zlib_license.html # @<@PROJECT_NAME@>@-@<@PROJECT_VERSION@>@.aar This Android archive allows use of @<@PROJECT_NAME@>@ in your Android project, without needing to copy any SDL source. ## Gradle integration For integration with CMake/ndk-build, it uses [prefab](https://google.github.io/prefab/). Copy the aar archive (@<@PROJECT_NAME@>@-@<@PROJECT_VERSION@>@.aar) to a `app/libs` directory of your project. In `app/build.gradle` of your Android project, add: ``` android { /* ... */ buildFeatures { prefab true } } dependencies { implementation files('libs/@<@PROJECT_NAME@>@-@<@PROJECT_VERSION@>@.aar') /* ... */ } ``` If you're using CMake, add the following to your CMakeLists.txt: ``` find_package(@<@PROJECT_NAME@>@ REQUIRED CONFIG) target_link_libraries(yourgame PRIVATE @<@PROJECT_NAME@>@::@<@PROJECT_NAME@>@) ``` If you use ndk-build, add the following before `include $(BUILD_SHARED_LIBRARY)` to your `Android.mk`: ``` LOCAL_SHARED_LIBARARIES := @<@PROJECT_NAME@>@ ``` And add the following at the bottom: ``` # https://google.github.io/prefab/build-systems.html # Add the prefab modules to the import path. $(call import-add-path,/out) # Import @<@PROJECT_NAME@>@ so we can depend on it. $(call import-module,prefab/@<@PROJECT_NAME@>@) ``` --- ## Other build systems (advanced) If you want to build a project without Gradle, running the following command will extract the Android archive into a more common directory structure. ``` python @<@PROJECT_NAME@>@-@<@PROJECT_VERSION@>@.aar -o android_prefix ``` Add `--help` for a list of all available options. Look at the example programs in ./examples (of the source archive), and check out online documentation: https://wiki.libsdl.org/SDL3/FrontPage Join the SDL discourse server if you want to join the community: https://discourse.libsdl.org/ That's it! Sam Lantinga libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/aar/000077500000000000000000000000001501405355700264535ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/aar/__main__.py.in000077500000000000000000000105221501405355700311550ustar00rootroot00000000000000#!/usr/bin/env python """ Create a @<@PROJECT_NAME@>@ SDK prefix from an Android archive This file is meant to be placed in a the root of an android .aar archive Example usage: ```sh python @<@PROJECT_NAME@>@-@<@PROJECT_VERSION@>@.aar -o /usr/opt/android-sdks cmake -S my-project \ -DCMAKE_PREFIX_PATH=/usr/opt/android-sdks \ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -B build-arm64 -DANDROID_ABI=arm64-v8a \ -DCMAKE_BUILD_TYPE=Releaase cmake --build build-arm64 ``` """ import argparse import io import json import os import pathlib import re import stat import zipfile AAR_PATH = pathlib.Path(__file__).resolve().parent ANDROID_ARCHS = { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" } def main(): parser = argparse.ArgumentParser( description="Convert a @<@PROJECT_NAME@>@ Android .aar archive into a SDK", allow_abbrev=False, ) parser.add_argument("--version", action="version", version="@<@PROJECT_NAME@>@ @<@PROJECT_VERSION@>@") parser.add_argument("-o", dest="output", type=pathlib.Path, required=True, help="Folder where to store the SDK") args = parser.parse_args() print(f"Creating a @<@PROJECT_NAME@>@ SDK at {args.output}...") prefix = args.output incdir = prefix / "include" libdir = prefix / "lib" RE_LIB_MODULE_ARCH = re.compile(r"prefab/modules/(?P[A-Za-z0-9_-]+)/libs/android\.(?P[a-zA-Z0-9_-]+)/(?Plib[A-Za-z0-9_]+\.(?:so|a))") RE_INC_MODULE_ARCH = re.compile(r"prefab/modules/(?P[A-Za-z0-9_-]+)/include/(?P
[a-zA-Z0-9_./-]+)") RE_LICENSE = re.compile(r"(?:.*/)?(?P(?:license|copying)(?:\.md|\.txt)?)", flags=re.I) RE_PROGUARD = re.compile(r"(?:.*/)?(?Pproguard.*\.(?:pro|txt))", flags=re.I) RE_CMAKE = re.compile(r"(?:.*/)?(?P.*\.cmake)", flags=re.I) with zipfile.ZipFile(AAR_PATH) as zf: project_description = json.loads(zf.read("description.json")) project_name = project_description["name"] project_version = project_description["version"] licensedir = prefix / "share/licenses" / project_name cmakedir = libdir / "cmake" / project_name javadir = prefix / "share/java" / project_name javadocdir = prefix / "share/javadoc" / project_name def read_zipfile_and_write(path: pathlib.Path, zippath: str): data = zf.read(zippath) path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data) for zip_info in zf.infolist(): zippath = zip_info.filename if m := RE_LIB_MODULE_ARCH.match(zippath): lib_path = libdir / m["arch"] / m["filename"] read_zipfile_and_write(lib_path, zippath) if m["filename"].endswith(".so"): os.chmod(lib_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) elif m := RE_INC_MODULE_ARCH.match(zippath): header_path = incdir / m["header"] read_zipfile_and_write(header_path, zippath) elif m:= RE_LICENSE.match(zippath): license_path = licensedir / m["filename"] read_zipfile_and_write(license_path, zippath) elif m:= RE_PROGUARD.match(zippath): proguard_path = javadir / m["filename"] read_zipfile_and_write(proguard_path, zippath) elif m:= RE_CMAKE.match(zippath): cmake_path = cmakedir / m["filename"] read_zipfile_and_write(cmake_path, zippath) elif zippath == "classes.jar": versioned_jar_path = javadir / f"{project_name}-{project_version}.jar" unversioned_jar_path = javadir / f"{project_name}.jar" read_zipfile_and_write(versioned_jar_path, zippath) os.symlink(src=versioned_jar_path.name, dst=unversioned_jar_path) elif zippath == "classes-sources.jar": jarpath = javadir / f"{project_name}-{project_version}-sources.jar" read_zipfile_and_write(jarpath, zippath) elif zippath == "classes-doc.jar": jarpath = javadocdir / f"{project_name}-{project_version}-javadoc.jar" read_zipfile_and_write(jarpath, zippath) print("... done") return 0 if __name__ == "__main__": raise SystemExit(main()) libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/aar/cmake/000077500000000000000000000000001501405355700275335ustar00rootroot00000000000000SDL3_mixerConfig.cmake000066400000000000000000000105551501405355700335230ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/aar/cmake# SDL CMake configuration file: # This file is meant to be placed in lib/cmake/SDL3_mixer subfolder of a reconstructed Android SDL3_mixer SDK cmake_minimum_required(VERSION 3.0...3.28) include(FeatureSummary) set_package_properties(SDL3_mixer PROPERTIES URL "https://www.libsdl.org/projects/SDL_mixer/" DESCRIPTION "SDL_mixer is a sample multi-channel audio mixer library" ) # Copied from `configure_package_config_file` macro(set_and_check _var _file) set(${_var} "${_file}") if(NOT EXISTS "${_file}") message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") endif() endmacro() # Copied from `configure_package_config_file` macro(check_required_components _NAME) foreach(comp ${${_NAME}_FIND_COMPONENTS}) if(NOT ${_NAME}_${comp}_FOUND) if(${_NAME}_FIND_REQUIRED_${comp}) set(${_NAME}_FOUND FALSE) endif() endif() endforeach() endmacro() set(SDL3_mixer_FOUND TRUE) set(SDLMIXER_VENDORED TRUE) set(SDLMIXER_FLAC_LIBFLAC FALSE) set(SDLMIXER_FLAC_DRFLAC TRUE) set(SDLMIXER_GME FALSE) set(SDLMIXER_MOD FALSE) set(SDLMIXER_MOD_XMP FALSE) set(SDLMIXER_MOD_XMP_LITE FALSE) set(SDLMIXER_MP3 TRUE) set(SDLMIXER_MP3_DRMP3 TRUE) set(SDLMIXER_MP3_MPG123 FALSE) set(SDLMIXER_MIDI FALSE) set(SDLMIXER_MIDI_FLUIDSYNTH FALSE) set(SDLMIXER_MIDI_NATIVE FALSE) set(SDLMIXER_MIDI_TIMIDITY TRUE) set(SDLMIXER_OPUS FALSE) set(SDLMIXER_VORBIS STB) set(SDLMIXER_VORBIS_STB TRUE) set(SDLMIXER_VORBIS_TREMOR FALSE) set(SDLMIXER_VORBIS_VORBISFILE FALSE) set(SDLMIXER_WAVE TRUE) set(SDL3_mixer_FOUND TRUE) if(SDL_CPU_X86) set(_sdl_arch_subdir "x86") elseif(SDL_CPU_X64) set(_sdl_arch_subdir "x86_64") elseif(SDL_CPU_ARM32) set(_sdl_arch_subdir "armeabi-v7a") elseif(SDL_CPU_ARM64) set(_sdl_arch_subdir "arm64-v8a") else() set(SDL3_mixer_FOUND FALSE) return() endif() get_filename_component(_sdl3mixer_prefix "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) get_filename_component(_sdl3mixer_prefix "${_sdl3mixer_prefix}/.." ABSOLUTE) get_filename_component(_sdl3mixer_prefix "${_sdl3mixer_prefix}/.." ABSOLUTE) set_and_check(_sdl3mixer_prefix "${_sdl3mixer_prefix}") set_and_check(_sdl3mixer_include_dirs "${_sdl3mixer_prefix}/include") set_and_check(_sdl3mixer_lib "${_sdl3mixer_prefix}/lib/${_sdl_arch_subdir}/libSDL3_mixer.so") unset(_sdl_arch_subdir) unset(_sdl3mixer_prefix) # All targets are created, even when some might not be requested though COMPONENTS. # This is done for compatibility with CMake generated SDL3_mixer-target.cmake files. set(SDL3_mixer_SDL3_mixer-shared_FOUND FALSE) if(EXISTS "${_sdl3mixer_lib}") if(NOT TARGET SDL3_mixer::SDL3_mixer-shared) add_library(SDL3_mixer::SDL3_mixer-shared SHARED IMPORTED) set_target_properties(SDL3_mixer::SDL3_mixer-shared PROPERTIES IMPORTED_LOCATION "${_sdl3mixer_lib}" INTERFACE_INCLUDE_DIRECTORIES "${_sdl3mixer_include_dirs}" COMPATIBLE_INTERFACE_BOOL "SDL3_SHARED" INTERFACE_SDL3_SHARED "ON" COMPATIBLE_INTERFACE_STRING "SDL_VERSION" INTERFACE_SDL_VERSION "SDL3" ) endif() set(SDL3_mixer_SDL3_mixer-shared_FOUND TRUE) endif() unset(_sdl3mixer_include_dirs) unset(_sdl3mixer_lib) set(SDL3_mixer_SDL3_mixer-static_FOUND FALSE) if(SDL3_mixer_SDL3_mixer-shared_FOUND) set(SDL3_mixer_SDL3_mixer_FOUND TRUE) endif() function(_sdl_create_target_alias_compat NEW_TARGET TARGET) if(CMAKE_VERSION VERSION_LESS "3.18") # Aliasing local targets is not supported on CMake < 3.18, so make it global. add_library(${NEW_TARGET} INTERFACE IMPORTED) set_target_properties(${NEW_TARGET} PROPERTIES INTERFACE_LINK_LIBRARIES "${TARGET}") else() add_library(${NEW_TARGET} ALIAS ${TARGET}) endif() endfunction() # Make sure SDL3_mixer::SDL3_mixer always exists if(NOT TARGET SDL3_mixer::SDL3_mixer) if(TARGET SDL3_mixer::SDL3_mixer-shared) _sdl_create_target_alias_compat(SDL3_mixer::SDL3_mixer SDL3_mixer::SDL3_mixer-shared) endif() endif() check_required_components(SDL3_mixer) SDL3_mixerConfigVersion.cmake.in000066400000000000000000000031651501405355700354750ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/aar/cmake# SDL3_mixer CMake version configuration file: # This file is meant to be placed in a lib/cmake/SDL3_mixer subfolder of a reconstructed Android SDL3_mixer SDK set(PACKAGE_VERSION "@<@PROJECT_VERSION@>@") if(PACKAGE_FIND_VERSION_RANGE) # Package version must be in the requested version range if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN) OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX) OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX))) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() set(PACKAGE_VERSION_COMPATIBLE TRUE) endif() else() if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() set(PACKAGE_VERSION_COMPATIBLE TRUE) if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif() endif() endif() # if the using project doesn't have CMAKE_SIZEOF_VOID_P set, fail. if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "") set(PACKAGE_VERSION_UNSUITABLE TRUE) endif() include("${CMAKE_CURRENT_LIST_DIR}/sdlcpu.cmake") SDL_DetectTargetCPUArchitectures(_detected_archs) # check that the installed version has a compatible architecture as the one which is currently searching: if(NOT(SDL_CPU_X86 OR SDL_CPU_X64 OR SDL_CPU_ARM32 OR SDL_CPU_ARM64)) set(PACKAGE_VERSION "${PACKAGE_VERSION} (X86,X64,ARM32,ARM64)") set(PACKAGE_VERSION_UNSUITABLE TRUE) endif() libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/android/aar/description.json.in000066400000000000000000000001651501405355700323000ustar00rootroot00000000000000{ "name": "@<@PROJECT_NAME@>@", "version": "@<@PROJECT_VERSION@>@", "git-hash": "@<@PROJECT_COMMIT@>@" } libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/mingw/000077500000000000000000000000001501405355700254115ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/mingw/Makefile000066400000000000000000000020461501405355700270530ustar00rootroot00000000000000# # Makefile for installing the mingw32 version of the SDL_mixer library DESTDIR = /usr/local ARCHITECTURES := i686-w64-mingw32 x86_64-w64-mingw32 default: @echo "Run \"make install-i686\" to install 32-bit" @echo "Run \"make install-x86_64\" to install 64-bit" @echo "Run \"make install-all\" to install both" @echo "Add DESTDIR=/custom/path to change the destination folder" install: @if test -d $(ARCH) && test -d $(DESTDIR); then \ (cd $(ARCH) && cp -rv bin include lib share $(DESTDIR)/); \ else \ echo "*** ERROR: $(ARCH) or $(DESTDIR) does not exist!"; \ exit 1; \ fi install-i686: $(MAKE) install ARCH=i686-w64-mingw32 install-x86_64: $(MAKE) install ARCH=x86_64-w64-mingw32 install-all: @if test -d $(DESTDIR); then \ mkdir -p $(DESTDIR)/cmake; \ cp -rv cmake/* $(DESTDIR)/cmake; \ for arch in $(ARCHITECTURES); do \ $(MAKE) install ARCH=$$arch DESTDIR=$(DESTDIR)/$$arch; \ done \ else \ echo "*** ERROR: $(DESTDIR) does not exist!"; \ exit 1; \ fi .PHONY: default install install-i686 install-x86_64 install-all libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/mingw/cmake/000077500000000000000000000000001501405355700264715ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/mingw/cmake/SDL3_mixerConfig.cmake000066400000000000000000000014141501405355700325320ustar00rootroot00000000000000# SDL3_mixer CMake configuration file: # This file is meant to be placed in a cmake subfolder of SDL3_mixer-devel-3.x.y-mingw if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL3_mixer/SDL3_mixerConfig.cmake") elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL3_mixer/SDL3_mixerConfig.cmake") else("${CMAKE_SIZEOF_VOID_P}" STREQUAL "") set(SDL3_mixer_FOUND FALSE) return() endif() if(NOT EXISTS "${sdl3_mixer_config_path}") message(WARNING "${sdl3_mixer_config_path} does not exist: MinGW development package is corrupted") set(SDL3_mixer_FOUND FALSE) return() endif() include("${sdl3_mixer_config_path}") SDL3_mixerConfigVersion.cmake000066400000000000000000000014641501405355700340260ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/mingw/cmake# SDL3_mixer CMake version configuration file: # This file is meant to be placed in a cmake subfolder of SDL3_mixer-devel-3.x.y-mingw if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL3_mixer/SDL3_mixerConfigVersion.cmake") elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL3_mixer/SDL3_mixerConfigVersion.cmake") else("${CMAKE_SIZEOF_VOID_P}" STREQUAL "") set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() if(NOT EXISTS "${sdl3_mixer_config_path}") message(WARNING "${sdl3_mixer_config_path} does not exist: MinGW development package is corrupted") set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() include("${sdl3_mixer_config_path}") libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/msvc/000077500000000000000000000000001501405355700252405ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/msvc/cmake/000077500000000000000000000000001501405355700263205ustar00rootroot00000000000000SDL3_mixerConfig.cmake.in000066400000000000000000000073421501405355700327150ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/msvc/cmake# @<@PROJECT_NAME@>@ CMake configuration file: # This file is meant to be placed in a cmake subfolder of @<@PROJECT_NAME@>@-devel-@<@PROJECT_VERSION@>@-VC.zip include(FeatureSummary) set_package_properties(SDL3_mixer PROPERTIES URL "https://www.libsdl.org/projects/SDL_mixer/" DESCRIPTION "SDL_mixer is a sample multi-channel audio mixer library" ) cmake_minimum_required(VERSION 3.0...3.28) # Copied from `configure_package_config_file` macro(check_required_components _NAME) foreach(comp ${${_NAME}_FIND_COMPONENTS}) if(NOT ${_NAME}_${comp}_FOUND) if(${_NAME}_FIND_REQUIRED_${comp}) set(${_NAME}_FOUND FALSE) endif() endif() endforeach() endmacro() set(SDL3_mixer_FOUND TRUE) set(SDLMIXER_VENDORED TRUE) set(SDLMIXER_FLAC_LIBFLAC FALSE) set(SDLMIXER_FLAC_DRFLAC TRUE) set(SDLMIXER_GME FALSE) set(SDLMIXER_MOD TRUE) set(SDLMIXER_MOD_XMP TRUE) set(SDLMIXER_MOD_XMP_LITE FALSE) set(SDLMIXER_MP3 TRUE) set(SDLMIXER_MP3_DRMP3 TRUE) set(SDLMIXER_MP3_MPG123 FALSE) set(SDLMIXER_MIDI TRUE) set(SDLMIXER_MIDI_FLUIDSYNTH FALSE) set(SDLMIXER_MIDI_NATIVE TRUE) set(SDLMIXER_MIDI_TIMIDITY TRUE) set(SDLMIXER_OPUS TRUE) set(SDLMIXER_VORBIS STB) set(SDLMIXER_VORBIS_STB TRUE) set(SDLMIXER_VORBIS_TREMOR FALSE) set(SDLMIXER_VORBIS_VORBISFILE FALSE) set(SDLMIXER_WAVE TRUE) if(SDL_CPU_X86) set(_sdl3_mixer_arch_subdir "x86") elseif(SDL_CPU_X64 OR SDL_CPU_ARM64EC) set(_sdl3_mixer_arch_subdir "x64") elseif(SDL_CPU_ARM64) set(_sdl3_mixer_arch_subdir "arm64") else() set(SDL3_mixer_FOUND FALSE) return() endif() set(_sdl3_mixer_incdir "${CMAKE_CURRENT_LIST_DIR}/../include") set(_sdl3_mixer_library "${CMAKE_CURRENT_LIST_DIR}/../lib/${_sdl3_mixer_arch_subdir}/SDL3_mixer.lib") set(_sdl3_mixer_dll "${CMAKE_CURRENT_LIST_DIR}/../lib/${_sdl3_mixer_arch_subdir}/SDL3_mixer.dll") # All targets are created, even when some might not be requested though COMPONENTS. # This is done for compatibility with CMake generated SDL3_mixer-target.cmake files. set(SDL3_mixer_SDL3_mixer-shared_FOUND TRUE) if(NOT TARGET SDL3_mixer::SDL3_mixer-shared) add_library(SDL3_mixer::SDL3_mixer-shared SHARED IMPORTED) set_target_properties(SDL3_mixer::SDL3_mixer-shared PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_sdl3_mixer_incdir}" IMPORTED_IMPLIB "${_sdl3_mixer_library}" IMPORTED_LOCATION "${_sdl3_mixer_dll}" COMPATIBLE_INTERFACE_BOOL "SDL3_SHARED" INTERFACE_SDL3_SHARED "ON" ) endif() set(SDL3_mixer_SDL3_mixer-static_FOUND FALSE) if(SDL3_mixer_SDL3_mixer-shared_FOUND OR SDL3_mixer_SDL3_mixer-static_FOUND) set(SDL3_mixer_SDL3_mixer_FOUND TRUE) endif() function(_sdl_create_target_alias_compat NEW_TARGET TARGET) if(CMAKE_VERSION VERSION_LESS "3.18") # Aliasing local targets is not supported on CMake < 3.18, so make it global. add_library(${NEW_TARGET} INTERFACE IMPORTED) set_target_properties(${NEW_TARGET} PROPERTIES INTERFACE_LINK_LIBRARIES "${TARGET}") else() add_library(${NEW_TARGET} ALIAS ${TARGET}) endif() endfunction() # Make sure SDL3_mixer::SDL3_mixer always exists if(NOT TARGET SDL3_mixer::SDL3_mixer) if(TARGET SDL3_mixer::SDL3_mixer-shared) _sdl_create_target_alias_compat(SDL3_mixer::SDL3_mixer SDL3_mixer::SDL3_mixer-shared) endif() endif() unset(_sdl3_mixer_arch_subdir) unset(_sdl3_mixer_incdir) unset(_sdl3_mixer_library) unset(_sdl3_mixer_dll) check_required_components(SDL3_mixer) SDL3_mixerConfigVersion.cmake.in000066400000000000000000000031021501405355700342510ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/pkg-support/msvc/cmake# @<@PROJECT_NAME@>@ CMake version configuration file: # This file is meant to be placed in a cmake subfolder of @<@PROJECT_NAME@>@-devel-@<@PROJECT_VERSION@>@-VC.zip set(PACKAGE_VERSION "@<@PROJECT_VERSION@>@") include("${CMAKE_CURRENT_LIST_DIR}/sdlcpu.cmake") SDL_DetectTargetCPUArchitectures(_detected_archs) if(PACKAGE_FIND_VERSION_RANGE) # Package version must be in the requested version range if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN) OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX) OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX))) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() set(PACKAGE_VERSION_COMPATIBLE TRUE) endif() else() if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() set(PACKAGE_VERSION_COMPATIBLE TRUE) if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif() endif() endif() #include("${CMAKE_CURRENT_LIST_DIR}/sdlcpu.cmake") #SDL_DetectTargetCPUArchitectures(_detected_archs) # check that the installed version has a compatible architecture as the one which is currently searching: if(NOT(SDL_CPU_X86 OR SDL_CPU_X64 OR SDL_CPU_ARM64 OR SDL_CPU_ARM64EC)) set(PACKAGE_VERSION "${PACKAGE_VERSION} (X86,X64,ARM64)") set(PACKAGE_VERSION_UNSUITABLE TRUE) endif() libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/release-info.json000066400000000000000000000205441501405355700252460ustar00rootroot00000000000000{ "name": "SDL3_mixer", "remote": "libsdl-org/SDL_mixer", "dependencies": { "SDL": { "startswith": "3.", "repo": "libsdl-org/SDL" } }, "version": { "file": "include/SDL3_mixer/SDL_mixer.h", "re_major": "^#define SDL_MIXER_MAJOR_VERSION\\s+([0-9]+)$", "re_minor": "^#define SDL_MIXER_MINOR_VERSION\\s+([0-9]+)$", "re_micro": "^#define SDL_MIXER_MICRO_VERSION\\s+([0-9]+)$" }, "source": { "checks": [ "src/mixer.c", "include/SDL3_mixer/SDL_mixer.h", "examples/playmus.c" ] }, "dmg": { "project": "Xcode/SDL_mixer.xcodeproj", "path": "Xcode/build/SDL3_mixer.dmg", "scheme": "SDL3_mixer.dmg", "build-xcconfig": "Xcode/pkg-support/build.xcconfig", "dependencies": { "SDL": { "artifact": "SDL3-*.dmg" } } }, "mingw": { "cmake": { "archs": ["x86", "x64"], "args": [ "-DBUILD_SHARED_LIBS=ON", "-DSDLMIXER_SNDFILE=ON", "-DSDLMIXER_FLAC=ON", "-DSDLMIXER_FLAC_DRFLAC=ON", "-DSDLMIXER_GME=OFF", "-DSDLMIXER_MOD=OFF", "-DSDLMIXER_MOD_XMP=OFF", "-DSDLMIXER_MP3=ON", "-DSDLMIXER_MP3_DRMP3=ON", "-DSDLMIXER_MP3_MPG123=OFF", "-DSDLMIXER_MIDI=ON", "-DSDLMIXER_MIDI_NATIVE=ON", "-DSDLMIXER_OPUS=OFF", "-DSDLMIXER_VORBIS=STB", "-DSDLMIXER_WAVE=ON", "-DSDLMIXER_WAVPACK=OFF", "-DSDLMIXER_RELOCATABLE=ON", "-DSDLMIXER_SAMPLES=OFF", "-DSDLMIXER_VENDORED=ON" ], "shared-static": "args" }, "files": { "": [ "CHANGES.txt", "LICENSE.txt", "README.txt", "build-scripts/pkg-support/mingw/Makefile" ], "cmake": [ "build-scripts/pkg-support/mingw/cmake/SDL3_mixerConfig.cmake", "build-scripts/pkg-support/mingw/cmake/SDL3_mixerConfigVersion.cmake" ] }, "dependencies": { "SDL": { "artifact": "SDL3-devel-*-mingw.tar.gz", "install-command": "make install-@<@ARCH@>@ DESTDIR=@<@PREFIX@>@" } } }, "msvc": { "msbuild": { "archs": [ "x86", "x64" ], "projects": [ "VisualC/SDL_mixer.vcxproj" ], "prebuilt": [ "VisualC/external/optional/@<@ARCH@>@/*" ], "files-lib": { "": [ "VisualC/@<@PLATFORM@>@/@<@CONFIGURATION@>@/SDL3_mixer.dll" ], "optional": [ "VisualC/external/optional/@<@ARCH@>@/libgme.dll", "VisualC/external/optional/@<@ARCH@>@/libogg-0.dll", "VisualC/external/optional/@<@ARCH@>@/libopus-0.dll", "VisualC/external/optional/@<@ARCH@>@/libopusfile-0.dll", "VisualC/external/optional/@<@ARCH@>@/libwavpack-1.dll", "VisualC/external/optional/@<@ARCH@>@/libxmp.dll", "VisualC/external/optional/@<@ARCH@>@/LICENSE.gme.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.ogg-vorbis.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.opus.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.opusfile.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.wavpack.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.xmp.txt" ] }, "files-devel": { "lib/@<@ARCH@>@": [ "VisualC/@<@PLATFORM@>@/@<@CONFIGURATION@>@/SDL3_mixer.dll", "VisualC/@<@PLATFORM@>@/@<@CONFIGURATION@>@/SDL3_mixer.lib", "VisualC/@<@PLATFORM@>@/@<@CONFIGURATION@>@/SDL3_mixer.pdb" ], "lib/@<@ARCH@>@/optional": [ "VisualC/external/optional/@<@ARCH@>@/libgme.dll", "VisualC/external/optional/@<@ARCH@>@/libogg-0.dll", "VisualC/external/optional/@<@ARCH@>@/libopus-0.dll", "VisualC/external/optional/@<@ARCH@>@/libopusfile-0.dll", "VisualC/external/optional/@<@ARCH@>@/libwavpack-1.dll", "VisualC/external/optional/@<@ARCH@>@/libxmp.dll", "VisualC/external/optional/@<@ARCH@>@/LICENSE.gme.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.ogg-vorbis.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.opus.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.opusfile.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.wavpack.txt", "VisualC/external/optional/@<@ARCH@>@/LICENSE.xmp.txt" ] } }, "cmake": { "archs": [ "arm64" ], "args": [ "-DSDLMIXER_SNDFILE=ON", "-DSDLMIXER_FLAC=ON", "-DSDLMIXER_FLAC_DRFLAC=ON", "-DSDLMIXER_GME=ON", "-DSDLMIXER_MOD=ON", "-DSDLMIXER_MOD_XMP=ON", "-DSDLMIXER_MP3=ON", "-DSDLMIXER_MP3_DRMP3=ON", "-DSDLMIXER_MP3_MPG123=OFF", "-DSDLMIXER_MIDI=ON", "-DSDLMIXER_MIDI_NATIVE=ON", "-DSDLMIXER_OPUS=ON", "-DSDLMIXER_VORBIS=STB", "-DSDLMIXER_WAVE=ON", "-DSDLMIXER_WAVPACK=ON", "-DSDLMIXER_RELOCATABLE=ON", "-DSDLMIXER_SAMPLES=OFF", "-DSDLMIXER_DEPS_SHARED=ON", "-DSDLMIXER_VENDORED=ON" ], "files-lib": { "": [ "bin/SDL3_mixer.dll" ], "optional": [ "bin/gme.dll", "bin/libxmp.dll", "bin/ogg-0.dll", "bin/opus-0.dll", "bin/opusfile-0.dll", "bin/libwavpack-1.dll" ] }, "files-devel": { "lib/@<@ARCH@>@": [ "bin/SDL3_mixer.dll", "bin/SDL3_mixer.pdb", "lib/SDL3_mixer.lib" ], "lib/@<@ARCH@>@/optional": [ "bin/gme.dll", "bin/libxmp.dll", "bin/ogg-0.dll", "bin/opus-0.dll", "bin/opusfile-0.dll", "bin/libwavpack-1.dll" ] } }, "files-lib": { "": [ "README.txt" ] }, "files-devel": { "": [ "CHANGES.txt", "LICENSE.txt", "README.txt" ], "cmake": [ "build-scripts/pkg-support/msvc/cmake/SDL3_mixerConfig.cmake.in:SDL3_mixerConfig.cmake", "build-scripts/pkg-support/msvc/cmake/SDL3_mixerConfigVersion.cmake.in:SDL3_mixerConvigVersion.cmake", "cmake/sdlcpu.cmake" ], "include/SDL3_mixer": [ "include/SDL3_mixer/SDL_mixer.h" ] }, "dependencies": { "SDL": { "artifact": "SDL3-devel-*-VC.zip", "copy": [ { "src": "lib/@<@ARCH@>@/SDL3.*", "dst": "../SDL/VisualC/@<@PLATFORM@>@/@<@CONFIGURATION@>@" }, { "src": "include/SDL3/*", "dst": "../SDL/include/SDL3" } ] } } }, "android": { "cmake": { "args": [ "-DBUILD_SHARED_LIBS=ON", "-DSDLMIXER_SNDFILE=ON", "-DSDLMIXER_FLAC=ON", "-DSDLMIXER_FLAC_DRFLAC=ON", "-DSDLMIXER_GME=OFF", "-DSDLMIXER_MOD=OFF", "-DSDLMIXER_MOD_XMP=OFF", "-DSDLMIXER_MP3=ON", "-DSDLMIXER_MP3_DRMP3=ON", "-DSDLMIXER_MP3_MPG123=OFF", "-DSDLMIXER_MIDI=ON", "-DSDLMIXER_MIDI_NATIVE=ON", "-DSDLMIXER_OPUS=OFF", "-DSDLMIXER_VORBIS=STB", "-DSDLMIXER_WAVE=ON", "-DSDLMIXER_WAVPACK=OFF", "-DSDLMIXER_SAMPLES=OFF", "-DSDLMIXER_VENDORED=ON" ] }, "modules": { "SDL3_mixer-shared": { "type": "library", "library": "lib/libSDL3_mixer.so", "includes": { "SDL3_mixer": ["include/SDL3_mixer/*.h"] } }, "SDL3_mixer": { "type": "interface", "export-libraries": [":SDL3_mixer-shared"] } }, "abis": [ "armeabi-v7a", "arm64-v8a", "x86", "x86_64" ], "api-minimum": 19, "api-target": 29, "ndk-minimum": 21, "aar-files": { "": [ "build-scripts/pkg-support/android/aar/__main__.py.in:__main__.py", "build-scripts/pkg-support/android/aar/description.json.in:description.json" ], "META-INF": [ "LICENSE.txt" ], "cmake": [ "cmake/sdlcpu.cmake", "build-scripts/pkg-support/android/aar/cmake/SDL3_mixerConfig.cmake", "build-scripts/pkg-support/android/aar/cmake/SDL3_mixerConfigVersion.cmake.in:SDL3_mixerConfigVersion.cmake" ] }, "files": { "": [ "build-scripts/pkg-support/android/README.md.in:README.md" ] }, "dependencies": { "SDL": { "artifact": "SDL3-devel-*-android.zip" } } } } libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/test-versioning.sh000077500000000000000000000103031501405355700254710ustar00rootroot00000000000000#!/bin/sh # Copyright 2022 Collabora Ltd. # SPDX-License-Identifier: Zlib set -eu cd `dirname $0`/.. # Needed so sed doesn't report illegal byte sequences on macOS export LC_CTYPE=C header=include/SDL3_mixer/SDL_mixer.h ref_major=$(sed -ne 's/^#define SDL_MIXER_MAJOR_VERSION *//p' $header) ref_minor=$(sed -ne 's/^#define SDL_MIXER_MINOR_VERSION *//p' $header) ref_micro=$(sed -ne 's/^#define SDL_MIXER_MICRO_VERSION *//p' $header) ref_version="${ref_major}.${ref_minor}.${ref_micro}" tests=0 failed=0 ok () { tests=$(( tests + 1 )) echo "ok - $*" } not_ok () { tests=$(( tests + 1 )) echo "not ok - $*" failed=1 } major=$(sed -ne 's/^set(MAJOR_VERSION \([0-9]*\))$/\1/p' CMakeLists.txt) minor=$(sed -ne 's/^set(MINOR_VERSION \([0-9]*\))$/\1/p' CMakeLists.txt) micro=$(sed -ne 's/^set(MICRO_VERSION \([0-9]*\))$/\1/p' CMakeLists.txt) ref_sdl_req=$(sed -ne 's/^set(SDL_REQUIRED_VERSION \([0-9.]*\))$/\1/p' CMakeLists.txt) version="${major}.${minor}.${micro}" if [ "$ref_version" = "$version" ]; then ok "CMakeLists.txt $version" else not_ok "CMakeLists.txt $version disagrees with SDL_mixer.h $ref_version" fi for rcfile in src/version.rc; do tuple=$(sed -ne 's/^ *FILEVERSION *//p' "$rcfile" | tr -d '\r') ref_tuple="${ref_major},${ref_minor},${ref_micro},0" if [ "$ref_tuple" = "$tuple" ]; then ok "$rcfile FILEVERSION $tuple" else not_ok "$rcfile FILEVERSION $tuple disagrees with SDL_mixer.h $ref_tuple" fi tuple=$(sed -ne 's/^ *PRODUCTVERSION *//p' "$rcfile" | tr -d '\r') if [ "$ref_tuple" = "$tuple" ]; then ok "$rcfile PRODUCTVERSION $tuple" else not_ok "$rcfile PRODUCTVERSION $tuple disagrees with SDL_mixer.h $ref_tuple" fi tuple=$(sed -Ene 's/^ *VALUE "FileVersion", "([0-9, ]*)\\0"\r?$/\1/p' "$rcfile" | tr -d '\r') ref_tuple="${ref_major}, ${ref_minor}, ${ref_micro}, 0" if [ "$ref_tuple" = "$tuple" ]; then ok "$rcfile FileVersion $tuple" else not_ok "$rcfile FileVersion $tuple disagrees with SDL_mixer.h $ref_tuple" fi tuple=$(sed -Ene 's/^ *VALUE "ProductVersion", "([0-9, ]*)\\0"\r?$/\1/p' "$rcfile" | tr -d '\r') if [ "$ref_tuple" = "$tuple" ]; then ok "$rcfile ProductVersion $tuple" else not_ok "$rcfile ProductVersion $tuple disagrees with SDL_mixer.h $ref_tuple" fi done version=$(sed -Ene '/CFBundleShortVersionString/,+1 s/.*(.*)<\/string>.*/\1/p' Xcode/Info-Framework.plist) if [ "$ref_version" = "$version" ]; then ok "Info-Framework.plist CFBundleShortVersionString $version" else not_ok "Info-Framework.plist CFBundleShortVersionString $version disagrees with SDL_mixer.h $ref_version" fi version=$(sed -Ene '/CFBundleVersion/,+1 s/.*(.*)<\/string>.*/\1/p' Xcode/Info-Framework.plist) if [ "$ref_version" = "$version" ]; then ok "Info-Framework.plist CFBundleVersion $version" else not_ok "Info-Framework.plist CFBundleVersion $version disagrees with SDL_mixer.h $ref_version" fi # For simplicity this assumes we'll never break ABI before SDL 3. dylib_compat=$(sed -Ene 's/.*DYLIB_COMPATIBILITY_VERSION = (.*);$/\1/p' Xcode/SDL_mixer.xcodeproj/project.pbxproj) case "$ref_minor" in (*[02468]) major="$(( ref_minor * 100 + 1 ))" minor="0" ;; (*) major="$(( ref_minor * 100 + ref_micro + 1 ))" minor="0" ;; esac ref="${major}.${minor}.0 ${major}.${minor}.0" if [ "$ref" = "$dylib_compat" ]; then ok "project.pbxproj DYLIB_COMPATIBILITY_VERSION is consistent" else not_ok "project.pbxproj DYLIB_COMPATIBILITY_VERSION is inconsistent, expected $ref, got $dylib_compat" fi dylib_cur=$(sed -Ene 's/.*DYLIB_CURRENT_VERSION = (.*);$/\1/p' Xcode/SDL_mixer.xcodeproj/project.pbxproj) case "$ref_minor" in (*[02468]) major="$(( ref_minor * 100 + 1 ))" minor="$ref_micro" ;; (*) major="$(( ref_minor * 100 + ref_micro + 1 ))" minor="0" ;; esac ref="${major}.${minor}.0 ${major}.${minor}.0" if [ "$ref" = "$dylib_cur" ]; then ok "project.pbxproj DYLIB_CURRENT_VERSION is consistent" else not_ok "project.pbxproj DYLIB_CURRENT_VERSION is inconsistent, expected $ref, got $dylib_cur" fi echo "1..$tests" exit "$failed" libsdl3-mixer-3~git20250523~daf0503+ds/build-scripts/wikiheaders.pl000077500000000000000000003501111501405355700246350ustar00rootroot00000000000000#!/usr/bin/perl -w use warnings; use strict; use File::Path; use Text::Wrap; $Text::Wrap::huge = 'overflow'; my $projectfullname = 'Simple Directmedia Layer'; my $projectshortname = 'SDL'; my $wikisubdir = ''; my $incsubdir = 'include'; my $readmesubdir = undef; my $apiprefixregex = undef; my $versionfname = 'include/SDL_version.h'; my $versionmajorregex = '\A\#define\s+SDL_MAJOR_VERSION\s+(\d+)\Z'; my $versionminorregex = '\A\#define\s+SDL_MINOR_VERSION\s+(\d+)\Z'; my $versionmicroregex = '\A\#define\s+SDL_MICRO_VERSION\s+(\d+)\Z'; my $mainincludefname = 'SDL.h'; my $selectheaderregex = '\ASDL.*?\.h\Z'; my $projecturl = 'https://libsdl.org/'; my $wikiurl = 'https://wiki.libsdl.org'; my $bugreporturl = 'https://github.com/libsdl-org/sdlwiki/issues/new'; my $srcpath = undef; my $wikipath = undef; my $wikireadmesubdir = 'README'; my $warn_about_missing = 0; my $copy_direction = 0; my $optionsfname = undef; my $wikipreamble = undef; my $wikiheaderfiletext = 'Defined in %fname%'; my $manpageheaderfiletext = 'Defined in %fname%'; my $headercategoryeval = undef; my $changeformat = undef; my $manpath = undef; my $gitrev = undef; foreach (@ARGV) { $warn_about_missing = 1, next if $_ eq '--warn-about-missing'; $copy_direction = 1, next if $_ eq '--copy-to-headers'; $copy_direction = 1, next if $_ eq '--copy-to-header'; $copy_direction = -1, next if $_ eq '--copy-to-wiki'; $copy_direction = -2, next if $_ eq '--copy-to-manpages'; $copy_direction = -3, next if $_ eq '--report-coverage-gaps'; $copy_direction = -4, next if $_ eq '--copy-to-latex'; if (/\A--options=(.*)\Z/) { $optionsfname = $1; next; } elsif (/\A--changeformat=(.*)\Z/) { $changeformat = $1; next; } elsif (/\A--manpath=(.*)\Z/) { $manpath = $1; next; } elsif (/\A--rev=(.*)\Z/) { $gitrev = $1; next; } $srcpath = $_, next if not defined $srcpath; $wikipath = $_, next if not defined $wikipath; } my $default_optionsfname = '.wikiheaders-options'; $default_optionsfname = "$srcpath/$default_optionsfname" if defined $srcpath; if ((not defined $optionsfname) && (-f $default_optionsfname)) { $optionsfname = $default_optionsfname; } if (defined $optionsfname) { open OPTIONS, '<', $optionsfname or die("Failed to open options file '$optionsfname': $!\n"); while () { next if /\A\s*\#/; # Skip lines that start with (optional whitespace, then) '#' as comments. chomp; if (/\A(.*?)\=(.*)\Z/) { my $key = $1; my $val = $2; $key =~ s/\A\s+//; $key =~ s/\s+\Z//; $val =~ s/\A\s+//; $val =~ s/\s+\Z//; $warn_about_missing = int($val), next if $key eq 'warn_about_missing'; $srcpath = $val, next if $key eq 'srcpath'; $wikipath = $val, next if $key eq 'wikipath'; $apiprefixregex = $val, next if $key eq 'apiprefixregex'; $projectfullname = $val, next if $key eq 'projectfullname'; $projectshortname = $val, next if $key eq 'projectshortname'; $wikisubdir = $val, next if $key eq 'wikisubdir'; $incsubdir = $val, next if $key eq 'incsubdir'; $readmesubdir = $val, next if $key eq 'readmesubdir'; $versionmajorregex = $val, next if $key eq 'versionmajorregex'; $versionminorregex = $val, next if $key eq 'versionminorregex'; $versionmicroregex = $val, next if $key eq 'versionmicroregex'; $versionfname = $val, next if $key eq 'versionfname'; $mainincludefname = $val, next if $key eq 'mainincludefname'; $selectheaderregex = $val, next if $key eq 'selectheaderregex'; $projecturl = $val, next if $key eq 'projecturl'; $wikiurl = $val, next if $key eq 'wikiurl'; $bugreporturl = $val, next if $key eq 'bugreporturl'; $wikipreamble = $val, next if $key eq 'wikipreamble'; $wikiheaderfiletext = $val, next if $key eq 'wikiheaderfiletext'; $manpageheaderfiletext = $val, next if $key eq 'manpageheaderfiletext'; $headercategoryeval = $val, next if $key eq 'headercategoryeval'; } } close(OPTIONS); } sub escLaTeX { my $str = shift; $str =~ s/([_\#\&\^])/\\$1/g; return $str; } my $wordwrap_mode = 'mediawiki'; sub wordwrap_atom { # don't call this directly. my $str = shift; my $retval = ''; # wordwrap but leave links intact, even if they overflow. if ($wordwrap_mode eq 'mediawiki') { while ($str =~ s/(.*?)\s*(\[https?\:\/\/.*?\s+.*?\])\s*//ms) { $retval .= fill('', '', $1); # wrap it. $retval .= "\n$2\n"; # don't wrap it. } } elsif ($wordwrap_mode eq 'md') { while ($str =~ s/(.*?)\s*(\[.*?\]\(https?\:\/\/.*?\))\s*//ms) { $retval .= fill('', '', $1); # wrap it. $retval .= "\n$2\n"; # don't wrap it. } } return $retval . fill('', '', $str); } sub wordwrap_with_bullet_indent { # don't call this directly. my $bullet = shift; my $str = shift; my $retval = ''; #print("WORDWRAP BULLET ('$bullet'):\n\n$str\n\n"); # You _can't_ (at least with Pandoc) have a bullet item with a newline in # MediaWiki, so _remove_ wrapping! if ($wordwrap_mode eq 'mediawiki') { $retval = "$bullet$str"; $retval =~ s/\n/ /gms; $retval =~ s/\s+$//gms; #print("WORDWRAP BULLET DONE:\n\n$retval\n\n"); return "$retval\n"; } my $bulletlen = length($bullet); # wrap it and then indent each line to be under the bullet. $Text::Wrap::columns -= $bulletlen; my @wrappedlines = split /\n/, wordwrap_atom($str); $Text::Wrap::columns += $bulletlen; my $prefix = $bullet; my $usual_prefix = ' ' x $bulletlen; foreach (@wrappedlines) { s/\s*\Z//; $retval .= "$prefix$_\n"; $prefix = $usual_prefix; } return $retval; } sub wordwrap_one_paragraph { # don't call this directly. my $retval = ''; my $p = shift; #print "\n\n\nPARAGRAPH: [$p]\n\n\n"; if ($p =~ s/\A([\*\-] )//) { # bullet list, starts with "* " or "- ". my $bullet = $1; my $item = ''; my @items = split /\n/, $p; foreach (@items) { if (s/\A([\*\-] )//) { $retval .= wordwrap_with_bullet_indent($bullet, $item); $item = ''; } s/\A\s*//; $item .= "$_\n"; # accumulate lines until we hit the end or another bullet. } if ($item ne '') { $retval .= wordwrap_with_bullet_indent($bullet, $item); } } elsif ($p =~ /\A\s*\|.*\|\s*\n/) { # Markdown table $retval = "$p\n"; # don't wrap it (!!! FIXME: but maybe parse by lines until we run out of table...) } else { $retval = wordwrap_atom($p) . "\n"; } return $retval; } sub wordwrap_paragraphs { # don't call this directly. my $str = shift; my $retval = ''; my @paragraphs = split /\n\n/, $str; foreach (@paragraphs) { next if $_ eq ''; $retval .= wordwrap_one_paragraph($_); $retval .= "\n"; } return $retval; } my $wordwrap_default_columns = 76; sub wordwrap { my $str = shift; my $columns = shift; $columns = $wordwrap_default_columns if not defined $columns; $columns += $wordwrap_default_columns if $columns < 0; $Text::Wrap::columns = $columns; my $retval = ''; #print("\n\nWORDWRAP:\n\n$str\n\n\n"); $str =~ s/\A\n+//ms; while ($str =~ s/(.*?)(\`\`\`.*?\`\`\`|\)//ms) { #print("\n\nWORDWRAP BLOCK:\n\n$1\n\n ===\n\n$2\n\n\n"); $retval .= wordwrap_paragraphs($1); # wrap it. $retval .= "$2\n\n"; # don't wrap it. } $retval .= wordwrap_paragraphs($str); # wrap what's left. $retval =~ s/\n+\Z//ms; #print("\n\nWORDWRAP DONE:\n\n$retval\n\n\n"); return $retval; } # This assumes you're moving from Markdown (in the Doxygen data) to Wiki, which # is why the 'md' section is so sparse. sub wikify_chunk { my $wikitype = shift; my $str = shift; my $codelang = shift; my $code = shift; #print("\n\nWIKIFY CHUNK:\n\n$str\n\n\n"); if ($wikitype eq 'mediawiki') { # convert `code` things first, so they aren't mistaken for other markdown items. my $codedstr = ''; while ($str =~ s/\A(.*?)\`(.*?)\`//ms) { my $codeblock = $2; $codedstr .= wikify_chunk($wikitype, $1, undef, undef); if (defined $apiprefixregex) { # Convert obvious API things to wikilinks, even inside `code` blocks. $codeblock =~ s/\b($apiprefixregex[a-zA-Z0-9_]+)/[[$1]]/gms; } $codedstr .= "$codeblock"; } # Convert obvious API things to wikilinks. if (defined $apiprefixregex) { $str =~ s/\b($apiprefixregex[a-zA-Z0-9_]+)/[[$1]]/gms; } # Make some Markdown things into MediaWiki... # links $str =~ s/\[(.*?)\]\((https?\:\/\/.*?)\)/\[$2 $1\]/g; # bold+italic $str =~ s/\*\*\*(.*?)\*\*\*/'''''$1'''''/gms; # bold $str =~ s/\*\*(.*?)\*\*/'''$1'''/gms; # italic $str =~ s/\*(.*?)\*/''$1''/gms; # bullets $str =~ s/^\- /* /gm; $str = $codedstr . $str; if (defined $code) { $str .= "$code<\/syntaxhighlight>"; } } elsif ($wikitype eq 'md') { # convert `code` things first, so they aren't mistaken for other markdown items. my $codedstr = ''; while ($str =~ s/\A(.*?)(\`.*?\`)//ms) { my $codeblock = $2; $codedstr .= wikify_chunk($wikitype, $1, undef, undef); if (defined $apiprefixregex) { # Convert obvious API things to wikilinks, even inside `code` blocks, # BUT ONLY IF the entire code block is the API thing, # So something like "just call `SDL_Whatever`" will become # "just call [`SDL_Whatever`](SDL_Whatever)", but # "just call `SDL_Whatever(7)`" will not. It's just the safest # way to do this without resorting to wrapping things in html tags. $codeblock =~ s/\A\`($apiprefixregex[a-zA-Z0-9_]+)\`\Z/[`$1`]($1)/gms; } $codedstr .= $codeblock; } # Convert obvious API things to wikilinks. if (defined $apiprefixregex) { $str =~ s/\b($apiprefixregex[a-zA-Z0-9_]+)/[$1]($1)/gms; } $str = $codedstr . $str; if (defined $code) { $str .= "```$codelang\n$code\n```\n"; } } #print("\n\nWIKIFY CHUNK DONE:\n\n$str\n\n\n"); return $str; } sub wikify { my $wikitype = shift; my $str = shift; my $retval = ''; #print("WIKIFY WHOLE:\n\n$str\n\n\n"); while ($str =~ s/\A(.*?)\`\`\`(.*?)\n(.*?)\n\`\`\`(\n|\Z)//ms) { $retval .= wikify_chunk($wikitype, $1, $2, $3); } $retval .= wikify_chunk($wikitype, $str, undef, undef); #print("WIKIFY WHOLE DONE:\n\n$retval\n\n\n"); return $retval; } my $dewikify_mode = 'md'; my $dewikify_manpage_code_indent = 1; sub dewikify_chunk { my $wikitype = shift; my $str = shift; my $codelang = shift; my $code = shift; #print("\n\nDEWIKIFY CHUNK:\n\n$str\n\n\n"); if ($dewikify_mode eq 'md') { if ($wikitype eq 'mediawiki') { # Doxygen supports Markdown (and it just simply looks better than MediaWiki # when looking at the raw headers), so do some conversions here as necessary. # Dump obvious wikilinks. if (defined $apiprefixregex) { $str =~ s/\[\[($apiprefixregex[a-zA-Z0-9_]+)\]\]/$1/gms; } # links $str =~ s/\[(https?\:\/\/.*?)\s+(.*?)\]/\[$2\]\($1\)/g; # is also popular. :/ $str =~ s/\(.*?)<\/code>/`$1`/gms; # bold+italic $str =~ s/'''''(.*?)'''''/***$1***/gms; # bold $str =~ s/'''(.*?)'''/**$1**/gms; # italic $str =~ s/''(.*?)''/*$1*/gms; # bullets $str =~ s/^\* /- /gm; } elsif ($wikitype eq 'md') { # Dump obvious wikilinks. The rest can just passthrough. if (defined $apiprefixregex) { $str =~ s/\[(\`?$apiprefixregex[a-zA-Z0-9_]+\`?)\]\($apiprefixregex[a-zA-Z0-9_]+\)/$1/gms; } } if (defined $code) { $str .= "\n```$codelang\n$code\n```\n"; } } elsif ($dewikify_mode eq 'manpage') { $str =~ s/\./\\[char46]/gms; # make sure these can't become control codes. if ($wikitype eq 'mediawiki') { # Dump obvious wikilinks. if (defined $apiprefixregex) { $str =~ s/\s*\[\[($apiprefixregex[a-zA-Z0-9_]+)\]\]\s*/\n.BR $1\n/gms; } # links $str =~ s/\[(https?\:\/\/.*?)\s+(.*?)\]/\n.URL "$1" "$2"\n/g; # is also popular. :/ $str =~ s/\s*\(.*?)<\/code>\s*/\n.BR $1\n/gms; # bold+italic (this looks bad, just make it bold). $str =~ s/\s*'''''(.*?)'''''\s*/\n.B $1\n/gms; # bold $str =~ s/\s*'''(.*?)'''\s*/\n.B $1\n/gms; # italic $str =~ s/\s*''(.*?)''\s*/\n.I $1\n/gms; # bullets $str =~ s/^\* /\n\\\(bu /gm; } elsif ($wikitype eq 'md') { # Dump obvious wikilinks. if (defined $apiprefixregex) { $str =~ s/\[(\`?$apiprefixregex[a-zA-Z0-9_]+\`?)\]\($apiprefixregex[a-zA-Z0-9_]+\)/\n.BR $1\n/gms; } # links $str =~ s/\[(.*?)]\((https?\:\/\/.*?)\)/\n.URL "$2" "$1"\n/g; # is also popular. :/ $str =~ s/\s*\`(.*?)\`\s*/\n.BR $1\n/gms; # bold+italic (this looks bad, just make it bold). $str =~ s/\s*\*\*\*(.*?)\*\*\*\s*/\n.B $1\n/gms; # bold $str =~ s/\s*\*\*(.*?)\*\*\s*/\n.B $1\n/gms; # italic $str =~ s/\s*\*(.*?)\*\s*/\n.I $1\n/gms; # bullets $str =~ s/^\- /\n\\\(bu /gm; } if (defined $code) { $code =~ s/\A\n+//gms; $code =~ s/\n+\Z//gms; if ($dewikify_manpage_code_indent) { $str .= "\n.IP\n" } else { $str .= "\n.PP\n" } $str .= ".EX\n$code\n.EE\n.PP\n"; } } elsif ($dewikify_mode eq 'LaTeX') { if ($wikitype eq 'mediawiki') { # Dump obvious wikilinks. if (defined $apiprefixregex) { $str =~ s/\s*\[\[($apiprefixregex[a-zA-Z0-9_]+)\]\]/$1/gms; } # links $str =~ s/\[(https?\:\/\/.*?)\s+(.*?)\]/\\href{$1}{$2}/g; # is also popular. :/ $str =~ s/\s*\(.*?)<\/code>/ \\texttt{$1}/gms; # bold+italic $str =~ s/\s*'''''(.*?)'''''/ \\textbf{\\textit{$1}}/gms; # bold $str =~ s/\s*'''(.*?)'''/ \\textbf{$1}/gms; # italic $str =~ s/\s*''(.*?)''/ \\textit{$1}/gms; # bullets $str =~ s/^\*\s+/ \\item /gm; } elsif ($wikitype eq 'md') { # Dump obvious wikilinks. if (defined $apiprefixregex) { $str =~ s/\[(\`?$apiprefixregex[a-zA-Z0-9_]+\`?)\]\($apiprefixregex[a-zA-Z0-9_]+\)/$1/gms; } # links $str =~ s/\[(.*?)]\((https?\:\/\/.*?)\)/\\href{$2}{$1}/g; # is also popular. :/ $str =~ s/\s*\`(.*?)\`/ \\texttt{$1}/gms; # bold+italic $str =~ s/\s*\*\*\*(.*?)\*\*\*/ \\textbf{\\textit{$1}}/gms; # bold $str =~ s/\s*\*\*(.*?)\*\*/ \\textbf{$1}/gms; # italic $str =~ s/\s*\*(.*?)\*/ \\textit{$1}/gms; # bullets $str =~ s/^\-\s+/ \\item /gm; } # Wrap bullet lists in itemize blocks... $str =~ s/^(\s*\\item .*?)(\n\n|\Z)/\n\\begin{itemize}\n$1$2\n\\end{itemize}\n\n/gms; $str = escLaTeX($str); if (defined $code) { $code =~ s/\A\n+//gms; $code =~ s/\n+\Z//gms; if (($codelang eq '') || ($codelang eq 'output')) { $str .= "\\begin{verbatim}\n$code\n\\end{verbatim}\n"; } else { if ($codelang eq 'c') { $codelang = 'C'; } elsif ($codelang eq 'c++') { $codelang = 'C++'; } else { die("Unexpected codelang '$codelang'"); } $str .= "\n\\lstset{language=$codelang}\n"; $str .= "\\begin{lstlisting}\n$code\n\\end{lstlisting}\n"; } } } else { die("Unexpected dewikify_mode"); } #print("\n\nDEWIKIFY CHUNK DONE:\n\n$str\n\n\n"); return $str; } sub dewikify { my $wikitype = shift; my $str = shift; return '' if not defined $str; #print("DEWIKIFY WHOLE:\n\n$str\n\n\n"); $str =~ s/\A[\s\n]*\= .*? \=\s*?\n+//ms; $str =~ s/\A[\s\n]*\=\= .*? \=\=\s*?\n+//ms; my $retval = ''; if ($wikitype eq 'mediawiki') { while ($str =~ s/\A(.*?)(.*?)<\/syntaxhighlight\>//ms) { $retval .= dewikify_chunk($wikitype, $1, $2, $3); } } elsif ($wikitype eq 'md') { while ($str =~ s/\A(.*?)\n```(.*?)\n(.*?)\n```\n//ms) { $retval .= dewikify_chunk($wikitype, $1, $2, $3); } } $retval .= dewikify_chunk($wikitype, $str, undef, undef); #print("DEWIKIFY WHOLE DONE:\n\n$retval\n\n\n"); return $retval; } sub filecopy { my $src = shift; my $dst = shift; my $endline = shift; $endline = "\n" if not defined $endline; open(COPYIN, '<', $src) or die("Failed to open '$src' for reading: $!\n"); open(COPYOUT, '>', $dst) or die("Failed to open '$dst' for writing: $!\n"); while () { chomp; s/[ \t\r\n]*\Z//; print COPYOUT "$_$endline"; } close(COPYOUT); close(COPYIN); } sub usage { die("USAGE: $0 [--copy-to-headers|--copy-to-wiki|--copy-to-manpages] [--warn-about-missing] [--manpath=]\n\n"); } usage() if not defined $srcpath; usage() if not defined $wikipath; #usage() if $copy_direction == 0; if (not defined $manpath) { $manpath = "$srcpath/man"; } my @standard_wiki_sections = ( 'Draft', '[Brief]', 'Deprecated', 'Header File', 'Syntax', 'Function Parameters', 'Macro Parameters', 'Fields', 'Values', 'Return Value', 'Remarks', 'Thread Safety', 'Version', 'Code Examples', 'See Also' ); # Sections that only ever exist in the wiki and shouldn't be deleted when # not found in the headers. my %only_wiki_sections = ( # The ones don't mean anything, I just need to check for key existence. 'Draft', 1, 'Code Examples', 1, 'Header File', 1 ); my %headers = (); # $headers{"SDL_audio.h"} -> reference to an array of all lines of text in SDL_audio.h. my %headersyms = (); # $headersyms{"SDL_OpenAudio"} -> string of header documentation for SDL_OpenAudio, with comment '*' bits stripped from the start. Newlines embedded! my %headerdecls = (); my %headersymslocation = (); # $headersymslocation{"SDL_OpenAudio"} -> name of header holding SDL_OpenAudio define ("SDL_audio.h" in this case). my %headersymschunk = (); # $headersymschunk{"SDL_OpenAudio"} -> offset in array in %headers that should be replaced for this symbol. my %headersymshasdoxygen = (); # $headersymshasdoxygen{"SDL_OpenAudio"} -> 1 if there was no existing doxygen for this function. my %headersymstype = (); # $headersymstype{"SDL_OpenAudio"} -> 1 (function), 2 (macro), 3 (struct), 4 (enum), 5 (other typedef) my %headersymscategory = (); # $headersymscategory{"SDL_OpenAudio"} -> 'Audio' ... this is set with a `/* WIKI CATEGEORY: Audio */` comment in the headers that sets it on all symbols until a new comment changes it. So usually, once at the top of the header file. my %headercategorydocs = (); # $headercategorydocs{"Audio"} -> (fake) symbol for this category's documentation. Undefined if not documented. my %headersymsparaminfo = (); # $headersymsparaminfo{"SDL_OpenAudio"} -> reference to array of parameters, pushed by name, then C type string, repeating. Undef'd if void params, or not a function. my %headersymsrettype = (); # $headersymsrettype{"SDL_OpenAudio"} -> string of C datatype of return value. Undef'd if not a function. my %wikitypes = (); # contains string of wiki page extension, like $wikitypes{"SDL_OpenAudio"} == 'mediawiki' my %wikisyms = (); # contains references to hash of strings, each string being the full contents of a section of a wiki page, like $wikisyms{"SDL_OpenAudio"}{"Remarks"}. my %wikisectionorder = (); # contains references to array, each array item being a key to a wikipage section in the correct order, like $wikisectionorder{"SDL_OpenAudio"}[2] == 'Remarks' my %referenceonly = (); # $referenceonly{"Y"} -> symbol name that this symbol is bound to. This makes wiki pages that say "See X" where "X" is a typedef and "Y" is a define attached to it. These pages are generated in the wiki only and do not bridge to the headers or manpages. my @coverage_gap = (); # array of strings that weren't part of documentation, or blank, or basic preprocessor logic. Lets you see what this script is missing! sub add_coverage_gap { if ($copy_direction == -3) { # --report-coverage-gaps my $text = shift; my $dent = shift; my $lineno = shift; return if $text =~ /\A\s*\Z/; # skip blank lines return if $text =~ /\A\s*\#\s*(if|el|endif|include)/; # skip preprocessor floof. push @coverage_gap, "$dent:$lineno: $text"; } } sub print_undocumented_section { my $fh = shift; my $typestr = shift; my $typeval = shift; print $fh "## $typestr defined in the headers, but not in the wiki\n\n"; my $header_only_sym = 0; foreach (sort keys %headersyms) { my $sym = $_; if ((not defined $wikisyms{$sym}) && ($headersymstype{$sym} == $typeval)) { print $fh "- [$sym]($sym)\n"; $header_only_sym = 1; } } if (!$header_only_sym) { print $fh "(none)\n"; } print $fh "\n"; if (0) { # !!! FIXME: this lists things that _shouldn't_ be in the headers, like MigrationGuide, etc, but also we don't know if they're functions, macros, etc at this point (can we parse that from the wiki page, though?) print $fh "## $typestr defined in the wiki, but not in the headers\n\n"; my $wiki_only_sym = 0; foreach (sort keys %wikisyms) { my $sym = $_; if ((not defined $headersyms{$sym}) && ($headersymstype{$sym} == $typeval)) { print $fh "- [$sym]($sym)\n"; $wiki_only_sym = 1; } } if (!$wiki_only_sym) { print $fh "(none)\n"; } print $fh "\n"; } } sub strip_fn_declaration_metadata { my $decl = shift; $decl =~ s/SDL_(PRINTF|SCANF)_FORMAT_STRING\s*//; # don't want this metadata as part of the documentation. $decl =~ s/SDL_ALLOC_SIZE2?\(.*?\)\s*//; # don't want this metadata as part of the documentation. $decl =~ s/SDL_MALLOC\s*//; # don't want this metadata as part of the documentation. $decl =~ s/SDL_(IN|OUT|INOUT)_.*?CAP\s*\(.*?\)\s*//g; # don't want this metadata as part of the documentation. $decl =~ s/\)(\s*SDL_[a-zA-Z_]+(\(.*?\)|))*;/);/; # don't want this metadata as part of the documentation. return $decl; } sub sanitize_c_typename { my $str = shift; $str =~ s/\A\s+//; $str =~ s/\s+\Z//; $str =~ s/const\s*(\*+)/const $1/g; # one space between `const` and pointer stars: `char const* const *` becomes `char const * const *`. $str =~ s/\*\s+\*/**/g; # drop spaces between pointers: `void * *` becomes `void **`. $str =~ s/\s*(\*+)\Z/ $1/; # one space between pointer stars and what it points to: `void**` becomes `void **`. return $str; } my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; my $wikireadmepath = "$wikipath/$wikireadmesubdir"; my $readmepath = undef; if (defined $readmesubdir) { $readmepath = "$srcpath/$readmesubdir"; } opendir(DH, $incpath) or die("Can't opendir '$incpath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; next if not $dent =~ /$selectheaderregex/; # just selected headers. open(FH, '<', "$incpath/$dent") or die("Can't open '$incpath/$dent': $!\n"); # You can optionally set a wiki category with Perl code in .wikiheaders-options that gets eval()'d per-header, # and also if you put `/* WIKI CATEGORY: blah */` on a line by itself, it'll change the category for any symbols # below it in the same file. If no category is set, one won't be added for the symbol (beyond the standard CategoryFunction, etc) my $current_wiki_category = undef; if (defined $headercategoryeval) { $_ = $dent; $current_wiki_category = eval($headercategoryeval); if (($current_wiki_category eq '') || ($current_wiki_category eq '-')) { $current_wiki_category = undef; } #print("CATEGORY FOR '$dent' IS " . (defined($current_wiki_category) ? "'$current_wiki_category'" : '(undef)') . "\n"); } my @contents = (); my $ignoring_lines = 0; my $header_comment = -1; my $saw_category_doxygen = -1; my $lineno = 0; while () { chomp; $lineno++; my $symtype = 0; # nothing, yet. my $decl; my @templines; my $str; my $has_doxygen = 1; # Since a lot of macros are just preprocessor logic spam and not all macros are worth documenting anyhow, we only pay attention to them when they have a Doxygen comment attached. # Functions and other things are a different story, though! if ($header_comment == -1) { $header_comment = /\A\/\*\s*\Z/ ? 1 : 0; } elsif (($header_comment == 1) && (/\A\*\/\s*\Z/)) { $header_comment = 0; } if ($ignoring_lines && /\A\s*\#\s*endif\s*\Z/) { $ignoring_lines = 0; push @contents, $_; next; } elsif ($ignoring_lines) { push @contents, $_; next; } elsif (/\A\s*\#\s*ifndef\s+SDL_WIKI_DOCUMENTATION_SECTION\s*\Z/) { $ignoring_lines = 1; push @contents, $_; next; } elsif (/\A\s*\/\*\s*WIKI CATEGORY:\s*(.*?)\s*\*\/\s*\Z/) { $current_wiki_category = (($1 eq '') || ($1 eq '-')) ? undef : $1; #print("CATEGORY FOR '$dent' CHANGED TO " . (defined($current_wiki_category) ? "'$current_wiki_category'" : '(undef)') . "\n"); push @contents, $_; next; } elsif (/\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC/) { # a function declaration without a doxygen comment? $symtype = 1; # function declaration @templines = (); $decl = $_; $str = ''; $has_doxygen = 0; } elsif (/\A\s*SDL_FORCE_INLINE/) { # a (forced-inline) function declaration without a doxygen comment? $symtype = 1; # function declaration @templines = (); $decl = $_; $str = ''; $has_doxygen = 0; } elsif (not /\A\/\*\*\s*\Z/) { # not doxygen comment start? push @contents, $_; add_coverage_gap($_, $dent, $lineno) if ($header_comment == 0); next; } else { # Start of a doxygen comment, parse it out. my $is_category_doxygen = 0; @templines = ( $_ ); while () { chomp; $lineno++; push @templines, $_; last if /\A\s*\*\/\Z/; if (s/\A\s*\*\s*\`\`\`/```/) { # this is a hack, but a lot of other code relies on the whitespace being trimmed, but we can't trim it in code blocks... $str .= "$_\n"; while () { chomp; $lineno++; push @templines, $_; s/\A\s*\*\s?//; if (s/\A\s*\`\`\`/```/) { $str .= "$_\n"; last; } else { $str .= "$_\n"; } } } else { s/\A\s*\*\s*//; # Strip off the " * " at the start of the comment line. # To add documentation to Category Pages, the rule is it has to # be the first Doxygen comment in the header, and it must start with `# CategoryX` # (otherwise we'll treat it as documentation for whatever's below it). `X` is # the category name, which doesn't _necessarily_ have to match # $current_wiki_category, but it probably should. # # For compatibility with Doxygen, if there's a `\file` here instead of # `# CategoryName`, we'll accept it and use the $current_wiki_category if set. if ($saw_category_doxygen == -1) { $saw_category_doxygen = defined($current_wiki_category) && /\A\\file\s+/; if ($saw_category_doxygen) { $_ = "# Category$current_wiki_category"; } else { $saw_category_doxygen = /\A\# Category/; } $is_category_doxygen = $saw_category_doxygen; } $str .= "$_\n"; } } if ($is_category_doxygen) { $str =~ s/\s*\Z//; $decl = ''; $symtype = -1; # not a symbol at all. } else { $decl = ; $lineno++ if defined $decl; $decl = '' if not defined $decl; chomp($decl); if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC/) { $symtype = 1; # function declaration } elsif ($decl =~ /\A\s*SDL_FORCE_INLINE/) { $symtype = 1; # (forced-inline) function declaration } elsif ($decl =~ /\A\s*\#\s*define\s+/) { $symtype = 2; # macro } elsif ($decl =~ /\A\s*(typedef\s+|)(struct|union)/) { $symtype = 3; # struct or union } elsif ($decl =~ /\A\s*(typedef\s+|)enum/) { $symtype = 4; # enum } elsif ($decl =~ /\A\s*typedef\s+.*\Z/) { $symtype = 5; # other typedef } else { #print "Found doxygen but no function sig:\n$str\n\n"; foreach (@templines) { push @contents, $_; add_coverage_gap($_, $dent, $lineno); } push @contents, $decl; add_coverage_gap($decl, $dent, $lineno); next; } } } my @paraminfo = (); my $rettype = undef; my @decllines = ( $decl ); my $sym = ''; if ($symtype == -1) { # category documentation with no symbol attached. @decllines = (); if ($str =~ /^#\s*Category(.*?)\s*$/m) { $sym = "[category documentation] $1"; # make a fake, unique symbol that's not valid C. } else { die("Unexpected category documentation line '$str' in '$incpath/$dent' ...?"); } $headercategorydocs{$current_wiki_category} = $sym; } elsif ($symtype == 1) { # a function my $is_forced_inline = ($decl =~ /\A\s*SDL_FORCE_INLINE/); if ($is_forced_inline) { if (not $decl =~ /\)\s*(\{.*|)\s*\Z/) { while () { chomp; $lineno++; push @decllines, $_; s/\A\s+//; s/\s+\Z//; $decl .= " $_"; last if /\)\s*(\{.*|)\s*\Z/; } } $decl =~ s/\s*\)\s*(\{.*|)\s*\Z/);/; } else { if (not $decl =~ /;/) { while () { chomp; $lineno++; push @decllines, $_; s/\A\s+//; s/\s+\Z//; $decl .= " $_"; last if /;/; } } $decl =~ s/\s+\);\Z/);/; $decl =~ s/\s+;\Z/;/; } $decl =~ s/\s+\Z//; $decl = strip_fn_declaration_metadata($decl); my $paramsstr = undef; if (!$is_forced_inline && $decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC\s+(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(\*?)\s*SDLCALL\s+(.*?)\s*\((.*?)\);/) { $sym = $8; $rettype = "$3$4$5$6"; $paramsstr = $9; } elsif ($is_forced_inline && $decl =~ /\A\s*SDL_FORCE_INLINE\s+(SDL_DEPRECATED\s+|)(const\s+|)(unsigned\s+|)(.*?)([\*\s]+)(.*?)\s*\((.*?)\);/) { $sym = $6; $rettype = "$2$3$4$5"; $paramsstr = $7; } else { #print "Found doxygen but no function sig:\n$str\n\n"; foreach (@templines) { push @contents, $_; } foreach (@decllines) { push @contents, $_; } next; } $rettype = sanitize_c_typename($rettype); if ($paramsstr =~ /\(/) { die("\n\n$0 FAILURE!\n" . "There's a '(' in the parameters for function '$sym' '$incpath/$dent'.\n" . "This usually means there's a parameter that's a function pointer type.\n" . "This causes problems for wikiheaders.pl and is less readable, too.\n" . "Please put that function pointer into a typedef,\n" . "and use the new type in this function's signature instead!\n\n"); } my @params = split(/,/, $paramsstr); my $dotdotdot = 0; foreach (@params) { my $p = $_; $p =~ s/\A\s+//; $p =~ s/\s+\Z//; if (($p eq 'void') || ($p eq '')) { die("Void parameter in a function with multiple params?! ('$sym' in '$incpath/$dent')") if (scalar(@params) != 1); } elsif ($p eq '...') { die("Mutiple '...' params?! ('$sym' in '$incpath/$dent')") if ($dotdotdot); $dotdotdot = 1; push @paraminfo, '...'; push @paraminfo, '...'; } elsif ($p =~ /\A(.*)\s+([a-zA-Z0-9_\*\[\]]+)\Z/) { die("Parameter after '...' param?! ('$sym' in '$incpath/$dent')") if ($dotdotdot); my $t = $1; my $n = $2; if ($n =~ s/\A(\*+)//) { $t .= $1; # move any `*` that stuck to the name over. } if ($n =~ s/\[\]\Z//) { $t = "$t*"; # move any `[]` that stuck to the name over, as a pointer. } $t = sanitize_c_typename($t); #print("$t\n"); #print("$n\n"); push @paraminfo, $n; push @paraminfo, $t; } else { die("Unexpected parameter '$p' in function '$sym' in '$incpath/$dent'!"); } } if (!$is_forced_inline) { # don't do with forced-inline because we don't want the implementation inserted in the wiki. $decl = ''; # rebuild this with the line breaks, since it looks better for syntax highlighting. foreach (@decllines) { if ($decl eq '') { $decl = $_; $decl =~ s/\Aextern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_|SDL_)?DECLSPEC\s+(.*?)\s+(\*?)SDLCALL\s+/$3$4 /; } else { my $trimmed = $_; # !!! FIXME: trim space for SDL_DEPRECATED if it was used, too. $trimmed =~ s/\A\s{28}//; # 28 for shrinking to match the removed "extern SDL_DECLSPEC SDLCALL " $decl .= $trimmed; } $decl .= "\n"; } } $decl = strip_fn_declaration_metadata($decl); # !!! FIXME: code duplication with typedef processing, below. # We assume any `#define`s directly after the function are related to it: probably bitflags for an integer typedef. # We'll also allow some other basic preprocessor lines. # Blank lines are allowed, anything else, even comments, are not. my $blank_lines = 0; my $lastpos = tell(FH); my $lastlineno = $lineno; my $additional_decl = ''; my $saw_define = 0; while () { chomp; $lineno++; if (/\A\s*\Z/) { $blank_lines++; } elsif (/\A\s*\#\s*(define|if|else|elif|endif)(\s+|\Z)/) { if (/\A\s*\#\s*define\s+([a-zA-Z0-9_]*)/) { $referenceonly{$1} = $sym; $saw_define = 1; } elsif (!$saw_define) { # if the first non-blank thing isn't a #define, assume we're done. seek(FH, $lastpos, 0); # re-read eaten lines again next time. $lineno = $lastlineno; last; } # update strings now that we know everything pending is to be applied to this declaration. Add pending blank lines and the new text. # At Sam's request, don't list property defines with functions. (See #9440) my $is_property = /\A\s*\#\s*define\s+SDL_PROP_/; if (!$is_property) { if ($blank_lines > 0) { while ($blank_lines > 0) { $additional_decl .= "\n"; push @decllines, ''; $blank_lines--; } } $additional_decl .= "\n$_"; push @decllines, $_; $lastpos = tell(FH); } } else { seek(FH, $lastpos, 0); # re-read eaten lines again next time. $lineno = $lastlineno; last; } } $decl .= $additional_decl; } elsif ($symtype == 2) { # a macro if ($decl =~ /\A\s*\#\s*define\s+(.*?)(\(.*?\)|)\s+/) { $sym = $1; } else { #print "Found doxygen but no macro:\n$str\n\n"; foreach (@templines) { push @contents, $_; } foreach (@decllines) { push @contents, $_; } next; } while ($decl =~ /\\\Z/) { my $l = ; last if not $l; $lineno++; chomp($l); push @decllines, $l; #$l =~ s/\A\s+//; $l =~ s/\s+\Z//; $decl .= "\n$l"; } } elsif (($symtype == 3) || ($symtype == 4)) { # struct or union or enum my $has_definition = 0; if ($decl =~ /\A\s*(typedef\s+|)(struct|union|enum)\s*(.*?)\s*(\n|\{|\;|\Z)/) { my $ctype = $2; my $origsym = $3; my $ending = $4; $sym = $origsym; if ($sym =~ s/\A(.*?)(\s+)(.*?)\Z/$1/) { die("Failed to parse '$origsym' correctly!") if ($sym ne $1); # Thought this was "typedef struct MySym MySym;" ... it was not. :( This is a hack! } if ($sym eq '') { die("\n\n$0 FAILURE!\n" . "There's a 'typedef $ctype' in $incpath/$dent without a name at the top.\n" . "Instead of `typedef $ctype {} x;`, this should be `typedef $ctype x {} x;`.\n" . "This causes problems for wikiheaders.pl and scripting language bindings.\n" . "Please fix it!\n\n"); } $has_definition = ($ending ne ';'); } else { #print "Found doxygen but no datatype:\n$str\n\n"; foreach (@templines) { push @contents, $_; } foreach (@decllines) { push @contents, $_; } next; } # This block attempts to find the whole struct/union/enum definition by counting matching brackets. Kind of yucky. if ($has_definition) { my $started = 0; my $brackets = 0; my $pending = $decl; $decl = ''; while (!$started || ($brackets != 0)) { foreach my $seg (split(/([{}])/, $pending)) { $decl .= $seg; if ($seg eq '{') { $started = 1; $brackets++; } elsif ($seg eq '}') { die("Something is wrong with header $incpath/$dent while parsing $sym; is a bracket missing?\n") if ($brackets <= 0); $brackets--; } } if (!$started || ($brackets != 0)) { $pending = ; die("EOF/error reading $incpath/$dent while parsing $sym\n") if not $pending; $lineno++; chomp($pending); push @decllines, $pending; $decl .= "\n"; } } # this currently assumes the struct/union/enum ends on the line with the final bracket. I'm not writing a C parser here, fix the header! } } elsif ($symtype == 5) { # other typedef if ($decl =~ /\A\s*typedef\s+(.*)\Z/) { my $tdstr = $1; if (not $decl =~ /;/) { while () { chomp; $lineno++; push @decllines, $_; s/\A\s+//; s/\s+\Z//; $decl .= " $_"; last if /;/; } } $decl =~ s/\s+(\))?;\Z/$1;/; $tdstr =~ s/;\s*\Z//; #my $datatype; if ($tdstr =~ /\A(.*?)\s*\((.*?)\s*\*\s*(.*?)\)\s*\((.*?)(\))?/) { # a function pointer type $sym = $3; #$datatype = "$1 ($2 *$sym)($4)"; } elsif ($tdstr =~ /\A(.*[\s\*]+)(.*?)\s*\Z/) { $sym = $2; #$datatype = $1; } else { die("Failed to parse typedef '$tdstr' in $incpath/$dent!\n"); # I'm hitting a C grammar nail with a regexp hammer here, y'all. } $sym =~ s/\A\s+//; $sym =~ s/\s+\Z//; #$datatype =~ s/\A\s+//; #$datatype =~ s/\s+\Z//; } else { #print "Found doxygen but no datatype:\n$str\n\n"; foreach (@templines) { push @contents, $_; } foreach (@decllines) { push @contents, $_; } next; } # We assume any `#define`s directly after the typedef are related to it: probably bitflags for an integer typedef. # We'll also allow some other basic preprocessor lines. # Blank lines are allowed, anything else, even comments, are not. my $blank_lines = 0; my $lastpos = tell(FH); my $lastlineno = $lineno; my $additional_decl = ''; my $saw_define = 0; while () { chomp; $lineno++; if (/\A\s*\Z/) { $blank_lines++; } elsif (/\A\s*\#\s*(define|if|else|elif|endif)(\s+|\Z)/) { if (/\A\s*\#\s*define\s+([a-zA-Z0-9_]*)/) { $referenceonly{$1} = $sym; $saw_define = 1; } elsif (!$saw_define) { # if the first non-blank thing isn't a #define, assume we're done. seek(FH, $lastpos, 0); # re-read eaten lines again next time. $lineno = $lastlineno; last; } # update strings now that we know everything pending is to be applied to this declaration. Add pending blank lines and the new text. if ($blank_lines > 0) { while ($blank_lines > 0) { $additional_decl .= "\n"; push @decllines, ''; $blank_lines--; } } $additional_decl .= "\n$_"; push @decllines, $_; $lastpos = tell(FH); } else { seek(FH, $lastpos, 0); # re-read eaten lines again next time. $lineno = $lastlineno; last; } } $decl .= $additional_decl; } else { die("Unexpected symtype $symtype"); } #print("DECL: [$decl]\n"); #print("$sym:\n$str\n\n"); # There might be multiple declarations of a function due to #ifdefs, # and only one of them will have documentation. If we hit an # undocumented one before, delete the placeholder line we left for # it so it doesn't accumulate a new blank line on each run. my $skipsym = 0; if (defined $headersymshasdoxygen{$sym}) { if ($headersymshasdoxygen{$sym} == 0) { # An undocumented declaration already exists, nuke its placeholder line. delete $contents[$headersymschunk{$sym}]; # delete DOES NOT RENUMBER existing elements! } else { # documented function already existed? $skipsym = 1; # don't add this copy to the list of functions. if ($has_doxygen) { print STDERR "WARNING: Symbol '$sym' appears to be documented in multiple locations. Only keeping the first one we saw!\n"; } push @contents, join("\n", @decllines) if (scalar(@decllines) > 0); # just put the existing declation in as-is. } } if (!$skipsym) { $headersymscategory{$sym} = $current_wiki_category if defined $current_wiki_category; $headersyms{$sym} = $str; $headerdecls{$sym} = $decl; $headersymslocation{$sym} = $dent; $headersymschunk{$sym} = scalar(@contents); $headersymshasdoxygen{$sym} = $has_doxygen; $headersymstype{$sym} = $symtype; $headersymsparaminfo{$sym} = \@paraminfo if (scalar(@paraminfo) > 0); $headersymsrettype{$sym} = $rettype if (defined($rettype)); push @contents, join("\n", @templines); push @contents, join("\n", @decllines) if (scalar(@decllines) > 0); } } close(FH); $headers{$dent} = \@contents; } closedir(DH); opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; my $type = ''; if ($dent =~ /\.(md|mediawiki)\Z/) { $type = $1; } else { next; # only dealing with wiki pages. } my $sym = $dent; $sym =~ s/\..*\Z//; # (There are other pages to ignore, but these are known ones to not bother parsing.) # Ignore FrontPage. next if $sym eq 'FrontPage'; open(FH, '<', "$wikipath/$dent") or die("Can't open '$wikipath/$dent': $!\n"); if ($sym =~ /\ACategory(.*?)\Z/) { # Special case for Category pages. # Find the end of the category documentation in the existing file and append everything else to the new file. my $cat = $1; my $docstr = ''; my $notdocstr = ''; my $docs = 1; while () { chomp; if ($docs) { $docs = 0 if /\A\-\-\-\-\Z/; # Hit a footer? We're done. $docs = 0 if /\A) { chomp; my $orig = $_; s/\A\s*//; s/\s*\Z//; if ($type eq 'mediawiki') { if (defined($wikipreamble) && $firstline && /\A\=\=\=\=\=\= (.*?) \=\=\=\=\=\=\Z/ && ($1 eq $wikipreamble)) { $firstline = 0; # skip this. next; } elsif (/\A\= (.*?) \=\Z/) { $firstline = 0; $current_section = ($1 eq $sym) ? '[Brief]' : $1; die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; push @section_order, $current_section; $sections{$current_section} = ''; } elsif (/\A\=\= (.*?) \=\=\Z/) { $firstline = 0; $current_section = ($1 eq $sym) ? '[Brief]' : $1; die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; push @section_order, $current_section; $sections{$current_section} = ''; next; } elsif (/\A\-\-\-\-\Z/) { $firstline = 0; $current_section = '[footer]'; die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; push @section_order, $current_section; $sections{$current_section} = ''; next; } } elsif ($type eq 'md') { if (defined($wikipreamble) && $firstline && /\A\#\#\#\#\#\# (.*?)\Z/ && ($1 eq $wikipreamble)) { $firstline = 0; # skip this. next; } elsif (/\A\#+ (.*?)\Z/) { $firstline = 0; $current_section = ($1 eq $sym) ? '[Brief]' : $1; die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; push @section_order, $current_section; $sections{$current_section} = ''; next; } elsif (/\A\-\-\-\-\Z/) { $firstline = 0; $current_section = '[footer]'; die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; push @section_order, $current_section; $sections{$current_section} = ''; next; } } else { die("Unexpected wiki file type. Fixme!"); } if ($firstline) { $firstline = ($_ ne ''); } if (!$firstline) { $sections{$current_section} .= "$orig\n"; } } close(FH); foreach (keys %sections) { $sections{$_} =~ s/\A\n+//; $sections{$_} =~ s/\n+\Z//; $sections{$_} .= "\n"; } # older section name we used, migrate over from it. if (defined $sections{'Related Functions'}) { if (not defined $sections{'See Also'}) { $sections{'See Also'} = $sections{'Related Functions'}; } delete $sections{'Related Functions'}; } if (0) { foreach (@section_order) { print("$sym SECTION '$_':\n"); print($sections{$_}); print("\n\n"); } } $wikitypes{$sym} = $type; $wikisyms{$sym} = \%sections; $wikisectionorder{$sym} = \@section_order; } closedir(DH); delete $wikisyms{"Undocumented"}; { my $path = "$wikipath/Undocumented.md"; open(my $fh, '>', $path) or die("Can't open '$path': $!\n"); print $fh "# Undocumented\n\n"; print_undocumented_section($fh, 'Functions', 1); #print_undocumented_section($fh, 'Macros', 2); close($fh); } if ($warn_about_missing) { foreach (keys %wikisyms) { my $sym = $_; if (not defined $headersyms{$sym}) { print STDERR "WARNING: $sym defined in the wiki but not the headers!\n"; } } foreach (keys %headersyms) { my $sym = $_; if (not defined $wikisyms{$sym}) { print STDERR "WARNING: $sym defined in the headers but not the wiki!\n"; } } } if ($copy_direction == 1) { # --copy-to-headers my %changed_headers = (); $dewikify_mode = 'md'; $wordwrap_mode = 'md'; # the headers use Markdown format. foreach (keys %headersyms) { my $sym = $_; next if not defined $wikisyms{$sym}; # don't have a page for that function, skip it. my $symtype = $headersymstype{$sym}; my $wikitype = $wikitypes{$sym}; my $sectionsref = $wikisyms{$sym}; my $remarks = $sectionsref->{'Remarks'}; my $returns = $sectionsref->{'Return Value'}; my $threadsafety = $sectionsref->{'Thread Safety'}; my $version = $sectionsref->{'Version'}; my $related = $sectionsref->{'See Also'}; my $deprecated = $sectionsref->{'Deprecated'}; my $brief = $sectionsref->{'[Brief]'}; my $addblank = 0; my $str = ''; my $params = undef; my $paramstr = undef; if ($symtype == -1) { # category documentation block. # nothing to be done here. } elsif (($symtype == 1) || (($symtype == 5))) { # we'll assume a typedef (5) with a \param is a function pointer typedef. $params = $sectionsref->{'Function Parameters'}; $paramstr = '\param'; } elsif ($symtype == 2) { $params = $sectionsref->{'Macro Parameters'}; $paramstr = '\param'; } elsif ($symtype == 3) { $params = $sectionsref->{'Fields'}; $paramstr = '\field'; } elsif ($symtype == 4) { $params = $sectionsref->{'Values'}; $paramstr = '\value'; } else { die("Unexpected symtype $symtype"); } $headersymshasdoxygen{$sym} = 1; # Added/changed doxygen for this header. $brief = dewikify($wikitype, $brief); $brief =~ s/\A(.*?\.) /$1\n/; # \brief should only be one sentence, delimited by a period+space. Split if necessary. my @briefsplit = split /\n/, $brief; $brief = shift @briefsplit; if (defined $remarks) { $remarks = join("\n", @briefsplit) . dewikify($wikitype, $remarks); } if (defined $brief) { $str .= "\n" if $addblank; $addblank = 1; $str .= wordwrap($brief) . "\n"; } if (defined $remarks) { $str .= "\n" if $addblank; $addblank = 1; $str .= wordwrap($remarks) . "\n"; } if (defined $deprecated) { # !!! FIXME: lots of code duplication in all of these. $str .= "\n" if $addblank; $addblank = 1; my $v = dewikify($wikitype, $deprecated); my $whitespacelen = length("\\deprecated") + 1; my $whitespace = ' ' x $whitespacelen; $v = wordwrap($v, -$whitespacelen); my @desclines = split /\n/, $v; my $firstline = shift @desclines; $str .= "\\deprecated $firstline\n"; foreach (@desclines) { $str .= "${whitespace}$_\n"; } } if (defined $params) { $str .= "\n" if $addblank; $addblank = (defined $returns) ? 0 : 1; my @lines = split /\n/, dewikify($wikitype, $params); if ($wikitype eq 'mediawiki') { die("Unexpected data parsing MediaWiki table") if (shift @lines ne '{|'); # Dump the '{|' start while (scalar(@lines) >= 3) { my $c_datatype = shift @lines; my $name = shift @lines; my $desc = shift @lines; my $terminator; # the '|-' or '|}' line. if (($desc eq '|-') or ($desc eq '|}') or (not $desc =~ /\A\|/)) { # we seem to be out of cells, which means there was no datatype column on this one. $terminator = $desc; $desc = $name; $name = $c_datatype; $c_datatype = ''; } else { $terminator = shift @lines; } last if ($terminator ne '|-') and ($terminator ne '|}'); # we seem to have run out of table. $name =~ s/\A\|\s*//; $name =~ s/\A\*\*(.*?)\*\*/$1/; $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; $desc =~ s/\A\|\s*//; #print STDERR "SYM: $sym CDATATYPE: $c_datatype NAME: $name DESC: $desc TERM: $terminator\n"; my $whitespacelen = length($name) + 8; my $whitespace = ' ' x $whitespacelen; $desc = wordwrap($desc, -$whitespacelen); my @desclines = split /\n/, $desc; my $firstline = shift @desclines; $str .= "$paramstr $name $firstline\n"; foreach (@desclines) { $str .= "${whitespace}$_\n"; } } } elsif ($wikitype eq 'md') { my $l; $l = shift @lines; die("Unexpected data parsing Markdown table") if (not $l =~ /\A(\s*\|)?\s*\|\s*\|\s*\|\s*\Z/); $l = shift @lines; die("Unexpected data parsing Markdown table") if (not $l =~ /\A\s*(\|\s*\-*\s*)?\|\s*\-*\s*\|\s*\-*\s*\|\s*\Z/); while (scalar(@lines) >= 1) { $l = shift @lines; my $name; my $desc; if ($l =~ /\A\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*\Z/) { # c datatype is $1, but we don't care about it here. $name = $2; $desc = $3; } elsif ($l =~ /\A\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*\Z/) { $name = $1; $desc = $2; } else { last; # we seem to have run out of table. } $name =~ s/\A\*\*(.*?)\*\*/$1/; $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; #print STDERR "SYM: $sym NAME: $name DESC: $desc\n"; my $whitespacelen = length($name) + 8; my $whitespace = ' ' x $whitespacelen; $desc = wordwrap($desc, -$whitespacelen); my @desclines = split /\n/, $desc; my $firstline = shift @desclines; $str .= "$paramstr $name $firstline\n"; foreach (@desclines) { $str .= "${whitespace}$_\n"; } } } else { die("write me"); } } if (defined $returns) { $str .= "\n" if $addblank; $addblank = 1; my $r = dewikify($wikitype, $returns); $r =~ s/\A\(.*?\)\s*//; # Chop datatype in parentheses off the front. my $retstr = "\\returns"; if ($r =~ s/\AReturn(s?)\s+//) { $retstr = "\\return$1"; } my $whitespacelen = length($retstr) + 1; my $whitespace = ' ' x $whitespacelen; $r = wordwrap($r, -$whitespacelen); my @desclines = split /\n/, $r; my $firstline = shift @desclines; $str .= "$retstr $firstline\n"; foreach (@desclines) { $str .= "${whitespace}$_\n"; } } if (defined $threadsafety) { # !!! FIXME: lots of code duplication in all of these. $str .= "\n" if $addblank; $addblank = 1; my $v = dewikify($wikitype, $threadsafety); my $whitespacelen = length("\\threadsafety") + 1; my $whitespace = ' ' x $whitespacelen; $v = wordwrap($v, -$whitespacelen); my @desclines = split /\n/, $v; my $firstline = shift @desclines; $str .= "\\threadsafety $firstline\n"; foreach (@desclines) { $str .= "${whitespace}$_\n"; } } if (defined $version) { # !!! FIXME: lots of code duplication in all of these. $str .= "\n" if $addblank; $addblank = 1; my $v = dewikify($wikitype, $version); my $whitespacelen = length("\\since") + 1; my $whitespace = ' ' x $whitespacelen; $v = wordwrap($v, -$whitespacelen); my @desclines = split /\n/, $v; my $firstline = shift @desclines; $str .= "\\since $firstline\n"; foreach (@desclines) { $str .= "${whitespace}$_\n"; } } if (defined $related) { # !!! FIXME: lots of code duplication in all of these. $str .= "\n" if $addblank; $addblank = 1; my $v = dewikify($wikitype, $related); my @desclines = split /\n/, $v; foreach (@desclines) { s/\(\)\Z//; # Convert "SDL_Func()" to "SDL_Func" s/\[\[(.*?)\]\]/$1/; # in case some wikilinks remain. s/\[(.*?)\]\(.*?\)/$1/; # in case some wikilinks remain. s/\A\/*//; s/\A\s*[\:\*\-]\s*//; s/\A\s+//; s/\s+\Z//; $str .= "\\sa $_\n"; } } my $header = $headersymslocation{$sym}; my $contentsref = $headers{$header}; my $chunk = $headersymschunk{$sym}; my @lines = split /\n/, $str; my $addnewline = (($chunk > 0) && ($$contentsref[$chunk-1] ne '')) ? "\n" : ''; my $output = "$addnewline/**\n"; foreach (@lines) { chomp; s/\s*\Z//; if ($_ eq '') { $output .= " *\n"; } else { $output .= " * $_\n"; } } $output .= " */"; #print("$sym:\n[$output]\n\n"); $$contentsref[$chunk] = $output; #$$contentsref[$chunk+1] = $headerdecls{$sym}; $changed_headers{$header} = 1; } foreach (keys %changed_headers) { my $header = $_; # this is kinda inefficient, but oh well. my @removelines = (); foreach (keys %headersymslocation) { my $sym = $_; next if $headersymshasdoxygen{$sym}; next if $headersymslocation{$sym} ne $header; # the index of the blank line we put before the function declaration in case we needed to replace it with new content from the wiki. push @removelines, $headersymschunk{$sym}; } my $contentsref = $headers{$header}; foreach (@removelines) { delete $$contentsref[$_]; # delete DOES NOT RENUMBER existing elements! } my $path = "$incpath/$header.tmp"; open(FH, '>', $path) or die("Can't open '$path': $!\n"); foreach (@$contentsref) { print FH "$_\n" if defined $_; } close(FH); rename($path, "$incpath/$header") or die("Can't rename '$path' to '$incpath/$header': $!\n"); } if (defined $readmepath) { if ( -d $wikireadmepath ) { mkdir($readmepath); # just in case opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); while (readdir(DH)) { my $dent = $_; if ($dent =~ /\A(.*?)\.md\Z/) { # we only bridge Markdown files here. next if $1 eq 'FrontPage'; filecopy("$wikireadmepath/$dent", "$readmepath/README-$dent", "\n"); } } closedir(DH); } } } elsif ($copy_direction == -1) { # --copy-to-wiki if (defined $changeformat) { $dewikify_mode = $changeformat; $wordwrap_mode = $changeformat; } foreach (keys %headersyms) { my $sym = $_; next if not $headersymshasdoxygen{$sym}; next if $sym =~ /\A\[category documentation\]/; # not real symbols, we handle this elsewhere. my $symtype = $headersymstype{$sym}; my $origwikitype = defined $wikitypes{$sym} ? $wikitypes{$sym} : 'md'; # default to MarkDown for new stuff. my $wikitype = (defined $changeformat) ? $changeformat : $origwikitype; die("Unexpected wikitype '$wikitype'") if (($wikitype ne 'mediawiki') and ($wikitype ne 'md') and ($wikitype ne 'manpage')); #print("$sym\n"); next; $wordwrap_mode = $wikitype; my $raw = $headersyms{$sym}; # raw doxygen text with comment characters stripped from start/end and start of each line. next if not defined $raw; $raw =~ s/\A\s*\\brief\s+//; # Technically we don't need \brief (please turn on JAVADOC_AUTOBRIEF if you use Doxygen), so just in case one is present, strip it. my @doxygenlines = split /\n/, $raw; my $brief = ''; while (@doxygenlines) { last if $doxygenlines[0] =~ /\A\\/; # some sort of doxygen command, assume we're past the general remarks. last if $doxygenlines[0] =~ /\A\s*\Z/; # blank line? End of paragraph, done. my $l = shift @doxygenlines; chomp($l); $l =~ s/\A\s*//; $l =~ s/\s*\Z//; $brief .= "$l "; } $brief =~ s/\s+\Z//; $brief =~ s/\A(.*?\.) /$1\n\n/; # \brief should only be one sentence, delimited by a period+space. Split if necessary. my @briefsplit = split /\n/, $brief; next if not defined $briefsplit[0]; # No brief text? Probably a bogus Doxygen comment, skip it. $brief = wikify($wikitype, shift @briefsplit) . "\n"; @doxygenlines = (@briefsplit, @doxygenlines); my $remarks = ''; while (@doxygenlines) { last if $doxygenlines[0] =~ /\A\\/; # some sort of doxygen command, assume we're past the general remarks. my $l = shift @doxygenlines; $remarks .= "$l\n"; } #print("REMARKS:\n\n $remarks\n\n"); $remarks = wordwrap(wikify($wikitype, $remarks)); $remarks =~ s/\A\s*//; $remarks =~ s/\s*\Z//; my $decl = $headerdecls{$sym}; my $syntax = ''; if ($wikitype eq 'mediawiki') { $syntax = "\n$decl\n"; } elsif ($wikitype eq 'md') { $decl =~ s/\n+\Z//; $syntax = "```c\n$decl\n```\n"; } else { die("Expected wikitype '$wikitype'"); } my %sections = (); $sections{'[Brief]'} = $brief; # include this section even if blank so we get a title line. $sections{'Remarks'} = "$remarks\n" if $remarks ne ''; $sections{'Syntax'} = $syntax; my %params = (); # have to parse these and build up the wiki tables after, since Markdown needs to know the length of the largest string. :/ my @paramsorder = (); my $fnsigparams = $headersymsparaminfo{$sym}; while (@doxygenlines) { my $l = shift @doxygenlines; # We allow param/field/value interchangeably, even if it doesn't make sense. The next --copy-to-headers will correct it anyhow. if ($l =~ /\A\\(param|field|value)\s+(.*?)\s+(.*)\Z/) { my $arg = $2; my $desc = $3; while (@doxygenlines) { my $subline = $doxygenlines[0]; $subline =~ s/\A\s*//; last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. shift @doxygenlines; # dump this line from the array; we're using it. if ($subline eq '') { # empty line, make sure it keeps the newline char. $desc .= "\n"; } else { $desc .= " $subline"; } } $desc =~ s/[\s\n]+\Z//ms; # Validate this param. if (defined($params{$arg})) { print STDERR "WARNING: Symbol '$sym' has multiple '\\param $arg' declarations! Only keeping the first one!\n"; } elsif (defined $fnsigparams) { my $found = 0; for (my $i = 0; $i < scalar(@$fnsigparams); $i += 2) { $found = 1, last if (@$fnsigparams[$i] eq $arg); } if (!$found) { print STDERR "WARNING: Symbol '$sym' has a '\\param $arg' for a param that doesn't exist. It will be removed!\n"; } } # We need to know the length of the longest string to make Markdown tables, so we just store these off until everything is parsed. $params{$arg} = $desc; push @paramsorder, $arg; } elsif ($l =~ /\A\\r(eturns?)\s+(.*)\Z/) { # !!! FIXME: complain if this isn't a function or macro. my $retstr = "R$1"; # "Return" or "Returns" my $desc = $2; while (@doxygenlines) { my $subline = $doxygenlines[0]; $subline =~ s/\A\s*//; last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. shift @doxygenlines; # dump this line from the array; we're using it. if ($subline eq '') { # empty line, make sure it keeps the newline char. $desc .= "\n"; } else { $desc .= " $subline"; } } $desc =~ s/[\s\n]+\Z//ms; # Make sure the \returns info is valid. my $rettype = $headersymsrettype{$sym}; die("Don't have a rettype for '$sym' for some reason!") if (($symtype == 1) && (not defined($rettype))); if (defined($sections{'Return Value'})) { print STDERR "WARNING: Symbol '$sym' has multiple '\\return' declarations! Only keeping the first one!\n"; } elsif (($symtype != 1) && ($symtype != 2) && ($symtype != 5)) { # !!! FIXME: if 5, make sure it's a function pointer typedef! print STDERR "WARNING: Symbol '$sym' has a '\\return' declaration but isn't a function or macro! Removing it!\n"; } elsif (($symtype == 1) && ($headersymsrettype{$sym} eq 'void')) { print STDERR "WARNING: Function '$sym' has a '\\returns' declaration but function returns void! Removing it!\n"; } else { my $rettypestr = defined($rettype) ? ('(' . wikify($wikitype, $rettype) . ') ') : ''; $sections{'Return Value'} = wordwrap("$rettypestr$retstr ". wikify($wikitype, $desc)) . "\n"; } } elsif ($l =~ /\A\\deprecated\s+(.*)\Z/) { my $desc = $1; while (@doxygenlines) { my $subline = $doxygenlines[0]; $subline =~ s/\A\s*//; last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. shift @doxygenlines; # dump this line from the array; we're using it. if ($subline eq '') { # empty line, make sure it keeps the newline char. $desc .= "\n"; } else { $desc .= " $subline"; } } $desc =~ s/[\s\n]+\Z//ms; $sections{'Deprecated'} = wordwrap(wikify($wikitype, $desc)) . "\n"; } elsif ($l =~ /\A\\since\s+(.*)\Z/) { my $desc = $1; while (@doxygenlines) { my $subline = $doxygenlines[0]; $subline =~ s/\A\s*//; last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. shift @doxygenlines; # dump this line from the array; we're using it. if ($subline eq '') { # empty line, make sure it keeps the newline char. $desc .= "\n"; } else { $desc .= " $subline"; } } $desc =~ s/[\s\n]+\Z//ms; $sections{'Version'} = wordwrap(wikify($wikitype, $desc)) . "\n"; } elsif ($l =~ /\A\\threadsafety\s+(.*)\Z/) { my $desc = $1; while (@doxygenlines) { my $subline = $doxygenlines[0]; $subline =~ s/\A\s*//; last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. shift @doxygenlines; # dump this line from the array; we're using it. if ($subline eq '') { # empty line, make sure it keeps the newline char. $desc .= "\n"; } else { $desc .= " $subline"; } } $desc =~ s/[\s\n]+\Z//ms; $sections{'Thread Safety'} = wordwrap(wikify($wikitype, $desc)) . "\n"; } elsif ($l =~ /\A\\sa\s+(.*)\Z/) { my $sa = $1; $sa =~ s/\(\)\Z//; # Convert "SDL_Func()" to "SDL_Func" $sections{'See Also'} = '' if not defined $sections{'See Also'}; if ($wikitype eq 'mediawiki') { $sections{'See Also'} .= ":[[$sa]]\n"; } elsif ($wikitype eq 'md') { $sections{'See Also'} .= "- [$sa]($sa)\n"; } else { die("Expected wikitype '$wikitype'"); } } } # Make sure %params is in the same order as the actual function signature and add C datatypes... my $params_has_c_datatype = 0; my @final_params = (); if (($symtype == 1) && (defined($headersymsparaminfo{$sym}))) { # is a function and we have param info for it... my $fnsigparams = $headersymsparaminfo{$sym}; for (my $i = 0; $i < scalar(@$fnsigparams); $i += 2) { my $paramname = @$fnsigparams[$i]; my $paramdesc = $params{$paramname}; if (defined($paramdesc)) { push @final_params, $paramname; # name push @final_params, @$fnsigparams[$i+1]; # C datatype push @final_params, $paramdesc; # description $params_has_c_datatype = 1 if (defined(@$fnsigparams[$i+1])); } else { print STDERR "WARNING: Symbol '$sym' is missing a '\\param $paramname' declaration!\n"; } } } else { foreach (@paramsorder) { my $paramname = $_; my $paramdesc = $params{$paramname}; if (defined($paramdesc)) { push @final_params, $_; push @final_params, undef; push @final_params, $paramdesc; } } } my $hfiletext = $wikiheaderfiletext; $hfiletext =~ s/\%fname\%/$headersymslocation{$sym}/g; $sections{'Header File'} = "$hfiletext\n"; # Make sure this ends with a double-newline. $sections{'See Also'} .= "\n" if defined $sections{'See Also'}; if (0) { # !!! FIXME: this was a useful hack, but this needs to be generalized if we're going to do this always. # Plug in a \since section if one wasn't listed. if (not defined $sections{'Version'}) { my $symtypename; if ($symtype == 1) { $symtypename = 'function'; } elsif ($symtype == 2) { $symtypename = 'macro'; } elsif ($symtype == 3) { $symtypename = 'struct'; } elsif ($symtype == 4) { $symtypename = 'enum'; } elsif ($symtype == 5) { $symtypename = 'datatype'; } else { die("Unexpected symbol type $symtype!"); } my $str = "This $symtypename is available since SDL 3.0.0."; $sections{'Version'} = wordwrap(wikify($wikitype, $str)) . "\n"; } } # We can build the wiki table now that we have all the data. if (scalar(@final_params) > 0) { my $str = ''; if ($wikitype eq 'mediawiki') { while (scalar(@final_params) > 0) { my $arg = shift @final_params; my $c_datatype = shift @final_params; my $desc = wikify($wikitype, shift @final_params); $c_datatype = '' if not defined $c_datatype; $str .= ($str eq '') ? "{|\n" : "|-\n"; $str .= "|$c_datatype\n" if $params_has_c_datatype; $str .= "|'''$arg'''\n"; $str .= "|$desc\n"; } $str .= "|}\n"; } elsif ($wikitype eq 'md') { my $longest_arg = 0; my $longest_c_datatype = 0; my $longest_desc = 0; my $which = 0; foreach (@final_params) { if ($which == 0) { my $len = length($_); $longest_arg = $len if ($len > $longest_arg); $which = 1; } elsif ($which == 1) { if (defined($_)) { my $len = length(wikify($wikitype, $_)); $longest_c_datatype = $len if ($len > $longest_c_datatype); } $which = 2; } else { my $len = length(wikify($wikitype, $_)); $longest_desc = $len if ($len > $longest_desc); $which = 0; } } # Markdown tables are sort of obnoxious. my $c_datatype_cell; $c_datatype_cell = ($longest_c_datatype > 0) ? ('| ' . (' ' x ($longest_c_datatype)) . ' ') : ''; $str .= $c_datatype_cell . '| ' . (' ' x ($longest_arg+4)) . ' | ' . (' ' x $longest_desc) . " |\n"; $c_datatype_cell = ($longest_c_datatype > 0) ? ('| ' . ('-' x ($longest_c_datatype)) . ' ') : ''; $str .= $c_datatype_cell . '| ' . ('-' x ($longest_arg+4)) . ' | ' . ('-' x $longest_desc) . " |\n"; while (@final_params) { my $arg = shift @final_params; my $c_datatype = shift @final_params; $c_datatype_cell = ''; if ($params_has_c_datatype) { $c_datatype = defined($c_datatype) ? wikify($wikitype, $c_datatype) : ''; $c_datatype_cell = ($longest_c_datatype > 0) ? ("| $c_datatype " . (' ' x ($longest_c_datatype - length($c_datatype)))) : ''; } my $desc = wikify($wikitype, shift @final_params); $str .= $c_datatype_cell . "| **$arg** " . (' ' x ($longest_arg - length($arg))) . "| $desc" . (' ' x ($longest_desc - length($desc))) . " |\n"; } } else { die("Unexpected wikitype!"); # should have checked this elsewhere. } $sections{'Function Parameters'} = $str; } my $path = "$wikipath/$sym.${wikitype}.tmp"; open(FH, '>', $path) or die("Can't open '$path': $!\n"); my $sectionsref = $wikisyms{$sym}; foreach (@standard_wiki_sections) { # drop sections we either replaced or removed from the original wiki's contents. if (not defined $only_wiki_sections{$_}) { delete($$sectionsref{$_}); } } my $wikisectionorderref = $wikisectionorder{$sym}; # Make sure there's a footer in the wiki that puts this function in CategoryAPI... if (not $$sectionsref{'[footer]'}) { $$sectionsref{'[footer]'} = ''; push @$wikisectionorderref, '[footer]'; } # If changing format, convert things that otherwise are passed through unmolested. if (defined $changeformat) { if (($dewikify_mode eq 'md') and ($origwikitype eq 'mediawiki')) { $$sectionsref{'[footer]'} =~ s/\[\[(Category[a-zA-Z0-9_]+)\]\]/[$1]($1)/g; } elsif (($dewikify_mode eq 'mediawiki') and ($origwikitype eq 'md')) { $$sectionsref{'[footer]'} =~ s/\[(Category[a-zA-Z0-9_]+)\]\(.*?\)/[[$1]]/g; } foreach (keys %only_wiki_sections) { my $sect = $_; if (defined $$sectionsref{$sect}) { $$sectionsref{$sect} = wikify($wikitype, dewikify($origwikitype, $$sectionsref{$sect})); } } } if ($symtype != -1) { # Don't do these in category documentation block my $footer = $$sectionsref{'[footer]'}; my $symtypename; if ($symtype == 1) { $symtypename = 'Function'; } elsif ($symtype == 2) { $symtypename = 'Macro'; } elsif ($symtype == 3) { $symtypename = 'Struct'; } elsif ($symtype == 4) { $symtypename = 'Enum'; } elsif ($symtype == 5) { $symtypename = 'Datatype'; } else { die("Unexpected symbol type $symtype!"); } my $symcategory = $headersymscategory{$sym}; if ($wikitype eq 'mediawiki') { $footer =~ s/\[\[CategoryAPI\]\],?\s*//g; $footer =~ s/\[\[CategoryAPI${symtypename}\]\],?\s*//g; $footer =~ s/\[\[Category${symcategory}\]\],?\s*//g if defined $symcategory; $footer = "[[CategoryAPI]], [[CategoryAPI$symtypename]]" . (defined $symcategory ? ", [[Category$symcategory]]" : '') . (($footer eq '') ? "\n" : ", $footer"); } elsif ($wikitype eq 'md') { $footer =~ s/\[CategoryAPI\]\(CategoryAPI\),?\s*//g; $footer =~ s/\[CategoryAPI${symtypename}\]\(CategoryAPI${symtypename}\),?\s*//g; $footer =~ s/\[Category${symcategory}\]\(Category${symcategory}\),?\s*//g if defined $symcategory; $footer = "[CategoryAPI](CategoryAPI), [CategoryAPI$symtypename](CategoryAPI$symtypename)" . (defined $symcategory ? ", [Category$symcategory](Category$symcategory)" : '') . (($footer eq '') ? '' : ', ') . $footer; } else { die("Unexpected wikitype '$wikitype'"); } $$sectionsref{'[footer]'} = $footer; if (defined $wikipreamble) { my $wikified_preamble = wikify($wikitype, $wikipreamble); if ($wikitype eq 'mediawiki') { print FH "====== $wikified_preamble ======\n"; } elsif ($wikitype eq 'md') { print FH "###### $wikified_preamble\n"; } else { die("Unexpected wikitype '$wikitype'"); } } } my $prevsectstr = ''; my @ordered_sections = (@standard_wiki_sections, defined $wikisectionorderref ? @$wikisectionorderref : ()); # this copies the arrays into one. foreach (@ordered_sections) { my $sect = $_; next if $sect eq '[start]'; next if (not defined $sections{$sect} and not defined $$sectionsref{$sect}); my $section = defined $sections{$sect} ? $sections{$sect} : $$sectionsref{$sect}; if ($sect eq '[footer]') { # Make sure previous section ends with two newlines. if (substr($prevsectstr, -1) ne "\n") { print FH "\n\n"; } elsif (substr($prevsectstr, -2) ne "\n\n") { print FH "\n"; } print FH "----\n"; # It's the same in Markdown and MediaWiki. } elsif ($sect eq '[Brief]') { if ($wikitype eq 'mediawiki') { print FH "= $sym =\n\n"; } elsif ($wikitype eq 'md') { print FH "# $sym\n\n"; } else { die("Unexpected wikitype '$wikitype'"); } } else { my $sectname = $sect; if ($sectname eq 'Function Parameters') { # We use this same table for different things depending on what we're documenting, so rename it now. if (($symtype == 1) || ($symtype == 5)) { # function (or typedef, in case it's a function pointer type). } elsif ($symtype == 2) { # macro $sectname = 'Macro Parameters'; } elsif ($symtype == 3) { # struct/union $sectname = 'Fields'; } elsif ($symtype == 4) { # enum $sectname = 'Values'; } else { die("Unexpected symtype $symtype"); } } if ($symtype != -1) { # Not for category documentation block if ($wikitype eq 'mediawiki') { print FH "\n== $sectname ==\n\n"; } elsif ($wikitype eq 'md') { print FH "\n## $sectname\n\n"; } else { die("Unexpected wikitype '$wikitype'"); } } } my $sectstr = defined $sections{$sect} ? $sections{$sect} : $$sectionsref{$sect}; print FH $sectstr; $prevsectstr = $sectstr; # make sure these don't show up twice. delete($sections{$sect}); delete($$sectionsref{$sect}); } print FH "\n\n"; close(FH); if (defined $changeformat and ($origwikitype ne $wikitype)) { system("cd '$wikipath' ; git mv '$_.${origwikitype}' '$_.${wikitype}'"); unlink("$wikipath/$_.${origwikitype}"); } rename($path, "$wikipath/$_.${wikitype}") or die("Can't rename '$path' to '$wikipath/$_.${wikitype}': $!\n"); } # Write out simple redirector pages if they don't already exist. foreach (keys %referenceonly) { my $sym = $_; my $refersto = $referenceonly{$sym}; my $path = "$wikipath/$sym.md"; # we only do Markdown for these. next if (-f $path); # don't overwrite if it already exists. Delete the file if you need a rebuild! open(FH, '>', $path) or die("Can't open '$path': $!\n"); if (defined $wikipreamble) { my $wikified_preamble = wikify('md', $wikipreamble); print FH "###### $wikified_preamble\n"; } print FH "# $sym\n\nPlease refer to [$refersto]($refersto) for details.\n\n"; print FH "----\n"; print FH "[CategoryAPI](CategoryAPI), [CategoryAPIMacro](CategoryAPIMacro)\n\n"; close(FH); } # Write out Category pages... foreach (keys %headercategorydocs) { my $cat = $_; my $sym = $headercategorydocs{$cat}; # fake symbol my $raw = $headersyms{$sym}; # raw doxygen text with comment characters stripped from start/end and start of each line. my $wikitype = defined($wikitypes{$sym}) ? $wikitypes{$sym} : 'md'; my $path = "$wikipath/Category$cat.$wikitype"; $raw = wordwrap(wikify($wikitype, $raw)); my $tmppath = "$path.tmp"; open(FH, '>', $tmppath) or die("Can't open '$tmppath': $!\n"); print FH "$raw\n\n"; if (! -f $path) { # Doesn't exist at all? Write out a template file. # If writing from scratch, it's always a Markdown file. die("Unexpected wikitype '$wikitype'!") if $wikitype ne 'md'; print FH <<__EOF__ ## Functions ## Datatypes ## Structs ## Enums ## Macros ---- [CategoryAPICategory](CategoryAPICategory) __EOF__ ; } else { my $endstr = $wikisyms{$sym}->{'[footer]'}; if (defined($endstr)) { print FH $endstr; } } close(FH); rename($tmppath, $path) or die("Can't rename '$tmppath' to '$path': $!\n"); } # Write out READMEs... if (defined $readmepath) { if ( -d $readmepath ) { mkdir($wikireadmepath); # just in case opendir(DH, $readmepath) or die("Can't opendir '$readmepath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; if ($dent =~ /\AREADME\-(.*?\.md)\Z/) { # we only bridge Markdown files here. my $wikifname = $1; next if $wikifname eq 'FrontPage.md'; filecopy("$readmepath/$dent", "$wikireadmepath/$wikifname", "\n"); } } closedir(DH); my @pages = (); opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; if ($dent =~ /\A(.*?)\.(mediawiki|md)\Z/) { my $wikiname = $1; next if $wikiname eq 'FrontPage'; push @pages, $wikiname; } } closedir(DH); open(FH, '>', "$wikireadmepath/FrontPage.md") or die("Can't open '$wikireadmepath/FrontPage.md': $!\n"); print FH "# All READMEs available here\n\n"; foreach (sort @pages) { my $wikiname = $_; print FH "- [$wikiname]($wikiname)\n"; } close(FH); } } } elsif ($copy_direction == -2) { # --copy-to-manpages # This only takes from the wiki data, since it has sections we omit from the headers, like code examples. File::Path::make_path("$manpath/man3"); $dewikify_mode = 'manpage'; $wordwrap_mode = 'manpage'; my $introtxt = ''; if (0) { open(FH, '<', "$srcpath/LICENSE.txt") or die("Can't open '$srcpath/LICENSE.txt': $!\n"); while () { chomp; $introtxt .= ".\\\" $_\n"; } close(FH); } if (!$gitrev) { $gitrev = `cd "$srcpath" ; git rev-list HEAD~..`; chomp($gitrev); } # !!! FIXME open(FH, '<', "$srcpath/$versionfname") or die("Can't open '$srcpath/$versionfname': $!\n"); my $majorver = 0; my $minorver = 0; my $microver = 0; while () { chomp; if (/$versionmajorregex/) { $majorver = int($1); } elsif (/$versionminorregex/) { $minorver = int($1); } elsif (/$versionmicroregex/) { $microver = int($1); } } close(FH); my $fullversion = "$majorver.$minorver.$microver"; foreach (keys %headersyms) { my $sym = $_; next if not defined $wikisyms{$sym}; # don't have a page for that function, skip it. next if $sym =~ /\A\[category documentation\]/; # not real symbols my $symtype = $headersymstype{$sym}; my $wikitype = $wikitypes{$sym}; my $sectionsref = $wikisyms{$sym}; my $remarks = $sectionsref->{'Remarks'}; my $params = $sectionsref->{'Function Parameters'}; my $returns = $sectionsref->{'Return Value'}; my $version = $sectionsref->{'Version'}; my $threadsafety = $sectionsref->{'Thread Safety'}; my $related = $sectionsref->{'See Also'}; my $examples = $sectionsref->{'Code Examples'}; my $deprecated = $sectionsref->{'Deprecated'}; my $headerfile = $manpageheaderfiletext; $headerfile =~ s/\%fname\%/$headersymslocation{$sym}/g; $headerfile .= "\n"; my $mansection; my $mansectionname; if (($symtype == 1) || ($symtype == 2)) { # functions or macros $mansection = '3'; $mansectionname = 'FUNCTIONS'; } elsif (($symtype >= 3) && ($symtype <= 5)) { # struct/union/enum/typedef $mansection = '3type'; $mansectionname = 'DATATYPES'; } else { die("Unexpected symtype $symtype"); } my $brief = $sectionsref->{'[Brief]'}; my $decl = $headerdecls{$sym}; my $str = ''; $brief = "$brief"; $brief =~ s/\A[\s\n]*\= .*? \=\s*?\n+//ms; $brief =~ s/\A[\s\n]*\=\= .*? \=\=\s*?\n+//ms; $brief =~ s/\A(.*?\.) /$1\n/; # \brief should only be one sentence, delimited by a period+space. Split if necessary. my @briefsplit = split /\n/, $brief; $brief = shift @briefsplit; $brief = dewikify($wikitype, $brief); if (defined $remarks) { $remarks = dewikify($wikitype, join("\n", @briefsplit) . $remarks); } $str .= $introtxt; $str .= ".\\\" This manpage content is licensed under Creative Commons\n"; $str .= ".\\\" Attribution 4.0 International (CC BY 4.0)\n"; $str .= ".\\\" https://creativecommons.org/licenses/by/4.0/\n"; $str .= ".\\\" This manpage was generated from ${projectshortname}'s wiki page for $sym:\n"; $str .= ".\\\" $wikiurl/$sym\n"; $str .= ".\\\" Generated with SDL/build-scripts/wikiheaders.pl\n"; $str .= ".\\\" revision $gitrev\n" if $gitrev ne ''; $str .= ".\\\" Please report issues in this manpage's content at:\n"; $str .= ".\\\" $bugreporturl\n"; $str .= ".\\\" Please report issues in the generation of this manpage from the wiki at:\n"; $str .= ".\\\" https://github.com/libsdl-org/SDL/issues/new?title=Misgenerated%20manpage%20for%20$sym\n"; $str .= ".\\\" $projectshortname can be found at $projecturl\n"; # Define a .URL macro. The "www.tmac" thing decides if we're using GNU roff (which has a .URL macro already), and if so, overrides the macro we just created. # This wizadry is from https://web.archive.org/web/20060102165607/http://people.debian.org/~branden/talks/wtfm/wtfm.pdf $str .= ".de URL\n"; $str .= '\\$2 \(laURL: \\$1 \(ra\\$3' . "\n"; $str .= "..\n"; $str .= '.if \n[.g] .mso www.tmac' . "\n"; $str .= ".TH $sym $mansection \"$projectshortname $fullversion\" \"$projectfullname\" \"$projectshortname$majorver $mansectionname\"\n"; $str .= ".SH NAME\n"; $str .= "$sym"; $str .= " \\- $brief" if (defined $brief); $str .= "\n"; if (defined $deprecated) { $str .= ".SH DEPRECATED\n"; $str .= dewikify($wikitype, $deprecated) . "\n"; } if (defined $headerfile) { $str .= ".SH HEADER FILE\n"; $str .= dewikify($wikitype, $headerfile) . "\n"; } $str .= ".SH SYNOPSIS\n"; $str .= ".nf\n"; $str .= ".B #include \\(dq$mainincludefname\\(dq\n"; $str .= ".PP\n"; my @decllines = split /\n/, $decl; foreach (@decllines) { $str .= ".BI \"$_\n"; } $str .= ".fi\n"; if (defined $remarks) { $str .= ".SH DESCRIPTION\n"; $str .= $remarks . "\n"; } if (defined $params) { if (($symtype == 1) || ($symtype == 5)) { $str .= ".SH FUNCTION PARAMETERS\n"; } elsif ($symtype == 2) { # macro $str .= ".SH MACRO PARAMETERS\n"; } elsif ($symtype == 3) { # struct/union $str .= ".SH FIELDS\n"; } elsif ($symtype == 4) { # enum $str .= ".SH VALUES\n"; } else { die("Unexpected symtype $symtype"); } my @lines = split /\n/, $params; if ($wikitype eq 'mediawiki') { die("Unexpected data parsing MediaWiki table") if (shift @lines ne '{|'); # Dump the '{|' start while (scalar(@lines) >= 3) { my $c_datatype = shift @lines; my $name = shift @lines; my $desc = shift @lines; my $terminator; # the '|-' or '|}' line. if (($desc eq '|-') or ($desc eq '|}') or (not $desc =~ /\A\|/)) { # we seem to be out of cells, which means there was no datatype column on this one. $terminator = $desc; $desc = $name; $name = $c_datatype; $c_datatype = ''; } else { $terminator = shift @lines; } last if ($terminator ne '|-') and ($terminator ne '|}'); # we seem to have run out of table. $name =~ s/\A\|\s*//; $name =~ s/\A\*\*(.*?)\*\*/$1/; $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; $desc =~ s/\A\|\s*//; $desc = dewikify($wikitype, $desc); #print STDERR "SYM: $sym CDATATYPE: $c_datatype NAME: $name DESC: $desc TERM: $terminator\n"; $str .= ".TP\n"; $str .= ".I $name\n"; $str .= "$desc\n"; } } elsif ($wikitype eq 'md') { my $l; $l = shift @lines; die("Unexpected data parsing Markdown table") if (not $l =~ /\A(\s*\|)?\s*\|\s*\|\s*\|\s*\Z/); $l = shift @lines; die("Unexpected data parsing Markdown table") if (not $l =~ /\A\s*(\|\s*\-*\s*)?\|\s*\-*\s*\|\s*\-*\s*\|\s*\Z/); while (scalar(@lines) >= 1) { $l = shift @lines; my $name; my $desc; if ($l =~ /\A\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*\Z/) { # c datatype is $1, but we don't care about it here. $name = $2; $desc = $3; } elsif ($l =~ /\A\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*\Z/) { $name = $1; $desc = $2; } else { last; # we seem to have run out of table. } $name =~ s/\A\*\*(.*?)\*\*/$1/; $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; $desc = dewikify($wikitype, $desc); $str .= ".TP\n"; $str .= ".I $name\n"; $str .= "$desc\n"; } } else { die("write me"); } } if (defined $returns) { $returns = dewikify($wikitype, $returns); $returns =~ s/\A\(.*?\)\s*//; # Chop datatype in parentheses off the front. $str .= ".SH RETURN VALUE\n"; $str .= "$returns\n"; } if (defined $examples) { $str .= ".SH CODE EXAMPLES\n"; $dewikify_manpage_code_indent = 0; $str .= dewikify($wikitype, $examples) . "\n"; $dewikify_manpage_code_indent = 1; } if (defined $threadsafety) { $str .= ".SH THREAD SAFETY\n"; $str .= dewikify($wikitype, $threadsafety) . "\n"; } if (defined $version) { $str .= ".SH AVAILABILITY\n"; $str .= dewikify($wikitype, $version) . "\n"; } if (defined $related) { $str .= ".SH SEE ALSO\n"; # !!! FIXME: lots of code duplication in all of these. my $v = dewikify($wikitype, $related); my @desclines = split /\n/, $v; my $nextstr = ''; foreach (@desclines) { s/\(\)\Z//; # Convert "SDL_Func()" to "SDL_Func" s/\[\[(.*?)\]\]/$1/; # in case some wikilinks remain. s/\[(.*?)\]\(.*?\)/$1/; # in case some wikilinks remain. s/\A\*\s*\Z//; s/\A\/*//; s/\A\.BR\s+//; # dewikify added this, but we want to handle it. s/\A\.I\s+//; # dewikify added this, but we want to handle it. s/\A\s*[\:\*\-]\s*//; s/\A\s+//; s/\s+\Z//; next if $_ eq ''; my $seealso_symtype = $headersymstype{$_}; my $seealso_mansection = '3'; if (defined($seealso_symtype) && ($seealso_symtype >= 3) && ($seealso_symtype <= 5)) { # struct/union/enum/typedef $seealso_mansection = '3type'; } $str .= "$nextstr.BR $_ ($seealso_mansection)"; $nextstr = ",\n"; } $str .= "\n"; } if (0) { $str .= ".SH COPYRIGHT\n"; $str .= "This manpage is licensed under\n"; $str .= ".UR https://creativecommons.org/licenses/by/4.0/\n"; $str .= "Creative Commons Attribution 4.0 International (CC BY 4.0)\n"; $str .= ".UE\n"; $str .= ".PP\n"; $str .= "This manpage was generated from\n"; $str .= ".UR $wikiurl/$sym\n"; $str .= "${projectshortname}'s wiki\n"; $str .= ".UE\n"; $str .= "using SDL/build-scripts/wikiheaders.pl"; $str .= " revision $gitrev" if $gitrev ne ''; $str .= ".\n"; $str .= "Please report issues in this manpage at\n"; $str .= ".UR $bugreporturl\n"; $str .= "our bugtracker!\n"; $str .= ".UE\n"; } my $path = "$manpath/man3/$_.$mansection"; my $tmppath = "$path.tmp"; open(FH, '>', $tmppath) or die("Can't open '$tmppath': $!\n"); print FH $str; close(FH); rename($tmppath, $path) or die("Can't rename '$tmppath' to '$path': $!\n"); } } elsif ($copy_direction == -4) { # --copy-to-latex # This only takes from the wiki data, since it has sections we omit from the headers, like code examples. print STDERR "\n(The --copy-to-latex code is known to not be ready for serious use; send patches, not bug reports, please.)\n\n"; $dewikify_mode = 'LaTeX'; $wordwrap_mode = 'LaTeX'; # !!! FIXME: code duplication with --copy-to-manpages section. my $introtxt = ''; if (0) { open(FH, '<', "$srcpath/LICENSE.txt") or die("Can't open '$srcpath/LICENSE.txt': $!\n"); while () { chomp; $introtxt .= ".\\\" $_\n"; } close(FH); } if (!$gitrev) { $gitrev = `cd "$srcpath" ; git rev-list HEAD~..`; chomp($gitrev); } # !!! FIXME open(FH, '<', "$srcpath/$versionfname") or die("Can't open '$srcpath/$versionfname': $!\n"); my $majorver = 0; my $minorver = 0; my $microver = 0; while () { chomp; if (/$versionmajorregex/) { $majorver = int($1); } elsif (/$versionminorregex/) { $minorver = int($1); } elsif (/$versionmicroregex/) { $microver = int($1); } } close(FH); my $fullversion = "$majorver.$minorver.$microver"; my $latex_fname = "$srcpath/$projectshortname.tex"; my $latex_tmpfname = "$latex_fname.tmp"; open(TEXFH, '>', "$latex_tmpfname") or die("Can't open '$latex_tmpfname' for writing: $!\n"); print TEXFH <<__EOF__ \\documentclass{book} \\usepackage{listings} \\usepackage{color} \\usepackage{hyperref} \\definecolor{dkgreen}{rgb}{0,0.6,0} \\definecolor{gray}{rgb}{0.5,0.5,0.5} \\definecolor{mauve}{rgb}{0.58,0,0.82} \\setcounter{secnumdepth}{0} \\lstset{frame=tb, language=C, aboveskip=3mm, belowskip=3mm, showstringspaces=false, columns=flexible, basicstyle={\\small\\ttfamily}, numbers=none, numberstyle=\\tiny\\color{gray}, keywordstyle=\\color{blue}, commentstyle=\\color{dkgreen}, stringstyle=\\color{mauve}, breaklines=true, breakatwhitespace=true, tabsize=3 } \\begin{document} \\frontmatter \\title{$projectfullname $majorver.$minorver.$microver Reference Manual} \\author{The $projectshortname Developers} \\maketitle \\mainmatter __EOF__ ; # !!! FIXME: Maybe put this in the book intro? print TEXFH $introtxt; # Sort symbols by symbol type, then alphabetically. my @headersymskeys = sort { my $symtypea = $headersymstype{$a}; my $symtypeb = $headersymstype{$b}; $symtypea = 3 if ($symtypea > 3); $symtypeb = 3 if ($symtypeb > 3); my $rc = $symtypea <=> $symtypeb; if ($rc == 0) { $rc = lc($a) cmp lc($b); } return $rc; } keys %headersyms; my $current_symtype = 0; my $current_chapter = ''; foreach (@headersymskeys) { my $sym = $_; next if not defined $wikisyms{$sym}; # don't have a page for that function, skip it. next if $sym =~ /\A\[category documentation\]/; # not real symbols. my $symtype = $headersymstype{$sym}; my $wikitype = $wikitypes{$sym}; my $sectionsref = $wikisyms{$sym}; my $remarks = $sectionsref->{'Remarks'}; my $params = $sectionsref->{'Function Parameters'}; my $returns = $sectionsref->{'Return Value'}; my $version = $sectionsref->{'Version'}; my $threadsafety = $sectionsref->{'Thread Safety'}; my $related = $sectionsref->{'See Also'}; my $examples = $sectionsref->{'Code Examples'}; my $deprecated = $sectionsref->{'Deprecated'}; my $headerfile = $manpageheaderfiletext; $headerfile =~ s/\%fname\%/$headersymslocation{$sym}/g; $headerfile .= "\n"; my $brief = $sectionsref->{'[Brief]'}; my $decl = $headerdecls{$sym}; my $str = ''; if ($current_symtype != $symtype) { my $newchapter = ''; if ($symtype == 1) { $newchapter = 'Functions'; } elsif ($symtype == 2) { $newchapter = 'Macros'; } else { $newchapter = 'Datatypes'; } if ($current_chapter ne $newchapter) { $str .= "\n\n\\chapter{$projectshortname $newchapter}\n\n\\clearpage\n\n"; $current_chapter = $newchapter; } $current_symtype = $symtype; } $brief = "$brief"; $brief =~ s/\A[\s\n]*\= .*? \=\s*?\n+//ms; $brief =~ s/\A[\s\n]*\=\= .*? \=\=\s*?\n+//ms; $brief =~ s/\A(.*?\.) /$1\n/; # \brief should only be one sentence, delimited by a period+space. Split if necessary. my @briefsplit = split /\n/, $brief; $brief = shift @briefsplit; $brief = dewikify($wikitype, $brief); if (defined $remarks) { $remarks = dewikify($wikitype, join("\n", @briefsplit) . $remarks); } my $escapedsym = escLaTeX($sym); $str .= "\\hypertarget{$sym}{%\n\\section{$escapedsym}\\label{$sym}}\n\n"; $str .= $brief if (defined $brief); $str .= "\n\n"; if (defined $deprecated) { $str .= "\\subsection{Deprecated}\n\n"; $str .= dewikify($wikitype, $deprecated) . "\n"; } if (defined $headerfile) { $str .= "\\subsection{Header File}\n\n"; $str .= dewikify($wikitype, $headerfile) . "\n"; } $str .= "\\subsection{Syntax}\n\n"; $str .= "\\begin{lstlisting}\n$decl\n\\end{lstlisting}\n"; if (defined $params) { if (($symtype == 1) || ($symtype == 5)) { $str .= "\\subsection{Function Parameters}\n\n"; } elsif ($symtype == 2) { # macro $str .= "\\subsection{Macro Parameters}\n\n"; } elsif ($symtype == 3) { # struct/union $str .= "\\subsection{Fields}\n\n"; } elsif ($symtype == 4) { # enum $str .= "\\subsection{Values}\n\n"; } else { die("Unexpected symtype $symtype"); } $str .= "\\begin{center}\n"; $str .= " \\begin{tabular}{ | l | p{0.75\\textwidth} |}\n"; $str .= " \\hline\n"; # !!! FIXME: this table parsing has gotten complicated and is pasted three times in this file; move it to a subroutine! my @lines = split /\n/, $params; if ($wikitype eq 'mediawiki') { die("Unexpected data parsing MediaWiki table") if (shift @lines ne '{|'); # Dump the '{|' start while (scalar(@lines) >= 3) { my $name = shift @lines; my $desc = shift @lines; my $terminator = shift @lines; # the '|-' or '|}' line. last if ($terminator ne '|-') and ($terminator ne '|}'); # we seem to have run out of table. $name =~ s/\A\|\s*//; $name =~ s/\A\*\*(.*?)\*\*/$1/; $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; $name = escLaTeX($name); $desc =~ s/\A\|\s*//; $desc = dewikify($wikitype, $desc); #print STDERR "FN: $sym NAME: $name DESC: $desc TERM: $terminator\n"; $str .= " \\textbf{$name} & $desc \\\\ \\hline\n"; } } elsif ($wikitype eq 'md') { my $l; $l = shift @lines; die("Unexpected data parsing Markdown table") if (not $l =~ /\A(\s*\|)?\s*\|\s*\|\s*\|\s*\Z/); $l = shift @lines; die("Unexpected data parsing Markdown table") if (not $l =~ /\A\s*(\|\s*\-*\s*)?\|\s*\-*\s*\|\s*\-*\s*\|\s*\Z/); while (scalar(@lines) >= 1) { $l = shift @lines; my $name; my $desc; if ($l =~ /\A\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*\Z/) { # c datatype is $1, but we don't care about it here. $name = $2; $desc = $3; } elsif ($l =~ /\A\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*\Z/) { $name = $1; $desc = $2; } else { last; # we seem to have run out of table. } $name =~ s/\A\*\*(.*?)\*\*/$1/; $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; $name = escLaTeX($name); $desc = dewikify($wikitype, $desc); $str .= " \\textbf{$name} & $desc \\\\ \\hline\n"; } } else { die("write me"); } $str .= " \\end{tabular}\n"; $str .= "\\end{center}\n"; } if (defined $returns) { $returns = dewikify($wikitype, $returns); $returns =~ s/\A\(.*?\)\s*//; # Chop datatype in parentheses off the front. $str .= "\\subsection{Return Value}\n\n"; $str .= "$returns\n"; } if (defined $remarks) { $str .= "\\subsection{Remarks}\n\n"; $str .= $remarks . "\n"; } if (defined $examples) { $str .= "\\subsection{Code Examples}\n\n"; $dewikify_manpage_code_indent = 0; $str .= dewikify($wikitype, $examples) . "\n"; $dewikify_manpage_code_indent = 1; } if (defined $threadsafety) { $str .= "\\subsection{Thread Safety}\n\n"; $str .= dewikify($wikitype, $threadsafety) . "\n"; } if (defined $version) { $str .= "\\subsection{Version}\n\n"; $str .= dewikify($wikitype, $version) . "\n"; } if (defined $related) { $str .= "\\subsection{See Also}\n\n"; $str .= "\\begin{itemize}\n"; # !!! FIXME: lots of code duplication in all of these. my $v = dewikify($wikitype, $related); my @desclines = split /\n/, $v; my $nextstr = ''; foreach (@desclines) { s/\(\)\Z//; # Convert "SDL_Func()" to "SDL_Func" s/\[\[(.*?)\]\]/$1/; # in case some wikilinks remain. s/\[(.*?)\]\(.*?\)/$1/; # in case some wikilinks remain. s/\A\*\s*\Z//; s/\A\s*\\item\s*//; s/\A\/*//; s/\A\s*[\:\*\-]\s*//; s/\A\s+//; s/\s+\Z//; next if $_ eq ''; next if $_ eq '\begin{itemize}'; next if $_ eq '\end{itemize}'; $str .= " \\item $_\n"; } $str .= "\\end{itemize}\n"; $str .= "\n"; } # !!! FIXME: Maybe put copyright in the book intro? if (0) { $str .= ".SH COPYRIGHT\n"; $str .= "This manpage is licensed under\n"; $str .= ".UR https://creativecommons.org/licenses/by/4.0/\n"; $str .= "Creative Commons Attribution 4.0 International (CC BY 4.0)\n"; $str .= ".UE\n"; $str .= ".PP\n"; $str .= "This manpage was generated from\n"; $str .= ".UR $wikiurl/$sym\n"; $str .= "${projectshortname}'s wiki\n"; $str .= ".UE\n"; $str .= "using SDL/build-scripts/wikiheaders.pl"; $str .= " revision $gitrev" if $gitrev ne ''; $str .= ".\n"; $str .= "Please report issues in this manpage at\n"; $str .= ".UR $bugreporturl\n"; $str .= "our bugtracker!\n"; $str .= ".UE\n"; } $str .= "\\clearpage\n\n"; print TEXFH $str; } print TEXFH "\\end{document}\n\n"; close(TEXFH); rename($latex_tmpfname, $latex_fname) or die("Can't rename '$latex_tmpfname' to '$latex_fname': $!\n"); } elsif ($copy_direction == -3) { # --report-coverage-gaps foreach (@coverage_gap) { print("$_\n"); } } # end of wikiheaders.pl ... libsdl3-mixer-3~git20250523~daf0503+ds/cmake/000077500000000000000000000000001501405355700202715ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/cmake/CPackProjectConfig.cmake.in000066400000000000000000000025321501405355700253400ustar00rootroot00000000000000if(CPACK_PACKAGE_FILE_NAME MATCHES ".*-src$") message(FATAL_ERROR "Creating source archives is not supported.") endif() set(PROJECT_NAME "@PROJECT_NAME@") set(PROJECT_VERSION "@PROJECT_VERSION@") set(PROJECT_SOURCE_DIR "@PROJECT_SOURCE_DIR@") set(SDL_CMAKE_PLATFORM "@SDL_CMAKE_PLATFORM@") set(SDL_CPU_NAMES "@SDL_CPU_NAMES@") list(SORT SDL_CPU_NAMES) string(TOLOWER "${SDL_CMAKE_PLATFORM}" SDL_CMAKE_PLATFORM) string(TOLOWER "${SDL_CPU_NAMES}" SDL_CPU_NAMES) if(lower_sdl_cmake_platform STREQUAL lower_sdl_cpu_names) set(SDL_CPU_NAMES_WITH_DASHES) endif() string(REPLACE ";" "-" SDL_CPU_NAMES_WITH_DASHES "${SDL_CPU_NAMES}") if(SDL_CPU_NAMES_WITH_DASHES) set(SDL_CPU_NAMES_WITH_DASHES "-${SDL_CPU_NAMES_WITH_DASHES}") endif() set(MSVC @MSVC@) set(MINGW @MINGW@) if(MSVC) set(SDL_CMAKE_PLATFORM "${SDL_CMAKE_PLATFORM}-VC") elseif(MINGW) set(SDL_CMAKE_PLATFORM "${SDL_CMAKE_PLATFORM}-mingw") endif() set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${SDL_CMAKE_PLATFORM}${SDL_CPU_NAMES_WITH_DASHES}") if(CPACK_GENERATOR STREQUAL "DragNDrop") set(CPACK_DMG_VOLUME_NAME "@PROJECT_NAME@ @PROJECT_VERSION@") # FIXME: use pre-built/create .DS_Store through AppleScript (CPACK_DMG_DS_STORE/CPACK_DMG_DS_STORE_SETUP_SCRIPT) set(CPACK_DMG_DS_STORE "${PROJECT_SOURCE_DIR}/Xcode/SDL/pkg-support/resources/SDL_DS_Store") endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindFLAC.cmake000066400000000000000000000025341501405355700226050ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_FLAC QUIET flac) find_library(FLAC_LIBRARY NAMES FLAC HINTS ${PC_FLAC_LIBDIR} ) find_path(FLAC_INCLUDE_PATH NAMES FLAC/all.h HINTS ${PC_FLAC_INCLUDEDIR} ) if(PC_FLAC_FOUND) get_flags_from_pkg_config("${FLAC_LIBRARY}" "PC_FLAC" "_flac") endif() set(FLAC_COMPILE_OPTIONS "${_flac_compile_options}" CACHE STRING "Extra compile options of FLAC") set(FLAC_LINK_LIBRARIES "${_flac_link_libraries}" CACHE STRING "Extra link libraries of FLAC") set(FLAC_LINK_OPTIONS "${_flac_link_options}" CACHE STRING "Extra link flags of FLAC") set(FLAC_LINK_DIRECTORIES "${_flac_link_directories}" CACHE PATH "Extra link directories of FLAC") find_package_handle_standard_args(FLAC REQUIRED_VARS FLAC_LIBRARY FLAC_INCLUDE_PATH ) if(FLAC_FOUND) if(NOT TARGET FLAC::FLAC) add_library(FLAC::FLAC UNKNOWN IMPORTED) set_target_properties(FLAC::FLAC PROPERTIES IMPORTED_LOCATION "${FLAC_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${FLAC_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${FLAC_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${FLAC_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${FLAC_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${FLAC_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindFluidSynth.cmake000066400000000000000000000031371501405355700241710ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_FLUIDSYNTH QUIET fluidsynth) find_library(FluidSynth_LIBRARY NAMES fluidsynth libfluidsynth HINTS ${PC_FLUIDSYNTH_LIBDIR} ) find_path(FluidSynth_INCLUDE_PATH NAMES fluidsynth.h HINTS ${PC_FLUIDSYNTH_INCLUDEDIR} ) if(PC_FLUIDSYNTH_FOUND) get_flags_from_pkg_config("${FluidSynth_LIBRARY}" "PC_FLUIDSYNTH" "_fluidsynth") endif() set(FluidSynth_COMPILE_OPTIONS "${_fluidsynth_compile_options}" CACHE STRING "Extra compile options of FluidSynth") set(FluidSynth_LINK_LIBRARIES "${_fluidsynth_link_libraries}" CACHE STRING "Extra link libraries of FluidSynth") set(FluidSynth_LINK_OPTIONS "${_fluidsynth_link_options}" CACHE STRING "Extra link flags of FluidSynth") set(FluidSynth_LINK_DIRECTORIES "${_fluidsynth_link_directories}" CACHE PATH "Extra link directories of FluidSynth") find_package_handle_standard_args(FluidSynth REQUIRED_VARS FluidSynth_LIBRARY FluidSynth_INCLUDE_PATH ) if(FluidSynth_FOUND) if(NOT TARGET FluidSynth::libfluidsynth) add_library(FluidSynth::libfluidsynth UNKNOWN IMPORTED) set_target_properties(FluidSynth::libfluidsynth PROPERTIES IMPORTED_LOCATION "${FluidSynth_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${FluidSynth_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${FluidSynth_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${FluidSynth_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${FluidSynth_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${FluidSynth_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindOgg.cmake000066400000000000000000000027521501405355700226160ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_OGG QUIET ogg) find_library(Ogg_LIBRARY NAMES ogg HINTS ${PC_OGG_LIBDIR} ) find_path(Ogg_INCLUDE_PATH NAMES ogg/ogg.h HINTS ${PC_OGG_INCLUDEDIR} ) if(PC_OGG_FOUND) get_flags_from_pkg_config("${Ogg_LIBRARY}" "PC_OGG" "_ogg") endif() set(Ogg_COMPILE_OPTIONS "${_ogg_compile_options}" CACHE STRING "Extra compile options of ogg") set(Ogg_LINK_LIBRARIES "${_ogg_link_libraries}" CACHE STRING "Extra link libraries of ogg") set(Ogg_LINK_OPTIONS "${_ogg_link_options}" CACHE STRING "Extra link flags of ogg") set(Ogg_LINK_DIRECTORIES "${_ogg_link_directories}" CACHE PATH "Extra link directories of ogg") find_package_handle_standard_args(Ogg REQUIRED_VARS Ogg_LIBRARY Ogg_INCLUDE_PATH ) if(Ogg_FOUND) set(Ogg_dirs ${Ogg_INCLUDE_PATH}) if(EXISTS "${Ogg_INCLUDE_PATH}/ogg") list(APPEND Ogg_dirs "${Ogg_INCLUDE_PATH}/ogg") endif() if(NOT TARGET Ogg::ogg) add_library(Ogg::ogg UNKNOWN IMPORTED) set_target_properties(Ogg::ogg PROPERTIES IMPORTED_LOCATION "${Ogg_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Ogg_dirs}" INTERFACE_COMPILE_OPTIONS "${Ogg_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${Ogg_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${Ogg_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${Ogg_LINK_DIRECTORIES}" ) endif() endif() set(Ogg_INCLUDE_DIRS ${Ogg_INCLUDE_PATH}) libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindOpus.cmake000066400000000000000000000032151501405355700230230ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_OPUS QUIET opus) find_library(Opus_LIBRARY NAMES opus HINTS ${PC_OPUS_LIBDIR} ) find_path(Opus_INCLUDE_PATH NAMES opus.h PATH_SUFFIXES opus HINTS ${PC_OPUS_INCLUDEDIR} ) if(EXISTS "${Opus_INCLUDE_PATH}/opus") list(APPEND Opus_INCLUDE_PATH "${Opus_INCLUDE_PATH}/opus") endif() if(PC_OPUS_FOUND) get_flags_from_pkg_config("${Opus_LIBRARY}" "PC_OPUS" "_opus") endif() set(Opus_COMPILE_OPTIONS "${_opus_compile_options}" CACHE STRING "Extra compile options of opus") set(Opus_LINK_LIBRARIES "${_opus_link_libraries}" CACHE STRING "Extra link libraries of opus") set(Opus_LINK_OPTIONS "${_opus_link_options}" CACHE STRING "Extra link flags of opus") set(Opus_LINK_DIRECTORIES "${_opus_link_directories}" CACHE PATH "Extra link directories of opus") find_package(Ogg ${required}) find_package_handle_standard_args(Opus REQUIRED_VARS Opus_LIBRARY Opus_INCLUDE_PATH Ogg_FOUND ) if(Opus_FOUND) set(Opus_dirs ${Opus_INCLUDE_PATH}) if(NOT TARGET Opus::opus) add_library(Opus::opus UNKNOWN IMPORTED) set_target_properties(Opus::opus PROPERTIES IMPORTED_LOCATION "${Opus_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Opus_dirs}" INTERFACE_COMPILE_OPTIONS "${Opus_COMPILE_OPTIONS}:$" INTERFACE_LINK_LIBRARIES "${Opus_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${Opus_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${Opus_LINK_DIRECTORIES}" ) endif() endif() set(Opus_INCLUDE_DIRS ${Opus_INCLUDE_PATH}) libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindOpusFile.cmake000066400000000000000000000035241501405355700236260ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_OPUSFILE QUIET opusfile) find_library(OpusFile_LIBRARY NAMES opusfile HINTS ${PC_OPUSFILE_LIBDIR} ) find_path(OpusFile_INCLUDE_PATH NAMES opus/opusfile.h HINTS ${PC_OPUSFILE_INCLUDEDIR} ) if(PC_OPUSFILE_FOUND) get_flags_from_pkg_config("${OpusFile_LIBRARY}" "PC_OPUSFILE" "_opusfile") endif() set(OpusFile_COMPILE_OPTIONS "${_opusfile_compile_options}" CACHE STRING "Extra compile options of opusfile") set(OpusFile_LINK_LIBRARIES "${_opusfile_link_libraries}" CACHE STRING "Extra link libraries of opusfile") set(OpusFile_LINK_OPTIONS "${_opusfile_link_options}" CACHE STRING "Extra link flags of opusfile") set(OpusFile_LINK_DIRECTORIES "${_opusfile_link_directories}" CACHE PATH "Extra link directories of opusfile") find_package(Ogg) find_package(Opus) find_package_handle_standard_args(OpusFile REQUIRED_VARS OpusFile_LIBRARY OpusFile_INCLUDE_PATH Ogg_FOUND Opus_FOUND ) if(OpusFile_FOUND) set(OpusFile_dirs ${OpusFile_INCLUDE_PATH}) if(EXISTS "${OpusFile_INCLUDE_PATH}/opus") list(APPEND OpusFile_dirs "${OpusFile_INCLUDE_PATH}/opus") endif() if(NOT TARGET OpusFile::opusfile) add_library(OpusFile::opusfile UNKNOWN IMPORTED) set_target_properties(OpusFile::opusfile PROPERTIES IMPORTED_LOCATION "${OpusFile_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${OpusFile_dirs};$;$" INTERFACE_COMPILE_OPTIONS "${OpusFile_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${OpusFile_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${OpusFile_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${OpusFile_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindSndFile.cmake000066400000000000000000000027461501405355700234310ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_SNDFILE QUIET sndfile) find_library(SndFile_LIBRARY NAMES sndfile sndfile-1 HINTS ${PC_SNDFILE_LIBDIR} ) find_path(SndFile_INCLUDE_PATH NAMES sndfile.h HINTS ${PC_SNDFILE_INCLUDEDIR} ) if(PC_SNDFILE_FOUND) get_flags_from_pkg_config("${SndFile_LIBRARY}" "PC_SNDFILE" "_sndfile") endif() set(SndFile_COMPILE_OPTIONS "${_sndfile_compile_options}" CACHE STRING "Extra compile options of libsndfile") set(SndFile_LINK_LIBRARIES "${_sndfile_link_libraries}" CACHE STRING "Extra link libraries of libsndfile") set(SndFile_LINK_OPTIONS "${_sndfile_link_options}" CACHE STRING "Extra link flags of libsndfile") set(SndFile_LINK_DIRECTORIES "${_sndfile_link_directories}" CACHE PATH "Extra link directories of libsndfile") find_package_handle_standard_args(SndFile REQUIRED_VARS SndFile_LIBRARY SndFile_INCLUDE_PATH ) if(SndFile_FOUND) if(NOT TARGET SndFile::sndfile) add_library(SndFile::sndfile UNKNOWN IMPORTED) set_target_properties(SndFile::sndfile PROPERTIES IMPORTED_LOCATION "${SndFile_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${SndFile_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${SndFile_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${SndFile_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${SndFile_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${SndFile_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/FindVorbis.cmake000066400000000000000000000032221501405355700233370ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_VORBIS QUIET vorbisfile) find_library(Vorbis_vorbisfile_LIBRARY NAMES vorbisfile HINTS ${PC_VORBIS_LIBDIR} ) find_path(Vorbis_vorbisfile_INCLUDE_PATH NAMES vorbis/vorbisfile.h HINTS ${PC_VORBIS_INCLUDEDIR} ) if(PC_VORBIS_FOUND) get_flags_from_pkg_config("${Vorbis_vorbisfile_LIBRARY}" "PC_VORBIS" "_vorbisfile") endif() set(Vorbis_vorbisfile_COMPILE_OPTIONS "${_vorbisfile_compile_options}" CACHE STRING "Extra compile options of vorbisfile") set(Vorbis_vorbisfile_LINK_LIBRARIES "${_vorbisfile_link_libraries}" CACHE STRING "Extra link libraries of vorbisfile") set(Vorbis_vorbisfile_LINK_OPTIONS "${_vorbisfile_link_options}" CACHE STRING "Extra link flags of vorbisfile") set(Vorbis_vorbisfile_LINK_DIRECTORIES "${_vorbisfile_link_directories}" CACHE PATH "Extra link directories of vorbisfile") find_package_handle_standard_args(Vorbis REQUIRED_VARS Vorbis_vorbisfile_LIBRARY Vorbis_vorbisfile_INCLUDE_PATH ) if (Vorbis_FOUND) if (NOT TARGET Vorbis::vorbisfile) add_library(Vorbis::vorbisfile UNKNOWN IMPORTED) set_target_properties(Vorbis::vorbisfile PROPERTIES IMPORTED_LOCATION "${Vorbis_vorbisfile_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Vorbis_vorbisfile_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${Vorbis_vorbisfile_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${Vorbis_vorbisfile_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${Vorbis_vorbisfile_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${Vorbis_vorbisfile_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/Findgme.cmake000066400000000000000000000027021501405355700226450ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_GME QUIET libgme) find_library(gme_LIBRARY NAMES gme HINTS ${PC_GME_LIBDIR} ) find_path(gme_INCLUDE_PATH NAMES gme/gme.h HINTS ${PC_GME_INCLUDEDIR} ) if(PC_GME_FOUND) get_flags_from_pkg_config("${gme_LIBRARY}" "PC_GME" "_gme") endif() set(gme_COMPILE_OPTIONS "${_gme_compile_options}" CACHE STRING "Extra compile options of gme") set(gme_LINK_LIBRARIES "${_gme_link_libraries}" CACHE STRING "Extra link libraries of gme") set(gme_LINK_OPTIONS "${_gme_link_options}" CACHE STRING "Extra link flags of gme") set(gme_LINK_DIRECTORIES "${_gme_link_directories}" CACHE PATH "Extra link directories of gme") find_package_handle_standard_args(gme REQUIRED_VARS gme_LIBRARY gme_INCLUDE_PATH ) if(gme_FOUND) set(gme_dirs ${gme_INCLUDE_PATH}) if(EXISTS "${gme_INCLUDE_PATH}/gme") list(APPEND gme_dirs "${gme_INCLUDE_PATH}/gme") endif() if(NOT TARGET gme::gme) add_library(gme::gme UNKNOWN IMPORTED) set_target_properties(gme::gme PROPERTIES IMPORTED_LOCATION "${gme_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${gme_dirs}" INTERFACE_COMPILE_OPTIONS "${gme_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${gme_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${gme_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${gme_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/Findlibxmp-lite.cmake000066400000000000000000000031731501405355700243260ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_XMPLITE QUIET libxmp-lite) find_library(libxmp_lite_LIBRARY NAMES xmp-lite libxmp-lite HINTS ${PC_XMPLITE_LIBDIR} ) find_path(libxmp_lite_INCLUDE_PATH NAMES xmp.h PATH_SUFFIXES libxmp-lite HINTS ${PC_XMPLITE_INCLUDEDIR} ) if(PC_XMPLITE_FOUND) get_flags_from_pkg_config("${libxmp_lite_LIBRARY}" "PC_XMPLITE" "_libxmp_lite") endif() set(libxmp_lite_COMPILE_OPTIONS "${_libxmp_lite_compile_options}" CACHE STRING "Extra compile options of libxmp_lite") set(libxmp_lite_LINK_LIBRARIES "${_libxmp_lite_link_libraries}" CACHE STRING "Extra link libraries of libxmp_lite") set(libxmp_lite_LINK_OPTIONS "${_libxmp_lite_link_options}" CACHE STRING "Extra link flags of libxmp_lite") set(libxmp_lite_LINK_DIRECTORIES "${_libxmp_lite_link_directories}" CACHE PATH "Extra link directories of libxmp_lite") find_package_handle_standard_args(libxmp-lite REQUIRED_VARS libxmp_lite_LIBRARY libxmp_lite_INCLUDE_PATH ) if(libxmp-lite_FOUND) if(NOT TARGET libxmp-lite::libxmp-lite) add_library(libxmp-lite::libxmp-lite UNKNOWN IMPORTED) set_target_properties(libxmp-lite::libxmp-lite PROPERTIES IMPORTED_LOCATION "${libxmp_lite_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${libxmp_lite_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${libxmp_lite_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${libxmp_lite_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${libxmp_lite_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${libxmp_lite_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/Findlibxmp.cmake000066400000000000000000000026151501405355700233730ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_XMP QUIET libxmp) find_library(libxmp_LIBRARY NAMES xmp HINTS ${PC_XMP_LIBDIR} ) find_path(libxmp_INCLUDE_PATH NAMES xmp.h HINTS ${PC_XMP_INCLUDEDIR} ) if(PC_XMP_FOUND) get_flags_from_pkg_config("${libxmp_LIBRARY}" "PC_XMP" "_libxmp") endif() set(libxmp_COMPILE_OPTIONS "${_libxmp_compile_options}" CACHE STRING "Extra compile options of libxmp") set(libxmp_LINK_LIBRARIES "${_libxmp_link_libraries}" CACHE STRING "Extra link libraries of libxmp") set(libxmp_LINK_OPTIONS "${_libxmp_link_options}" CACHE STRING "Extra link flags of libxmp") set(libxmp_LINK_DIRECTORIES "${_libxmp_link_directories}" CACHE PATH "Extra link flags of libxmp") find_package_handle_standard_args(libxmp REQUIRED_VARS libxmp_LIBRARY libxmp_INCLUDE_PATH ) if(libxmp_FOUND) if(NOT TARGET libxmp::libxmp) add_library(libxmp::libxmp UNKNOWN IMPORTED) set_target_properties(libxmp::libxmp PROPERTIES IMPORTED_LOCATION "${libxmp_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${libxmp_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${libxmp_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${libxmp_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${libxmp_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${libxmp_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/Findmpg123.cmake000066400000000000000000000026641501405355700231150ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_MPG123 QUIET libmpg123) find_library(mpg123_LIBRARY NAMES mpg123 HINTS ${PC_MPG123_LIBDIR} ) find_path(mpg123_INCLUDE_PATH NAMES mpg123.h HINTS ${PC_MPG123_INCLUDEDIR} ) if(PC_MPG123_FOUND) get_flags_from_pkg_config("${mpg123_LIBRARY}" "PC_MPG123" "_mpg123") endif() set(mpg123_COMPILE_OPTIONS "${_mpg123_compile_options}" CACHE STRING "Extra compile options of mpg123") set(mpg123_LINK_LIBRARIES "${_mpg123_link_libraries}" CACHE STRING "Extra link libraries of mpg123") set(mpg123_LINK_OPTIONS "${_mpg123_link_options}" CACHE STRING "Extra link flags of mpg123") set(mpg123_LINK_DIRECTORIES "${_mpg123_link_directories}" CACHE PATH "Extra link directories of mpg123") find_package_handle_standard_args(mpg123 REQUIRED_VARS mpg123_LIBRARY mpg123_INCLUDE_PATH ) if(mpg123_FOUND) if(NOT TARGET MPG123::libmpg123) add_library(MPG123::libmpg123 UNKNOWN IMPORTED) set_target_properties(MPG123::libmpg123 PROPERTIES IMPORTED_LOCATION "${mpg123_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${mpg123_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${mpg123_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${mpg123_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${mpg123_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${mpg123_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/Findtremor.cmake000066400000000000000000000027141501405355700234100ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) pkg_check_modules(PC_TREMOR QUIET vorbisidec) find_library(tremor_LIBRARY NAMES vorbisidec libvorbisidec HINTS ${PC_TREMOR_LIBDIR} ) find_path(tremor_INCLUDE_PATH NAMES tremor/ivorbisfile.h HINTS ${PC_TREMOR_INCLUDEDIR} ) if(PC_TREMOR_FOUND) get_flags_from_pkg_config("${tremor_LIBRARY}" "PC_TREMOR" "_tremor") endif() set(tremor_COMPILE_OPTIONS "${_tremor_compile_options}" CACHE STRING "Extra compile options of vorbis") set(tremor_LINK_LIBRARIES "${_tremor_link_libraries}" CACHE STRING "Extra link libraries of vorbis") set(tremor_LINK_OPTIONS "${_tremor_link_options}" CACHE STRING "Extra link flags of vorbis") set(tremor_LINK_DIRECTORIES "${_tremor_link_directories}" CACHE PATH "Extra link directories of vorbis") find_package_handle_standard_args(tremor REQUIRED_VARS tremor_LIBRARY tremor_INCLUDE_PATH ) if (tremor_FOUND) if (NOT TARGET tremor::tremor) add_library(tremor::tremor UNKNOWN IMPORTED) set_target_properties(tremor::tremor PROPERTIES IMPORTED_LOCATION "${tremor_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${tremor_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${tremor_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${tremor_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${tremor_LINK_OPTIONS}" INTERFACE_LINK_DIRECTORIES "${tremor_LINK_DIRECTORIES}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/Findwavpack.cmake000066400000000000000000000021511501405355700235270ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) if(WIN32) set(wavpack_find_names wavpack libwavpack wavpackdll) else() set(wavpack_find_names wavpack) endif() find_library(wavpack_LIBRARY NAMES ${wavpack_find_names} ) find_path(wavpack_INCLUDE_PATH NAMES wavpack/wavpack.h ) set(wavpack_COMPILE_OPTIONS "" CACHE STRING "Extra compile options of wavpack") set(wavpack_LINK_LIBRARIES "" CACHE STRING "Extra link libraries of wavpack") set(wavpack_LINK_OPTIONS "" CACHE STRING "Extra link flags of wavpack") find_package_handle_standard_args(wavpack REQUIRED_VARS wavpack_LIBRARY wavpack_INCLUDE_PATH ) if (wavpack_FOUND) if (NOT TARGET WavPack::WavPack) add_library(WavPack::WavPack UNKNOWN IMPORTED) set_target_properties(WavPack::WavPack PROPERTIES IMPORTED_LOCATION "${wavpack_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${wavpack_INCLUDE_PATH}" INTERFACE_COMPILE_OPTIONS "${wavpack_COMPILE_OPTIONS}" INTERFACE_LINK_LIBRARIES "${wavpack_LINK_LIBRARIES}" INTERFACE_LINK_OPTIONS "${wavpack_LINK_OPTIONS}" ) endif() endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/GetGitRevisionDescription.cmake000066400000000000000000000224601501405355700264050ustar00rootroot00000000000000# - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_describe_working_tree( [ ...]) # # Returns the results of git describe on the working tree (--dirty option), # and adjusting the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # git_local_changes() # # Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. # Uses the return code of "git diff-index --quiet HEAD --". # Does not regard untracked files. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2020 Ryan Pavlik # http://academic.cleardefinition.com # # Copyright 2009-2013, Iowa State University. # Copyright 2013-2020, Ryan Pavlik # Copyright 2013-2020, Contributors # SPDX-License-Identifier: BSL-1.0 # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) # Function _git_find_closest_git_dir finds the next closest .git directory # that is part of any directory in the path defined by _start_dir. # The result is returned in the parent scope variable whose name is passed # as variable _git_dir_var. If no .git directory can be found, the # function returns an empty string via _git_dir_var. # # Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and # neither foo nor bar contain a file/directory .git. This will return # C:/bla/.git # function(_git_find_closest_git_dir _start_dir _git_dir_var) set(cur_dir "${_start_dir}") set(git_dir "${_start_dir}/.git") while(NOT EXISTS "${git_dir}") # .git dir not found, search parent directories set(git_previous_parent "${cur_dir}") get_filename_component(cur_dir "${cur_dir}" DIRECTORY) if(cur_dir STREQUAL git_previous_parent) # We have reached the root directory, we are not in git set(${_git_dir_var} "" PARENT_SCOPE) return() endif() set(git_dir "${cur_dir}/.git") endwhile() set(${_git_dir_var} "${git_dir}" PARENT_SCOPE) endfunction() function(get_git_head_revision _refspecvar _hashvar) _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) else() set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) endif() if(NOT "${GIT_DIR}" STREQUAL "") file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" "${GIT_DIR}") if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) # We've gone above the CMake root dir. set(GIT_DIR "") endif() endif() if("${GIT_DIR}" STREQUAL "") set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() # Check if the current source dir is a git submodule or a worktree. # In both cases .git is a file instead of a directory. # if(NOT IS_DIRECTORY ${GIT_DIR}) # The following git command will return a non empty string that # points to the super project working tree if the current # source dir is inside a git submodule. # Otherwise the command will return an empty string. # execute_process( COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT "${out}" STREQUAL "") # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule}) string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree file(READ ${GIT_DIR} worktree_ref) # The .git directory contains a path to the worktree information directory # inside the parent git repo of the worktree. # string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref}) string(STRIP ${git_worktree_dir} git_worktree_dir) _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") endif() else() set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") endif() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${HEAD_SOURCE_FILE}") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() # TODO sanitize #if((${ARGN}" MATCHES "&&") OR # (ARGN MATCHES "||") OR # (ARGN MATCHES "\\;")) # message("Please report the following error to the project!") # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") #endif() #message(STATUS "Arguments to execute_process: ${ARGN}") execute_process( COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_describe_working_tree _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() execute_process( COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out --exact-match ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_local_changes _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() execute_process( COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(res EQUAL 0) set(${_var} "CLEAN" PARENT_SCOPE) else() set(${_var} "DIRTY" PARENT_SCOPE) endif() endfunction() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/GetGitRevisionDescription.cmake.in000066400000000000000000000025121501405355700270060ustar00rootroot00000000000000# # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright 2009-2012, Iowa State University # Copyright 2011-2015, Contributors # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) # SPDX-License-Identifier: BSL-1.0 set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/PkgConfigHelper.cmake000066400000000000000000000023451501405355700243060ustar00rootroot00000000000000# Helper for Find modules function(get_flags_from_pkg_config _library _pc_prefix _out_prefix) if("${_library}" MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$") set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER) set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES) set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER) set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS) else() set(_cflags ${_pc_prefix}_CFLAGS_OTHER) set(_link_libraries ${_pc_prefix}_LIBRARIES) set(_link_options ${_pc_prefix}_LDFLAGS_OTHER) set(_library_dirs ${_pc_prefix}_LIBRARY_DIRS) endif() # The *_LIBRARIES lists always start with the library itself list(POP_FRONT "${_link_libraries}") # Work around CMake's flag deduplication when pc files use `-framework A` instead of `-Wl,-framework,A` string(REPLACE "-framework;" "-Wl,-framework," "_filtered_link_options" "${${_link_options}}") set(${_out_prefix}_compile_options "${${_cflags}}" PARENT_SCOPE) set(${_out_prefix}_link_libraries "${${_link_libraries}}" PARENT_SCOPE) set(${_out_prefix}_link_options "${_filtered_link_options}" PARENT_SCOPE) set(${_out_prefix}_link_directories "${${_library_dirs}}" PARENT_SCOPE) endfunction() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/PrivateSdlFunctions.cmake000066400000000000000000000367331501405355700252550ustar00rootroot00000000000000# This file is shared amongst SDL_image/SDL_mixer/SDL_ttf include(CheckCCompilerFlag) include(CheckCSourceCompiles) include(CMakePushCheckState) macro(sdl_calculate_derived_version_variables MAJOR MINOR MICRO) set(SO_VERSION_MAJOR "0") set(SO_VERSION_MINOR "${MINOR_VERSION}") set(SO_VERSION_MICRO "${MICRO_VERSION}") set(SO_VERSION "${SO_VERSION_MAJOR}.${SO_VERSION_MINOR}.${SO_VERSION_MICRO}") if(MINOR MATCHES "[02468]$") math(EXPR DYLIB_COMPAT_VERSION_MAJOR "100 * ${MINOR} + 1") set(DYLIB_COMPAT_VERSION_MINOR "0") math(EXPR DYLIB_CURRENT_VERSION_MAJOR "${DYLIB_COMPAT_VERSION_MAJOR}") set(DYLIB_CURRENT_VERSION_MINOR "${MICRO}") else() math(EXPR DYLIB_COMPAT_VERSION_MAJOR "100 * ${MINOR} + ${MICRO} + 1") set(DYLIB_COMPAT_VERSION_MINOR "0") math(EXPR DYLIB_CURRENT_VERSION_MAJOR "${DYLIB_COMPAT_VERSION_MAJOR}") set(DYLIB_CURRENT_VERSION_MINOR "0") endif() set(DYLIB_COMPAT_VERSION_MICRO "0") set(DYLIB_CURRENT_VERSION_MICRO "0") set(DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION_MAJOR}.${DYLIB_CURRENT_VERSION_MINOR}.${DYLIB_CURRENT_VERSION_MICRO}") set(DYLIB_COMPAT_VERSION "${DYLIB_COMPAT_VERSION_MAJOR}.${DYLIB_COMPAT_VERSION_MINOR}.${DYLIB_COMPAT_VERSION_MICRO}") endmacro() function(read_absolute_symlink DEST PATH) file(READ_SYMLINK "${PATH}" p) if(NOT IS_ABSOLUTE "${p}") get_filename_component(pdir "${PATH}" DIRECTORY) set(p "${pdir}/${p}") endif() get_filename_component(p "${p}" ABSOLUTE) set("${DEST}" "${p}" PARENT_SCOPE) endfunction() function(win32_implib_identify_dll DEST IMPLIB) cmake_parse_arguments(ARGS "NOTFATAL" "" "" ${ARGN}) if(CMAKE_DLLTOOL) execute_process( COMMAND "${CMAKE_DLLTOOL}" --identify "${IMPLIB}" RESULT_VARIABLE retcode OUTPUT_VARIABLE stdout ERROR_VARIABLE stderr) if(NOT retcode EQUAL 0) if(NOT ARGS_NOTFATAL) message(FATAL_ERROR "${CMAKE_DLLTOOL} failed.") else() set("${DEST}" "${DEST}-NOTFOUND" PARENT_SCOPE) return() endif() endif() string(STRIP "${stdout}" result) set(${DEST} "${result}" PARENT_SCOPE) elseif(MSVC) get_filename_component(CMAKE_C_COMPILER_DIRECTORY "${CMAKE_C_COMPILER}" DIRECTORY CACHE) find_program(CMAKE_DUMPBIN NAMES dumpbin PATHS "${CMAKE_C_COMPILER_DIRECTORY}") if(CMAKE_DUMPBIN) execute_process( COMMAND "${CMAKE_DUMPBIN}" "-headers" "${IMPLIB}" RESULT_VARIABLE retcode OUTPUT_VARIABLE stdout ERROR_VARIABLE stderr) if(NOT retcode EQUAL 0) if(NOT ARGS_NOTFATAL) message(FATAL_ERROR "dumpbin failed.") else() set(${DEST} "${DEST}-NOTFOUND" PARENT_SCOPE) return() endif() endif() string(REGEX MATCH "DLL name[ ]+:[ ]+([^\n]+)\n" match "${stdout}") if(NOT match) if(NOT ARGS_NOTFATAL) message(FATAL_ERROR "dumpbin did not find any associated dll for ${IMPLIB}.") else() set(${DEST} "${DEST}-NOTFOUND" PARENT_SCOPE) return() endif() endif() set(result "${CMAKE_MATCH_1}") set(${DEST} "${result}" PARENT_SCOPE) else() message(FATAL_ERROR "Cannot find dumpbin, please set CMAKE_DUMPBIN cmake variable") endif() else() if(NOT ARGS_NOTFATAL) message(FATAL_ERROR "Don't know how to identify dll from import library. Set CMAKE_DLLTOOL (for mingw) or CMAKE_DUMPBIN (for MSVC)") else() set(${DEST} "${DEST}-NOTFOUND") endif() endif() endfunction() function(get_actual_target) set(dst "${ARGV0}") set(target "${${dst}}") set(input "${target}") get_target_property(alias "${target}" ALIASED_TARGET) while(alias) set(target "${alias}") get_target_property(alias "${target}" ALIASED_TARGET) endwhile() message(DEBUG "get_actual_target(\"${input}\") -> \"${target}\"") set("${dst}" "${target}" PARENT_SCOPE) endfunction() function(target_get_dynamic_library DEST TARGET) set(result) get_actual_target(TARGET) if(WIN32) # Use the target dll of the import library set(props_to_check IMPORTED_IMPLIB) if(CMAKE_BUILD_TYPE) list(APPEND props_to_check IMPORTED_IMPLIB_${CMAKE_BUILD_TYPE}) endif() list(APPEND props_to_check IMPORTED_LOCATION) if(CMAKE_BUILD_TYPE) list(APPEND props_to_check IMPORTED_LOCATION_${CMAKE_BUILD_TYPE}) endif() foreach (config_type ${CMAKE_CONFIGURATION_TYPES} RELEASE DEBUG RELWITHDEBINFO MINSIZEREL) list(APPEND props_to_check IMPORTED_IMPLIB_${config_type}) list(APPEND props_to_check IMPORTED_LOCATION_${config_type}) endforeach() foreach(prop_to_check ${props_to_check}) if(NOT result) get_target_property(propvalue "${TARGET}" ${prop_to_check}) if(propvalue AND EXISTS "${propvalue}") win32_implib_identify_dll(result "${propvalue}" NOTFATAL) endif() endif() endforeach() else() # 1. find the target library a file might be symbolic linking to # 2. find all other files in the same folder that symolic link to it # 3. sort all these files, and select the 1st item on Linux, and last on Macos set(location_properties IMPORTED_LOCATION) if(CMAKE_BUILD_TYPE) list(APPEND location_properties IMPORTED_LOCATION_${CMAKE_BUILD_TYPE}) endif() foreach (config_type ${CMAKE_CONFIGURATION_TYPES} RELEASE DEBUG RELWITHDEBINFO MINSIZEREL) list(APPEND location_properties IMPORTED_LOCATION_${config_type}) endforeach() if(APPLE) set(valid_shared_library_regex "\\.[0-9]+\\.dylib$") else() set(valid_shared_library_regex "\\.so\\.([0-9.]+)?[0-9]") endif() foreach(location_property ${location_properties}) if(NOT result) get_target_property(library_path "${TARGET}" ${location_property}) message(DEBUG "get_target_property(${TARGET} ${location_propert}) -> ${library_path}") if(EXISTS "${library_path}") get_filename_component(library_path "${library_path}" ABSOLUTE) while (IS_SYMLINK "${library_path}") read_absolute_symlink(library_path "${library_path}") endwhile() message(DEBUG "${TARGET} -> ${library_path}") get_filename_component(libdir "${library_path}" DIRECTORY) file(GLOB subfiles "${libdir}/*") set(similar_files "${library_path}") foreach(subfile ${subfiles}) if(IS_SYMLINK "${subfile}") read_absolute_symlink(subfile_target "${subfile}") while(IS_SYMLINK "${subfile_target}") read_absolute_symlink(subfile_target "${subfile_target}") endwhile() get_filename_component(subfile_target "${subfile_target}" ABSOLUTE) if(subfile_target STREQUAL library_path AND subfile MATCHES "${valid_shared_library_regex}") list(APPEND similar_files "${subfile}") endif() endif() endforeach() list(SORT similar_files) message(DEBUG "files that are similar to \"${library_path}\"=${similar_files}") if(APPLE) list(REVERSE similar_files) endif() list(GET similar_files 0 item) get_filename_component(result "${item}" NAME) endif() endif() endforeach() endif() if(result) string(TOLOWER "${result}" result_lower) if(WIN32 OR OS2) if(NOT result_lower MATCHES ".*dll") message(FATAL_ERROR "\"${result}\" is not a .dll library") endif() elseif(APPLE) if(NOT result_lower MATCHES ".*dylib.*") message(FATAL_ERROR "\"${result}\" is not a .dylib shared library") endif() else() if(NOT result_lower MATCHES ".*so.*") message(FATAL_ERROR "\"${result}\" is not a .so shared library") endif() endif() else() get_target_property(target_type ${TARGET} TYPE) if(target_type MATCHES "SHARED_LIBRARY|MODULE_LIBRARY") # OK elseif(target_type MATCHES "STATIC_LIBRARY|OBJECT_LIBRARY|INTERFACE_LIBRARY|EXECUTABLE") message(SEND_ERROR "${TARGET} is not a shared library, but has type=${target_type}") else() message(WARNING "Unable to extract dynamic library from target=${TARGET}, type=${target_type}.") endif() # TARGET_SONAME_FILE is not allowed for DLL target platforms. if(WIN32) set(result "$") else() set(result "$") endif() endif() set(${DEST} ${result} PARENT_SCOPE) endfunction() function(sdl_check_project_in_subfolder relative_subfolder name vendored_option) cmake_parse_arguments(ARG "" "FILE" "" ${ARGN}) if(NOT ARG_FILE) set(ARG_FILE "CMakeLists.txt") endif() if(NOT EXISTS "${PROJECT_SOURCE_DIR}/${relative_subfolder}/${ARG_FILE}") message(FATAL_ERROR "Could not find ${ARG_FILE} for ${name} in ${relative_subfolder}.\n" "Run the download script in the external folder, or re-configure with -D${vendored_option}=OFF to use system packages.") endif() endfunction() macro(sdl_check_linker_flag flag var) # FIXME: Use CheckLinkerFlag module once cmake minimum version >= 3.18 cmake_push_check_state(RESET) set(CMAKE_REQUIRED_LINK_OPTIONS "${flag}") check_c_source_compiles("int main() { return 0; }" ${var} FAIL_REGEX "(unsupported|syntax error|unrecognized option)") cmake_pop_check_state() endmacro() function(SDL_detect_linker) if(CMAKE_VERSION VERSION_LESS 3.29) if(NOT DEFINED SDL_CMAKE_C_COMPILER_LINKER_ID) execute_process(COMMAND ${CMAKE_LINKER} -v OUTPUT_VARIABLE LINKER_OUTPUT ERROR_VARIABLE LINKER_OUTPUT) string(REGEX REPLACE "[\r\n]" " " LINKER_OUTPUT "${LINKER_OUTPUT}") if(LINKER_OUTPUT MATCHES ".*Microsoft.*") set(linker MSVC) else() set(linker GNUlike) endif() message(STATUS "Linker identification: ${linker}") set(SDL_CMAKE_C_COMPILER_LINKER_ID "${linker}" CACHE STRING "Linker identification") mark_as_advanced(SDL_CMAKE_C_COMPILER_LINKER_ID) endif() set(CMAKE_C_COMPILER_LINKER_ID "${SDL_CMAKE_C_COMPILER_LINKER_ID}" PARENT_SCOPE) endif() endfunction() function(check_linker_support_version_script VAR) SDL_detect_linker() if(CMAKE_C_COMPILER_LINKER_ID MATCHES "^(MSVC)$") set(LINKER_SUPPORTS_VERSION_SCRIPT FALSE) else() cmake_push_check_state(RESET) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/dummy.sym" "n_0 {\n global:\n func;\n local: *;\n};\n") list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/dummy.sym") check_c_source_compiles("int func(void) {return 0;} int main(int argc,char*argv[]){(void)argc;(void)argv;return func();}" LINKER_SUPPORTS_VERSION_SCRIPT FAIL_REGEX "(unsupported|syntax error|unrecognized option)") cmake_pop_check_state() endif() set(${VAR} "${LINKER_SUPPORTS_VERSION_SCRIPT}" PARENT_SCOPE) endfunction() function(sdl_target_link_options_no_undefined TARGET) if(NOT MSVC AND NOT CMAKE_SYSTEM_NAME MATCHES ".*OpenBSD.*") if(CMAKE_C_COMPILER_ID MATCHES "AppleClang") target_link_options(${TARGET} PRIVATE "-Wl,-undefined,error") else() sdl_check_linker_flag("-Wl,--no-undefined" HAVE_WL_NO_UNDEFINED) if(HAVE_WL_NO_UNDEFINED AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") AND WIN32)) target_link_options(${TARGET} PRIVATE "-Wl,--no-undefined") endif() endif() endif() endfunction() function(sdl_target_link_option_version_file TARGET VERSION_SCRIPT) check_linker_support_version_script(HAVE_WL_VERSION_SCRIPT) if(HAVE_WL_VERSION_SCRIPT) target_link_options(${TARGET} PRIVATE "-Wl,--version-script=${VERSION_SCRIPT}") set_property(TARGET ${TARGET} APPEND PROPERTY LINK_DEPENDS "${VERSION_SCRIPT}") else() if(LINUX OR ANDROID) message(FATAL_ERROR "Linker does not support '-Wl,--version-script=xxx.sym'. This is required on the current host platform.") endif() endif() endfunction() function(sdl_add_warning_options TARGET) cmake_parse_arguments(ARGS "" "WARNING_AS_ERROR" "" ${ARGN}) if(MSVC) target_compile_options(${TARGET} PRIVATE /W2) else() target_compile_options(${TARGET} PRIVATE -Wall -Wextra) endif() if(ARGS_WARNING_AS_ERROR) if(MSVC) target_compile_options(${TARGET} PRIVATE /WX) else() target_compile_options(${TARGET} PRIVATE -Werror) endif() endif() endfunction() function(sdl_no_deprecated_errors TARGET) check_c_compiler_flag(-Wno-error=deprecated-declarations HAVE_WNO_ERROR_DEPRECATED_DECLARATIONS) if(HAVE_WNO_ERROR_DEPRECATED_DECLARATIONS) target_compile_options(${TARGET} PRIVATE "-Wno-error=deprecated-declarations") endif() endfunction() function(sdl_get_git_revision_hash VARNAME) set("${VARNAME}" "" CACHE STRING "${PROJECT_NAME} revision") set(revision "${${VARNAME}}") if(NOT revision) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt") # If VERSION.txt exists, it contains the SDL version file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt" revision_version) string(STRIP "${revision_version}" revision_version) else() # If VERSION.txt does not exist, use git to calculate a version git_describe(revision_version) if(NOT revision_version) set(revision_version "${PROJECT_VERSION}-no-vcs") endif() endif() set(revision "${revision_version}") endif() set("${VARNAME}" "${revision}" PARENT_SCOPE) endfunction() function(SDL_install_pdb TARGET DIRECTORY) get_property(type TARGET ${TARGET} PROPERTY TYPE) if(type MATCHES "^(SHARED_LIBRARY|EXECUTABLE)$") install(FILES $ DESTINATION "${DIRECTORY}" OPTIONAL) elseif(type STREQUAL "STATIC_LIBRARY") # FIXME: Use $= @SDL_REQUIRED_VERSION@ Libs: -L${libdir} -lSDL3_mixer Requires.private: @PC_REQUIRES@ Libs.private: @PC_LIBS@ Cflags: -I${includedir} libsdl3-mixer-3~git20250523~daf0503+ds/cmake/sdlcpu.cmake000066400000000000000000000120741501405355700225710ustar00rootroot00000000000000function(SDL_DetectTargetCPUArchitectures DETECTED_ARCHS) set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 POWERPC32 POWERPC64 X86 X64) if(APPLE AND CMAKE_OSX_ARCHITECTURES) foreach(known_arch IN LISTS known_archs) set(SDL_CPU_${known_arch} "0") endforeach() set(detected_archs) foreach(osx_arch IN LISTS CMAKE_OSX_ARCHITECTURES) if(osx_arch STREQUAL "x86_64") set(SDL_CPU_X64 "1") list(APPEND detected_archs "X64") elseif(osx_arch STREQUAL "arm64") set(SDL_CPU_ARM64 "1") list(APPEND detected_archs "ARM64") endif() endforeach() set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE) return() endif() set(detected_archs) foreach(known_arch IN LISTS known_archs) if(SDL_CPU_${known_arch}) list(APPEND detected_archs "${known_arch}") endif() endforeach() if(detected_archs) set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE) return() endif() set(arch_check_ARM32 "defined(__arm__) || defined(_M_ARM)") set(arch_check_ARM64 "defined(__aarch64__) || defined(_M_ARM64)") set(arch_check_ARM64EC "defined(_M_ARM64EC)") set(arch_check_EMSCRIPTEN "defined(__EMSCRIPTEN__)") set(arch_check_LOONGARCH64 "defined(__loongarch64)") set(arch_check_POWERPC32 "(defined(__PPC__) || defined(__powerpc__)) && !defined(__powerpc64__)") set(arch_check_POWERPC64 "defined(__PPC64__) || defined(__powerpc64__)") set(arch_check_X86 "defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86)") set(arch_check_X64 "(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)) && !defined(_M_ARM64EC)") set(src_vars "") set(src_main "") foreach(known_arch IN LISTS known_archs) set(detected_${known_arch} "0") string(APPEND src_vars " #if ${arch_check_${known_arch}} #define ARCH_${known_arch} \"1\" #else #define ARCH_${known_arch} \"0\" #endif const char *arch_${known_arch} = \"INFO<${known_arch}=\" ARCH_${known_arch} \">\"; ") string(APPEND src_main " result += arch_${known_arch}[argc];") endforeach() set(src_arch_detect "${src_vars} int main(int argc, char *argv[]) { int result = 0; (void)argv; ${src_main} return result; }") if(CMAKE_C_COMPILER) set(ext ".c") elseif(CMAKE_CXX_COMPILER) set(ext ".cpp") else() enable_language(C) set(ext ".c") endif() set(path_src_arch_detect "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/SDL_detect_arch${ext}") file(WRITE "${path_src_arch_detect}" "${src_arch_detect}") set(path_dir_arch_detect "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/SDL_detect_arch") set(path_bin_arch_detect "${path_dir_arch_detect}/bin") set(detected_archs) set(msg "Detecting Target CPU Architecture") message(STATUS "${msg}") include(CMakePushCheckState) set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") cmake_push_check_state(RESET) try_compile(SDL_CPU_CHECK_ALL "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/SDL_detect_arch" SOURCES "${path_src_arch_detect}" COPY_FILE "${path_bin_arch_detect}" ) cmake_pop_check_state() if(NOT SDL_CPU_CHECK_ALL) message(STATUS "${msg} - ") message(WARNING "Failed to compile source detecting the target CPU architecture") else() set(re "INFO<([A-Z0-9]+)=([01])>") file(STRINGS "${path_bin_arch_detect}" infos REGEX "${re}") foreach(info_arch_01 IN LISTS infos) string(REGEX MATCH "${re}" A "${info_arch_01}") if(NOT "${CMAKE_MATCH_1}" IN_LIST known_archs) message(WARNING "Unknown architecture: \"${CMAKE_MATCH_1}\"") continue() endif() set(arch "${CMAKE_MATCH_1}") set(arch_01 "${CMAKE_MATCH_2}") set(detected_${arch} "${arch_01}") endforeach() foreach(known_arch IN LISTS known_archs) if(detected_${known_arch}) list(APPEND detected_archs ${known_arch}) endif() endforeach() endif() if(detected_archs) foreach(known_arch IN LISTS known_archs) set("SDL_CPU_${known_arch}" "${detected_${known_arch}}" CACHE BOOL "Detected architecture ${known_arch}") endforeach() message(STATUS "${msg} - ${detected_archs}") else() include(CheckCSourceCompiles) cmake_push_check_state(RESET) foreach(known_arch IN LISTS known_archs) if(NOT detected_archs) set(cache_variable "SDL_CPU_${known_arch}") set(test_src " int main(int argc, char *argv[]) { #if ${arch_check_${known_arch}} return 0; #else choke #endif } ") check_c_source_compiles("${test_src}" "${cache_variable}") if(${cache_variable}) set(SDL_CPU_${known_arch} "1" CACHE BOOL "Detected architecture ${known_arch}") set(detected_archs ${known_arch}) else() set(SDL_CPU_${known_arch} "0" CACHE BOOL "Detected architecture ${known_arch}") endif() endif() endforeach() cmake_pop_check_state() endif() set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE) endfunction() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/sdlmanpages.cmake000066400000000000000000000046271501405355700236020ustar00rootroot00000000000000include(CMakeParseArguments) include(GNUInstallDirs) function(SDL_generate_manpages) cmake_parse_arguments(ARG "" "RESULT_VARIABLE;NAME;BUILD_DOCDIR;HEADERS_DIR;SOURCE_DIR;SYMBOL;OPTION_FILE;WIKIHEADERS_PL_PATH;REVISION" "" ${ARGN}) set(wikiheaders_extra_args) if(NOT ARG_NAME) set(ARG_NAME "${PROJECT_NAME}") endif() if(NOT ARG_SOURCE_DIR) set(ARG_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") endif() if(NOT ARG_OPTION_FILE) set(ARG_OPTION_FILE "${PROJECT_SOURCE_DIR}/.wikiheaders-options") endif() if(NOT ARG_HEADERS_DIR) message(FATAL_ERROR "Missing required HEADERS_DIR argument") endif() # FIXME: get rid of SYMBOL and let the perl script figure out the dependencies if(NOT ARG_SYMBOL) message(FATAL_ERROR "Missing required SYMBOL argument") endif() if(ARG_REVISION) list(APPEND wikiheaders_extra_args "--rev=${ARG_REVISION}") endif() if(NOT ARG_BUILD_DOCDIR) set(ARG_BUILD_DOCDIR "${CMAKE_CURRENT_BINARY_DIR}/docs") endif() set(BUILD_WIKIDIR "${ARG_BUILD_DOCDIR}/wiki") set(BUILD_MANDIR "${ARG_BUILD_DOCDIR}/man") find_package(Perl) file(GLOB HEADER_FILES "${ARG_HEADERS_DIR}/*.h") set(result FALSE) if(PERL_FOUND AND EXISTS "${ARG_WIKIHEADERS_PL_PATH}") add_custom_command( OUTPUT "${BUILD_WIKIDIR}/${ARG_SYMBOL}.md" COMMAND "${CMAKE_COMMAND}" -E make_directory "${BUILD_WIKIDIR}" COMMAND "${PERL_EXECUTABLE}" "${ARG_WIKIHEADERS_PL_PATH}" "${ARG_SOURCE_DIR}" "${BUILD_WIKIDIR}" "--options=${ARG_OPTION_FILE}" --copy-to-wiki ${wikiheaders_extra_args} DEPENDS ${HEADER_FILES} "${ARG_WIKIHEADERS_PL_PATH}" "${ARG_OPTION_FILE}" COMMENT "Generating ${ARG_NAME} wiki markdown files" ) add_custom_command( OUTPUT "${BUILD_MANDIR}/man3/${ARG_SYMBOL}.3" COMMAND "${PERL_EXECUTABLE}" "${ARG_WIKIHEADERS_PL_PATH}" "${ARG_SOURCE_DIR}" "${BUILD_WIKIDIR}" "--options=${ARG_OPTION_FILE}" "--manpath=${BUILD_MANDIR}" --copy-to-manpages ${wikiheaders_extra_args} DEPENDS "${BUILD_WIKIDIR}/${ARG_SYMBOL}.md" "${ARG_WIKIHEADERS_PL_PATH}" "${ARG_OPTION_FILE}" COMMENT "Generating ${ARG_NAME} man pages" ) add_custom_target(${ARG_NAME}-docs ALL DEPENDS "${BUILD_MANDIR}/man3/${ARG_SYMBOL}.3") install(DIRECTORY "${BUILD_MANDIR}/" DESTINATION "${CMAKE_INSTALL_MANDIR}") set(result TRUE) endif() if(ARG_RESULT_VARIABLE) set(${ARG_RESULT_VARIABLE} ${result} PARENT_SCOPE) endif() endfunction() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/sdlplatform.cmake000066400000000000000000000071171501405355700236300ustar00rootroot00000000000000macro(SDL_DetectCMakePlatform) set(SDL_CMAKE_PLATFORM ) # Get the platform if(WIN32) set(SDL_CMAKE_PLATFORM Windows) elseif(PSP) set(SDL_CMAKE_PLATFORM psp) elseif(APPLE) if(CMAKE_SYSTEM_NAME MATCHES ".*Darwin.*") set(SDL_CMAKE_PLATFORM Darwin) elseif(CMAKE_SYSTEM_NAME MATCHES ".*MacOS.*") set(SDL_CMAKE_PLATFORM MacosX) elseif(CMAKE_SYSTEM_NAME MATCHES ".*tvOS.*") set(SDL_CMAKE_PLATFORM tvOS) elseif(CMAKE_SYSTEM_NAME MATCHES ".*iOS.*") set(SDL_CMAKE_PLATFORM iOS) endif() elseif(CMAKE_SYSTEM_NAME MATCHES "Haiku.*") set(SDL_CMAKE_PLATFORM Haiku) elseif(NINTENDO_3DS) set(SDL_CMAKE_PLATFORM n3ds) elseif(PS2) set(SDL_CMAKE_PLATFORM ps2) elseif(VITA) set(SDL_CMAKE_PLATFORM Vita) elseif(CMAKE_SYSTEM_NAME MATCHES ".*Linux") set(SDL_CMAKE_PLATFORM Linux) elseif(CMAKE_SYSTEM_NAME MATCHES "kFreeBSD.*") set(SDL_CMAKE_PLATFORM FreeBSD) elseif(CMAKE_SYSTEM_NAME MATCHES "kNetBSD.*|NetBSD.*") set(SDL_CMAKE_PLATFORM NetBSD) elseif(CMAKE_SYSTEM_NAME MATCHES "kOpenBSD.*|OpenBSD.*") set(SDL_CMAKE_PLATFORM OpenBSD) elseif(CMAKE_SYSTEM_NAME MATCHES ".*GNU.*") set(SDL_CMAKE_PLATFORM GNU) elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") set(SDL_CMAKE_PLATFORM BSDi) elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly.*|FreeBSD") set(SDL_CMAKE_PLATFORM FreeBSD) elseif(CMAKE_SYSTEM_NAME MATCHES "SYSV5.*") set(SDL_CMAKE_PLATFORM SYSV5) elseif(CMAKE_SYSTEM_NAME MATCHES "Solaris.*|SunOS.*") set(SDL_CMAKE_PLATFORM Solaris) elseif(CMAKE_SYSTEM_NAME MATCHES "HP-UX.*") set(SDL_CMAKE_PLATFORM HPUX) elseif(CMAKE_SYSTEM_NAME MATCHES "AIX.*") set(SDL_CMAKE_PLATFORM AIX) elseif(CMAKE_SYSTEM_NAME MATCHES "Minix.*") set(SDL_CMAKE_PLATFORM Minix) elseif(CMAKE_SYSTEM_NAME MATCHES "Android.*") set(SDL_CMAKE_PLATFORM Android) elseif(CMAKE_SYSTEM_NAME MATCHES "Emscripten.*") set(SDL_CMAKE_PLATFORM Emscripten) elseif(CMAKE_SYSTEM_NAME MATCHES "QNX.*") set(SDL_CMAKE_PLATFORM QNX) elseif(CMAKE_SYSTEM_NAME MATCHES "BeOS.*") message(FATAL_ERROR "BeOS support has been removed as of SDL 2.0.2.") endif() if(SDL_CMAKE_PLATFORM) string(TOUPPER "${SDL_CMAKE_PLATFORM}" _upper_platform) set(${_upper_platform} TRUE) else() set(SDL_CMAKE_PLATFORM} "unknown") endif() endmacro() function(SDL_DetectCPUArchitecture) set(sdl_cpu_names) if(APPLE AND CMAKE_OSX_ARCHITECTURES) foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if(osx_arch STREQUAL "x86_64") list(APPEND sdl_cpu_names "x64") elseif(osx_arch STREQUAL "arm64") list(APPEND sdl_cpu_names "arm64") endif() endforeach() endif() set(sdl_known_archs x64 x86 arm64 arm32 emscripten powerpc64 powerpc32 loongarch64) if(NOT sdl_cpu_names) set(found FALSE) foreach(sdl_known_arch ${sdl_known_archs}) if(NOT found) string(TOUPPER "${sdl_known_arch}" sdl_known_arch_upper) set(var_name "SDL_CPU_${sdl_known_arch_upper}") check_cpu_architecture(${sdl_known_arch} ${var_name}) if(${var_name}) list(APPEND sdl_cpu_names ${sdl_known_arch}) set(found TRUE) endif() endif() endforeach() endif() foreach(sdl_known_arch ${sdl_known_archs}) string(TOUPPER "${sdl_known_arch}" sdl_known_arch_upper) set(var_name "SDL_CPU_${sdl_known_arch_upper}") if(sdl_cpu_names MATCHES "(^|;)${sdl_known_arch}($|;)") # FIXME: use if(IN_LIST) set(${var_name} 1 PARENT_SCOPE) else() set(${var_name} 0 PARENT_SCOPE) endif() endforeach() set(SDL_CPU_NAMES ${sdl_cpu_names} PARENT_SCOPE) endfunction() libsdl3-mixer-3~git20250523~daf0503+ds/cmake/test/000077500000000000000000000000001501405355700212505ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/cmake/test/CMakeLists.txt000066400000000000000000000027301501405355700240120ustar00rootroot00000000000000# This cmake build script is meant for verifying the various CMake configuration script. cmake_minimum_required(VERSION 3.12...3.28) project(sdl_test LANGUAGES C) cmake_policy(SET CMP0074 NEW) # Override CMAKE_FIND_ROOT_PATH_MODE to allow search for SDL3_mixer outside of sysroot set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) include(FeatureSummary) option(TEST_SHARED "Test linking to shared SDL3_mixer library" ON) add_feature_info("TEST_SHARED" TEST_SHARED "Test linking with shared library") option(TEST_STATIC "Test linking to static SDL3_mixer libary" ON) add_feature_info("TEST_STATIC" TEST_STATIC "Test linking with static library") if(ANDROID) macro(add_executable NAME) set(args ${ARGN}) list(REMOVE_ITEM args WIN32) add_library(${NAME} SHARED ${args}) unset(args) endmacro() endif() if(TEST_SHARED) find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3) find_package(SDL3_mixer REQUIRED CONFIG) add_executable(main_shared main.c) target_link_libraries(main_shared PRIVATE SDL3_mixer::SDL3_mixer-shared SDL3::SDL3) endif() if(TEST_STATIC) find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3) # some static vendored libraries use c++ (enable CXX after `find_package` might show a warning) enable_language(CXX) find_package(SDL3_mixer REQUIRED CONFIG) add_executable(main_static main.c) target_link_libraries(main_static PRIVATE SDL3_mixer::SDL3_mixer-static SDL3::SDL3) endif() feature_summary(WHAT ALL) libsdl3-mixer-3~git20250523~daf0503+ds/cmake/test/main.c000066400000000000000000000006341501405355700223430ustar00rootroot00000000000000#include #include #include int main(int argc, char *argv[]) { if (!SDL_Init(0)) { SDL_Log("SDL_Init: could not initialize SDL: %s\n", SDL_GetError()); return 1; } if (Mix_Init(0) == 0) { SDL_Log("Mix_Init: no sound/music loaders supported (%s)\n", SDL_GetError()); } Mix_Quit(); SDL_Quit(); return 0; } libsdl3-mixer-3~git20250523~daf0503+ds/examples/000077500000000000000000000000001501405355700210275ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/examples/playmus.c000066400000000000000000000212461501405355700226720ustar00rootroot00000000000000/* PLAYMUS: A test application for the SDL mixer library. Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* Quiet windows compiler warnings */ #define _CRT_SECURE_NO_WARNINGS #include #include #include #if defined(__unix__) || defined(__APPLE__) #include #endif #include #include #include #ifdef HAVE_SIGNAL_H #include #endif static int audio_open = 0; static Mix_Music *music = NULL; static int next_track = 0; static void CleanUp(int exitcode) { if(Mix_PlayingMusic()) { Mix_FadeOutMusic(1500); SDL_Delay(1500); } if (music) { Mix_FreeMusic(music); music = NULL; } if (audio_open) { Mix_CloseAudio(); audio_open = 0; } SDL_Quit(); exit(exitcode); } static void Usage(char *argv0) { SDL_Log("Usage: %s [-i] [-l] [-8] [-f32] [-r rate] [-c channels] [-b buffers] [-v N] [-io] \n", argv0); } /*#define SEEK_TEST */ static void Menu(void) { char buf[10]; printf("Available commands: (p)ause (r)esume (h)alt volume(v#) > "); if (fgets(buf, sizeof(buf), stdin)) { switch(buf[0]) { #if defined(SEEK_TEST) case '0': Mix_SetMusicPosition(0); break; case '1': Mix_SetMusicPosition(10);break; case '2': Mix_SetMusicPosition(20);break; case '3': Mix_SetMusicPosition(30);break; case '4': Mix_SetMusicPosition(40);break; #endif /* SEEK_TEST */ case 'p': case 'P': Mix_PauseMusic(); break; case 'r': case 'R': Mix_ResumeMusic(); break; case 'h': case 'H': Mix_HaltMusic(); break; case 'v': case 'V': Mix_VolumeMusic(SDL_atoi(buf+1)); break; } } printf("Music playing: %s Paused: %s\n", Mix_PlayingMusic() ? "yes" : "no", Mix_PausedMusic() ? "yes" : "no"); } #ifdef HAVE_SIGNAL_H static void IntHandler(int sig) { switch (sig) { case SIGINT: next_track++; break; } } #endif int main(int argc, char *argv[]) { int audio_volume = MIX_MAX_VOLUME; int looping = 0; bool interactive = false; bool use_io = false; int i; const char *typ; const char *tag_title = NULL; const char *tag_artist = NULL; const char *tag_album = NULL; const char *tag_copyright = NULL; double loop_start, loop_end, loop_length, current_position; SDL_AudioSpec spec; (void) argc; /* Initialize variables */ spec.freq = MIX_DEFAULT_FREQUENCY; spec.format = MIX_DEFAULT_FORMAT; spec.channels = MIX_DEFAULT_CHANNELS; /* Check command line usage */ for (i = 1; argv[i] && (*argv[i] == '-'); ++i) { if ((SDL_strcmp(argv[i], "-r") == 0) && argv[i+1]) { ++i; spec.freq = SDL_atoi(argv[i]); } else if (SDL_strcmp(argv[i], "-m") == 0) { spec.channels = 1; } else if ((SDL_strcmp(argv[i], "-c") == 0) && argv[i+1]) { ++i; spec.channels = SDL_atoi(argv[i]); } else if ((SDL_strcmp(argv[i], "-b") == 0) && argv[i+1]) { ++i; /*ignored now. audio_buffers = SDL_atoi(argv[i]); */ } else if ((SDL_strcmp(argv[i], "-v") == 0) && argv[i+1]) { ++i; audio_volume = SDL_atoi(argv[i]); } else if (SDL_strcmp(argv[i], "-l") == 0) { looping = -1; } else if (SDL_strcmp(argv[i], "-i") == 0) { interactive = true; } else if (SDL_strcmp(argv[i], "-8") == 0) { spec.format = SDL_AUDIO_U8; } else if (SDL_strcmp(argv[i], "-f32") == 0) { spec.format = SDL_AUDIO_F32; } else if (SDL_strcmp(argv[i], "-io") == 0) { use_io = 1; } else { Usage(argv[0]); return 1; } } if (!argv[i]) { Usage(argv[0]); return 1; } /* Initialize the SDL library */ if (!SDL_Init(SDL_INIT_AUDIO)) { SDL_Log("Couldn't initialize SDL: %s\n",SDL_GetError()); return 255; } #ifdef HAVE_SIGNAL_H signal(SIGINT, IntHandler); signal(SIGTERM, CleanUp); #endif /* Open the audio device */ if (!Mix_OpenAudio(0, &spec)) { SDL_Log("Couldn't open audio: %s\n", SDL_GetError()); return 2; } else { Mix_QuerySpec(&spec.freq, &spec.format, &spec.channels); SDL_Log("Opened audio at %d Hz %d bit%s %s audio buffer\n", spec.freq, (spec.format&0xFF), (SDL_AUDIO_ISFLOAT(spec.format) ? " (float)" : ""), (spec.channels > 2) ? "surround" : (spec.channels > 1) ? "stereo" : "mono"); } audio_open = 1; /* Set the music volume */ Mix_VolumeMusic(audio_volume); while (argv[i]) { next_track = 0; /* Load the requested music file */ if (use_io) { music = Mix_LoadMUS_IO(SDL_IOFromFile(argv[i], "rb"), true); } else { music = Mix_LoadMUS(argv[i]); } if (music == NULL) { SDL_Log("Couldn't load %s: %s\n", argv[i], SDL_GetError()); CleanUp(2); } switch (Mix_GetMusicType(music)) { case MUS_WAV: typ = "WAV"; break; case MUS_MOD: typ = "MOD"; break; case MUS_FLAC: typ = "FLAC"; break; case MUS_MID: typ = "MIDI"; break; case MUS_OGG: typ = "OGG Vorbis"; break; case MUS_MP3: typ = "MP3"; break; case MUS_OPUS: typ = "OPUS"; break; case MUS_WAVPACK: typ = "WavPack"; break; case MUS_NONE: default: typ = "NONE"; break; } SDL_Log("Detected music type: %s", typ); tag_title = Mix_GetMusicTitleTag(music); if (tag_title && SDL_strlen(tag_title) > 0) { SDL_Log("Title: %s", tag_title); } tag_artist = Mix_GetMusicArtistTag(music); if (tag_artist && SDL_strlen(tag_artist) > 0) { SDL_Log("Artist: %s", tag_artist); } tag_album = Mix_GetMusicAlbumTag(music); if (tag_album && SDL_strlen(tag_album) > 0) { SDL_Log("Album: %s", tag_album); } tag_copyright = Mix_GetMusicCopyrightTag(music); if (tag_copyright && SDL_strlen(tag_copyright) > 0) { SDL_Log("Copyright: %s", tag_copyright); } loop_start = Mix_GetMusicLoopStartTime(music); loop_end = Mix_GetMusicLoopEndTime(music); loop_length = Mix_GetMusicLoopLengthTime(music); /* Play and then exit */ SDL_Log("Playing %s, duration %f\n", argv[i], Mix_MusicDuration(music)); if (loop_start > 0.0 && loop_end > 0.0 && loop_length > 0.0) { SDL_Log("Loop points: start %g s, end %g s, length %g s\n", loop_start, loop_end, loop_length); } Mix_FadeInMusic(music,looping,2000); while (!next_track && (Mix_PlayingMusic() || Mix_PausedMusic())) { if (interactive) { Menu(); } else { current_position = Mix_GetMusicPosition(music); if (current_position >= 0.0) { printf("Position: %g seconds \r", current_position); fflush(stdout); } SDL_Delay(100); } } Mix_FreeMusic(music); music = NULL; /* If the user presses Ctrl-C more than once, exit. */ SDL_Delay(500); if (next_track > 1) { break; } i++; } CleanUp(0); /* Not reached, but fixes compiler warnings */ return 0; } /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/examples/playwave.c000066400000000000000000000320671501405355700230330ustar00rootroot00000000000000/* PLAYWAVE: A test application for the SDL mixer library. Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include #include #include #include #if defined(__unix__) || defined(__APPLE__) #include #endif #ifdef HAVE_SIGNAL_H #include #endif static int audio_open = 0; static Mix_Chunk *g_wave = NULL; static bool verbose = false; static bool test_position = false; static bool test_distance = false; static bool test_panning = false; static void report_decoders(void) { int i, total; SDL_Log("Supported decoders...\n"); total = Mix_GetNumChunkDecoders(); for (i = 0; i < total; i++) { SDL_Log(" - chunk decoder: %s\n", Mix_GetChunkDecoder(i)); } total = Mix_GetNumMusicDecoders(); for (i = 0; i < total; i++) { SDL_Log(" - music decoder: %s\n", Mix_GetMusicDecoder(i)); } } static void output_versions(const char *libname, int compiled, int linked) { SDL_Log("This program was compiled against %s %d.%d.%d,\n" " and is dynamically linked to %d.%d.%d.\n", libname, SDL_VERSIONNUM_MAJOR(compiled), SDL_VERSIONNUM_MINOR(compiled), SDL_VERSIONNUM_MICRO(compiled), SDL_VERSIONNUM_MAJOR(linked), SDL_VERSIONNUM_MINOR(linked), SDL_VERSIONNUM_MICRO(linked)); } static void test_versions(void) { output_versions("SDL", SDL_VERSION, SDL_GetVersion()); output_versions("SDL_mixer", SDL_MIXER_VERSION, Mix_Version()); } static int channel_is_done = 0; static void SDLCALL channel_complete_callback (int chan) { if (verbose) { Mix_Chunk *done_chunk = Mix_GetChunk(chan); SDL_Log("We were just alerted that Mixer channel #%d is done.\n", chan); SDL_Log("Channel's chunk pointer is (%p).\n", (void *) done_chunk); SDL_Log(" Which %s correct.\n", (g_wave == done_chunk) ? "is" : "is NOT"); } channel_is_done = 1; } /* rcg06192001 abstract this out for testing purposes. */ static int still_playing(void) { return Mix_Playing(0); } static void do_panning_update(void) { static Uint8 leftvol = 128; static Uint8 rightvol = 128; static Sint8 leftincr = -1; static Sint8 rightincr = 1; static int panningok = 1; static Uint64 next_panning_update = 0; if (panningok && (SDL_GetTicks() >= next_panning_update)) { panningok = Mix_SetPanning(0, leftvol, rightvol); if (!panningok) { SDL_Log("Mix_SetPanning(0, %d, %d) failed!\n", (int) leftvol, (int) rightvol); SDL_Log("Reason: [%s].\n", SDL_GetError()); } if ((leftvol == 255) || (leftvol == 0)) { if (leftvol == 255) { SDL_Log("All the way in the left speaker.\n"); } leftincr *= -1; } if ((rightvol == 255) || (rightvol == 0)) { if (rightvol == 255) { SDL_Log("All the way in the right speaker.\n"); } rightincr *= -1; } leftvol += leftincr; rightvol += rightincr; next_panning_update = SDL_GetTicks() + 10; } } static void do_distance_update(void) { static Uint8 distance = 1; static Sint8 distincr = 1; static int distanceok = 1; static Uint64 next_distance_update = 0; if ((distanceok) && (SDL_GetTicks() >= next_distance_update)) { distanceok = Mix_SetDistance(0, distance); if (!distanceok) { SDL_Log("Mix_SetDistance(0, %d) failed!\n", (int) distance); SDL_Log("Reason: [%s].\n", SDL_GetError()); } if (distance == 0) { SDL_Log("Distance at nearest point.\n"); distincr *= -1; } else if (distance == 255) { SDL_Log("Distance at furthest point.\n"); distincr *= -1; } distance += distincr; next_distance_update = SDL_GetTicks() + 15; } } static void do_position_update(void) { static Sint16 distance = 1; static Sint8 distincr = 1; static Sint16 angle = 0; static Sint8 angleincr = 1; static int positionok = 1; static Uint64 next_position_update = 0; if (positionok && (SDL_GetTicks() >= next_position_update)) { positionok = Mix_SetPosition(0, angle, (Uint8)distance); if (!positionok) { SDL_Log("Mix_SetPosition(0, %d, %d) failed!\n", (int) angle, (int) distance); SDL_Log("Reason: [%s].\n", SDL_GetError()); } if (angle == 0) { SDL_Log("Due north; now rotating clockwise...\n"); angleincr = 1; } else if (angle == 360) { SDL_Log("Due north; now rotating counter-clockwise...\n"); angleincr = -1; } distance += distincr; if (distance < 0) { distance = 0; distincr = 3; SDL_Log("Distance is very, very near. Stepping away by threes...\n"); } else if (distance > 255) { distance = 255; distincr = -3; SDL_Log("Distance is very, very far. Stepping towards by threes...\n"); } angle += angleincr; next_position_update = SDL_GetTicks() + 30; } } static void CleanUp(int exitcode) { if (g_wave) { Mix_FreeChunk(g_wave); g_wave = NULL; } if (audio_open) { Mix_CloseAudio(); audio_open = 0; } SDL_Quit(); exit(exitcode); } /* * rcg06182001 This is sick, but cool. * * Actually, it's meant to be an example of how to manipulate a voice * without having to use the mixer effects API. This is more processing * up front, but no extra during the mixing process. Also, in a case like * this, when you need to touch the whole sample at once, it's the only * option you've got. And, with the effects API, you are altering a copy of * the original sample for each playback, and thus, your changes aren't * permanent; here, you've got a reversed sample, and that's that until * you either reverse it again, or reload it. */ static void flip_sample(Mix_Chunk *wave) { SDL_AudioFormat format; int channels, i, incr; Uint8 *start = wave->abuf; Uint8 *end = wave->abuf + wave->alen; Mix_QuerySpec(NULL, &format, &channels); incr = SDL_AUDIO_BITSIZE(format) * channels; end -= incr; switch (incr) { case 8: for (i = wave->alen / 2; i >= 0; i -= 1) { Uint8 tmp = *start; *start = *end; *end = tmp; start++; end--; } break; case 16: for (i = wave->alen / 2; i >= 0; i -= 2) { Uint16 tmp = *start; *((Uint16 *) start) = *((Uint16 *) end); *((Uint16 *) end) = tmp; start += 2; end -= 2; } break; case 32: for (i = wave->alen / 2; i >= 0; i -= 4) { Uint32 tmp = *start; *((Uint32 *) start) = *((Uint32 *) end); *((Uint32 *) end) = tmp; start += 4; end -= 4; } break; case 64: for (i = wave->alen / 2; i >= 0; i -= 8) { Uint64 tmp = *start; *((Uint64 *) start) = *((Uint64 *) end); *((Uint64 *) end) = tmp; start += 8; end -= 8; } break; default: SDL_Log("Unhandled format in sample flipping.\n"); return; } } int main(int argc, char *argv[]) { SDL_AudioSpec spec; int loops = 0; int i; int reverse_stereo = 0; int reverse_sample = 0; const char *filename = NULL; /* Enable standard application logging */ SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); #ifdef HAVE_SETBUF setbuf(stdout, NULL); /* rcg06132001 for debugging purposes. */ setbuf(stderr, NULL); /* rcg06192001 for debugging purposes, too. */ #endif /* Initialize variables */ spec.freq = MIX_DEFAULT_FREQUENCY; spec.format = MIX_DEFAULT_FORMAT; spec.channels = MIX_DEFAULT_CHANNELS; /* Parse commandline */ for (i = 1; i < argc;) { int consumed = 0; if (SDL_strcmp("-r", argv[i]) == 0) { spec.freq = SDL_atoi(argv[i + 1]); consumed = 2; } else if (SDL_strcmp("-m", argv[i]) == 0) { spec.channels = 1; consumed = 1; } else if (SDL_strcmp("-c", argv[i]) == 0) { spec.channels = SDL_atoi(argv[i + 1]); consumed = 2; } else if (SDL_strcmp("-l", argv[i]) == 0) { loops = -1; consumed = 1; } else if (SDL_strcmp("-8", argv[i]) == 0) { spec.format = SDL_AUDIO_U8; consumed = 1; } else if (SDL_strcmp("-f32", argv[i]) == 0) { spec.format = SDL_AUDIO_F32; consumed = 1; } else if (SDL_strcmp("-f", argv[i]) == 0) { reverse_stereo = 1; consumed = 1; } else if (SDL_strcmp("-F", argv[i]) == 0) { reverse_sample = 1; consumed = 1; } else if (SDL_strcmp("--panning", argv[i]) == 0) { test_panning = true; consumed = 1; } else if (SDL_strcmp("--distance", argv[i]) == 0) { test_distance = true; consumed = 1; } else if (SDL_strcmp("--position", argv[i]) == 0) { test_position = true; consumed = 1; } else if (SDL_strcmp("--version", argv[i]) == 0) { test_versions(); CleanUp(0); consumed = 1; } else if (SDL_strcmp("--verbose", argv[i]) == 0) { verbose = true; consumed = 1; } else if (argv[i][0] != '-' && !filename) { filename = argv[i]; consumed = 1; } if (consumed <= 0) { SDL_Log("Usage: %s [-r rate] [-m] [-c channels] [-l] [-8] [-f32] [-f] [-F] [--distance] [--panning] [--position] [--version] ", argv[0]); return 1; } i += consumed; } if (test_position && (test_distance || test_panning)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "position cannot be combined with distance or panning"); CleanUp(1); } /* Initialize the SDL library */ if (!SDL_Init(SDL_INIT_AUDIO)) { SDL_Log("Couldn't initialize SDL: %s\n",SDL_GetError()); return 255; } #ifdef HAVE_SIGNAL_H signal(SIGINT, CleanUp); signal(SIGTERM, CleanUp); #endif /* Open the audio device */ if (!Mix_OpenAudio(0, &spec)) { SDL_Log("Couldn't open audio: %s\n", SDL_GetError()); CleanUp(2); } else { Mix_QuerySpec(&spec.freq, &spec.format, &spec.channels); SDL_Log("Opened audio at %d Hz %d bit%s %s", spec.freq, (spec.format&0xFF), (SDL_AUDIO_ISFLOAT(spec.format) ? " (float)" : ""), (spec.channels > 2) ? "surround" : (spec.channels > 1) ? "stereo" : "mono"); if (loops) { SDL_Log(" (looping)\n"); } else { putchar('\n'); } } audio_open = 1; if (verbose) { report_decoders(); } /* Load the requested wave file */ g_wave = Mix_LoadWAV(filename); if (g_wave == NULL) { SDL_Log("Couldn't load %s: %s\n", filename, SDL_GetError()); CleanUp(2); } if (reverse_sample) { flip_sample(g_wave); } Mix_ChannelFinished(channel_complete_callback); if ((!Mix_SetReverseStereo(MIX_CHANNEL_POST, reverse_stereo)) && (reverse_stereo)) { SDL_Log("Failed to set up reverse stereo effect!\n"); SDL_Log("Reason: [%s].\n", SDL_GetError()); } /* Play and then exit */ Mix_PlayChannel(0, g_wave, loops); while (still_playing()) { if (test_panning) { do_panning_update(); } if (test_distance) { do_distance_update(); } if (test_position) { do_position_update(); } SDL_Delay(1); } /* while still_playing() loop... */ CleanUp(0); /* Not reached, but fixes compiler warnings */ return 0; } /* end of playwave.c ... */ libsdl3-mixer-3~git20250523~daf0503+ds/include/000077500000000000000000000000001501405355700206345ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/include/SDL3_mixer/000077500000000000000000000000001501405355700225455ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/include/SDL3_mixer/SDL_mixer.h000066400000000000000000003035431501405355700245540ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* WIKI CATEGORY: SDLMixer */ /** * # CategorySDLMixer * * Header file for SDL_mixer library * * A simple library to play and mix sounds and musics */ #ifndef SDL_MIXER_H_ #define SDL_MIXER_H_ #include #include /* Set up for C function definitions, even when using C++ */ #ifdef __cplusplus extern "C" { #endif /** * Printable format: "%d.%d.%d", MAJOR, MINOR, MICRO */ #define SDL_MIXER_MAJOR_VERSION 3 #define SDL_MIXER_MINOR_VERSION 0 #define SDL_MIXER_MICRO_VERSION 0 /** * This is the version number macro for the current SDL_mixer version. */ #define SDL_MIXER_VERSION \ SDL_VERSIONNUM(SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_MICRO_VERSION) /** * This macro will evaluate to true if compiled with SDL_mixer at least X.Y.Z. */ #define SDL_MIXER_VERSION_ATLEAST(X, Y, Z) \ ((SDL_MIXER_MAJOR_VERSION >= X) && \ (SDL_MIXER_MAJOR_VERSION > X || SDL_MIXER_MINOR_VERSION >= Y) && \ (SDL_MIXER_MAJOR_VERSION > X || SDL_MIXER_MINOR_VERSION > Y || SDL_MIXER_MICRO_VERSION >= Z)) /** * This function gets the version of the dynamically linked SDL_mixer library. * * \returns SDL_mixer version. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_Version(void); /** * Initialization flags */ typedef Uint32 MIX_InitFlags; #define MIX_INIT_FLAC 0x00000001 #define MIX_INIT_MOD 0x00000002 #define MIX_INIT_MP3 0x00000008 #define MIX_INIT_OGG 0x00000010 #define MIX_INIT_MID 0x00000020 #define MIX_INIT_OPUS 0x00000040 #define MIX_INIT_WAVPACK 0x00000080 /** * Initialize SDL_mixer. * * This function loads dynamic libraries that SDL_mixer needs, and prepares * them for use. * * Note that, unlike other SDL libraries, this call is optional! If you load a * music file, SDL_mixer will handle initialization on the fly. This function * will let you know, up front, whether a specific format will be available * for use. * * Flags should be one or more flags from MIX_InitFlags OR'd together. It * returns the flags successfully initialized, or 0 on failure. * * Currently, these flags are: * * - `MIX_INIT_FLAC` * - `MIX_INIT_MOD` * - `MIX_INIT_MP3` * - `MIX_INIT_OGG` * - `MIX_INIT_MID` * - `MIX_INIT_OPUS` * - `MIX_INIT_WAVPACK` * * More flags may be added in a future SDL_mixer release. * * This function may need to load external shared libraries to support various * codecs, which means this function can fail to initialize that support on an * otherwise-reasonable system if the library isn't available; this is not * just a question of exceptional circumstances like running out of memory at * startup! * * Note that you may call this function more than once to initialize with * additional flags. The return value will reflect both new flags that * successfully initialized, and also include flags that had previously been * initialized as well. * * As this will return previously-initialized flags, it's legal to call this * with zero (no flags set). This is a safe no-op that can be used to query * the current initialization state without changing it at all. * * Since this returns previously-initialized flags as well as new ones, and * you can call this with zero, you should not check for a zero return value * to determine an error condition. Instead, you should check to make sure all * the flags you require are set in the return value. If you have a game with * data in a specific format, this might be a fatal error. If you're a generic * media player, perhaps you are fine with only having WAV and MP3 support and * can live without Opus playback, even if you request support for everything. * * Unlike other SDL satellite libraries, calls to Mix_Init do not stack; a * single call to Mix_Quit() will deinitialize everything and does not have to * be paired with a matching Mix_Init call. For that reason, it's considered * best practices to have a single Mix_Init and Mix_Quit call in your program. * While this isn't required, be aware of the risks of deviating from that * behavior. * * After initializing SDL_mixer, the next step is to open an audio device to * prepare to play sound (with Mix_OpenAudio()), and load audio data to play * with that device. * * \param flags initialization flags, OR'd together. * \returns all currently initialized flags. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_Quit */ extern SDL_DECLSPEC MIX_InitFlags SDLCALL Mix_Init(MIX_InitFlags flags); /** * Deinitialize SDL_mixer. * * This should be the last function you call in SDL_mixer, after freeing all * other resources and closing all audio devices. This will unload any shared * libraries it is using for various codecs. * * After this call, a call to Mix_Init(0) will return 0 (no codecs loaded). * * You can safely call Mix_Init() to reload various codec support after this * call. * * Unlike other SDL satellite libraries, calls to Mix_Init do not stack; a * single call to Mix_Quit() will deinitialize everything and does not have to * be paired with a matching Mix_Init call. For that reason, it's considered * best practices to have a single Mix_Init and Mix_Quit call in your program. * While this isn't required, be aware of the risks of deviating from that * behavior. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_Init */ extern SDL_DECLSPEC void SDLCALL Mix_Quit(void); /** * The default mixer has 8 simultaneous mixing channels */ #ifndef MIX_CHANNELS #define MIX_CHANNELS 8 #endif /* Good default values for a PC soundcard */ #define MIX_DEFAULT_FREQUENCY 44100 #define MIX_DEFAULT_FORMAT SDL_AUDIO_S16 #define MIX_DEFAULT_CHANNELS 2 #define MIX_MAX_VOLUME 128 /* Volume of a chunk */ /** * The internal format for an audio chunk */ typedef struct Mix_Chunk { int allocated; Uint8 *abuf; Uint32 alen; Uint8 volume; /* Per-sample volume, 0-128 */ } Mix_Chunk; /** * The different fading types supported */ typedef enum Mix_Fading { MIX_NO_FADING, MIX_FADING_OUT, MIX_FADING_IN } Mix_Fading; /** * These are types of music files (not libraries used to load them) */ typedef enum Mix_MusicType { MUS_NONE, MUS_WAV, MUS_MOD, MUS_MID, MUS_OGG, MUS_MP3, MUS_FLAC, MUS_OPUS, MUS_WAVPACK, MUS_GME } Mix_MusicType; /** * The internal format for a music chunk interpreted via codecs */ typedef struct Mix_Music Mix_Music; /** * Open an audio device for playback. * * An audio device is what generates sound, so the app must open one to make * noise. * * This function will check if SDL's audio system is initialized, and if not, * it will initialize it by calling `SDL_Init(SDL_INIT_AUDIO)` on your behalf. * You are free to (and encouraged to!) initialize it yourself before calling * this function, as this gives your program more control over the process. * * If you aren't particularly concerned with the specifics of the audio * device, and your data isn't in a specific format, you can pass a NULL for * the `spec` parameter and SDL_mixer will choose a reasonable default. * SDL_mixer will convert audio data you feed it to the hardware's format * behind the scenes. * * That being said, if you have control of your audio data and you know its * format ahead of time, you may save CPU time by opening the audio device in * that exact format so SDL_mixer does not have to spend time converting * anything behind the scenes, and can just pass the data straight through to * the hardware. * * The other reason to care about specific formats: if you plan to touch the * mix buffer directly (with Mix_SetPostMix, a registered effect, or * Mix_HookMusic), you might have code that expects it to be in a specific * format, and you should specify that here. * * This function allows you to select specific audio hardware on the system * with the `devid` parameter. If you specify 0, SDL_mixer will choose the * best default it can on your behalf (which, in many cases, is exactly what * you want anyhow). This is equivalent to specifying * `SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK`, but is less wordy. SDL_mixer does not * offer a mechanism to determine device IDs to open, but you can use * SDL_GetAudioOutputDevices() to get a list of available devices. If you do * this, be sure to call `SDL_Init(SDL_INIT_AUDIO)` first to initialize SDL's * audio system! * * If this function reports success, you are ready to start making noise! Load * some audio data and start playing! * * When done with an audio device, probably at the end of the program, the app * should close the audio with Mix_CloseAudio(). * * \param devid the device name to open, or 0 for a reasonable default. * \param spec the audio format you'd like SDL_mixer to work in. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_CloseAudio * \sa Mix_QuerySpec */ extern SDL_DECLSPEC bool SDLCALL Mix_OpenAudio(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec); /** * Suspend or resume the whole audio output. * * \param pause_on 1 to pause audio output, or 0 to resume. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_PauseAudio(int pause_on); /** * Find out what the actual audio device parameters are. * * Note this is only important if the app intends to touch the audio buffers * being sent to the hardware directly. If an app just wants to play audio * files and let SDL_mixer handle the low-level details, this function can * probably be ignored. * * If the audio device is not opened, this function will return 0. * * \param frequency On return, will be filled with the audio device's * frequency in Hz. * \param format On return, will be filled with the audio device's format. * \param channels On return, will be filled with the audio device's channel * count. * \returns true if the audio device has been opened, false otherwise. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_OpenAudio */ extern SDL_DECLSPEC bool SDLCALL Mix_QuerySpec(int *frequency, SDL_AudioFormat *format, int *channels); /** * Dynamically change the number of channels managed by the mixer. * * SDL_mixer deals with "channels," which is not the same thing as the * mono/stereo channels; they might be better described as "tracks," as each * one corresponds to a separate source of audio data. Three different WAV * files playing at the same time would be three separate SDL_mixer channels, * for example. * * An app needs as many channels as it has audio data it wants to play * simultaneously, mixing them into a single stream to send to the audio * device. * * SDL_mixer allocates `MIX_CHANNELS` (currently 8) channels when you open an * audio device, which may be more than an app needs, but if the app needs * more or wants less, this function can change it. * * If decreasing the number of channels, any upper channels currently playing * are stopped. This will deregister all effects on those channels and call * any callback specified by Mix_ChannelFinished() for each removed channel. * * If `numchans` is less than zero, this will return the current number of * channels without changing anything. * * \param numchans the new number of channels, or < 0 to query current channel * count. * \returns the new number of allocated channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_AllocateChannels(int numchans); /** * Load a supported audio format into a chunk. * * SDL_mixer has two separate data structures for audio data. One it calls a * "chunk," which is meant to be a file completely decoded into memory up * front, and the other it calls "music" which is a file intended to be * decoded on demand. Originally, simple formats like uncompressed WAV files * were meant to be chunks and compressed things, like MP3s, were meant to be * music, and you would stream one thing for a game's music and make repeating * sound effects with the chunks. * * In modern times, this isn't split by format anymore, and most are * interchangeable, so the question is what the app thinks is worth * predecoding or not. Chunks might take more memory, but once they are loaded * won't need to decode again, whereas music always needs to be decoded on the * fly. Also, crucially, there are as many channels for chunks as the app can * allocate, but SDL_mixer only offers a single "music" channel. * * If `closeio` is true, the IOStream will be closed before returning, whether * this function succeeds or not. SDL_mixer reads everything it needs from the * IOStream during this call in any case. * * There is a separate function (a macro, before SDL_mixer 3.0.0) to read * files from disk without having to deal with SDL_IOStream: * `Mix_LoadWAV("filename.wav")` will call this function and manage those * details for you. * * When done with a chunk, the app should dispose of it with a call to * Mix_FreeChunk(). * * \param src an SDL_IOStream that data will be read from. * \param closeio true to close the SDL_IOStream before returning, false to * leave it open. * \returns a new chunk, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0 * * \sa Mix_LoadWAV * \sa Mix_FreeChunk */ extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV_IO(SDL_IOStream *src, bool closeio); /** * Load a supported audio format into a chunk. * * SDL_mixer has two separate data structures for audio data. One it calls a * "chunk," which is meant to be a file completely decoded into memory up * front, and the other it calls "music" which is a file intended to be * decoded on demand. Originally, simple formats like uncompressed WAV files * were meant to be chunks and compressed things, like MP3s, were meant to be * music, and you would stream one thing for a game's music and make repeating * sound effects with the chunks. * * In modern times, this isn't split by format anymore, and most are * interchangeable, so the question is what the app thinks is worth * predecoding or not. Chunks might take more memory, but once they are loaded * won't need to decode again, whereas music always needs to be decoded on the * fly. Also, crucially, there are as many channels for chunks as the app can * allocate, but SDL_mixer only offers a single "music" channel. * * If you would rather use the abstract SDL_IOStream interface to load data * from somewhere other than the filesystem, you can use Mix_LoadWAV_IO() * instead. * * When done with a chunk, the app should dispose of it with a call to * Mix_FreeChunk(). * * Note that before SDL_mixer 3.0.0, this function was a macro that called * Mix_LoadWAV_IO(), creating a IOStream and setting `closeio` to true. This * macro has since been promoted to a proper API function. Older binaries * linked against a newer SDL_mixer will still call Mix_LoadWAV_IO directly, * as they are using the macro, which was available since the dawn of time. * * \param file the filesystem path to load data from. * \returns a new chunk, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0 * * \sa Mix_LoadWAV_IO * \sa Mix_FreeChunk */ extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV(const char *file); /** * Load a supported audio format into a music object. * * SDL_mixer has two separate data structures for audio data. One it calls a * "chunk," which is meant to be a file completely decoded into memory up * front, and the other it calls "music" which is a file intended to be * decoded on demand. Originally, simple formats like uncompressed WAV files * were meant to be chunks and compressed things, like MP3s, were meant to be * music, and you would stream one thing for a game's music and make repeating * sound effects with the chunks. * * In modern times, this isn't split by format anymore, and most are * interchangeable, so the question is what the app thinks is worth * predecoding or not. Chunks might take more memory, but once they are loaded * won't need to decode again, whereas music always needs to be decoded on the * fly. Also, crucially, there are as many channels for chunks as the app can * allocate, but SDL_mixer only offers a single "music" channel. * * When done with this music, the app should dispose of it with a call to * Mix_FreeMusic(). * * \param file a file path from where to load music data. * \returns a new music object, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_FreeMusic */ extern SDL_DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS(const char *file); /** * Load a supported audio format into a music object. * * SDL_mixer has two separate data structures for audio data. One it calls a * "chunk," which is meant to be a file completely decoded into memory up * front, and the other it calls "music" which is a file intended to be * decoded on demand. Originally, simple formats like uncompressed WAV files * were meant to be chunks and compressed things, like MP3s, were meant to be * music, and you would stream one thing for a game's music and make repeating * sound effects with the chunks. * * In modern times, this isn't split by format anymore, and most are * interchangeable, so the question is what the app thinks is worth * predecoding or not. Chunks might take more memory, but once they are loaded * won't need to decode again, whereas music always needs to be decoded on the * fly. Also, crucially, there are as many channels for chunks as the app can * allocate, but SDL_mixer only offers a single "music" channel. * * If `closeio` is true, the IOStream will be closed before returning, whether * this function succeeds or not. SDL_mixer reads everything it needs from the * IOStream during this call in any case. * * As a convenience, there is a function to read files from disk without * having to deal with SDL_IOStream: `Mix_LoadMUS("filename.mp3")` will manage * those details for you. * * This function attempts to guess the file format from incoming data. If the * caller knows the format, or wants to force it, it should use * Mix_LoadMUSType_IO() instead. * * When done with this music, the app should dispose of it with a call to * Mix_FreeMusic(). * * \param src an SDL_IOStream that data will be read from. * \param closeio true to close the SDL_IOStream before returning, false to * leave it open. * \returns a new music object, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_FreeMusic */ extern SDL_DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS_IO(SDL_IOStream *src, bool closeio); /** * Load an audio format into a music object, assuming a specific format. * * SDL_mixer has two separate data structures for audio data. One it calls a * "chunk," which is meant to be a file completely decoded into memory up * front, and the other it calls "music" which is a file intended to be * decoded on demand. Originally, simple formats like uncompressed WAV files * were meant to be chunks and compressed things, like MP3s, were meant to be * music, and you would stream one thing for a game's music and make repeating * sound effects with the chunks. * * In modern times, this isn't split by format anymore, and most are * interchangeable, so the question is what the app thinks is worth * predecoding or not. Chunks might take more memory, but once they are loaded * won't need to decode again, whereas music always needs to be decoded on the * fly. Also, crucially, there are as many channels for chunks as the app can * allocate, but SDL_mixer only offers a single "music" channel. * * This function loads music data, and lets the application specify the type * of music being loaded, which might be useful if SDL_mixer cannot figure it * out from the data stream itself. * * Currently, the following types are supported: * * - `MUS_NONE` (SDL_mixer should guess, based on the data) * - `MUS_WAV` (Microsoft WAV files) * - `MUS_MOD` (Various tracker formats) * - `MUS_MID` (MIDI files) * - `MUS_OGG` (Ogg Vorbis files) * - `MUS_MP3` (MP3 files) * - `MUS_FLAC` (FLAC files) * - `MUS_OPUS` (Opus files) * - `MUS_WAVPACK` (WavPack files) * * If `closeio` is true, the IOStream will be closed before returning, whether * this function succeeds or not. SDL_mixer reads everything it needs from the * IOStream during this call in any case. * * As a convenience, there is a function to read files from disk without * having to deal with SDL_IOStream: `Mix_LoadMUS("filename.mp3")` will manage * those details for you (but not let you specify the music type explicitly).. * * When done with this music, the app should dispose of it with a call to * Mix_FreeMusic(). * * \param src an SDL_IOStream that data will be read from. * \param type the type of audio data provided by `src`. * \param closeio true to close the SDL_IOStream before returning, false to * leave it open. * \returns a new music object, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_FreeMusic */ extern SDL_DECLSPEC Mix_Music * SDLCALL Mix_LoadMUSType_IO(SDL_IOStream *src, Mix_MusicType type, bool closeio); /** * Load a WAV file from memory as quickly as possible. * * Unlike Mix_LoadWAV_IO, this function has several requirements, and unless * you control all your audio data and know what you're doing, you should * consider this function unsafe and not use it. * * - The provided audio data MUST be in Microsoft WAV format. * - The provided audio data shouldn't use any strange WAV extensions. * - The audio data MUST be in the exact same format as the audio device. This * function will not attempt to convert it, or even verify it's in the right * format. * - The audio data must be valid; this function does not know the size of the * memory buffer, so if the WAV data is corrupted, it can read past the end * of the buffer, causing a crash. * - The audio data must live at least as long as the returned Mix_Chunk, * because SDL_mixer will use that data directly and not make a copy of it. * * This function will do NO error checking! Be extremely careful here! * * (Seriously, use Mix_LoadWAV_IO instead.) * * If this function is successful, the provided memory buffer must remain * available until Mix_FreeChunk() is called on the returned chunk. * * \param mem memory buffer containing of a WAV file. * \returns a new chunk, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_LoadWAV_IO * \sa Mix_FreeChunk */ extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_WAV(Uint8 *mem); /** * Load a raw audio data from memory as quickly as possible. * * The audio data MUST be in the exact same format as the audio device. This * function will not attempt to convert it, or even verify it's in the right * format. * * If this function is successful, the provided memory buffer must remain * available until Mix_FreeChunk() is called on the returned chunk. * * \param mem memory buffer containing raw PCM data. * \param len length of buffer pointed to by `mem`, in bytes. * \returns a new chunk, or NULL on error. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_FreeChunk */ extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len); /** * Free an audio chunk. * * An app should call this function when it is done with a Mix_Chunk and wants * to dispose of its resources. * * SDL_mixer will stop any channels this chunk is currently playing on. This * will deregister all effects on those channels and call any callback * specified by Mix_ChannelFinished() for each removed channel. * * \param chunk the chunk to free. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_LoadWAV * \sa Mix_LoadWAV_IO * \sa Mix_QuickLoad_WAV * \sa Mix_QuickLoad_RAW */ extern SDL_DECLSPEC void SDLCALL Mix_FreeChunk(Mix_Chunk *chunk); /** * Free a music object. * * If this music is currently playing, it will be stopped. * * If this music is in the process of fading out (via Mix_FadeOutMusic()), * this function will *block* until the fade completes. If you need to avoid * this, be sure to call Mix_HaltMusic() before freeing the music. * * \param music the music object to free. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_LoadMUS * \sa Mix_LoadMUS_IO * \sa Mix_LoadMUSType_IO */ extern SDL_DECLSPEC void SDLCALL Mix_FreeMusic(Mix_Music *music); /** * Get a list of chunk decoders that this build of SDL_mixer provides. * * This list can change between builds AND runs of the program, if external * libraries that add functionality become available. You must successfully * call Mix_OpenAudio() before calling this function, as decoders are * activated at device open time. * * Appearing in this list doesn't promise your specific audio file will * decode...but it's handy to know if you have, say, a functioning Ogg Vorbis * install. * * These return values are static, read-only data; do not modify or free it. * The pointers remain valid until you call Mix_CloseAudio(). * * \returns number of chunk decoders available. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetChunkDecoder * \sa Mix_HasChunkDecoder */ extern SDL_DECLSPEC int SDLCALL Mix_GetNumChunkDecoders(void); /** * Get a chunk decoder's name. * * The requested decoder's index must be between zero and * Mix_GetNumChunkDecoders()-1. It's safe to call this with an invalid index; * this function will return NULL in that case. * * This list can change between builds AND runs of the program, if external * libraries that add functionality become available. You must successfully * call Mix_OpenAudio() before calling this function, as decoders are * activated at device open time. * * \param index index of the chunk decoder. * \returns the chunk decoder's name. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetNumChunkDecoders */ extern SDL_DECLSPEC const char * SDLCALL Mix_GetChunkDecoder(int index); /** * Check if a chunk decoder is available by name. * * This result can change between builds AND runs of the program, if external * libraries that add functionality become available. You must successfully * call Mix_OpenAudio() before calling this function, as decoders are * activated at device open time. * * Decoder names are arbitrary but also obvious, so you have to know what * you're looking for ahead of time, but usually it's the file extension in * capital letters (some example names are "AIFF", "VOC", "WAV"). * * \param name the decoder name to query. * \returns true if a decoder by that name is available, false otherwise. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetNumChunkDecoders * \sa Mix_GetChunkDecoder */ extern SDL_DECLSPEC bool SDLCALL Mix_HasChunkDecoder(const char *name); /** * Get a list of music decoders that this build of SDL_mixer provides. * * This list can change between builds AND runs of the program, if external * libraries that add functionality become available. You must successfully * call Mix_OpenAudio() before calling this function, as decoders are * activated at device open time. * * Appearing in this list doesn't promise your specific audio file will * decode...but it's handy to know if you have, say, a functioning Ogg Vorbis * install. * * These return values are static, read-only data; do not modify or free it. * The pointers remain valid until you call Mix_CloseAudio(). * * \returns number of music decoders available. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetMusicDecoder * \sa Mix_HasMusicDecoder */ extern SDL_DECLSPEC int SDLCALL Mix_GetNumMusicDecoders(void); /** * Get a music decoder's name. * * The requested decoder's index must be between zero and * Mix_GetNumMusicDecoders()-1. It's safe to call this with an invalid index; * this function will return NULL in that case. * * This list can change between builds AND runs of the program, if external * libraries that add functionality become available. You must successfully * call Mix_OpenAudio() before calling this function, as decoders are * activated at device open time. * * \param index index of the music decoder. * \returns the music decoder's name. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetNumMusicDecoders */ extern SDL_DECLSPEC const char * SDLCALL Mix_GetMusicDecoder(int index); /** * Check if a music decoder is available by name. * * This result can change between builds AND runs of the program, if external * libraries that add functionality become available. You must successfully * call Mix_OpenAudio() before calling this function, as decoders are * activated at device open time. * * Decoder names are arbitrary but also obvious, so you have to know what * you're looking for ahead of time, but usually it's the file extension in * capital letters (some example names are "MOD", "MP3", "FLAC"). * * \param name the decoder name to query. * \returns true if a decoder by that name is available, false otherwise. * * \since This function is available since SDL_mixer 3.0.0 * * \sa Mix_GetNumMusicDecoders * \sa Mix_GetMusicDecoder */ extern SDL_DECLSPEC bool SDLCALL Mix_HasMusicDecoder(const char *name); /** * Find out the format of a mixer music. * * If `music` is NULL, this will query the currently playing music (and return * MUS_NONE if nothing is currently playing). * * \param music the music object to query, or NULL for the currently-playing * music. * \returns the Mix_MusicType for the music object. * * \since This function is available since SDL_mixer 3.0.0 */ extern SDL_DECLSPEC Mix_MusicType SDLCALL Mix_GetMusicType(const Mix_Music *music); /** * Get the title for a music object, or its filename. * * This returns format-specific metadata. Not all file formats supply this! * * If `music` is NULL, this will query the currently-playing music. * * If music's title tag is missing or empty, the filename will be returned. If * you'd rather have the actual metadata or nothing, use * Mix_GetMusicTitleTag() instead. * * Please note that if the music was loaded from an SDL_IOStream instead of a * filename, the filename returned will be an empty string (""). * * This function never returns NULL! If no data is available, it will return * an empty string (""). * * \param music the music object to query, or NULL for the currently-playing * music. * \returns the music's title if available, or the filename if not, or "". * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetMusicTitleTag * \sa Mix_GetMusicArtistTag * \sa Mix_GetMusicAlbumTag * \sa Mix_GetMusicCopyrightTag */ extern SDL_DECLSPEC const char *SDLCALL Mix_GetMusicTitle(const Mix_Music *music); /** * Get the title for a music object. * * This returns format-specific metadata. Not all file formats supply this! * * If `music` is NULL, this will query the currently-playing music. * * Unlike this function, Mix_GetMusicTitle() produce a string with the music's * filename if a title isn't available, which might be preferable for some * applications. * * This function never returns NULL! If no data is available, it will return * an empty string (""). * * \param music the music object to query, or NULL for the currently-playing * music. * \returns the music's title if available, or "". * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetMusicTitle * \sa Mix_GetMusicArtistTag * \sa Mix_GetMusicAlbumTag * \sa Mix_GetMusicCopyrightTag */ extern SDL_DECLSPEC const char *SDLCALL Mix_GetMusicTitleTag(const Mix_Music *music); /** * Get the artist name for a music object. * * This returns format-specific metadata. Not all file formats supply this! * * If `music` is NULL, this will query the currently-playing music. * * This function never returns NULL! If no data is available, it will return * an empty string (""). * * \param music the music object to query, or NULL for the currently-playing * music. * \returns the music's artist name if available, or "". * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetMusicTitleTag * \sa Mix_GetMusicAlbumTag * \sa Mix_GetMusicCopyrightTag */ extern SDL_DECLSPEC const char *SDLCALL Mix_GetMusicArtistTag(const Mix_Music *music); /** * Get the album name for a music object. * * This returns format-specific metadata. Not all file formats supply this! * * If `music` is NULL, this will query the currently-playing music. * * This function never returns NULL! If no data is available, it will return * an empty string (""). * * \param music the music object to query, or NULL for the currently-playing * music. * \returns the music's album name if available, or "". * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetMusicTitleTag * \sa Mix_GetMusicArtistTag * \sa Mix_GetMusicCopyrightTag */ extern SDL_DECLSPEC const char *SDLCALL Mix_GetMusicAlbumTag(const Mix_Music *music); /** * Get the copyright text for a music object. * * This returns format-specific metadata. Not all file formats supply this! * * If `music` is NULL, this will query the currently-playing music. * * This function never returns NULL! If no data is available, it will return * an empty string (""). * * \param music the music object to query, or NULL for the currently-playing * music. * \returns the music's copyright text if available, or "". * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetMusicTitleTag * \sa Mix_GetMusicArtistTag * \sa Mix_GetMusicAlbumTag */ extern SDL_DECLSPEC const char *SDLCALL Mix_GetMusicCopyrightTag(const Mix_Music *music); typedef void (SDLCALL *Mix_MixCallback)(void *udata, Uint8 *stream, int len); /** * Set a function that is called after all mixing is performed. * * This can be used to provide real-time visual display of the audio stream or * add a custom mixer filter for the stream data. * * The callback will fire every time SDL_mixer is ready to supply more data to * the audio device, after it has finished all its mixing work. This runs * inside an SDL audio callback, so it's important that the callback return * quickly, or there could be problems in the audio playback. * * The data provided to the callback is in the format that the audio device * was opened in, and it represents the exact waveform SDL_mixer has mixed * from all playing chunks and music for playback. You are allowed to modify * the data, but it cannot be resized (so you can't add a reverb effect that * goes past the end of the buffer without saving some state between runs to * add it into the next callback, or resample the buffer to a smaller size to * speed it up, etc). * * The `arg` pointer supplied here is passed to the callback as-is, for * whatever the callback might want to do with it (keep track of some ongoing * state, settings, etc). * * Passing a NULL callback disables the post-mix callback until such a time as * a new one callback is set. * * There is only one callback available. If you need to mix multiple inputs, * be prepared to handle them from a single function. * * \param mix_func the callback function to become the new post-mix callback. * \param arg a pointer that is passed, untouched, to the callback. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_HookMusic */ extern SDL_DECLSPEC void SDLCALL Mix_SetPostMix(Mix_MixCallback mix_func, void *arg); /** * Add your own music player or additional mixer function. * * This works something like Mix_SetPostMix(), but it has some crucial * differences. Note that an app can use this _and_ Mix_SetPostMix() at the * same time. This allows an app to replace the built-in music playback, * either with it's own music decoder or with some sort of * procedurally-generated audio output. * * The supplied callback will fire every time SDL_mixer is preparing to supply * more data to the audio device. This runs inside an SDL audio callback, so * it's important that the callback return quickly, or there could be problems * in the audio playback. * * Running this callback is the first thing SDL_mixer will do when starting to * mix more audio. The buffer will contain silence upon entry, so the callback * does not need to mix into existing data or initialize the buffer. * * Note that while a callback is set through this function, SDL_mixer will not * mix any playing music; this callback is used instead. To disable this * callback (and thus reenable built-in music playback) call this function * with a NULL callback. * * The data written to by the callback is in the format that the audio device * was opened in, and upon return from the callback, SDL_mixer will mix any * playing chunks (but not music!) into the buffer. The callback cannot resize * the buffer (so you must be prepared to provide exactly the amount of data * demanded or leave it as silence). * * The `arg` pointer supplied here is passed to the callback as-is, for * whatever the callback might want to do with it (keep track of some ongoing * state, settings, etc). * * As there is only one music "channel" mixed, there is only one callback * available. If you need to mix multiple inputs, be prepared to handle them * from a single function. * * \param mix_func the callback function to become the new post-mix callback. * \param arg a pointer that is passed, untouched, to the callback. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_SetPostMix */ extern SDL_DECLSPEC void SDLCALL Mix_HookMusic(Mix_MixCallback mix_func, void *arg); typedef void (SDLCALL *Mix_MusicFinishedCallback)(void); /** * Set a callback that runs when a music object has stopped playing. * * This callback will fire when the currently-playing music has completed, or * when it has been explicitly stopped from a call to Mix_HaltMusic. As such, * this callback might fire from an arbitrary background thread at almost any * time; try to limit what you do here. * * It is legal to start a new music object playing in this callback (or * restart the one that just stopped). If the music finished normally, this * can be used to loop the music without a gap in the audio playback. * * A NULL pointer will disable the callback. * * \param music_finished the callback function to become the new notification * mechanism. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_HookMusicFinished(Mix_MusicFinishedCallback music_finished); /** * Get a pointer to the user data for the current music hook. * * This returns the `arg` pointer last passed to Mix_HookMusic(), or NULL if * that function has never been called. * * \returns pointer to the user data previously passed to Mix_HookMusic. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void * SDLCALL Mix_GetMusicHookData(void); typedef void (SDLCALL *Mix_ChannelFinishedCallback)(int channel); /** * Set a callback that runs when a channel has finished playing. * * The callback may be called from the mixer's audio callback or it could be * called as a result of Mix_HaltChannel(), etc. * * The callback has a single parameter, `channel`, which says what mixer * channel has just stopped. * * A NULL pointer will disable the callback. * * \param channel_finished the callback function to become the new * notification mechanism. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_ChannelFinished(Mix_ChannelFinishedCallback channel_finished); /** * Magic number for effects to operate on the postmix instead of a channel. */ #define MIX_CHANNEL_POST (-2) /** * This is the format of a special effect callback: * * myeffect(int chan, void *stream, int len, void *udata); * * (chan) is the channel number that your effect is affecting. (stream) is the * buffer of data to work upon. (len) is the size of (stream), and (udata) is * a user-defined bit of data, which you pass as the last arg of * Mix_RegisterEffect(), and is passed back unmolested to your callback. Your * effect changes the contents of (stream) based on whatever parameters are * significant, or just leaves it be, if you prefer. You can do whatever you * like to the buffer, though, and it will continue in its changed state down * the mixing pipeline, through any other effect functions, then finally to be * mixed with the rest of the channels and music for the final output stream. */ typedef void (SDLCALL *Mix_EffectFunc_t)(int chan, void *stream, int len, void *udata); /** * This is a callback that signifies that a channel has finished all its loops * and has completed playback. * * This gets called if the buffer plays out normally, or if you call * Mix_HaltChannel(), implicitly stop a channel via Mix_AllocateChannels(), or * unregister a callback while it's still playing. */ typedef void (SDLCALL *Mix_EffectDone_t)(int chan, void *udata); /** * Register a special effect function. * * At mixing time, the channel data is copied into a buffer and passed through * each registered effect function. After it passes through all the functions, * it is mixed into the final output stream. The copy to buffer is performed * once, then each effect function performs on the output of the previous * effect. Understand that this extra copy to a buffer is not performed if * there are no effects registered for a given chunk, which saves CPU cycles, * and any given effect will be extra cycles, too, so it is crucial that your * code run fast. Also note that the data that your function is given is in * the format of the sound device, and not the format you gave to * Mix_OpenAudio(), although they may in reality be the same. This is an * unfortunate but necessary speed concern. Use Mix_QuerySpec() to determine * if you can handle the data before you register your effect, and take * appropriate actions. * * You may also specify a callback (Mix_EffectDone_t) that is called when the * channel finishes playing. This gives you a more fine-grained control than * Mix_ChannelFinished(), in case you need to free effect-specific resources, * etc. If you don't need this, you can specify NULL. * * You may set the callbacks before or after calling Mix_PlayChannel(). * * Things like Mix_SetPanning() are just internal special effect functions, so * if you are using that, you've already incurred the overhead of a copy to a * separate buffer, and that these effects will be in the queue with any * functions you've registered. The list of registered effects for a channel * is reset when a chunk finishes playing, so you need to explicitly set them * with each call to Mix_PlayChannel*(). * * You may also register a special effect function that is to be run after * final mixing occurs. The rules for these callbacks are identical to those * in Mix_RegisterEffect, but they are run after all the channels and the * music have been mixed into a single stream, whereas channel-specific * effects run on a given channel before any other mixing occurs. These global * effect callbacks are call "posteffects". Posteffects only have their * Mix_EffectDone_t function called when they are unregistered (since the main * output stream is never "done" in the same sense as a channel). You must * unregister them manually when you've had enough. Your callback will be told * that the channel being mixed is `MIX_CHANNEL_POST` if the processing is * considered a posteffect. * * After all these effects have finished processing, the callback registered * through Mix_SetPostMix() runs, and then the stream goes to the audio * device. * * \param chan the channel to register an effect to, or MIX_CHANNEL_POST. * \param f effect the callback to run when more of this channel is to be * mixed. * \param d effect done callback. * \param arg argument. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg); /** * Explicitly unregister a special effect function. * * You may not need to call this at all, unless you need to stop an effect * from processing in the middle of a chunk's playback. * * Posteffects are never implicitly unregistered as they are for channels (as * the output stream does not have an end), but they may be explicitly * unregistered through this function by specifying MIX_CHANNEL_POST for a * channel. * * \param channel the channel to unregister an effect on, or MIX_CHANNEL_POST. * \param f effect the callback stop calling in future mixing iterations. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f); /** * Explicitly unregister all special effect functions. * * You may not need to call this at all, unless you need to stop all effects * from processing in the middle of a chunk's playback. * * Note that this will also shut off some internal effect processing, since * Mix_SetPanning() and others may use this API under the hood. This is called * internally when a channel completes playback. Posteffects are never * implicitly unregistered as they are for channels, but they may be * explicitly unregistered through this function by specifying * MIX_CHANNEL_POST for a channel. * * \param channel the channel to unregister all effects on, or * MIX_CHANNEL_POST. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_UnregisterAllEffects(int channel); /** * Environment variable that makes some mixing effects favor speed over * quality. */ #define MIX_EFFECTSMAXSPEED "MIX_EFFECTSMAXSPEED" /* * These are the internally-defined mixing effects. They use the same API that * effects defined in the application use, but are provided here as a * convenience. Some effects can reduce their quality or use more memory in * the name of speed; to enable this, make sure the environment variable * MIX_EFFECTSMAXSPEED (see above) is defined before you call * Mix_OpenAudio(). */ /** * Set the panning of a channel. * * The left and right channels are specified as integers between 0 and 255, * quietest to loudest, respectively. * * Technically, this is just individual volume control for a sample with two * (stereo) channels, so it can be used for more than just panning. If you * want real panning, call it like this: * * ```c * Mix_SetPanning(channel, left, 255 - left); * ``` * * Setting `channel` to MIX_CHANNEL_POST registers this as a posteffect, and * the panning will be done to the final mixed stream before passing it on to * the audio device. * * This uses the Mix_RegisterEffect() API internally, and returns without * registering the effect function if the audio device is not configured for * stereo output. Setting both `left` and `right` to 255 causes this effect to * be unregistered, since that is the data's normal state. * * Note that an audio device in mono mode is a no-op, but this call will * return successful in that case. Error messages can be retrieved from * Mix_GetError(). * * \param channel The mixer channel to pan or MIX_CHANNEL_POST. * \param left Volume of stereo left channel, 0 is silence, 255 is full * volume. * \param right Volume of stereo right channel, 0 is silence, 255 is full * volume. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_SetPosition * \sa Mix_SetDistance */ extern SDL_DECLSPEC bool SDLCALL Mix_SetPanning(int channel, Uint8 left, Uint8 right); /** * Set the position of a channel. * * `angle` is an integer from 0 to 360, that specifies the location of the * sound in relation to the listener. `angle` will be reduced as necessary * (540 becomes 180 degrees, -100 becomes 260). Angle 0 is due north, and * rotates clockwise as the value increases. For efficiency, the precision of * this effect may be limited (angles 1 through 7 might all produce the same * effect, 8 through 15 are equal, etc). `distance` is an integer between 0 * and 255 that specifies the space between the sound and the listener. The * larger the number, the further away the sound is. Using 255 does not * guarantee that the channel will be removed from the mixing process or be * completely silent. For efficiency, the precision of this effect may be * limited (distance 0 through 5 might all produce the same effect, 6 through * 10 are equal, etc). Setting `angle` and `distance` to 0 unregisters this * effect, since the data would be unchanged. * * If you need more precise positional audio, consider using OpenAL for * spatialized effects instead of SDL_mixer. This is only meant to be a basic * effect for simple "3D" games. * * If the audio device is configured for mono output, then you won't get any * effectiveness from the angle; however, distance attenuation on the channel * will still occur. While this effect will function with stereo voices, it * makes more sense to use voices with only one channel of sound, so when they * are mixed through this effect, the positioning will sound correct. You can * convert them to mono through SDL before giving them to the mixer in the * first place if you like. * * Setting the channel to MIX_CHANNEL_POST registers this as a posteffect, and * the positioning will be done to the final mixed stream before passing it on * to the audio device. * * This is a convenience wrapper over Mix_SetDistance() and Mix_SetPanning(). * * \param channel The mixer channel to position, or MIX_CHANNEL_POST. * \param angle angle, in degrees. North is 0, and goes clockwise. * \param distance distance; 0 is the listener, 255 is maxiumum distance away. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_SetPosition(int channel, Sint16 angle, Uint8 distance); /** * Set the "distance" of a channel. * * `distance` is an integer from 0 to 255 that specifies the location of the * sound in relation to the listener. Distance 0 is overlapping the listener, * and 255 is as far away as possible. A distance of 255 does not guarantee * silence; in such a case, you might want to try changing the chunk's volume, * or just cull the sample from the mixing process with Mix_HaltChannel(). For * efficiency, the precision of this effect may be limited (distances 1 * through 7 might all produce the same effect, 8 through 15 are equal, etc). * (distance) is an integer between 0 and 255 that specifies the space between * the sound and the listener. The larger the number, the further away the * sound is. Setting the distance to 0 unregisters this effect, since the data * would be unchanged. If you need more precise positional audio, consider * using OpenAL for spatialized effects instead of SDL_mixer. This is only * meant to be a basic effect for simple "3D" games. * * Setting the channel to MIX_CHANNEL_POST registers this as a posteffect, and * the distance attenuation will be done to the final mixed stream before * passing it on to the audio device. * * This uses the Mix_RegisterEffect() API internally. * * \param channel The mixer channel to attenuate, or MIX_CHANNEL_POST. * \param distance distance; 0 is the listener, 255 is maxiumum distance away. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_SetDistance(int channel, Uint8 distance); /** * Cause a channel to reverse its stereo. * * This is handy if the user has his speakers hooked up backwards, or you * would like to have a trippy sound effect. * * Calling this function with `flip` set to non-zero reverses the chunks's * usual channels. If `flip` is zero, the effect is unregistered. * * This uses the Mix_RegisterEffect() API internally, and thus is probably * more CPU intensive than having the user just plug in his speakers * correctly. Mix_SetReverseStereo() returns without registering the effect * function if the audio device is not configured for stereo output. * * If you specify MIX_CHANNEL_POST for `channel`, then this effect is used on * the final mixed stream before sending it on to the audio device (a * posteffect). * * \param channel The mixer channel to reverse, or MIX_CHANNEL_POST. * \param flip non-zero to reverse stereo, zero to disable this effect. * \returns true on success or false on failure; call SDL_GetError() for more * information. Note that an audio device in mono mode is a no-op, * but this call will return successful in that case. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_SetReverseStereo(int channel, int flip); /* end of effects API. */ /** * Reserve the first channels for the application. * * While SDL_mixer will use up to the number of channels allocated by * Mix_AllocateChannels(), this sets channels aside that will not be available * when calling Mix_PlayChannel with a channel of -1 (play on the first unused * channel). In this case, SDL_mixer will treat reserved channels as "used" * whether anything is playing on them at the moment or not. * * This is useful if you've budgeted some channels for dedicated audio and the * rest are just used as they are available. * * Calling this function will set channels 0 to `n - 1` to be reserved. This * will not change channel allocations. The number of reserved channels will * be clamped to the current number allocated. * * By default, no channels are reserved. * * \param num number of channels to reserve, starting at index zero. * \returns the number of reserved channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_ReserveChannels(int num); /* Channel grouping functions */ /** * Assign a tag to a channel. * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * If 'tag' is -1, the tag is removed (actually -1 is the tag used to * represent the group of all the channels). * * This function replaces the requested channel's current tag; you may only * have one tag per channel. * * You may not specify MAX_CHANNEL_POST for a channel. * * \param which the channel to set the tag on. * \param tag an arbitrary value to assign a channel. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_GroupChannel(int which, int tag); /** * Assign several consecutive channels to the same tag. * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * If 'tag' is -1, the tag is removed (actually -1 is the tag used to * represent the group of all the channels). * * This function replaces the requested channels' current tags; you may only * have one tag per channel. * * You may not specify MAX_CHANNEL_POST for a channel. * * Note that this returns success and failure in the _opposite_ way from * Mix_GroupChannel(). We regret the API design mistake. * * \param from the first channel to set the tag on. * \param to the last channel to set the tag on, inclusive. * \param tag an arbitrary value to assign a channel. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_GroupChannels(int from, int to, int tag); /** * Finds the first available channel in a group of channels. * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * This function searches all channels with a specified tag, and returns the * channel number of the first one it finds that is currently unused. * * If no channels with the specified tag are unused, this function returns -1. * * \param tag an arbitrary value, assigned to channels, to search for. * \returns first available channel, or -1 if none are available. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_GroupAvailable(int tag); /** * Returns the number of channels in a group. * * If tag is -1, this will return the total number of channels allocated, * regardless of what their tag might be. * * \param tag an arbitrary value, assigned to channels, to search for. * \returns the number of channels assigned the specified tag. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_GroupCount(int tag); /** * Find the "oldest" sample playing in a group of channels. * * Specifically, this function returns the channel number that is assigned the * specified tag, is currently playing, and has the lowest start time, based * on the value of SDL_GetTicks() when the channel started playing. * * If no channel with this tag is currently playing, this function returns -1. * * \param tag an arbitrary value, assigned to channels, to search through. * \returns the "oldest" sample playing in a group of channels. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GroupNewer */ extern SDL_DECLSPEC int SDLCALL Mix_GroupOldest(int tag); /** * Find the "most recent" sample playing in a group of channels. * * Specifically, this function returns the channel number that is assigned the * specified tag, is currently playing, and has the highest start time, based * on the value of SDL_GetTicks() when the channel started playing. * * If no channel with this tag is currently playing, this function returns -1. * * \param tag an arbitrary value, assigned to channels, to search through. * \returns the "most recent" sample playing in a group of channels. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GroupOldest */ extern SDL_DECLSPEC int SDLCALL Mix_GroupNewer(int tag); /** * Play an audio chunk on a specific channel. * * If the specified channel is -1, play on the first free channel (and return * -1 without playing anything new if no free channel was available). * * If a specific channel was requested, and there is a chunk already playing * there, that chunk will be halted and the new chunk will take its place. * * If `loops` is greater than zero, loop the sound that many times. If `loops` * is -1, loop "infinitely" (~65000 times). * * Note that before SDL_mixer 3.0.0, this function was a macro that called * Mix_PlayChannelTimed() with a fourth parameter ("ticks") of -1. This * function still does the same thing, but promotes it to a proper API * function. Older binaries linked against a newer SDL_mixer will still call * Mix_PlayChannelTimed directly, as they are using the macro, which was * available since the dawn of time. * * \param channel the channel on which to play the new chunk. * \param chunk the new chunk to play. * \param loops the number of times the chunk should loop, -1 to loop (not * actually) infinitely. * \returns which channel was used to play the sound, or -1 if sound could not * be played. * * \since This function is available since SDL_mixer 3.0.0 */ extern SDL_DECLSPEC int SDLCALL Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops); /** * Play an audio chunk on a specific channel for a maximum time. * * If the specified channel is -1, play on the first free channel (and return * -1 without playing anything new if no free channel was available). * * If a specific channel was requested, and there is a chunk already playing * there, that chunk will be halted and the new chunk will take its place. * * If `loops` is greater than zero, loop the sound that many times. If `loops` * is -1, loop "infinitely" (~65000 times). * * `ticks` specifies the maximum number of milliseconds to play this chunk * before halting it. If you want the chunk to play until all data has been * mixed, specify -1. * * Note that this function does not block for the number of ticks requested; * it just schedules the chunk to play and notes the maximum for the mixer to * manage later, and returns immediately. * * \param channel the channel on which to play the new chunk. * \param chunk the new chunk to play. * \param loops the number of times the chunk should loop, -1 to loop (not * actually) infinitely. * \param ticks the maximum number of milliseconds of this chunk to mix for * playback. * \returns which channel was used to play the sound, or -1 if sound could not * be played. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_PlayChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ticks); /** * Play a new music object. * * This will schedule the music object to begin mixing for playback. * * There is only ever one music object playing at a time; if this is called * when another music object is playing, the currently-playing music is halted * and the new music will replace it. * * Please note that if the currently-playing music is in the process of fading * out (via Mix_FadeOutMusic()), this function will *block* until the fade * completes. If you need to avoid this, be sure to call Mix_HaltMusic() * before starting new music. * * \param music the new music object to schedule for mixing. * \param loops the number of loops to play the music for (0 means "play once * and stop"). * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_PlayMusic(Mix_Music *music, int loops); /** * Play a new music object, fading in the audio. * * This will start the new music playing, much like Mix_PlayMusic() will, but * will start the music playing at silence and fade in to its normal volume * over the specified number of milliseconds. * * If there is already music playing, that music will be halted and the new * music object will take its place. * * If `loops` is greater than zero, loop the music that many times. If `loops` * is -1, loop "infinitely" (~65000 times). * * Fading music will change it's volume progressively, as if Mix_VolumeMusic() * was called on it (which is to say: you probably shouldn't call * Mix_VolumeMusic() on fading music). * * \param music the new music object to play. * \param loops the number of times the chunk should loop, -1 to loop (not * actually) infinitely. * \param ms the number of milliseconds to spend fading in. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_FadeInMusic(Mix_Music *music, int loops, int ms); /** * Play a new music object, fading in the audio, from a starting position. * * This will start the new music playing, much like Mix_PlayMusic() will, but * will start the music playing at silence and fade in to its normal volume * over the specified number of milliseconds. * * If there is already music playing, that music will be halted and the new * music object will take its place. * * If `loops` is greater than zero, loop the music that many times. If `loops` * is -1, loop "infinitely" (~65000 times). * * Fading music will change it's volume progressively, as if Mix_VolumeMusic() * was called on it (which is to say: you probably shouldn't call * Mix_VolumeMusic() on fading music). * * This function allows the caller to start the music playback past the * beginning of its audio data. You may specify a start position, in seconds, * and the playback and fade-in will start there instead of with the first * samples of the music. * * An app can specify a `position` of 0.0 to start at the beginning of the * music (or just call Mix_FadeInMusic() instead). * * To convert from milliseconds, divide by 1000.0. * * \param music the new music object to play. * \param loops the number of times the chunk should loop, -1 to loop (not * actually) infinitely. * \param ms the number of milliseconds to spend fading in. * \param position the start position within the music, in seconds, where * playback should start. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_FadeInMusicPos(Mix_Music *music, int loops, int ms, double position); /** * Play an audio chunk on a specific channel, fading in the audio. * * This will start the new sound playing, much like Mix_PlayChannel() will, * but will start the sound playing at silence and fade in to its normal * volume over the specified number of milliseconds. * * If the specified channel is -1, play on the first free channel (and return * -1 without playing anything new if no free channel was available). * * If a specific channel was requested, and there is a chunk already playing * there, that chunk will be halted and the new chunk will take its place. * * If `loops` is greater than zero, loop the sound that many times. If `loops` * is -1, loop "infinitely" (~65000 times). * * A fading channel will change it's volume progressively, as if Mix_Volume() * was called on it (which is to say: you probably shouldn't call Mix_Volume() * on a fading channel). * * Note that before SDL_mixer 3.0.0, this function was a macro that called * Mix_FadeInChannelTimed() with a fourth parameter ("ticks") of -1. This * function still does the same thing, but promotes it to a proper API * function. Older binaries linked against a newer SDL_mixer will still call * Mix_FadeInChannelTimed directly, as they are using the macro, which was * available since the dawn of time. * * \param channel the channel on which to play the new chunk, or -1 to find * any available. * \param chunk the new chunk to play. * \param loops the number of times the chunk should loop, -1 to loop (not * actually) infinitely. * \param ms the number of milliseconds to spend fading in. * \returns which channel was used to play the sound, or -1 if sound could not * be played. * * \since This function is available since SDL_mixer 3.0.0 */ extern SDL_DECLSPEC int SDLCALL Mix_FadeInChannel(int channel, Mix_Chunk *chunk, int loops, int ms); /** * Play an audio chunk on a specific channel, fading in the audio, for a * maximum time. * * This will start the new sound playing, much like Mix_PlayChannel() will, * but will start the sound playing at silence and fade in to its normal * volume over the specified number of milliseconds. * * If the specified channel is -1, play on the first free channel (and return * -1 without playing anything new if no free channel was available). * * If a specific channel was requested, and there is a chunk already playing * there, that chunk will be halted and the new chunk will take its place. * * If `loops` is greater than zero, loop the sound that many times. If `loops` * is -1, loop "infinitely" (~65000 times). * * `ticks` specifies the maximum number of milliseconds to play this chunk * before halting it. If you want the chunk to play until all data has been * mixed, specify -1. * * Note that this function does not block for the number of ticks requested; * it just schedules the chunk to play and notes the maximum for the mixer to * manage later, and returns immediately. * * A fading channel will change it's volume progressively, as if Mix_Volume() * was called on it (which is to say: you probably shouldn't call Mix_Volume() * on a fading channel). * * \param channel the channel on which to play the new chunk, or -1 to find * any available. * \param chunk the new chunk to play. * \param loops the number of times the chunk should loop, -1 to loop (not * actually) infinitely. * \param ms the number of milliseconds to spend fading in. * \param ticks the maximum number of milliseconds of this chunk to mix for * playback. * \returns which channel was used to play the sound, or -1 if sound could not * be played. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_FadeInChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ms, int ticks); /** * Set the volume for a specific channel. * * The volume must be between 0 (silence) and MIX_MAX_VOLUME (full volume). * Note that MIX_MAX_VOLUME is 128. Values greater than MIX_MAX_VOLUME are * clamped to MIX_MAX_VOLUME. * * Specifying a negative volume will not change the current volume; as such, * this can be used to query the current volume without making changes, as * this function returns the previous (in this case, still-current) value. * * If the specified channel is -1, this function sets the volume for all * channels, and returns _the average_ of all channels' volumes prior to this * call. * * The default volume for a channel is MIX_MAX_VOLUME (no attenuation). * * \param channel the channel on set/query the volume on, or -1 for all * channels. * \param volume the new volume, between 0 and MIX_MAX_VOLUME, or -1 to query. * \returns the previous volume. If the specified volume is -1, this returns * the current volume. If `channel` is -1, this returns the average * of all channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_Volume(int channel, int volume); /** * Set the volume for a specific chunk. * * In addition to channels having a volume setting, individual chunks also * maintain a separate volume. Both values are considered when mixing, so both * affect the final attenuation of the sound. This lets an app adjust the * volume for all instances of a sound in addition to specific instances of * that sound. * * The volume must be between 0 (silence) and MIX_MAX_VOLUME (full volume). * Note that MIX_MAX_VOLUME is 128. Values greater than MIX_MAX_VOLUME are * clamped to MIX_MAX_VOLUME. * * Specifying a negative volume will not change the current volume; as such, * this can be used to query the current volume without making changes, as * this function returns the previous (in this case, still-current) value. * * The default volume for a chunk is MIX_MAX_VOLUME (no attenuation). * * \param chunk the chunk whose volume to adjust. * \param volume the new volume, between 0 and MIX_MAX_VOLUME, or -1 to query. * \returns the previous volume. If the specified volume is -1, this returns * the current volume. If `chunk` is NULL, this returns -1. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_VolumeChunk(Mix_Chunk *chunk, int volume); /** * Set the volume for the music channel. * * The volume must be between 0 (silence) and MIX_MAX_VOLUME (full volume). * Note that MIX_MAX_VOLUME is 128. Values greater than MIX_MAX_VOLUME are * clamped to MIX_MAX_VOLUME. * * Specifying a negative volume will not change the current volume; as such, * this can be used to query the current volume without making changes, as * this function returns the previous (in this case, still-current) value. * * The default volume for music is MIX_MAX_VOLUME (no attenuation). * * \param volume the new volume, between 0 and MIX_MAX_VOLUME, or -1 to query. * \returns the previous volume. If the specified volume is -1, this returns * the current volume. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_VolumeMusic(int volume); /** * Query the current volume value for a music object. * * \param music the music object to query. * \returns the music's current volume, between 0 and MIX_MAX_VOLUME (128). * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_GetMusicVolume(Mix_Music *music); /** * Set the master volume for all channels. * * SDL_mixer keeps a per-channel volume, a per-chunk volume, and a master * volume, and considers all three when mixing audio. This function sets the * master volume, which is applied to all playing channels when mixing. * * The volume must be between 0 (silence) and MIX_MAX_VOLUME (full volume). * Note that MIX_MAX_VOLUME is 128. Values greater than MIX_MAX_VOLUME are * clamped to MIX_MAX_VOLUME. * * Specifying a negative volume will not change the current volume; as such, * this can be used to query the current volume without making changes, as * this function returns the previous (in this case, still-current) value. * * Note that the master volume does not affect any playing music; it is only * applied when mixing chunks. Use Mix_VolumeMusic() for that. * * \param volume the new volume, between 0 and MIX_MAX_VOLUME, or -1 to query. * \returns the previous volume. If the specified volume is -1, this returns * the current volume. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_MasterVolume(int volume); /** * Halt playing of a particular channel. * * This will stop further playback on that channel until a new chunk is * started there. * * Specifying a channel of -1 will halt _all_ channels, except for any playing * music. * * Any halted channels will have any currently-registered effects * deregistered, and will call any callback specified by Mix_ChannelFinished() * before this function returns. * * You may not specify MAX_CHANNEL_POST for a channel. * * \param channel channel to halt, or -1 to halt all channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_HaltChannel(int channel); /** * Halt playing of a group of channels by arbitrary tag. * * This will stop further playback on all channels with a specific tag, until * a new chunk is started there. * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * The default tag for a channel is -1. * * Any halted channels will have any currently-registered effects * deregistered, and will call any callback specified by Mix_ChannelFinished() * before this function returns. * * \param tag an arbitrary value, assigned to channels, to search for. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_HaltGroup(int tag); /** * Halt playing of the music stream. * * This will stop further playback of music until a new music object is * started there. * * Any halted music will call any callback specified by * Mix_HookMusicFinished() before this function returns. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_HaltMusic(void); /** * Change the expiration delay for a particular channel. * * The channel will halt after the 'ticks' milliseconds have elapsed, or * remove the expiration if 'ticks' is -1. * * This overrides the value passed to the fourth parameter of * Mix_PlayChannelTimed(). * * Specifying a channel of -1 will set an expiration for _all_ channels. * * Any halted channels will have any currently-registered effects * deregistered, and will call any callback specified by Mix_ChannelFinished() * once the halt occurs. * * Note that this function does not block for the number of ticks requested; * it just schedules the chunk to expire and notes the time for the mixer to * manage later, and returns immediately. * * \param channel the channel to change the expiration time on. * \param ticks number of milliseconds from now to let channel play before * halting, -1 to not halt. * \returns the number of channels that changed expirations. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_ExpireChannel(int channel, int ticks); /** * Halt a channel after fading it out for a specified time. * * This will begin a channel fading from its current volume to silence over * `ms` milliseconds. After that time, the channel is halted. * * Any halted channels will have any currently-registered effects * deregistered, and will call any callback specified by Mix_ChannelFinished() * once the halt occurs. * * A fading channel will change it's volume progressively, as if Mix_Volume() * was called on it (which is to say: you probably shouldn't call Mix_Volume() * on a fading channel). * * Note that this function does not block for the number of milliseconds * requested; it just schedules the chunk to fade and notes the time for the * mixer to manage later, and returns immediately. * * \param which the channel to fade out. * \param ms number of milliseconds to fade before halting the channel. * \returns the number of channels scheduled to fade. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_FadeOutChannel(int which, int ms); /** * Halt a playing group of channels by arbitrary tag, after fading them out * for a specified time. * * This will begin fading a group of channels with a specific tag from their * current volumes to silence over `ms` milliseconds. After that time, those * channels are halted. * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * The default tag for a channel is -1. * * Any halted channels will have any currently-registered effects * deregistered, and will call any callback specified by Mix_ChannelFinished() * once the halt occurs. * * A fading channel will change it's volume progressively, as if Mix_Volume() * was called on it (which is to say: you probably shouldn't call Mix_Volume() * on a fading channel). * * Note that this function does not block for the number of milliseconds * requested; it just schedules the group to fade and notes the time for the * mixer to manage later, and returns immediately. * * \param tag an arbitrary value, assigned to channels, to search for. * \param ms number of milliseconds to fade before halting the group. * \returns the number of channels that were scheduled for fading. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_FadeOutGroup(int tag, int ms); /** * Halt the music stream after fading it out for a specified time. * * This will begin the music fading from its current volume to silence over * `ms` milliseconds. After that time, the music is halted. * * Any halted music will call any callback specified by * Mix_HookMusicFinished() once the halt occurs. * * Fading music will change it's volume progressively, as if Mix_VolumeMusic() * was called on it (which is to say: you probably shouldn't call * Mix_VolumeMusic() on a fading channel). * * Note that this function does not block for the number of milliseconds * requested; it just schedules the music to fade and notes the time for the * mixer to manage later, and returns immediately. * * \param ms number of milliseconds to fade before halting the channel. * \returns true if music was scheduled to fade, false otherwise. If no music * is currently playing, this returns false. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_FadeOutMusic(int ms); /** * Query the fading status of the music stream. * * This reports one of three values: * * - `MIX_NO_FADING` * - `MIX_FADING_OUT` * - `MIX_FADING_IN` * * If music is not currently playing, this returns `MIX_NO_FADING`. * * \returns the current fading status of the music stream. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC Mix_Fading SDLCALL Mix_FadingMusic(void); /** * Query the fading status of a channel. * * This reports one of three values: * * - `MIX_NO_FADING` * - `MIX_FADING_OUT` * - `MIX_FADING_IN` * * If nothing is currently playing on the channel, or an invalid channel is * specified, this returns `MIX_NO_FADING`. * * You may not specify MAX_CHANNEL_POST for a channel. * * You may not specify -1 for all channels; only individual channels may be * queried. * * \param which the channel to query. * \returns the current fading status of the channel. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC Mix_Fading SDLCALL Mix_FadingChannel(int which); /** * Pause a particular channel. * * Pausing a channel will prevent further playback of the assigned chunk but * will maintain the chunk's current mixing position. When resumed, this * channel will continue to mix the chunk where it left off. * * A paused channel can be resumed by calling Mix_Resume(). * * A paused channel with an expiration will not expire while paused (the * expiration countdown will be adjusted once resumed). * * It is legal to halt a paused channel. Playing a new chunk on a paused * channel will replace the current chunk and unpause the channel. * * Specifying a channel of -1 will pause _all_ channels. Any music is * unaffected. * * You may not specify MAX_CHANNEL_POST for a channel. * * \param channel the channel to pause, or -1 to pause all channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_Pause(int channel); /** * Pause playing of a group of channels by arbitrary tag. * * Pausing a channel will prevent further playback of the assigned chunk but * will maintain the chunk's current mixing position. When resumed, this * channel will continue to mix the chunk where it left off. * * A paused channel can be resumed by calling Mix_Resume() or * Mix_ResumeGroup(). * * A paused channel with an expiration will not expire while paused (the * expiration countdown will be adjusted once resumed). * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * The default tag for a channel is -1. * * \param tag an arbitrary value, assigned to channels, to search for. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_PauseGroup(int tag); /** * Resume a particular channel. * * It is legal to resume an unpaused or invalid channel; it causes no effect * and reports no error. * * If the paused channel has an expiration, its expiration countdown resumes * now, as well. * * Specifying a channel of -1 will resume _all_ paused channels. Any music is * unaffected. * * \param channel the channel to resume, or -1 to resume all paused channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_Resume(int channel); /** * Resume playing of a group of channels by arbitrary tag. * * It is legal to resume an unpaused or invalid channel; it causes no effect * and reports no error. * * If the paused channel has an expiration, its expiration countdown resumes * now, as well. * * A tag is an arbitrary number that can be assigned to several mixer * channels, to form groups of channels. * * The default tag for a channel is -1. * * \param tag an arbitrary value, assigned to channels, to search for. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_ResumeGroup(int tag); /** * Query whether a particular channel is paused. * * If an invalid channel is specified, this function returns zero. * * \param channel the channel to query, or -1 to query all channels. * \return 1 if channel paused, 0 otherwise. If `channel` is -1, returns the * number of paused channels. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_Paused(int channel); /** * Pause the music stream. * * Pausing the music stream will prevent further playback of the assigned * music object, but will maintain the object's current mixing position. When * resumed, this channel will continue to mix the music where it left off. * * Paused music can be resumed by calling Mix_ResumeMusic(). * * It is legal to halt paused music. Playing a new music object when music is * paused will replace the current music and unpause the music stream. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_PauseMusic(void); /** * Resume the music stream. * * It is legal to resume an unpaused music stream; it causes no effect and * reports no error. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_ResumeMusic(void); /** * Rewind the music stream. * * This causes the currently-playing music to start mixing from the beginning * of the music, as if it were just started. * * It's a legal no-op to rewind the music stream when not playing. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC void SDLCALL Mix_RewindMusic(void); /** * Query whether the music stream is paused. * * \return true if music is paused, false otherwise. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_PauseMusic * \sa Mix_ResumeMusic */ extern SDL_DECLSPEC bool SDLCALL Mix_PausedMusic(void); /** * Jump to a given order in mod music. * * This only applies to MOD music formats. * * \param order order. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_ModMusicJumpToOrder(int order); /** * Start a track in music object. * * This only applies to GME music formats. * * \param music the music object. * \param track the track number to play. 0 is the first track. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_StartTrack(Mix_Music *music, int track); /** * Get number of tracks in music object. * * This only applies to GME music formats. * * \param music the music object. * \returns number of tracks if successful, or -1 if failed or isn't * implemented. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_GetNumTracks(Mix_Music *music); /** * Set the current position in the music stream, in seconds. * * To convert from milliseconds, divide by 1000.0. * * This function is only implemented for MOD music formats (set pattern order * number) and for WAV, OGG, FLAC, MP3, and MOD music at the moment. * * \param position the new position, in seconds (as a double). * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_SetMusicPosition(double position); /** * Get the time current position of music stream, in seconds. * * To convert to milliseconds, multiply by 1000.0. * * \param music the music object to query. * \returns -1.0 if this feature is not supported for some codec. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC double SDLCALL Mix_GetMusicPosition(Mix_Music *music); /** * Get a music object's duration, in seconds. * * To convert to milliseconds, multiply by 1000.0. * * If NULL is passed, returns duration of current playing music. * * \param music the music object to query. * \returns music duration in seconds, or -1.0 on error. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC double SDLCALL Mix_MusicDuration(Mix_Music *music); /** * Get the loop start time position of music stream, in seconds. * * To convert to milliseconds, multiply by 1000.0. * * If NULL is passed, returns duration of current playing music. * * \param music the music object to query. * \returns -1.0 if this feature is not used for this music or not supported * for some codec. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC double SDLCALL Mix_GetMusicLoopStartTime(Mix_Music *music); /** * Get the loop end time position of music stream, in seconds. * * To convert to milliseconds, multiply by 1000.0. * * If NULL is passed, returns duration of current playing music. * * \param music the music object to query. * \returns -1.0 if this feature is not used for this music or not supported * for some codec. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC double SDLCALL Mix_GetMusicLoopEndTime(Mix_Music *music); /** * Get the loop time length of music stream, in seconds. * * To convert to milliseconds, multiply by 1000.0. * * If NULL is passed, returns duration of current playing music. * * \param music the music object to query. * \returns -1.0 if this feature is not used for this music or not supported * for some codec. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC double SDLCALL Mix_GetMusicLoopLengthTime(Mix_Music *music); /** * Check the playing status of a specific channel. * * If the channel is currently playing, this function returns 1. Otherwise it * returns 0. * * If the specified channel is -1, all channels are checked, and this function * returns the number of channels currently playing. * * You may not specify MAX_CHANNEL_POST for a channel. * * Paused channels are treated as playing, even though they are not currently * making forward progress in mixing. * * \param channel channel. * \returns non-zero if channel is playing, zero otherwise. If `channel` is * -1, return the total number of channel playings. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC int SDLCALL Mix_Playing(int channel); /** * Check the playing status of the music stream. * * If music is currently playing, this function returns 1. Otherwise it * returns 0. * * Paused music is treated as playing, even though it is not currently making * forward progress in mixing. * * \returns true if music is playing, false otherwise. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_PlayingMusic(void); /** * Set SoundFonts paths to use by supported MIDI backends. * * You may specify multiple paths in a single string by separating them with * semicolons; they will be searched in the order listed. * * This function replaces any previously-specified paths. * * Passing a NULL path will remove any previously-specified paths. * * \param paths Paths on the filesystem where SoundFonts are available, * separated by semicolons. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_SetSoundFonts(const char *paths); /** * Get SoundFonts paths to use by supported MIDI backends. * * There are several factors that determine what will be reported by this * function: * * - If the boolean _SDL hint_ `"SDL_FORCE_SOUNDFONTS"` is set, AND the * `"SDL_SOUNDFONTS"` _environment variable_ is also set, this function will * return that environment variable regardless of whether * Mix_SetSoundFonts() was ever called. * - Otherwise, if Mix_SetSoundFonts() was successfully called with a non-NULL * path, this function will return the string passed to that function. * - Otherwise, if the `"SDL_SOUNDFONTS"` variable is set, this function will * return that environment variable. * - Otherwise, this function will search some common locations on the * filesystem, and if it finds a SoundFont there, it will return that. * - Failing everything else, this function returns NULL. * * This returns a pointer to internal (possibly read-only) memory, and it * should not be modified or free'd by the caller. * * \returns semicolon-separated list of sound font paths. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC const char* SDLCALL Mix_GetSoundFonts(void); typedef bool (SDLCALL *Mix_EachSoundFontCallback)(const char*, void*); /** * Iterate SoundFonts paths to use by supported MIDI backends. * * This function will take the string reported by Mix_GetSoundFonts(), split * it up into separate paths, as delimited by semicolons in the string, and * call a callback function for each separate path. * * If there are no paths available, this returns 0 without calling the * callback at all. * * If the callback returns non-zero, this function stops iterating and returns * non-zero. If the callback returns 0, this function will continue iterating, * calling the callback again for further paths. If the callback never returns * 1, this function returns 0, so this can be used to decide if an available * soundfont is acceptable for use. * * \param function the callback function to call once per path. * \param data a pointer to pass to the callback for its own personal use. * \returns true if callback ever returned true, false on error or if the * callback never returned true. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_GetSoundFonts */ extern SDL_DECLSPEC bool SDLCALL Mix_EachSoundFont(Mix_EachSoundFontCallback function, void *data); /** * Set full path of the Timidity config file. * * For example, "/etc/timidity.cfg" * * This is obviously only useful if SDL_mixer is using Timidity internally to * play MIDI files. * * \param path path to a Timidity config file. * \returns true on success or false on failure; call SDL_GetError() for more * information. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC bool SDLCALL Mix_SetTimidityCfg(const char *path); /** * Get full path of a previously-specified Timidity config file. * * For example, "/etc/timidity.cfg" * * If a path has never been specified, this returns NULL. * * This returns a pointer to internal memory, and it should not be modified or * free'd by the caller. * * \returns the previously-specified path, or NULL if not set. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_SetTimidityCfg */ extern SDL_DECLSPEC const char* SDLCALL Mix_GetTimidityCfg(void); /** * Get the Mix_Chunk currently associated with a mixer channel. * * You may not specify MAX_CHANNEL_POST or -1 for a channel. * * \param channel the channel to query. * \returns the associated chunk, if any, or NULL if it's an invalid channel. * * \since This function is available since SDL_mixer 3.0.0. */ extern SDL_DECLSPEC Mix_Chunk * SDLCALL Mix_GetChunk(int channel); /** * Close the mixer, halting all playing audio. * * Any halted channels will have any currently-registered effects * deregistered, and will call any callback specified by Mix_ChannelFinished() * before this function returns. * * Any halted music will call any callback specified by * Mix_HookMusicFinished() before this function returns. * * Do not start any new audio playing during callbacks in this function. * * This will close the audio device. Attempting to play new audio after this * function returns will fail, until another successful call to * Mix_OpenAudio(). * * Note that (unlike Mix_OpenAudio optionally calling SDL_Init(SDL_INIT_AUDIO) * on the app's behalf), this will _not_ deinitialize the SDL audio subsystem * in any case. At some point after calling this function and Mix_Quit(), some * part of the application should be responsible for calling SDL_Quit() to * deinitialize all of SDL, including its audio subsystem. * * This function should be the last thing you call in SDL_mixer before * Mix_Quit(). However, the following notes apply if you don't follow this * advice: * * Note that this will not free any loaded chunks or music; you should dispose * of those resources separately. It is probably poor form to dispose of them * _after_ this function, but it is safe to call Mix_FreeChunk() and * Mix_FreeMusic() after closing the device. * * Note that any chunks or music you don't free may or may not work if you * call Mix_OpenAudio again, as the audio device may be in a new format and * the existing chunks will not be converted to match. * * \since This function is available since SDL_mixer 3.0.0. * * \sa Mix_Quit */ extern SDL_DECLSPEC void SDLCALL Mix_CloseAudio(void); /* Ends C function definitions when using C++ */ #ifdef __cplusplus } #endif #include #endif /* SDL_MIXER_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/mingw/000077500000000000000000000000001501405355700203325ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/mingw/pkg-support/000077500000000000000000000000001501405355700226255ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/mingw/pkg-support/cmake/000077500000000000000000000000001501405355700237055ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/mingw/pkg-support/cmake/sdl3_mixer-config-version.cmake000066400000000000000000000014501501405355700317060ustar00rootroot00000000000000# SDL3_mixer CMake version configuration file: # This file is meant to be placed in a cmake subfolder of SDL3_mixer-devel-3.x.y-mingw if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL3_mixer/SDL3ConfigVersion.cmake") elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL3_mixer/SDL3ConfigVersion.cmake") else("${CMAKE_SIZEOF_VOID_P}" STREQUAL "") set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() if(NOT EXISTS "${sdl3_mixer_config_path}") message(WARNING "${sdl3_mixer_config_path} does not exist: MinGW development package is corrupted") set(PACKAGE_VERSION_UNSUITABLE TRUE) return() endif() include("${sdl3_mixer_config_path}") libsdl3-mixer-3~git20250523~daf0503+ds/mingw/pkg-support/cmake/sdl3_mixer-config.cmake000066400000000000000000000015571501405355700302330ustar00rootroot00000000000000# SDL3_mixer CMake configuration file: # This file is meant to be placed in a cmake subfolder of SDL3_mixer-devel-3.x.y-mingw if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../i686-w64-mingw32/lib/cmake/SDL3_mixer/SDL3Config.cmake") elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) set(sdl3_mixer_config_path "${CMAKE_CURRENT_LIST_DIR}/../x86_64-w64-mingw32/lib/cmake/SDL3_mixer/SDL3Config.cmake") else("${CMAKE_SIZEOF_VOID_P}" STREQUAL "") set(SDL3_mixer_FOUND FALSE) return() endif() if(NOT EXISTS "${sdl3_mixer_config_path}") message(WARNING "${sdl3_mixer_config_path} does not exist: MinGW development package is corrupted") set(SDL3_mixer_FOUND FALSE) return() endif() include("${sdl3_mixer_config_path}") # The SDL_mixer MinGW development package ships with vendored libraries set(SDLMIXER_VENDORED 1) libsdl3-mixer-3~git20250523~daf0503+ds/release_checklist.md000066400000000000000000000052101501405355700232020ustar00rootroot00000000000000# Release checklist ## New feature release * Update `CHANGES.txt` * Bump version number to 3.EVEN.0 in all these locations: * `include/SDL3_mixer/SDL_mixer.h`: `SDL_MIXER_MAJOR_VERSION`, `SDL_MIXER_MINOR_VERSION`, `SDL_MIXER_MICRO_VERSION` * `CMakeLists.txt`: `MAJOR_VERSION`, `MINOR_VERSION`, `MICRO_VERSION` * `version.rc`: `FILEVERSION`, `PRODUCTVERSION`, `FileVersion`, `ProductVersion` * `VisualC/Version.rc`: `FILEVERSION`, `PRODUCTVERSION`, `FileVersion`, `ProductVersion` * `Xcode/Info-Framework.plist`: `CFBundleShortVersionString`, `CFBundleVersion` * Bump ABI version information * `Xcode/SDL_mixer.xcodeproj/project.pbxproj`: `DYLIB_CURRENT_VERSION`, `DYLIB_COMPATIBILITY_VERSION` * set first number in `DYLIB_CURRENT_VERSION` to (100 * *minor*) + 1 * set second number in `DYLIB_CURRENT_VERSION` to 0 * set `DYLIB_COMPATIBILITY_VERSION` to the same value * Regenerate `configure` * Run `./test-versioning.sh` to verify that everything is consistent * Do the release ## New bugfix release * Check that no new API/ABI was added * If it was, do a new feature release (see above) instead * Bump version number from 3.Y.Z to 3.Y.(Z+1) (Y is even) * Same places as listed above * Bump ABI version information * `Xcode/SDL_mixer.xcodeproj/project.pbxproj`: `DYLIB_CURRENT_VERSION`, `DYLIB_COMPATIBILITY_VERSION` * set second number in `DYLIB_CURRENT_VERSION` to *micro* * Leave `DYLIB_COMPATIBILITY_VERSION` unchanged * Regenerate `configure` * Run test/versioning.sh to verify that everything is consistent * Do the release ## After a feature release * Create a branch like `release-3.6.x` * Bump version number to 3.ODD.0 for next development branch * Same places as listed above * Bump ABI version information * Same places as listed above * Assume that the next feature release will contain new API/ABI * Run test/versioning.sh to verify that everything is consistent * Add a new milestone for issues ## New development prerelease * Bump version number from 3.Y.Z to 3.Y.(Z+1) (Y is odd) * Same places as listed above * Bump ABI version information * `Xcode/SDL_mixer.xcodeproj/project.pbxproj`: `DYLIB_CURRENT_VERSION`, `DYLIB_COMPATIBILITY_VERSION` * set first number in `DYLIB_CURRENT_VERSION` to (100 * *minor*) + *micro* + 1 * set second number in `DYLIB_CURRENT_VERSION` to 0 * set `DYLIB_COMPATIBILITY_VERSION` to the same value * Regenerate `configure` * Run test/versioning.sh to verify that everything is consistent * Do the release libsdl3-mixer-3~git20250523~daf0503+ds/src/000077500000000000000000000000001501405355700200005ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/src/SDL_mixer.sym000066400000000000000000000041721501405355700223640ustar00rootroot00000000000000SDL3_mixer_0.0.0 { global: Mix_AllocateChannels; Mix_ChannelFinished; Mix_CloseAudio; Mix_EachSoundFont; Mix_ExpireChannel; Mix_FadeInChannel; Mix_FadeInChannelTimed; Mix_FadeInMusic; Mix_FadeInMusicPos; Mix_FadeOutChannel; Mix_FadeOutGroup; Mix_FadeOutMusic; Mix_FadingChannel; Mix_FadingMusic; Mix_FreeChunk; Mix_FreeMusic; Mix_GetChunk; Mix_GetChunkDecoder; Mix_GetMusicAlbumTag; Mix_GetMusicArtistTag; Mix_GetMusicCopyrightTag; Mix_GetMusicDecoder; Mix_GetMusicHookData; Mix_GetMusicLoopEndTime; Mix_GetMusicLoopLengthTime; Mix_GetMusicLoopStartTime; Mix_GetMusicPosition; Mix_GetMusicTitle; Mix_GetMusicTitleTag; Mix_GetMusicType; Mix_GetMusicVolume; Mix_GetNumChunkDecoders; Mix_GetNumMusicDecoders; Mix_GetNumTracks; Mix_GetSoundFonts; Mix_GetTimidityCfg; Mix_GroupAvailable; Mix_GroupChannel; Mix_GroupChannels; Mix_GroupCount; Mix_GroupNewer; Mix_GroupOldest; Mix_HaltChannel; Mix_HaltGroup; Mix_HaltMusic; Mix_HasChunkDecoder; Mix_HasMusicDecoder; Mix_HookMusic; Mix_HookMusicFinished; Mix_Init; Mix_LoadMUS; Mix_LoadMUSType_IO; Mix_LoadMUS_IO; Mix_LoadWAV; Mix_LoadWAV_IO; Mix_MasterVolume; Mix_ModMusicJumpToOrder; Mix_MusicDuration; Mix_OpenAudio; Mix_Pause; Mix_PauseGroup; Mix_PauseAudio; Mix_PauseMusic; Mix_Paused; Mix_PausedMusic; Mix_PlayChannel; Mix_PlayChannelTimed; Mix_PlayMusic; Mix_Playing; Mix_PlayingMusic; Mix_QuerySpec; Mix_QuickLoad_RAW; Mix_QuickLoad_WAV; Mix_Quit; Mix_RegisterEffect; Mix_ReserveChannels; Mix_Resume; Mix_ResumeGroup; Mix_ResumeMusic; Mix_RewindMusic; Mix_SetDistance; Mix_SetMusicPosition; Mix_SetPanning; Mix_SetPosition; Mix_SetPostMix; Mix_SetReverseStereo; Mix_SetSoundFonts; Mix_SetTimidityCfg; Mix_StartTrack; Mix_UnregisterAllEffects; Mix_UnregisterEffect; Mix_Version; Mix_Volume; Mix_VolumeChunk; Mix_VolumeMusic; local: *; }; libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/000077500000000000000000000000001501405355700212405ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/dr_libs/000077500000000000000000000000001501405355700226565ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/dr_libs/LICENSE000066400000000000000000000050461501405355700236700ustar00rootroot00000000000000This software is available as a choice of the following licenses. Choose whichever you prefer. =============================================================================== ALTERNATIVE 1 - Public Domain (www.unlicense.org) =============================================================================== This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== Copyright 2020 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/dr_libs/README.md000066400000000000000000000022761501405355700241440ustar00rootroot00000000000000

Public domain, single file audio decoding libraries for C and C++.

discord

All development of released libraries happens on the master branch. There may exist some decoder-specific branches for work in progress. Check tags for the latest version of a particular library. Library | Description ----------------------------------------------- | ----------- [dr_flac](dr_flac.h) | FLAC audio decoder. [dr_mp3](dr_mp3.h) | MP3 audio decoder. Based off [minimp3](https://github.com/lieff/minimp3). [dr_wav](dr_wav.h) | WAV audio loader and writer. # Other Libraries Below are some of my other libraries you may be interested in. Library | Description ------------------------------------------------- | ----------- [miniaudio](https://github.com/mackron/miniaudio) | A public domain, single file library for audio playback and recording. libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/dr_libs/dr_flac.h000066400000000000000000020047211501405355700244270ustar00rootroot00000000000000/* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. dr_flac - v0.12.44 - TBD David Reid - mackron@gmail.com GitHub: https://github.com/mackron/dr_libs */ /* RELEASE NOTES - v0.12.0 ======================= Version 0.12.0 has breaking API changes including changes to the existing API and the removal of deprecated APIs. Improved Client-Defined Memory Allocation ----------------------------------------- The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The existing system of DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE are still in place and will be used by default when no custom allocation callbacks are specified. To use the new system, you pass in a pointer to a drflac_allocation_callbacks object to drflac_open() and family, like this: void* my_malloc(size_t sz, void* pUserData) { return malloc(sz); } void* my_realloc(void* p, size_t sz, void* pUserData) { return realloc(p, sz); } void my_free(void* p, void* pUserData) { free(p); } ... drflac_allocation_callbacks allocationCallbacks; allocationCallbacks.pUserData = &myData; allocationCallbacks.onMalloc = my_malloc; allocationCallbacks.onRealloc = my_realloc; allocationCallbacks.onFree = my_free; drflac* pFlac = drflac_open_file("my_file.flac", &allocationCallbacks); The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. Passing in null for the allocation callbacks object will cause dr_flac to use defaults which is the same as DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE and the equivalent of how it worked in previous versions. Every API that opens a drflac object now takes this extra parameter. These include the following: drflac_open() drflac_open_relaxed() drflac_open_with_metadata() drflac_open_with_metadata_relaxed() drflac_open_file() drflac_open_file_with_metadata() drflac_open_memory() drflac_open_memory_with_metadata() drflac_open_and_read_pcm_frames_s32() drflac_open_and_read_pcm_frames_s16() drflac_open_and_read_pcm_frames_f32() drflac_open_file_and_read_pcm_frames_s32() drflac_open_file_and_read_pcm_frames_s16() drflac_open_file_and_read_pcm_frames_f32() drflac_open_memory_and_read_pcm_frames_s32() drflac_open_memory_and_read_pcm_frames_s16() drflac_open_memory_and_read_pcm_frames_f32() Optimizations ------------- Seeking performance has been greatly improved. A new binary search based seeking algorithm has been introduced which significantly improves performance over the brute force method which was used when no seek table was present. Seek table based seeking also takes advantage of the new binary search seeking system to further improve performance there as well. Note that this depends on CRC which means it will be disabled when DR_FLAC_NO_CRC is used. The SSE4.1 pipeline has been cleaned up and optimized. You should see some improvements with decoding speed of 24-bit files in particular. 16-bit streams should also see some improvement. drflac_read_pcm_frames_s16() has been optimized. Previously this sat on top of drflac_read_pcm_frames_s32() and performed it's s32 to s16 conversion in a second pass. This is now all done in a single pass. This includes SSE2 and ARM NEON optimized paths. A minor optimization has been implemented for drflac_read_pcm_frames_s32(). This will now use an SSE2 optimized pipeline for stereo channel reconstruction which is the last part of the decoding process. The ARM build has seen a few improvements. The CLZ (count leading zeroes) and REV (byte swap) instructions are now used when compiling with GCC and Clang which is achieved using inline assembly. The CLZ instruction requires ARM architecture version 5 at compile time and the REV instruction requires ARM architecture version 6. An ARM NEON optimized pipeline has been implemented. To enable this you'll need to add -mfpu=neon to the command line when compiling. Removed APIs ------------ The following APIs were deprecated in version 0.11.0 and have been completely removed in version 0.12.0: drflac_read_s32() -> drflac_read_pcm_frames_s32() drflac_read_s16() -> drflac_read_pcm_frames_s16() drflac_read_f32() -> drflac_read_pcm_frames_f32() drflac_seek_to_sample() -> drflac_seek_to_pcm_frame() drflac_open_and_decode_s32() -> drflac_open_and_read_pcm_frames_s32() drflac_open_and_decode_s16() -> drflac_open_and_read_pcm_frames_s16() drflac_open_and_decode_f32() -> drflac_open_and_read_pcm_frames_f32() drflac_open_and_decode_file_s32() -> drflac_open_file_and_read_pcm_frames_s32() drflac_open_and_decode_file_s16() -> drflac_open_file_and_read_pcm_frames_s16() drflac_open_and_decode_file_f32() -> drflac_open_file_and_read_pcm_frames_f32() drflac_open_and_decode_memory_s32() -> drflac_open_memory_and_read_pcm_frames_s32() drflac_open_and_decode_memory_s16() -> drflac_open_memory_and_read_pcm_frames_s16() drflac_open_and_decode_memory_f32() -> drflac_open_memroy_and_read_pcm_frames_f32() Prior versions of dr_flac operated on a per-sample basis whereas now it operates on PCM frames. The removed APIs all relate to the old per-sample APIs. You now need to use the "pcm_frame" versions. */ /* Introduction ============ dr_flac is a single file library. To use it, do something like the following in one .c file. ```c #define DR_FLAC_IMPLEMENTATION #include "dr_flac.h" ``` You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: ```c drflac* pFlac = drflac_open_file("MySong.flac", NULL); if (pFlac == NULL) { // Failed to open FLAC file } drflac_int32* pSamples = malloc(pFlac->totalPCMFrameCount * pFlac->channels * sizeof(drflac_int32)); drflac_uint64 numberOfInterleavedSamplesActuallyRead = drflac_read_pcm_frames_s32(pFlac, pFlac->totalPCMFrameCount, pSamples); ``` The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of channels and the bits per sample, should be directly accessible - just make sure you don't change their values. Samples are always output as interleaved signed 32-bit PCM. In the example above a native FLAC stream was opened, however dr_flac has seamless support for Ogg encapsulated FLAC streams as well. You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and the decoder will give you as many samples as it can, up to the amount requested. Later on when you need the next batch of samples, just call it again. Example: ```c while (drflac_read_pcm_frames_s32(pFlac, chunkSizeInPCMFrames, pChunkSamples) > 0) { do_something(); } ``` You can seek to a specific PCM frame with `drflac_seek_to_pcm_frame()`. If you just want to quickly decode an entire FLAC file in one go you can do something like this: ```c unsigned int channels; unsigned int sampleRate; drflac_uint64 totalPCMFrameCount; drflac_int32* pSampleData = drflac_open_file_and_read_pcm_frames_s32("MySong.flac", &channels, &sampleRate, &totalPCMFrameCount, NULL); if (pSampleData == NULL) { // Failed to open and decode FLAC file. } ... drflac_free(pSampleData, NULL); ``` You can read samples as signed 16-bit integer and 32-bit floating-point PCM with the *_s16() and *_f32() family of APIs respectively, but note that these should be considered lossy. If you need access to metadata (album art, etc.), use `drflac_open_with_metadata()`, `drflac_open_file_with_metdata()` or `drflac_open_memory_with_metadata()`. The rationale for keeping these APIs separate is that they're slightly slower than the normal versions and also just a little bit harder to use. dr_flac reports metadata to the application through the use of a callback, and every metadata block is reported before `drflac_open_with_metdata()` returns. The main opening APIs (`drflac_open()`, etc.) will fail if the header is not present. The presents a problem in certain scenarios such as broadcast style streams or internet radio where the header may not be present because the user has started playback mid-stream. To handle this, use the relaxed APIs: `drflac_open_relaxed()` `drflac_open_with_metadata_relaxed()` It is not recommended to use these APIs for file based streams because a missing header would usually indicate a corrupt or perverse file. In addition, these APIs can take a long time to initialize because they may need to spend a lot of time finding the first frame. Build Options ============= #define these options before including this file. #define DR_FLAC_NO_STDIO Disable `drflac_open_file()` and family. #define DR_FLAC_NO_OGG Disables support for Ogg/FLAC streams. #define DR_FLAC_BUFFER_SIZE Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls back to the client for more data. Larger values means more memory, but better performance. My tests show diminishing returns after about 4KB (which is the default). Consider reducing this if you have a very efficient implementation of onRead(), or increase it if it's very inefficient. Must be a multiple of 8. #define DR_FLAC_NO_CRC Disables CRC checks. This will offer a performance boost when CRC is unnecessary. This will disable binary search seeking. When seeking, the seek table will be used if available. Otherwise the seek will be performed using brute force. #define DR_FLAC_NO_SIMD Disables SIMD optimizations (SSE on x86/x64 architectures, NEON on ARM architectures). Use this if you are having compatibility issues with your compiler. #define DR_FLAC_NO_WCHAR Disables all functions ending with `_w`. Use this if your compiler does not provide wchar.h. Not required if DR_FLAC_NO_STDIO is also defined. Notes ===== - dr_flac does not support changing the sample rate nor channel count mid stream. - dr_flac is not thread-safe, but its APIs can be called from any thread so long as you do your own synchronization. - When using Ogg encapsulation, a corrupted metadata block will result in `drflac_open_with_metadata()` and `drflac_open()` returning inconsistent samples due to differences in corrupted stream recorvery logic between the two APIs. */ #ifndef dr_flac_h #define dr_flac_h #ifdef __cplusplus extern "C" { #endif #define DRFLAC_STRINGIFY(x) #x #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 #define DRFLAC_VERSION_REVISION 44 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include /* For size_t. */ /* Sized Types */ typedef signed char drflac_int8; typedef unsigned char drflac_uint8; typedef signed short drflac_int16; typedef unsigned short drflac_uint16; typedef signed int drflac_int32; typedef unsigned int drflac_uint32; #if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) #pragma GCC diagnostic ignored "-Wc++11-long-long" #endif #endif typedef signed long long drflac_int64; typedef unsigned long long drflac_uint64; #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) typedef drflac_uint64 drflac_uintptr; #else typedef drflac_uint32 drflac_uintptr; #endif typedef drflac_uint8 drflac_bool8; typedef drflac_uint32 drflac_bool32; #define DRFLAC_TRUE 1 #define DRFLAC_FALSE 0 /* End Sized Types */ /* Decorations */ #if !defined(DRFLAC_API) #if defined(DRFLAC_DLL) #if defined(_WIN32) #define DRFLAC_DLL_IMPORT __declspec(dllimport) #define DRFLAC_DLL_EXPORT __declspec(dllexport) #define DRFLAC_DLL_PRIVATE static #else #if defined(__GNUC__) && __GNUC__ >= 4 #define DRFLAC_DLL_IMPORT __attribute__((visibility("default"))) #define DRFLAC_DLL_EXPORT __attribute__((visibility("default"))) #define DRFLAC_DLL_PRIVATE __attribute__((visibility("hidden"))) #else #define DRFLAC_DLL_IMPORT #define DRFLAC_DLL_EXPORT #define DRFLAC_DLL_PRIVATE static #endif #endif #if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) #define DRFLAC_API DRFLAC_DLL_EXPORT #else #define DRFLAC_API DRFLAC_DLL_IMPORT #endif #define DRFLAC_PRIVATE DRFLAC_DLL_PRIVATE #else #define DRFLAC_API extern #define DRFLAC_PRIVATE static #endif #endif /* End Decorations */ #if defined(_MSC_VER) && _MSC_VER >= 1700 /* Visual Studio 2012 */ #define DRFLAC_DEPRECATED __declspec(deprecated) #elif (defined(__GNUC__) && __GNUC__ >= 4) /* GCC 4 */ #define DRFLAC_DEPRECATED __attribute__((deprecated)) #elif defined(__has_feature) /* Clang */ #if __has_feature(attribute_deprecated) #define DRFLAC_DEPRECATED __attribute__((deprecated)) #else #define DRFLAC_DEPRECATED #endif #else #define DRFLAC_DEPRECATED #endif DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision); DRFLAC_API const char* drflac_version_string(void); /* Allocation Callbacks */ typedef struct { void* pUserData; void* (* onMalloc)(size_t sz, void* pUserData); void* (* onRealloc)(void* p, size_t sz, void* pUserData); void (* onFree)(void* p, void* pUserData); } drflac_allocation_callbacks; /* End Allocation Callbacks */ /* As data is read from the client it is placed into an internal buffer for fast access. This controls the size of that buffer. Larger values means more speed, but also more memory. In my testing there is diminishing returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. */ #ifndef DR_FLAC_BUFFER_SIZE #define DR_FLAC_BUFFER_SIZE 4096 #endif /* Architecture Detection */ #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) #define DRFLAC_64BIT #endif #if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)) #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 #elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define DRFLAC_ARM #endif /* End Architecture Detection */ #ifdef DRFLAC_64BIT typedef drflac_uint64 drflac_cache_t; #else typedef drflac_uint32 drflac_cache_t; #endif /* The various metadata block types. */ #define DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO 0 #define DRFLAC_METADATA_BLOCK_TYPE_PADDING 1 #define DRFLAC_METADATA_BLOCK_TYPE_APPLICATION 2 #define DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE 3 #define DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT 4 #define DRFLAC_METADATA_BLOCK_TYPE_CUESHEET 5 #define DRFLAC_METADATA_BLOCK_TYPE_PICTURE 6 #define DRFLAC_METADATA_BLOCK_TYPE_INVALID 127 /* The various picture types specified in the PICTURE block. */ #define DRFLAC_PICTURE_TYPE_OTHER 0 #define DRFLAC_PICTURE_TYPE_FILE_ICON 1 #define DRFLAC_PICTURE_TYPE_OTHER_FILE_ICON 2 #define DRFLAC_PICTURE_TYPE_COVER_FRONT 3 #define DRFLAC_PICTURE_TYPE_COVER_BACK 4 #define DRFLAC_PICTURE_TYPE_LEAFLET_PAGE 5 #define DRFLAC_PICTURE_TYPE_MEDIA 6 #define DRFLAC_PICTURE_TYPE_LEAD_ARTIST 7 #define DRFLAC_PICTURE_TYPE_ARTIST 8 #define DRFLAC_PICTURE_TYPE_CONDUCTOR 9 #define DRFLAC_PICTURE_TYPE_BAND 10 #define DRFLAC_PICTURE_TYPE_COMPOSER 11 #define DRFLAC_PICTURE_TYPE_LYRICIST 12 #define DRFLAC_PICTURE_TYPE_RECORDING_LOCATION 13 #define DRFLAC_PICTURE_TYPE_DURING_RECORDING 14 #define DRFLAC_PICTURE_TYPE_DURING_PERFORMANCE 15 #define DRFLAC_PICTURE_TYPE_SCREEN_CAPTURE 16 #define DRFLAC_PICTURE_TYPE_BRIGHT_COLORED_FISH 17 #define DRFLAC_PICTURE_TYPE_ILLUSTRATION 18 #define DRFLAC_PICTURE_TYPE_BAND_LOGOTYPE 19 #define DRFLAC_PICTURE_TYPE_PUBLISHER_LOGOTYPE 20 typedef enum { drflac_container_native, drflac_container_ogg, drflac_container_unknown } drflac_container; typedef enum { drflac_seek_origin_start, drflac_seek_origin_current } drflac_seek_origin; /* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */ typedef struct { drflac_uint64 firstPCMFrame; drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */ drflac_uint16 pcmFrameCount; } drflac_seekpoint; typedef struct { drflac_uint16 minBlockSizeInPCMFrames; drflac_uint16 maxBlockSizeInPCMFrames; drflac_uint32 minFrameSizeInPCMFrames; drflac_uint32 maxFrameSizeInPCMFrames; drflac_uint32 sampleRate; drflac_uint8 channels; drflac_uint8 bitsPerSample; drflac_uint64 totalPCMFrameCount; drflac_uint8 md5[16]; } drflac_streaminfo; typedef struct { /* The metadata type. Use this to know how to interpret the data below. Will be set to one of the DRFLAC_METADATA_BLOCK_TYPE_* tokens. */ drflac_uint32 type; /* A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to not modify the contents of this buffer. Use the structures below for more meaningful and structured information about the metadata. It's possible for this to be null. */ const void* pRawData; /* The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. */ drflac_uint32 rawDataSize; union { drflac_streaminfo streaminfo; struct { int unused; } padding; struct { drflac_uint32 id; const void* pData; drflac_uint32 dataSize; } application; struct { drflac_uint32 seekpointCount; const drflac_seekpoint* pSeekpoints; } seektable; struct { drflac_uint32 vendorLength; const char* vendor; drflac_uint32 commentCount; const void* pComments; } vorbis_comment; struct { char catalog[128]; drflac_uint64 leadInSampleCount; drflac_bool32 isCD; drflac_uint8 trackCount; const void* pTrackData; } cuesheet; struct { drflac_uint32 type; drflac_uint32 mimeLength; const char* mime; drflac_uint32 descriptionLength; const char* description; drflac_uint32 width; drflac_uint32 height; drflac_uint32 colorDepth; drflac_uint32 indexColorCount; drflac_uint32 pictureDataSize; const drflac_uint8* pPictureData; } picture; } data; } drflac_metadata; /* Callback for when data needs to be read from the client. Parameters ---------- pUserData (in) The user data that was passed to drflac_open() and family. pBufferOut (out) The output buffer. bytesToRead (in) The number of bytes to read. Return Value ------------ The number of bytes actually read. Remarks ------- A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until either the entire bytesToRead is filled or you have reached the end of the stream. */ typedef size_t (* drflac_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); /* Callback for when data needs to be seeked. Parameters ---------- pUserData (in) The user data that was passed to drflac_open() and family. offset (in) The number of bytes to move, relative to the origin. Will never be negative. origin (in) The origin of the seek - the current position or the start of the stream. Return Value ------------ Whether or not the seek was successful. Remarks ------- The offset will never be negative. Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drflac_seek_origin_start or drflac_seek_origin_current. When seeking to a PCM frame using drflac_seek_to_pcm_frame(), dr_flac may call this with an offset beyond the end of the FLAC stream. This needs to be detected and handled by returning DRFLAC_FALSE. */ typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); /* Callback for when a metadata block is read. Parameters ---------- pUserData (in) The user data that was passed to drflac_open() and family. pMetadata (in) A pointer to a structure containing the data of the metadata block. Remarks ------- Use pMetadata->type to determine which metadata block is being handled and how to read the data. This will be set to one of the DRFLAC_METADATA_BLOCK_TYPE_* tokens. */ typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata); /* Structure for internal use. Only used for decoders opened with drflac_open_memory. */ typedef struct { const drflac_uint8* data; size_t dataSize; size_t currentReadPos; } drflac__memory_stream; /* Structure for internal use. Used for bit streaming. */ typedef struct { /* The function to call when more data needs to be read. */ drflac_read_proc onRead; /* The function to call when the current read position needs to be moved. */ drflac_seek_proc onSeek; /* The user data to pass around to onRead and onSeek. */ void* pUserData; /* The number of unaligned bytes in the L2 cache. This will always be 0 until the end of the stream is hit. At the end of the stream there will be a number of bytes that don't cleanly fit in an L1 cache line, so we use this variable to know whether or not the bistreamer needs to run on a slower path to read those last bytes. This will never be more than sizeof(drflac_cache_t). */ size_t unalignedByteCount; /* The content of the unaligned bytes. */ drflac_cache_t unalignedCache; /* The index of the next valid cache line in the "L2" cache. */ drflac_uint32 nextL2Line; /* The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. */ drflac_uint32 consumedBits; /* The cached data which was most recently read from the client. There are two levels of cache. Data flows as such: Client -> L2 -> L1. The L2 -> L1 movement is aligned and runs on a fast path in just a few instructions. */ drflac_cache_t cacheL2[DR_FLAC_BUFFER_SIZE/sizeof(drflac_cache_t)]; drflac_cache_t cache; /* CRC-16. This is updated whenever bits are read from the bit stream. Manually set this to 0 to reset the CRC. For FLAC, this is reset to 0 at the beginning of each frame. */ drflac_uint16 crc16; drflac_cache_t crc16Cache; /* A cache for optimizing CRC calculations. This is filled when when the L1 cache is reloaded. */ drflac_uint32 crc16CacheIgnoredBytes; /* The number of bytes to ignore when updating the CRC-16 from the CRC-16 cache. */ } drflac_bs; typedef struct { /* The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. */ drflac_uint8 subframeType; /* The number of wasted bits per sample as specified by the sub-frame header. */ drflac_uint8 wastedBitsPerSample; /* The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. */ drflac_uint8 lpcOrder; /* A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pExtraData. */ drflac_int32* pSamplesS32; } drflac_subframe; typedef struct { /* If the stream uses variable block sizes, this will be set to the index of the first PCM frame. If fixed block sizes are used, this will always be set to 0. This is 64-bit because the decoded PCM frame number will be 36 bits. */ drflac_uint64 pcmFrameNumber; /* If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. This is 32-bit because in fixed block sizes, the maximum frame number will be 31 bits. */ drflac_uint32 flacFrameNumber; /* The sample rate of this frame. */ drflac_uint32 sampleRate; /* The number of PCM frames in each sub-frame within this frame. */ drflac_uint16 blockSizeInPCMFrames; /* The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. */ drflac_uint8 channelAssignment; /* The number of bits per sample within this frame. */ drflac_uint8 bitsPerSample; /* The frame's CRC. */ drflac_uint8 crc8; } drflac_frame_header; typedef struct { /* The header. */ drflac_frame_header header; /* The number of PCM frames left to be read in this FLAC frame. This is initially set to the block size. As PCM frames are read, this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. */ drflac_uint32 pcmFramesRemaining; /* The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. */ drflac_subframe subframes[8]; } drflac_frame; typedef struct { /* The function to call when a metadata block is read. */ drflac_meta_proc onMeta; /* The user data posted to the metadata callback function. */ void* pUserDataMD; /* Memory allocation callbacks. */ drflac_allocation_callbacks allocationCallbacks; /* The sample rate. Will be set to something like 44100. */ drflac_uint32 sampleRate; /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the value specified in the STREAMINFO block. */ drflac_uint8 channels; /* The bits per sample. Will be set to something like 16, 24, etc. */ drflac_uint8 bitsPerSample; /* The maximum block size, in samples. This number represents the number of samples in each channel (not combined). */ drflac_uint16 maxBlockSizeInPCMFrames; /* The total number of PCM Frames making up the stream. Can be 0 in which case it's still a valid stream, but just means the total PCM frame count is unknown. Likely the case with streams like internet radio. */ drflac_uint64 totalPCMFrameCount; /* The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. */ drflac_container container; /* The number of seekpoints in the seektable. */ drflac_uint32 seekpointCount; /* Information about the frame the decoder is currently sitting on. */ drflac_frame currentFLACFrame; /* The index of the PCM frame the decoder is currently sitting on. This is only used for seeking. */ drflac_uint64 currentPCMFrame; /* The position of the first FLAC frame in the stream. This is only ever used for seeking. */ drflac_uint64 firstFLACFramePosInBytes; /* A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). */ drflac__memory_stream memoryStream; /* A pointer to the decoded sample data. This is an offset of pExtraData. */ drflac_int32* pDecodedSamples; /* A pointer to the seek table. This is an offset of pExtraData, or NULL if there is no seek table. */ drflac_seekpoint* pSeekpoints; /* Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. */ void* _oggbs; /* Internal use only. Used for profiling and testing different seeking modes. */ drflac_bool32 _noSeekTableSeek : 1; drflac_bool32 _noBinarySearchSeek : 1; drflac_bool32 _noBruteForceSeek : 1; /* The bit streamer. The raw FLAC data is fed through this object. */ drflac_bs bs; /* Variable length extra data. We attach this to the end of the object so we can avoid unnecessary mallocs. */ drflac_uint8 pExtraData[1]; } drflac; /* Opens a FLAC decoder. Parameters ---------- onRead (in) The function to call when data needs to be read from the client. onSeek (in) The function to call when the read position of the client data needs to move. pUserData (in, optional) A pointer to application defined data that will be passed to onRead and onSeek. pAllocationCallbacks (in, optional) A pointer to application defined callbacks for managing memory allocations. Return Value ------------ Returns a pointer to an object representing the decoder. Remarks ------- Close the decoder with `drflac_close()`. `pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. This function will automatically detect whether or not you are attempting to open a native or Ogg encapsulated FLAC, both of which should work seamlessly without any manual intervention. Ogg encapsulation also works with multiplexed streams which basically means it can play FLAC encoded audio tracks in videos. This is the lowest level function for opening a FLAC stream. You can also use `drflac_open_file()` and `drflac_open_memory()` to open the stream from a file or from a block of memory respectively. The STREAMINFO block must be present for this to succeed. Use `drflac_open_relaxed()` to open a FLAC stream where the header may not be present. Use `drflac_open_with_metadata()` if you need access to metadata. Seek Also --------- drflac_open_file() drflac_open_memory() drflac_open_with_metadata() drflac_close() */ DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC stream with relaxed validation of the header block. Parameters ---------- onRead (in) The function to call when data needs to be read from the client. onSeek (in) The function to call when the read position of the client data needs to move. container (in) Whether or not the FLAC stream is encapsulated using standard FLAC encapsulation or Ogg encapsulation. pUserData (in, optional) A pointer to application defined data that will be passed to onRead and onSeek. pAllocationCallbacks (in, optional) A pointer to application defined callbacks for managing memory allocations. Return Value ------------ A pointer to an object representing the decoder. Remarks ------- The same as drflac_open(), except attempts to open the stream even when a header block is not present. Because the header is not necessarily available, the caller must explicitly define the container (Native or Ogg). Do not set this to `drflac_container_unknown` as that is for internal use only. Opening in relaxed mode will continue reading data from onRead until it finds a valid frame. If a frame is never found it will continue forever. To abort, force your `onRead` callback to return 0, which dr_flac will use as an indicator that the end of the stream was found. Use `drflac_open_with_metadata_relaxed()` if you need access to metadata. */ DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). Parameters ---------- onRead (in) The function to call when data needs to be read from the client. onSeek (in) The function to call when the read position of the client data needs to move. onMeta (in) The function to call for every metadata block. pUserData (in, optional) A pointer to application defined data that will be passed to onRead, onSeek and onMeta. pAllocationCallbacks (in, optional) A pointer to application defined callbacks for managing memory allocations. Return Value ------------ A pointer to an object representing the decoder. Remarks ------- Close the decoder with `drflac_close()`. `pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. This is slower than `drflac_open()`, so avoid this one if you don't need metadata. Internally, this will allocate and free memory on the heap for every metadata block except for STREAMINFO and PADDING blocks. The caller is notified of the metadata via the `onMeta` callback. All metadata blocks will be handled before the function returns. This callback takes a pointer to a `drflac_metadata` object which is a union containing the data of all relevant metadata blocks. Use the `type` member to discriminate against the different metadata types. The STREAMINFO block must be present for this to succeed. Use `drflac_open_with_metadata_relaxed()` to open a FLAC stream where the header may not be present. Note that this will behave inconsistently with `drflac_open()` if the stream is an Ogg encapsulated stream and a metadata block is corrupted. This is due to the way the Ogg stream recovers from corrupted pages. When `drflac_open_with_metadata()` is being used, the open routine will try to read the contents of the metadata block, whereas `drflac_open()` will simply seek past it (for the sake of efficiency). This inconsistency can result in different samples being returned depending on whether or not the stream is being opened with metadata. Seek Also --------- drflac_open_file_with_metadata() drflac_open_memory_with_metadata() drflac_open() drflac_close() */ DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. See Also -------- drflac_open_with_metadata() drflac_open_relaxed() */ DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Closes the given FLAC decoder. Parameters ---------- pFlac (in) The decoder to close. Remarks ------- This will destroy the decoder object. See Also -------- drflac_open() drflac_open_with_metadata() drflac_open_file() drflac_open_file_w() drflac_open_file_with_metadata() drflac_open_file_with_metadata_w() drflac_open_memory() drflac_open_memory_with_metadata() */ DRFLAC_API void drflac_close(drflac* pFlac); /* Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. Parameters ---------- pFlac (in) The decoder. framesToRead (in) The number of PCM frames to read. pBufferOut (out, optional) A pointer to the buffer that will receive the decoded samples. Return Value ------------ Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. Remarks ------- pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. */ DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut); /* Reads sample data from the given FLAC decoder, output as interleaved signed 16-bit PCM. Parameters ---------- pFlac (in) The decoder. framesToRead (in) The number of PCM frames to read. pBufferOut (out, optional) A pointer to the buffer that will receive the decoded samples. Return Value ------------ Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. Remarks ------- pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. Note that this is lossy for streams where the bits per sample is larger than 16. */ DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut); /* Reads sample data from the given FLAC decoder, output as interleaved 32-bit floating point PCM. Parameters ---------- pFlac (in) The decoder. framesToRead (in) The number of PCM frames to read. pBufferOut (out, optional) A pointer to the buffer that will receive the decoded samples. Return Value ------------ Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. Remarks ------- pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. Note that this should be considered lossy due to the nature of floating point numbers not being able to exactly represent every possible number. */ DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut); /* Seeks to the PCM frame at the given index. Parameters ---------- pFlac (in) The decoder. pcmFrameIndex (in) The index of the PCM frame to seek to. See notes below. Return Value ------------- `DRFLAC_TRUE` if successful; `DRFLAC_FALSE` otherwise. */ DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); #ifndef DR_FLAC_NO_STDIO /* Opens a FLAC decoder from the file at the given path. Parameters ---------- pFileName (in) The path of the file to open, either absolute or relative to the current directory. pAllocationCallbacks (in, optional) A pointer to application defined callbacks for managing memory allocations. Return Value ------------ A pointer to an object representing the decoder. Remarks ------- Close the decoder with drflac_close(). Remarks ------- This will hold a handle to the file until the decoder is closed with drflac_close(). Some platforms will restrict the number of files a process can have open at any given time, so keep this mind if you have many decoders open at the same time. See Also -------- drflac_open_file_with_metadata() drflac_open() drflac_close() */ DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) Parameters ---------- pFileName (in) The path of the file to open, either absolute or relative to the current directory. pAllocationCallbacks (in, optional) A pointer to application defined callbacks for managing memory allocations. onMeta (in) The callback to fire for each metadata block. pUserData (in) A pointer to the user data to pass to the metadata callback. pAllocationCallbacks (in) A pointer to application defined callbacks for managing memory allocations. Remarks ------- Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. See Also -------- drflac_open_with_metadata() drflac_open() drflac_close() */ DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); #endif /* Opens a FLAC decoder from a pre-allocated block of memory Parameters ---------- pData (in) A pointer to the raw encoded FLAC data. dataSize (in) The size in bytes of `data`. pAllocationCallbacks (in) A pointer to application defined callbacks for managing memory allocations. Return Value ------------ A pointer to an object representing the decoder. Remarks ------- This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for the lifetime of the decoder. See Also -------- drflac_open() drflac_close() */ DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) Parameters ---------- pData (in) A pointer to the raw encoded FLAC data. dataSize (in) The size in bytes of `data`. onMeta (in) The callback to fire for each metadata block. pUserData (in) A pointer to the user data to pass to the metadata callback. pAllocationCallbacks (in) A pointer to application defined callbacks for managing memory allocations. Remarks ------- Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. See Also ------- drflac_open_with_metadata() drflac_open() drflac_close() */ DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* High Level APIs */ /* Opens a FLAC stream from the given callbacks and fully decodes it in a single operation. The return value is a pointer to the sample data as interleaved signed 32-bit PCM. The returned data must be freed with drflac_free(). You can pass in custom memory allocation callbacks via the pAllocationCallbacks parameter. This can be NULL in which case it will use DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. Sometimes a FLAC file won't keep track of the total sample count. In this situation the function will continuously read samples into a dynamically sized buffer on the heap until no samples are left. Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). */ DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); #ifndef DR_FLAC_NO_STDIO /* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. */ DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); #endif /* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a block of memory. */ DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Frees memory that was allocated internally by dr_flac. Set pAllocationCallbacks to the same object that was passed to drflac_open_*_and_read_pcm_frames_*(). If you originally passed in NULL, pass in NULL for this. */ DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks); /* Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. */ typedef struct { drflac_uint32 countRemaining; const char* pRunningData; } drflac_vorbis_comment_iterator; /* Initializes a vorbis comment iterator. This can be used for iterating over the vorbis comments in a VORBIS_COMMENT metadata block. */ DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments); /* Goes to the next vorbis comment in the given iterator. If null is returned it means there are no more comments. The returned string is NOT null terminated. */ DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut); /* Structure representing an iterator for cuesheet tracks in a CUESHEET metadata block. */ typedef struct { drflac_uint32 countRemaining; const char* pRunningData; } drflac_cuesheet_track_iterator; /* The order of members here is important because we map this directly to the raw data within the CUESHEET metadata block. */ typedef struct { drflac_uint64 offset; drflac_uint8 index; drflac_uint8 reserved[3]; } drflac_cuesheet_track_index; typedef struct { drflac_uint64 offset; drflac_uint8 trackNumber; char ISRC[12]; drflac_bool8 isAudio; drflac_bool8 preEmphasis; drflac_uint8 indexCount; const drflac_cuesheet_track_index* pIndexPoints; } drflac_cuesheet_track; /* Initializes a cuesheet track iterator. This can be used for iterating over the cuesheet tracks in a CUESHEET metadata block. */ DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData); /* Goes to the next cuesheet track in the given iterator. If DRFLAC_FALSE is returned it means there are no more comments. */ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack); #ifdef __cplusplus } #endif #endif /* dr_flac_h */ /************************************************************************************************************************************************************ ************************************************************************************************************************************************************ IMPLEMENTATION ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ #if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) #ifndef dr_flac_c #define dr_flac_c /* Disable some annoying warnings. */ #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #if __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #endif #endif #ifdef __linux__ #ifndef _BSD_SOURCE #define _BSD_SOURCE #endif #ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #ifndef __USE_BSD #define __USE_BSD #endif #include #endif #include #include /* Inline */ #ifdef _MSC_VER #define DRFLAC_INLINE __forceinline #elif defined(__GNUC__) /* I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) #define DRFLAC_GNUC_INLINE_HINT __inline__ #else #define DRFLAC_GNUC_INLINE_HINT inline #endif #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline)) #else #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRFLAC_INLINE __inline #else #define DRFLAC_INLINE #endif /* End Inline */ /* Intrinsics Support There's a bug in GCC 4.2.x which results in an incorrect compilation error when using _mm_slli_epi32() where it complains with "error: shift must be an immediate" Unfortuantely dr_flac depends on this for a few things so we're just going to disable SSE on GCC 4.2 and below. */ #if !defined(DR_FLAC_NO_SIMD) #if defined(DRFLAC_X64) || defined(DRFLAC_X86) #if defined(_MSC_VER) && !defined(__clang__) /* MSVC. */ #if _MSC_VER >= 1400 && !defined(DRFLAC_NO_SSE2) /* 2005 */ #define DRFLAC_SUPPORT_SSE2 #endif #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ #define DRFLAC_SUPPORT_SSE41 #endif #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 #endif #if defined(__SSE4_1__) && !defined(DRFLAC_NO_SSE41) #define DRFLAC_SUPPORT_SSE41 #endif #endif /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) #if !defined(DRFLAC_SUPPORT_SSE2) && !defined(DRFLAC_NO_SSE2) && __has_include() #define DRFLAC_SUPPORT_SSE2 #endif #if !defined(DRFLAC_SUPPORT_SSE41) && !defined(DRFLAC_NO_SSE41) && __has_include() #define DRFLAC_SUPPORT_SSE41 #endif #endif #if defined(DRFLAC_SUPPORT_SSE41) #include #elif defined(DRFLAC_SUPPORT_SSE2) #include #endif #endif #if defined(DRFLAC_ARM) #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) #define DRFLAC_SUPPORT_NEON #include #endif #endif #endif /* Compile-time CPU feature support. */ #if !defined(DR_FLAC_NO_SIMD) && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) #if defined(_MSC_VER) && !defined(__clang__) #if _MSC_VER >= 1400 #include static void drflac__cpuid(int info[4], int fid) { __cpuid(info, fid); } #else #define DRFLAC_NO_CPUID #endif #else #if defined(__GNUC__) || defined(__clang__) static void drflac__cpuid(int info[4], int fid) { /* It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for supporting different assembly dialects. What's basically happening is that we're saving and restoring the ebx register manually. */ #if defined(DRFLAC_X86) && defined(__PIC__) __asm__ __volatile__ ( "xchg{l} {%%}ebx, %k1;" "cpuid;" "xchg{l} {%%}ebx, %k1;" : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) ); #else __asm__ __volatile__ ( "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) ); #endif } #else #define DRFLAC_NO_CPUID #endif #endif #else #define DRFLAC_NO_CPUID #endif static DRFLAC_INLINE drflac_bool32 drflac_has_sse2(void) { #if defined(DRFLAC_SUPPORT_SSE2) #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE2) #if defined(DRFLAC_X64) return DRFLAC_TRUE; /* 64-bit targets always support SSE2. */ #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE2 code we can assume support. */ #else #if defined(DRFLAC_NO_CPUID) return DRFLAC_FALSE; #else int info[4]; drflac__cpuid(info, 1); return (info[3] & (1 << 26)) != 0; #endif #endif #else return DRFLAC_FALSE; /* SSE2 is only supported on x86 and x64 architectures. */ #endif #else return DRFLAC_FALSE; /* No compiler support. */ #endif } static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void) { #if defined(DRFLAC_SUPPORT_SSE41) #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) #if defined(__SSE4_1__) || defined(__AVX__) return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */ #else #if defined(DRFLAC_NO_CPUID) return DRFLAC_FALSE; #else int info[4]; drflac__cpuid(info, 1); return (info[2] & (1 << 19)) != 0; #endif #endif #else return DRFLAC_FALSE; /* SSE41 is only supported on x86 and x64 architectures. */ #endif #else return DRFLAC_FALSE; /* No compiler support. */ #endif } #if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) && !defined(__clang__) #define DRFLAC_HAS_LZCNT_INTRINSIC #elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) #define DRFLAC_HAS_LZCNT_INTRINSIC #elif defined(__clang__) #if defined(__has_builtin) #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) #define DRFLAC_HAS_LZCNT_INTRINSIC #endif #endif #endif #if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(__clang__) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #define DRFLAC_HAS_BYTESWAP32_INTRINSIC #define DRFLAC_HAS_BYTESWAP64_INTRINSIC #elif defined(__clang__) #if defined(__has_builtin) #if __has_builtin(__builtin_bswap16) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #endif #if __has_builtin(__builtin_bswap32) #define DRFLAC_HAS_BYTESWAP32_INTRINSIC #endif #if __has_builtin(__builtin_bswap64) #define DRFLAC_HAS_BYTESWAP64_INTRINSIC #endif #endif #elif defined(__GNUC__) #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) #define DRFLAC_HAS_BYTESWAP32_INTRINSIC #define DRFLAC_HAS_BYTESWAP64_INTRINSIC #endif #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #endif #elif defined(__WATCOMC__) && defined(__386__) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #define DRFLAC_HAS_BYTESWAP32_INTRINSIC #define DRFLAC_HAS_BYTESWAP64_INTRINSIC extern __inline drflac_uint16 _watcom_bswap16(drflac_uint16); extern __inline drflac_uint32 _watcom_bswap32(drflac_uint32); extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64); #pragma aux _watcom_bswap16 = \ "xchg al, ah" \ parm [ax] \ value [ax] \ modify nomemory; #pragma aux _watcom_bswap32 = \ "bswap eax" \ parm [eax] \ value [eax] \ modify nomemory; #pragma aux _watcom_bswap64 = \ "bswap eax" \ "bswap edx" \ "xchg eax,edx" \ parm [eax edx] \ value [eax edx] \ modify nomemory; #endif /* Standard library stuff. */ #ifndef DRFLAC_ASSERT #include #define DRFLAC_ASSERT(expression) assert(expression) #endif #ifndef DRFLAC_MALLOC #define DRFLAC_MALLOC(sz) malloc((sz)) #endif #ifndef DRFLAC_REALLOC #define DRFLAC_REALLOC(p, sz) realloc((p), (sz)) #endif #ifndef DRFLAC_FREE #define DRFLAC_FREE(p) free((p)) #endif #ifndef DRFLAC_COPY_MEMORY #define DRFLAC_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) #endif #ifndef DRFLAC_ZERO_MEMORY #define DRFLAC_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) #endif #ifndef DRFLAC_ZERO_OBJECT #define DRFLAC_ZERO_OBJECT(p) DRFLAC_ZERO_MEMORY((p), sizeof(*(p))) #endif #define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ /* Result Codes */ typedef drflac_int32 drflac_result; #define DRFLAC_SUCCESS 0 #define DRFLAC_ERROR -1 /* A generic error. */ #define DRFLAC_INVALID_ARGS -2 #define DRFLAC_INVALID_OPERATION -3 #define DRFLAC_OUT_OF_MEMORY -4 #define DRFLAC_OUT_OF_RANGE -5 #define DRFLAC_ACCESS_DENIED -6 #define DRFLAC_DOES_NOT_EXIST -7 #define DRFLAC_ALREADY_EXISTS -8 #define DRFLAC_TOO_MANY_OPEN_FILES -9 #define DRFLAC_INVALID_FILE -10 #define DRFLAC_TOO_BIG -11 #define DRFLAC_PATH_TOO_LONG -12 #define DRFLAC_NAME_TOO_LONG -13 #define DRFLAC_NOT_DIRECTORY -14 #define DRFLAC_IS_DIRECTORY -15 #define DRFLAC_DIRECTORY_NOT_EMPTY -16 #define DRFLAC_END_OF_FILE -17 #define DRFLAC_NO_SPACE -18 #define DRFLAC_BUSY -19 #define DRFLAC_IO_ERROR -20 #define DRFLAC_INTERRUPT -21 #define DRFLAC_UNAVAILABLE -22 #define DRFLAC_ALREADY_IN_USE -23 #define DRFLAC_BAD_ADDRESS -24 #define DRFLAC_BAD_SEEK -25 #define DRFLAC_BAD_PIPE -26 #define DRFLAC_DEADLOCK -27 #define DRFLAC_TOO_MANY_LINKS -28 #define DRFLAC_NOT_IMPLEMENTED -29 #define DRFLAC_NO_MESSAGE -30 #define DRFLAC_BAD_MESSAGE -31 #define DRFLAC_NO_DATA_AVAILABLE -32 #define DRFLAC_INVALID_DATA -33 #define DRFLAC_TIMEOUT -34 #define DRFLAC_NO_NETWORK -35 #define DRFLAC_NOT_UNIQUE -36 #define DRFLAC_NOT_SOCKET -37 #define DRFLAC_NO_ADDRESS -38 #define DRFLAC_BAD_PROTOCOL -39 #define DRFLAC_PROTOCOL_UNAVAILABLE -40 #define DRFLAC_PROTOCOL_NOT_SUPPORTED -41 #define DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED -42 #define DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED -43 #define DRFLAC_SOCKET_NOT_SUPPORTED -44 #define DRFLAC_CONNECTION_RESET -45 #define DRFLAC_ALREADY_CONNECTED -46 #define DRFLAC_NOT_CONNECTED -47 #define DRFLAC_CONNECTION_REFUSED -48 #define DRFLAC_NO_HOST -49 #define DRFLAC_IN_PROGRESS -50 #define DRFLAC_CANCELLED -51 #define DRFLAC_MEMORY_ALREADY_MAPPED -52 #define DRFLAC_AT_END -53 #define DRFLAC_CRC_MISMATCH -100 /* End Result Codes */ #define DRFLAC_SUBFRAME_CONSTANT 0 #define DRFLAC_SUBFRAME_VERBATIM 1 #define DRFLAC_SUBFRAME_FIXED 8 #define DRFLAC_SUBFRAME_LPC 32 #define DRFLAC_SUBFRAME_RESERVED 255 #define DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE 0 #define DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2 1 #define DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT 0 #define DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE 8 #define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9 #define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10 #define DRFLAC_SEEKPOINT_SIZE_IN_BYTES 18 #define DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES 36 #define DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES 12 #define drflac_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision) { if (pMajor) { *pMajor = DRFLAC_VERSION_MAJOR; } if (pMinor) { *pMinor = DRFLAC_VERSION_MINOR; } if (pRevision) { *pRevision = DRFLAC_VERSION_REVISION; } } DRFLAC_API const char* drflac_version_string(void) { return DRFLAC_VERSION_STRING; } /* CPU caps. */ #if defined(__has_feature) #if __has_feature(thread_sanitizer) #define DRFLAC_NO_THREAD_SANITIZE __attribute__((no_sanitize("thread"))) #else #define DRFLAC_NO_THREAD_SANITIZE #endif #else #define DRFLAC_NO_THREAD_SANITIZE #endif #if defined(DRFLAC_HAS_LZCNT_INTRINSIC) static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; #endif #ifndef DRFLAC_NO_CPUID static drflac_bool32 drflac__gIsSSE2Supported = DRFLAC_FALSE; static drflac_bool32 drflac__gIsSSE41Supported = DRFLAC_FALSE; /* I've had a bug report that Clang's ThreadSanitizer presents a warning in this function. Having reviewed this, this does actually make sense. However, since CPU caps should never differ for a running process, I don't think the trade off of complicating internal API's by passing around CPU caps versus just disabling the warnings is worthwhile. I'm therefore just going to disable these warnings. This is disabled via the DRFLAC_NO_THREAD_SANITIZE attribute. */ DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) { static drflac_bool32 isCPUCapsInitialized = DRFLAC_FALSE; if (!isCPUCapsInitialized) { /* LZCNT */ #if defined(DRFLAC_HAS_LZCNT_INTRINSIC) int info[4] = {0}; drflac__cpuid(info, 0x80000001); drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; #endif /* SSE2 */ drflac__gIsSSE2Supported = drflac_has_sse2(); /* SSE4.1 */ drflac__gIsSSE41Supported = drflac_has_sse41(); /* Initialized. */ isCPUCapsInitialized = DRFLAC_TRUE; } } #else static drflac_bool32 drflac__gIsNEONSupported = DRFLAC_FALSE; static DRFLAC_INLINE drflac_bool32 drflac__has_neon(void) { #if defined(DRFLAC_SUPPORT_NEON) #if defined(DRFLAC_ARM) && !defined(DRFLAC_NO_NEON) #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) return DRFLAC_TRUE; /* If the compiler is allowed to freely generate NEON code we can assume support. */ #else /* TODO: Runtime check. */ return DRFLAC_FALSE; #endif #else return DRFLAC_FALSE; /* NEON is only supported on ARM architectures. */ #endif #else return DRFLAC_FALSE; /* No compiler support. */ #endif } DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) { drflac__gIsNEONSupported = drflac__has_neon(); #if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) drflac__gIsLZCNTSupported = DRFLAC_TRUE; #endif } #endif /* Endian Management */ static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian(void) { #if defined(DRFLAC_X86) || defined(DRFLAC_X64) return DRFLAC_TRUE; #elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN return DRFLAC_TRUE; #else int n = 1; return (*(char*)&n) == 1; #endif } static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) { #ifdef DRFLAC_HAS_BYTESWAP16_INTRINSIC #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_ushort(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap16(n); #elif defined(__WATCOMC__) && defined(__386__) return _watcom_bswap16(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else return ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); #endif } static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) { #ifdef DRFLAC_HAS_BYTESWAP32_INTRINSIC #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_ulong(n); #elif defined(__GNUC__) || defined(__clang__) #if defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ drflac_uint32 r; __asm__ __volatile__ ( #if defined(DRFLAC_64BIT) "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ #else "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) #endif ); return r; #else return __builtin_bswap32(n); #endif #elif defined(__WATCOMC__) && defined(__386__) return _watcom_bswap32(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else return ((n & 0xFF000000) >> 24) | ((n & 0x00FF0000) >> 8) | ((n & 0x0000FF00) << 8) | ((n & 0x000000FF) << 24); #endif } static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) { #ifdef DRFLAC_HAS_BYTESWAP64_INTRINSIC #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_uint64(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap64(n); #elif defined(__WATCOMC__) && defined(__386__) return _watcom_bswap64(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | ((n & ((drflac_uint64)0xFF000000 )) << 8) | ((n & ((drflac_uint64)0x00FF0000 )) << 24) | ((n & ((drflac_uint64)0x0000FF00 )) << 40) | ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) { if (drflac__is_little_endian()) { return drflac__swap_endian_uint16(n); } return n; } static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) { if (drflac__is_little_endian()) { return drflac__swap_endian_uint32(n); } return n; } static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData) { const drflac_uint8* pNum = (drflac_uint8*)pData; return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3); } static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) { if (drflac__is_little_endian()) { return drflac__swap_endian_uint64(n); } return n; } static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) { if (!drflac__is_little_endian()) { return drflac__swap_endian_uint32(n); } return n; } static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData) { const drflac_uint8* pNum = (drflac_uint8*)pData; return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24; } static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) { drflac_uint32 result = 0; result |= (n & 0x7F000000) >> 3; result |= (n & 0x007F0000) >> 2; result |= (n & 0x00007F00) >> 1; result |= (n & 0x0000007F) >> 0; return result; } /* The CRC code below is based on this document: http://zlib.net/crc_v3.txt */ static drflac_uint8 drflac__crc8_table[] = { 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 }; static drflac_uint16 drflac__crc16_table[] = { 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041, 0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, 0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082, 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1, 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2, 0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, 0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101, 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321, 0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, 0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2, 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381, 0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2, 0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, 0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261, 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202 }; static DRFLAC_INLINE drflac_uint8 drflac_crc8_byte(drflac_uint8 crc, drflac_uint8 data) { return drflac__crc8_table[crc ^ data]; } static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 data, drflac_uint32 count) { #ifdef DR_FLAC_NO_CRC (void)crc; (void)data; (void)count; return 0; #else #if 0 /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc8(crc, 0, 8);") */ drflac_uint8 p = 0x07; for (int i = count-1; i >= 0; --i) { drflac_uint8 bit = (data & (1 << i)) >> i; if (crc & 0x80) { crc = ((crc << 1) | bit) ^ p; } else { crc = ((crc << 1) | bit); } } return crc; #else drflac_uint32 wholeBytes; drflac_uint32 leftoverBits; drflac_uint64 leftoverDataMask; static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; DRFLAC_ASSERT(count <= 32); wholeBytes = count >> 3; leftoverBits = count - (wholeBytes*8); leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { case 4: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); case 3: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); case 2: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); case 1: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); case 0: if (leftoverBits > 0) crc = (drflac_uint8)((crc << leftoverBits) ^ drflac__crc8_table[(crc >> (8 - leftoverBits)) ^ (data & leftoverDataMask)]); } return crc; #endif #endif } static DRFLAC_INLINE drflac_uint16 drflac_crc16_byte(drflac_uint16 crc, drflac_uint8 data) { return (crc << 8) ^ drflac__crc16_table[(drflac_uint8)(crc >> 8) ^ data]; } static DRFLAC_INLINE drflac_uint16 drflac_crc16_cache(drflac_uint16 crc, drflac_cache_t data) { #ifdef DRFLAC_64BIT crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); #endif crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); return crc; } static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 byteCount) { switch (byteCount) { #ifdef DRFLAC_64BIT case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); #endif case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); } return crc; } #if 0 static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac_uint32 data, drflac_uint32 count) { #ifdef DR_FLAC_NO_CRC (void)crc; (void)data; (void)count; return 0; #else #if 0 /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc16(crc, 0, 16);") */ drflac_uint16 p = 0x8005; for (int i = count-1; i >= 0; --i) { drflac_uint16 bit = (data & (1ULL << i)) >> i; if (r & 0x8000) { r = ((r << 1) | bit) ^ p; } else { r = ((r << 1) | bit); } } return crc; #else drflac_uint32 wholeBytes; drflac_uint32 leftoverBits; drflac_uint64 leftoverDataMask; static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; DRFLAC_ASSERT(count <= 64); wholeBytes = count >> 3; leftoverBits = count & 7; leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { default: case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; } return crc; #endif #endif } static DRFLAC_INLINE drflac_uint16 drflac_crc16__64bit(drflac_uint16 crc, drflac_uint64 data, drflac_uint32 count) { #ifdef DR_FLAC_NO_CRC (void)crc; (void)data; (void)count; return 0; #else drflac_uint32 wholeBytes; drflac_uint32 leftoverBits; drflac_uint64 leftoverDataMask; static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; DRFLAC_ASSERT(count <= 64); wholeBytes = count >> 3; leftoverBits = count & 7; leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { default: case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 << 32) << leftoverBits)) >> (56 + leftoverBits))); /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 << 32) << leftoverBits)) >> (48 + leftoverBits))); case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 << 32) << leftoverBits)) >> (40 + leftoverBits))); case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF << 32) << leftoverBits)) >> (32 + leftoverBits))); case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 ) << leftoverBits)) >> (24 + leftoverBits))); case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 ) << leftoverBits)) >> (16 + leftoverBits))); case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 ) << leftoverBits)) >> ( 8 + leftoverBits))); case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF ) << leftoverBits)) >> ( 0 + leftoverBits))); case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; } return crc; #endif } static DRFLAC_INLINE drflac_uint16 drflac_crc16(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 count) { #ifdef DRFLAC_64BIT return drflac_crc16__64bit(crc, data, count); #else return drflac_crc16__32bit(crc, data, count); #endif } #endif #ifdef DRFLAC_64BIT #define drflac__be2host__cache_line drflac__be2host_64 #else #define drflac__be2host__cache_line drflac__be2host_32 #endif /* BIT READING ATTEMPT #2 This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data from onRead() is read into. */ #define DRFLAC_CACHE_L1_SIZE_BYTES(bs) (sizeof((bs)->cache)) #define DRFLAC_CACHE_L1_SIZE_BITS(bs) (sizeof((bs)->cache)*8) #define DRFLAC_CACHE_L1_BITS_REMAINING(bs) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (bs)->consumedBits) #define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~((~(drflac_cache_t)0) >> (_bitCount))) #define DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (_bitCount)) #define DRFLAC_CACHE_L1_SELECT(bs, _bitCount) (((bs)->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) #define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount))) #define DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, _bitCount)(DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> (DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount)) & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1))) #define DRFLAC_CACHE_L2_SIZE_BYTES(bs) (sizeof((bs)->cacheL2)) #define DRFLAC_CACHE_L2_LINE_COUNT(bs) (DRFLAC_CACHE_L2_SIZE_BYTES(bs) / sizeof((bs)->cacheL2[0])) #define DRFLAC_CACHE_L2_LINES_REMAINING(bs) (DRFLAC_CACHE_L2_LINE_COUNT(bs) - (bs)->nextL2Line) #ifndef DR_FLAC_NO_CRC static DRFLAC_INLINE void drflac__reset_crc16(drflac_bs* bs) { bs->crc16 = 0; bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; } static DRFLAC_INLINE void drflac__update_crc16(drflac_bs* bs) { if (bs->crc16CacheIgnoredBytes == 0) { bs->crc16 = drflac_crc16_cache(bs->crc16, bs->crc16Cache); } else { bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache, DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bs->crc16CacheIgnoredBytes); bs->crc16CacheIgnoredBytes = 0; } } static DRFLAC_INLINE drflac_uint16 drflac__flush_crc16(drflac_bs* bs) { /* We should never be flushing in a situation where we are not aligned on a byte boundary. */ DRFLAC_ASSERT((DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7) == 0); /* The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined by the number of bits that have been consumed. */ if (DRFLAC_CACHE_L1_BITS_REMAINING(bs) == 0) { drflac__update_crc16(bs); } else { /* We only accumulate the consumed bits. */ bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache >> DRFLAC_CACHE_L1_BITS_REMAINING(bs), (bs->consumedBits >> 3) - bs->crc16CacheIgnoredBytes); /* The bits that we just accumulated should never be accumulated again. We need to keep track of how many bytes were accumulated so we can handle that later. */ bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; } return bs->crc16; } #endif static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs) { size_t bytesRead; size_t alignedL1LineCount; /* Fast path. Try loading straight from L2. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { bs->cache = bs->cacheL2[bs->nextL2Line++]; return DRFLAC_TRUE; } /* If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client, if there's any left. */ if (bs->unalignedByteCount > 0) { return DRFLAC_FALSE; /* If we have any unaligned bytes it means there's no more aligned bytes left in the client. */ } bytesRead = bs->onRead(bs->pUserData, bs->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES(bs)); bs->nextL2Line = 0; if (bytesRead == DRFLAC_CACHE_L2_SIZE_BYTES(bs)) { bs->cache = bs->cacheL2[bs->nextL2Line++]; return DRFLAC_TRUE; } /* If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably means we've just reached the end of the file. We need to move the valid data down to the end of the buffer and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to the size of the L1 so we'll need to seek backwards by any misaligned bytes. */ alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES(bs); /* We need to keep track of any unaligned bytes for later use. */ bs->unalignedByteCount = bytesRead - (alignedL1LineCount * DRFLAC_CACHE_L1_SIZE_BYTES(bs)); if (bs->unalignedByteCount > 0) { bs->unalignedCache = bs->cacheL2[alignedL1LineCount]; } if (alignedL1LineCount > 0) { size_t offset = DRFLAC_CACHE_L2_LINE_COUNT(bs) - alignedL1LineCount; size_t i; for (i = alignedL1LineCount; i > 0; --i) { bs->cacheL2[i-1 + offset] = bs->cacheL2[i-1]; } bs->nextL2Line = (drflac_uint32)offset; bs->cache = bs->cacheL2[bs->nextL2Line++]; return DRFLAC_TRUE; } else { /* If we get into this branch it means we weren't able to load any L1-aligned data. */ bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); return DRFLAC_FALSE; } } static drflac_bool32 drflac__reload_cache(drflac_bs* bs) { size_t bytesRead; #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif /* Fast path. Try just moving the next value in the L2 cache to the L1 cache. */ if (drflac__reload_l1_cache_from_l2(bs)) { bs->cache = drflac__be2host__cache_line(bs->cache); bs->consumedBits = 0; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs->cache; #endif return DRFLAC_TRUE; } /* Slow path. */ /* If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the data from the unaligned cache. */ bytesRead = bs->unalignedByteCount; if (bytesRead == 0) { bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- The stream has been exhausted, so marked the bits as consumed. */ return DRFLAC_FALSE; } DRFLAC_ASSERT(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES(bs)); bs->consumedBits = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bytesRead) * 8; bs->cache = drflac__be2host__cache_line(bs->unalignedCache); bs->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_BITS_REMAINING(bs)); /* <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. */ bs->unalignedByteCount = 0; /* <-- At this point the unaligned bytes have been moved into the cache and we thus have no more unaligned bytes. */ #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs->cache >> bs->consumedBits; bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; #endif return DRFLAC_TRUE; } static void drflac__reset_cache(drflac_bs* bs) { bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); /* <-- This clears the L2 cache. */ bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- This clears the L1 cache. */ bs->cache = 0; bs->unalignedByteCount = 0; /* <-- This clears the trailing unaligned bytes. */ bs->unalignedCache = 0; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = 0; bs->crc16CacheIgnoredBytes = 0; #endif } static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned int bitCount, drflac_uint32* pResultOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pResultOut != NULL); DRFLAC_ASSERT(bitCount > 0); DRFLAC_ASSERT(bitCount <= 32); if (bs->consumedBits == DRFLAC_CACHE_L1_SIZE_BITS(bs)) { if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } } if (bitCount <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { /* If we want to load all 32-bits from a 32-bit cache we need to do it slightly differently because we can't do a 32-bit shift on a 32-bit integer. This will never be the case on 64-bit caches, so we can have a slightly more optimal solution for this. */ #ifdef DRFLAC_64BIT *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); bs->consumedBits += bitCount; bs->cache <<= bitCount; #else if (bitCount < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); bs->consumedBits += bitCount; bs->cache <<= bitCount; } else { /* Cannot shift by 32-bits, so need to do it differently. */ *pResultOut = (drflac_uint32)bs->cache; bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); bs->cache = 0; } #endif return DRFLAC_TRUE; } else { /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ drflac_uint32 bitCountHi = DRFLAC_CACHE_L1_BITS_REMAINING(bs); drflac_uint32 bitCountLo = bitCount - bitCountHi; drflac_uint32 resultHi; DRFLAC_ASSERT(bitCountHi > 0); DRFLAC_ASSERT(bitCountHi < 32); resultHi = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountHi); if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { /* This happens when we get to end of stream */ return DRFLAC_FALSE; } *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); bs->consumedBits += bitCountLo; bs->cache <<= bitCountLo; return DRFLAC_TRUE; } } static drflac_bool32 drflac__read_int32(drflac_bs* bs, unsigned int bitCount, drflac_int32* pResult) { drflac_uint32 result; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pResult != NULL); DRFLAC_ASSERT(bitCount > 0); DRFLAC_ASSERT(bitCount <= 32); if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } /* Do not attempt to shift by 32 as it's undefined. */ if (bitCount < 32) { drflac_uint32 signbit; signbit = ((result >> (bitCount-1)) & 0x01); result |= (~signbit + 1) << bitCount; } *pResult = (drflac_int32)result; return DRFLAC_TRUE; } #ifdef DRFLAC_64BIT static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, drflac_uint64* pResultOut) { drflac_uint32 resultHi; drflac_uint32 resultLo; DRFLAC_ASSERT(bitCount <= 64); DRFLAC_ASSERT(bitCount > 32); if (!drflac__read_uint32(bs, bitCount - 32, &resultHi)) { return DRFLAC_FALSE; } if (!drflac__read_uint32(bs, 32, &resultLo)) { return DRFLAC_FALSE; } *pResultOut = (((drflac_uint64)resultHi) << 32) | ((drflac_uint64)resultLo); return DRFLAC_TRUE; } #endif /* Function below is unused, but leaving it here in case I need to quickly add it again. */ #if 0 static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, drflac_int64* pResultOut) { drflac_uint64 result; drflac_uint64 signbit; DRFLAC_ASSERT(bitCount <= 64); if (!drflac__read_uint64(bs, bitCount, &result)) { return DRFLAC_FALSE; } signbit = ((result >> (bitCount-1)) & 0x01); result |= (~signbit + 1) << bitCount; *pResultOut = (drflac_int64)result; return DRFLAC_TRUE; } #endif static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, drflac_uint16* pResult) { drflac_uint32 result; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pResult != NULL); DRFLAC_ASSERT(bitCount > 0); DRFLAC_ASSERT(bitCount <= 16); if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } *pResult = (drflac_uint16)result; return DRFLAC_TRUE; } #if 0 static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, drflac_int16* pResult) { drflac_int32 result; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pResult != NULL); DRFLAC_ASSERT(bitCount > 0); DRFLAC_ASSERT(bitCount <= 16); if (!drflac__read_int32(bs, bitCount, &result)) { return DRFLAC_FALSE; } *pResult = (drflac_int16)result; return DRFLAC_TRUE; } #endif static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, drflac_uint8* pResult) { drflac_uint32 result; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pResult != NULL); DRFLAC_ASSERT(bitCount > 0); DRFLAC_ASSERT(bitCount <= 8); if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } *pResult = (drflac_uint8)result; return DRFLAC_TRUE; } static drflac_bool32 drflac__read_int8(drflac_bs* bs, unsigned int bitCount, drflac_int8* pResult) { drflac_int32 result; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pResult != NULL); DRFLAC_ASSERT(bitCount > 0); DRFLAC_ASSERT(bitCount <= 8); if (!drflac__read_int32(bs, bitCount, &result)) { return DRFLAC_FALSE; } *pResult = (drflac_int8)result; return DRFLAC_TRUE; } static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) { if (bitsToSeek <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { bs->consumedBits += (drflac_uint32)bitsToSeek; bs->cache <<= bitsToSeek; return DRFLAC_TRUE; } else { /* It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. */ bitsToSeek -= DRFLAC_CACHE_L1_BITS_REMAINING(bs); bs->consumedBits += DRFLAC_CACHE_L1_BITS_REMAINING(bs); bs->cache = 0; /* Simple case. Seek in groups of the same number as bits that fit within a cache line. */ #ifdef DRFLAC_64BIT while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { drflac_uint64 bin; if (!drflac__read_uint64(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { return DRFLAC_FALSE; } bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); } #else while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { drflac_uint32 bin; if (!drflac__read_uint32(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { return DRFLAC_FALSE; } bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); } #endif /* Whole leftover bytes. */ while (bitsToSeek >= 8) { drflac_uint8 bin; if (!drflac__read_uint8(bs, 8, &bin)) { return DRFLAC_FALSE; } bitsToSeek -= 8; } /* Leftover bits. */ if (bitsToSeek > 0) { drflac_uint8 bin; if (!drflac__read_uint8(bs, (drflac_uint32)bitsToSeek, &bin)) { return DRFLAC_FALSE; } bitsToSeek = 0; /* <-- Necessary for the assert below. */ } DRFLAC_ASSERT(bitsToSeek == 0); return DRFLAC_TRUE; } } /* This function moves the bit streamer to the first bit after the sync code (bit 15 of the of the frame header). It will also update the CRC-16. */ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) { DRFLAC_ASSERT(bs != NULL); /* The sync code is always aligned to 8 bits. This is convenient for us because it means we can do byte-aligned movements. The first thing to do is align to the next byte. */ if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { return DRFLAC_FALSE; } for (;;) { drflac_uint8 hi; #ifndef DR_FLAC_NO_CRC drflac__reset_crc16(bs); #endif if (!drflac__read_uint8(bs, 8, &hi)) { return DRFLAC_FALSE; } if (hi == 0xFF) { drflac_uint8 lo; if (!drflac__read_uint8(bs, 6, &lo)) { return DRFLAC_FALSE; } if (lo == 0x3E) { return DRFLAC_TRUE; } else { if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { return DRFLAC_FALSE; } } } } /* Should never get here. */ /*return DRFLAC_FALSE;*/ } #if defined(DRFLAC_HAS_LZCNT_INTRINSIC) #define DRFLAC_IMPLEMENT_CLZ_LZCNT #endif #if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(__clang__) #define DRFLAC_IMPLEMENT_CLZ_MSVC #endif #if defined(__WATCOMC__) && defined(__386__) #define DRFLAC_IMPLEMENT_CLZ_WATCOM #endif #ifdef __MRC__ #include #define DRFLAC_IMPLEMENT_CLZ_MRC #endif static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) { drflac_uint32 n; static drflac_uint32 clz_table_4[] = { 0, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1 }; if (x == 0) { return sizeof(x)*8; } n = clz_table_4[x >> (sizeof(x)*8 - 4)]; if (n == 0) { #ifdef DRFLAC_64BIT if ((x & ((drflac_uint64)0xFFFFFFFF << 32)) == 0) { n = 32; x <<= 32; } if ((x & ((drflac_uint64)0xFFFF0000 << 32)) == 0) { n += 16; x <<= 16; } if ((x & ((drflac_uint64)0xFF000000 << 32)) == 0) { n += 8; x <<= 8; } if ((x & ((drflac_uint64)0xF0000000 << 32)) == 0) { n += 4; x <<= 4; } #else if ((x & 0xFFFF0000) == 0) { n = 16; x <<= 16; } if ((x & 0xFF000000) == 0) { n += 8; x <<= 8; } if ((x & 0xF0000000) == 0) { n += 4; x <<= 4; } #endif n += clz_table_4[x >> (sizeof(x)*8 - 4)]; } return n - 1; } #ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported(void) { /* Fast compile time check for ARM. */ #if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) return DRFLAC_TRUE; #elif defined(__MRC__) return DRFLAC_TRUE; #else /* If the compiler itself does not support the intrinsic then we'll need to return false. */ #ifdef DRFLAC_HAS_LZCNT_INTRINSIC return drflac__gIsLZCNTSupported; #else return DRFLAC_FALSE; #endif #endif } static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) { /* It's critical for competitive decoding performance that this function be highly optimal. With MSVC we can use the __lzcnt64() and __lzcnt() intrinsics to achieve good performance, however on GCC and Clang it's a little bit more annoying. The __builtin_clzl() and __builtin_clzll() intrinsics leave it undefined as to the return value when `x` is 0. We need this to be well defined as returning 32 or 64, depending on whether or not it's a 32- or 64-bit build. To work around this we would need to add a conditional to check for the x = 0 case, but this creates unnecessary inefficiency. To work around this problem I have written some inline assembly to emit the LZCNT (x86) or CLZ (ARM) instruction directly which removes the need to include the conditional. This has worked well in the past, but for some reason Clang's MSVC compatible driver, clang-cl, does not seem to be handling this in the same way as the normal Clang driver. It seems that `clang-cl` is just outputting the wrong results sometimes, maybe due to some register getting clobbered? I'm not sure if this is a bug with dr_flac's inlined assembly (most likely), a bug in `clang-cl` or just a misunderstanding on my part with inline assembly rules for `clang-cl`. If somebody can identify an error in dr_flac's inlined assembly I'm happy to get that fixed. Fortunately there is an easy workaround for this. Clang implements MSVC-specific intrinsics for compatibility. It also defines _MSC_VER for extra compatibility. We can therefore just check for _MSC_VER and use the MSVC intrinsic which, fortunately for us, Clang supports. It would still be nice to know how to fix the inlined assembly for correctness sake, however. */ #if defined(_MSC_VER) /*&& !defined(__clang__)*/ /* <-- Intentionally wanting Clang to use the MSVC __lzcnt64/__lzcnt intrinsics due to above ^. */ #ifdef DRFLAC_64BIT return (drflac_uint32)__lzcnt64(x); #else return (drflac_uint32)__lzcnt(x); #endif #else #if defined(__GNUC__) || defined(__clang__) #if defined(DRFLAC_X64) { drflac_uint64 r; __asm__ __volatile__ ( "lzcnt{ %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" ); return (drflac_uint32)r; } #elif defined(DRFLAC_X86) { drflac_uint32 r; __asm__ __volatile__ ( "lzcnt{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" ); return r; } #elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */ { unsigned int r; __asm__ __volatile__ ( #if defined(DRFLAC_64BIT) "clz %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(x) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ #else "clz %[out], %[in]" : [out]"=r"(r) : [in]"r"(x) #endif ); return r; } #else if (x == 0) { return sizeof(x)*8; } #ifdef DRFLAC_64BIT return (drflac_uint32)__builtin_clzll((drflac_uint64)x); #else return (drflac_uint32)__builtin_clzl((drflac_uint32)x); #endif #endif #else /* Unsupported compiler. */ #error "This compiler does not support the lzcnt intrinsic." #endif #endif } #endif #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC #include /* For BitScanReverse(). */ static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) { drflac_uint32 n; if (x == 0) { return sizeof(x)*8; } #ifdef DRFLAC_64BIT _BitScanReverse64((unsigned long*)&n, x); #else _BitScanReverse((unsigned long*)&n, x); #endif return sizeof(x)*8 - n - 1; } #endif #ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32); #ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT /* Use the LZCNT instruction (only available on some processors since the 2010s). */ #pragma aux drflac__clz_watcom_lzcnt = \ "db 0F3h, 0Fh, 0BDh, 0C0h" /* lzcnt eax, eax */ \ parm [eax] \ value [eax] \ modify nomemory; #else /* Use the 386+-compatible implementation. */ #pragma aux drflac__clz_watcom = \ "bsr eax, eax" \ "xor eax, 31" \ parm [eax] nomemory \ value [eax] \ modify exact [eax] nomemory; #endif #endif static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) { #ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT if (drflac__is_lzcnt_supported()) { return drflac__clz_lzcnt(x); } else #endif { #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC return drflac__clz_msvc(x); #elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT) return drflac__clz_watcom_lzcnt(x); #elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM) return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x); #elif defined(__MRC__) return __cntlzw(x); #else return drflac__clz_software(x); #endif } } static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsigned int* pOffsetOut) { drflac_uint32 zeroCounter = 0; drflac_uint32 setBitOffsetPlus1; while (bs->cache == 0) { zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } } if (bs->cache == 1) { /* Not catching this would lead to undefined behaviour: a shift of a 32-bit number by 32 or more is undefined */ *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1; if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; } setBitOffsetPlus1 = drflac__clz(bs->cache); setBitOffsetPlus1 += 1; if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { /* This happens when we get to end of stream */ return DRFLAC_FALSE; } bs->consumedBits += setBitOffsetPlus1; bs->cache <<= setBitOffsetPlus1; *pOffsetOut = zeroCounter + setBitOffsetPlus1 - 1; return DRFLAC_TRUE; } static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFromStart) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(offsetFromStart > 0); /* Seeking from the start is not quite as trivial as it sounds because the onSeek callback takes a signed 32-bit integer (which is intentional because it simplifies the implementation of the onSeek callbacks), however offsetFromStart is unsigned 64-bit. To resolve we just need to do an initial seek from the start, and then a series of offset seeks to make up the remainder. */ if (offsetFromStart > 0x7FFFFFFF) { drflac_uint64 bytesRemaining = offsetFromStart; if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { return DRFLAC_FALSE; } bytesRemaining -= 0x7FFFFFFF; while (bytesRemaining > 0x7FFFFFFF) { if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { return DRFLAC_FALSE; } bytesRemaining -= 0x7FFFFFFF; } if (bytesRemaining > 0) { if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, drflac_seek_origin_current)) { return DRFLAC_FALSE; } } } else { if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, drflac_seek_origin_start)) { return DRFLAC_FALSE; } } /* The cache should be reset to force a reload of fresh data from the client. */ drflac__reset_cache(bs); return DRFLAC_TRUE; } static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64* pNumberOut, drflac_uint8* pCRCOut) { drflac_uint8 crc; drflac_uint64 result; drflac_uint8 utf8[7] = {0}; int byteCount; int i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pNumberOut != NULL); DRFLAC_ASSERT(pCRCOut != NULL); crc = *pCRCOut; if (!drflac__read_uint8(bs, 8, utf8)) { *pNumberOut = 0; return DRFLAC_AT_END; } crc = drflac_crc8(crc, utf8[0], 8); if ((utf8[0] & 0x80) == 0) { *pNumberOut = utf8[0]; *pCRCOut = crc; return DRFLAC_SUCCESS; } /*byteCount = 1;*/ if ((utf8[0] & 0xE0) == 0xC0) { byteCount = 2; } else if ((utf8[0] & 0xF0) == 0xE0) { byteCount = 3; } else if ((utf8[0] & 0xF8) == 0xF0) { byteCount = 4; } else if ((utf8[0] & 0xFC) == 0xF8) { byteCount = 5; } else if ((utf8[0] & 0xFE) == 0xFC) { byteCount = 6; } else if ((utf8[0] & 0xFF) == 0xFE) { byteCount = 7; } else { *pNumberOut = 0; return DRFLAC_CRC_MISMATCH; /* Bad UTF-8 encoding. */ } /* Read extra bytes. */ DRFLAC_ASSERT(byteCount > 1); result = (drflac_uint64)(utf8[0] & (0xFF >> (byteCount + 1))); for (i = 1; i < byteCount; ++i) { if (!drflac__read_uint8(bs, 8, utf8 + i)) { *pNumberOut = 0; return DRFLAC_AT_END; } crc = drflac_crc8(crc, utf8[i], 8); result = (result << 6) | (utf8[i] & 0x3F); } *pNumberOut = result; *pCRCOut = crc; return DRFLAC_SUCCESS; } static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x) { #if 1 /* Needs optimizing. */ drflac_uint32 result = 0; while (x > 0) { result += 1; x >>= 1; } return result; #endif } static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision) { /* https://web.archive.org/web/20220205005724/https://github.com/ietf-wg-cellar/flac-specification/blob/37a49aa48ba4ba12e8757badfc59c0df35435fec/rfc_backmatter.md */ return bitsPerSample + precision + drflac__ilog2_u32(order) > 32; } /* The next two functions are responsible for calculating the prediction. When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. */ #if defined(__clang__) __attribute__((no_sanitize("signed-integer-overflow"))) #endif static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_int32 prediction = 0; DRFLAC_ASSERT(order <= 32); /* 32-bit version. */ /* VC++ optimizes this to a single jmp. I've not yet verified this for other compilers. */ switch (order) { case 32: prediction += coefficients[31] * pDecodedSamples[-32]; case 31: prediction += coefficients[30] * pDecodedSamples[-31]; case 30: prediction += coefficients[29] * pDecodedSamples[-30]; case 29: prediction += coefficients[28] * pDecodedSamples[-29]; case 28: prediction += coefficients[27] * pDecodedSamples[-28]; case 27: prediction += coefficients[26] * pDecodedSamples[-27]; case 26: prediction += coefficients[25] * pDecodedSamples[-26]; case 25: prediction += coefficients[24] * pDecodedSamples[-25]; case 24: prediction += coefficients[23] * pDecodedSamples[-24]; case 23: prediction += coefficients[22] * pDecodedSamples[-23]; case 22: prediction += coefficients[21] * pDecodedSamples[-22]; case 21: prediction += coefficients[20] * pDecodedSamples[-21]; case 20: prediction += coefficients[19] * pDecodedSamples[-20]; case 19: prediction += coefficients[18] * pDecodedSamples[-19]; case 18: prediction += coefficients[17] * pDecodedSamples[-18]; case 17: prediction += coefficients[16] * pDecodedSamples[-17]; case 16: prediction += coefficients[15] * pDecodedSamples[-16]; case 15: prediction += coefficients[14] * pDecodedSamples[-15]; case 14: prediction += coefficients[13] * pDecodedSamples[-14]; case 13: prediction += coefficients[12] * pDecodedSamples[-13]; case 12: prediction += coefficients[11] * pDecodedSamples[-12]; case 11: prediction += coefficients[10] * pDecodedSamples[-11]; case 10: prediction += coefficients[ 9] * pDecodedSamples[-10]; case 9: prediction += coefficients[ 8] * pDecodedSamples[- 9]; case 8: prediction += coefficients[ 7] * pDecodedSamples[- 8]; case 7: prediction += coefficients[ 6] * pDecodedSamples[- 7]; case 6: prediction += coefficients[ 5] * pDecodedSamples[- 6]; case 5: prediction += coefficients[ 4] * pDecodedSamples[- 5]; case 4: prediction += coefficients[ 3] * pDecodedSamples[- 4]; case 3: prediction += coefficients[ 2] * pDecodedSamples[- 3]; case 2: prediction += coefficients[ 1] * pDecodedSamples[- 2]; case 1: prediction += coefficients[ 0] * pDecodedSamples[- 1]; } return (drflac_int32)(prediction >> shift); } static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_int64 prediction; DRFLAC_ASSERT(order <= 32); /* 64-bit version. */ /* This method is faster on the 32-bit build when compiling with VC++. See note below. */ #ifndef DRFLAC_64BIT if (order == 8) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; } else if (order == 7) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; } else if (order == 3) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; } else if (order == 6) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; } else if (order == 5) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; } else if (order == 4) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; } else if (order == 12) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; } else if (order == 2) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; } else if (order == 1) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; } else if (order == 10) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; } else if (order == 9) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; } else if (order == 11) { prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; } else { int j; prediction = 0; for (j = 0; j < (int)order; ++j) { prediction += coefficients[j] * (drflac_int64)pDecodedSamples[-j-1]; } } #endif /* VC++ optimizes this to a single jmp instruction, but only the 64-bit build. The 32-bit build generates less efficient code for some reason. The ugly version above is faster so we'll just switch between the two depending on the target platform. */ #ifdef DRFLAC_64BIT prediction = 0; switch (order) { case 32: prediction += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; case 31: prediction += coefficients[30] * (drflac_int64)pDecodedSamples[-31]; case 30: prediction += coefficients[29] * (drflac_int64)pDecodedSamples[-30]; case 29: prediction += coefficients[28] * (drflac_int64)pDecodedSamples[-29]; case 28: prediction += coefficients[27] * (drflac_int64)pDecodedSamples[-28]; case 27: prediction += coefficients[26] * (drflac_int64)pDecodedSamples[-27]; case 26: prediction += coefficients[25] * (drflac_int64)pDecodedSamples[-26]; case 25: prediction += coefficients[24] * (drflac_int64)pDecodedSamples[-25]; case 24: prediction += coefficients[23] * (drflac_int64)pDecodedSamples[-24]; case 23: prediction += coefficients[22] * (drflac_int64)pDecodedSamples[-23]; case 22: prediction += coefficients[21] * (drflac_int64)pDecodedSamples[-22]; case 21: prediction += coefficients[20] * (drflac_int64)pDecodedSamples[-21]; case 20: prediction += coefficients[19] * (drflac_int64)pDecodedSamples[-20]; case 19: prediction += coefficients[18] * (drflac_int64)pDecodedSamples[-19]; case 18: prediction += coefficients[17] * (drflac_int64)pDecodedSamples[-18]; case 17: prediction += coefficients[16] * (drflac_int64)pDecodedSamples[-17]; case 16: prediction += coefficients[15] * (drflac_int64)pDecodedSamples[-16]; case 15: prediction += coefficients[14] * (drflac_int64)pDecodedSamples[-15]; case 14: prediction += coefficients[13] * (drflac_int64)pDecodedSamples[-14]; case 13: prediction += coefficients[12] * (drflac_int64)pDecodedSamples[-13]; case 12: prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; case 11: prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; case 10: prediction += coefficients[ 9] * (drflac_int64)pDecodedSamples[-10]; case 9: prediction += coefficients[ 8] * (drflac_int64)pDecodedSamples[- 9]; case 8: prediction += coefficients[ 7] * (drflac_int64)pDecodedSamples[- 8]; case 7: prediction += coefficients[ 6] * (drflac_int64)pDecodedSamples[- 7]; case 6: prediction += coefficients[ 5] * (drflac_int64)pDecodedSamples[- 6]; case 5: prediction += coefficients[ 4] * (drflac_int64)pDecodedSamples[- 5]; case 4: prediction += coefficients[ 3] * (drflac_int64)pDecodedSamples[- 4]; case 3: prediction += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 3]; case 2: prediction += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 2]; case 1: prediction += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; } #endif return (drflac_int32)(prediction >> shift); } #if 0 /* Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the sake of readability and should only be used as a reference. */ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); for (i = 0; i < count; ++i) { drflac_uint32 zeroCounter = 0; for (;;) { drflac_uint8 bit; if (!drflac__read_uint8(bs, 1, &bit)) { return DRFLAC_FALSE; } if (bit == 0) { zeroCounter += 1; } else { break; } } drflac_uint32 decodedRice; if (riceParam > 0) { if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { return DRFLAC_FALSE; } } else { decodedRice = 0; } decodedRice |= (zeroCounter << riceParam); if ((decodedRice & 0x01)) { decodedRice = ~(decodedRice >> 1); } else { decodedRice = (decodedRice >> 1); } if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } return DRFLAC_TRUE; } #endif #if 0 static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) { drflac_uint32 zeroCounter = 0; drflac_uint32 decodedRice; for (;;) { drflac_uint8 bit; if (!drflac__read_uint8(bs, 1, &bit)) { return DRFLAC_FALSE; } if (bit == 0) { zeroCounter += 1; } else { break; } } if (riceParam > 0) { if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { return DRFLAC_FALSE; } } else { decodedRice = 0; } *pZeroCounterOut = zeroCounter; *pRiceParamPartOut = decodedRice; return DRFLAC_TRUE; } #endif #if 0 static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) { drflac_cache_t riceParamMask; drflac_uint32 zeroCounter; drflac_uint32 setBitOffsetPlus1; drflac_uint32 riceParamPart; drflac_uint32 riceLength; DRFLAC_ASSERT(riceParam > 0); /* <-- riceParam should never be 0. drflac__read_rice_parts__param_equals_zero() should be used instead for this case. */ riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); zeroCounter = 0; while (bs->cache == 0) { zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } } setBitOffsetPlus1 = drflac__clz(bs->cache); zeroCounter += setBitOffsetPlus1; setBitOffsetPlus1 += 1; riceLength = setBitOffsetPlus1 + riceParam; if (riceLength < DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { riceParamPart = (drflac_uint32)((bs->cache & (riceParamMask >> setBitOffsetPlus1)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceLength)); bs->consumedBits += riceLength; bs->cache <<= riceLength; } else { drflac_uint32 bitCountLo; drflac_cache_t resultHi; bs->consumedBits += riceLength; bs->cache <<= setBitOffsetPlus1 & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1); /* <-- Equivalent to "if (setBitOffsetPlus1 < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { bs->cache <<= setBitOffsetPlus1; }" */ /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ bitCountLo = bs->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS(bs); resultHi = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, riceParam); /* <-- Use DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE() if ever this function allows riceParam=0. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif bs->cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); bs->consumedBits = 0; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs->cache; #endif } else { /* Slow path. We need to fetch more data from the client. */ if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { /* This happens when we get to end of stream */ return DRFLAC_FALSE; } } riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); bs->consumedBits += bitCountLo; bs->cache <<= bitCountLo; } pZeroCounterOut[0] = zeroCounter; pRiceParamPartOut[0] = riceParamPart; return DRFLAC_TRUE; } #endif static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) { drflac_uint32 riceParamPlus1 = riceParam + 1; /*drflac_cache_t riceParamPlus1Mask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParamPlus1);*/ drflac_uint32 riceParamPlus1Shift = DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPlus1); drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; /* The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have no idea how this will work in practice... */ drflac_cache_t bs_cache = bs->cache; drflac_uint32 bs_consumedBits = bs->consumedBits; /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ drflac_uint32 lzcount = drflac__clz(bs_cache); if (lzcount < sizeof(bs_cache)*8) { pZeroCounterOut[0] = lzcount; /* It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled outside of this function at a higher level. */ extract_rice_param_part: bs_cache <<= lzcount; bs_consumedBits += lzcount; if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { /* Getting here means the rice parameter part is wholly contained within the current cache line. */ pRiceParamPartOut[0] = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); bs_cache <<= riceParamPlus1; bs_consumedBits += riceParamPlus1; } else { drflac_uint32 riceParamPartHi; drflac_uint32 riceParamPartLo; drflac_uint32 riceParamPartLoBitCount; /* Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache line, reload the cache, and then combine it with the head of the next cache line. */ /* Grab the high part of the rice parameter part. */ riceParamPartHi = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); /* Before reloading the cache we need to grab the size in bits of the low part. */ riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); /* Now reload the cache. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); bs_consumedBits = riceParamPartLoBitCount; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs_cache; #endif } else { /* Slow path. We need to fetch more data from the client. */ if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { /* This happens when we get to end of stream */ return DRFLAC_FALSE; } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } /* We should now have enough information to construct the rice parameter part. */ riceParamPartLo = (drflac_uint32)(bs_cache >> (DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPartLoBitCount))); pRiceParamPartOut[0] = riceParamPartHi | riceParamPartLo; bs_cache <<= riceParamPartLoBitCount; } } else { /* Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call to drflac__clz() and we need to reload the cache. */ drflac_uint32 zeroCounter = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs_consumedBits); for (;;) { if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); bs_consumedBits = 0; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs_cache; #endif } else { /* Slow path. We need to fetch more data from the client. */ if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits; } lzcount = drflac__clz(bs_cache); zeroCounter += lzcount; if (lzcount < sizeof(bs_cache)*8) { break; } } pZeroCounterOut[0] = zeroCounter; goto extract_rice_param_part; } /* Make sure the cache is restored at the end of it all. */ bs->cache = bs_cache; bs->consumedBits = bs_consumedBits; return DRFLAC_TRUE; } static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac_uint8 riceParam) { drflac_uint32 riceParamPlus1 = riceParam + 1; drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; /* The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have no idea how this will work in practice... */ drflac_cache_t bs_cache = bs->cache; drflac_uint32 bs_consumedBits = bs->consumedBits; /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ drflac_uint32 lzcount = drflac__clz(bs_cache); if (lzcount < sizeof(bs_cache)*8) { /* It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled outside of this function at a higher level. */ extract_rice_param_part: bs_cache <<= lzcount; bs_consumedBits += lzcount; if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { /* Getting here means the rice parameter part is wholly contained within the current cache line. */ bs_cache <<= riceParamPlus1; bs_consumedBits += riceParamPlus1; } else { /* Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache line, reload the cache, and then combine it with the head of the next cache line. */ /* Before reloading the cache we need to grab the size in bits of the low part. */ drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); /* Now reload the cache. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); bs_consumedBits = riceParamPartLoBitCount; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs_cache; #endif } else { /* Slow path. We need to fetch more data from the client. */ if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { /* This happens when we get to end of stream */ return DRFLAC_FALSE; } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } bs_cache <<= riceParamPartLoBitCount; } } else { /* Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call to drflac__clz() and we need to reload the cache. */ for (;;) { if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC drflac__update_crc16(bs); #endif bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); bs_consumedBits = 0; #ifndef DR_FLAC_NO_CRC bs->crc16Cache = bs_cache; #endif } else { /* Slow path. We need to fetch more data from the client. */ if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits; } lzcount = drflac__clz(bs_cache); if (lzcount < sizeof(bs_cache)*8) { break; } } goto extract_rice_param_part; } /* Make sure the cache is restored at the end of it all. */ bs->cache = bs_cache; bs->consumedBits = bs_consumedBits; return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorder(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0; drflac_uint32 riceParamPart0; drflac_uint32 riceParamMask; drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); (void)bitsPerSample; (void)order; (void)shift; (void)coefficients; riceParamMask = (drflac_uint32)~((~0UL) << riceParam); i = 0; while (i < count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { return DRFLAC_FALSE; } /* Rice reconstruction. */ riceParamPart0 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; pSamplesOut[i] = riceParamPart0; i += 1; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0 = 0; drflac_uint32 zeroCountPart1 = 0; drflac_uint32 zeroCountPart2 = 0; drflac_uint32 zeroCountPart3 = 0; drflac_uint32 riceParamPart0 = 0; drflac_uint32 riceParamPart1 = 0; drflac_uint32 riceParamPart2 = 0; drflac_uint32 riceParamPart3 = 0; drflac_uint32 riceParamMask; const drflac_int32* pSamplesOutEnd; drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); if (lpcOrder == 0) { return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } riceParamMask = (drflac_uint32)~((~0UL) << riceParam); pSamplesOutEnd = pSamplesOut + (count & ~3); if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { while (pSamplesOut < pSamplesOutEnd) { /* Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version against an array. Not sure why, but perhaps it's making more efficient use of registers? */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { return DRFLAC_FALSE; } riceParamPart0 &= riceParamMask; riceParamPart1 &= riceParamMask; riceParamPart2 &= riceParamMask; riceParamPart3 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart1 |= (zeroCountPart1 << riceParam); riceParamPart2 |= (zeroCountPart2 << riceParam); riceParamPart3 |= (zeroCountPart3 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } } else { while (pSamplesOut < pSamplesOutEnd) { if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { return DRFLAC_FALSE; } riceParamPart0 &= riceParamMask; riceParamPart1 &= riceParamMask; riceParamPart2 &= riceParamMask; riceParamPart3 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart1 |= (zeroCountPart1 << riceParam); riceParamPart2 |= (zeroCountPart2 << riceParam); riceParamPart3 |= (zeroCountPart3 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } } i = (count & ~3); while (i < count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { return DRFLAC_FALSE; } /* Rice reconstruction. */ riceParamPart0 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; /*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/ /* Sample reconstruction. */ if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } else { pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } i += 1; pSamplesOut += 1; } return DRFLAC_TRUE; } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE __m128i drflac__mm_packs_interleaved_epi32(__m128i a, __m128i b) { __m128i r; /* Pack. */ r = _mm_packs_epi32(a, b); /* a3a2 a1a0 b3b2 b1b0 -> a3a2 b3b2 a1a0 b1b0 */ r = _mm_shuffle_epi32(r, _MM_SHUFFLE(3, 1, 2, 0)); /* a3a2 b3b2 a1a0 b1b0 -> a3b3 a2b2 a1b1 a0b0 */ r = _mm_shufflehi_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); r = _mm_shufflelo_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); return r; } #endif #if defined(DRFLAC_SUPPORT_SSE41) static DRFLAC_INLINE __m128i drflac__mm_not_si128(__m128i a) { return _mm_xor_si128(a, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); } static DRFLAC_INLINE __m128i drflac__mm_hadd_epi32(__m128i x) { __m128i x64 = _mm_add_epi32(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); __m128i x32 = _mm_shufflelo_epi16(x64, _MM_SHUFFLE(1, 0, 3, 2)); return _mm_add_epi32(x64, x32); } static DRFLAC_INLINE __m128i drflac__mm_hadd_epi64(__m128i x) { return _mm_add_epi64(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); } static DRFLAC_INLINE __m128i drflac__mm_srai_epi64(__m128i x, int count) { /* To simplify this we are assuming count < 32. This restriction allows us to work on a low side and a high side. The low side is shifted with zero bits, whereas the right side is shifted with sign bits. */ __m128i lo = _mm_srli_epi64(x, count); __m128i hi = _mm_srai_epi32(x, count); hi = _mm_and_si128(hi, _mm_set_epi32(0xFFFFFFFF, 0, 0xFFFFFFFF, 0)); /* The high part needs to have the low part cleared. */ return _mm_or_si128(lo, hi); } static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { int i; drflac_uint32 riceParamMask; drflac_int32* pDecodedSamples = pSamplesOut; drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); drflac_uint32 zeroCountParts0 = 0; drflac_uint32 zeroCountParts1 = 0; drflac_uint32 zeroCountParts2 = 0; drflac_uint32 zeroCountParts3 = 0; drflac_uint32 riceParamParts0 = 0; drflac_uint32 riceParamParts1 = 0; drflac_uint32 riceParamParts2 = 0; drflac_uint32 riceParamParts3 = 0; __m128i coefficients128_0; __m128i coefficients128_4; __m128i coefficients128_8; __m128i samples128_0; __m128i samples128_4; __m128i samples128_8; __m128i riceParamMask128; const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; riceParamMask = (drflac_uint32)~((~0UL) << riceParam); riceParamMask128 = _mm_set1_epi32(riceParamMask); /* Pre-load. */ coefficients128_0 = _mm_setzero_si128(); coefficients128_4 = _mm_setzero_si128(); coefficients128_8 = _mm_setzero_si128(); samples128_0 = _mm_setzero_si128(); samples128_4 = _mm_setzero_si128(); samples128_8 = _mm_setzero_si128(); /* Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted so I think there's opportunity for this to be simplified. */ #if 1 { int runningOrder = order; /* 0 - 3. */ if (runningOrder >= 4) { coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); runningOrder -= 4; } else { switch (runningOrder) { case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; } runningOrder = 0; } /* 4 - 7 */ if (runningOrder >= 4) { coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); runningOrder -= 4; } else { switch (runningOrder) { case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; } runningOrder = 0; } /* 8 - 11 */ if (runningOrder == 4) { coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); runningOrder -= 4; } else { switch (runningOrder) { case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; } runningOrder = 0; } /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); } #else /* This causes strict-aliasing warnings with GCC. */ switch (order) { case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; } #endif /* For this version we are doing one sample at a time. */ while (pDecodedSamples < pDecodedSamplesEnd) { __m128i prediction128; __m128i zeroCountPart128; __m128i riceParamPart128; if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { return DRFLAC_FALSE; } zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01))), _mm_set1_epi32(0x01))); /* <-- SSE2 compatible */ /*riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01)), _mm_set1_epi32(0xFFFFFFFF)));*/ /* <-- Only supported from SSE4.1 and is slower in my testing... */ if (order <= 4) { for (i = 0; i < 4; i += 1) { prediction128 = _mm_mullo_epi32(coefficients128_0, samples128_0); /* Horizontal add and shift. */ prediction128 = drflac__mm_hadd_epi32(prediction128); prediction128 = _mm_srai_epi32(prediction128, shift); prediction128 = _mm_add_epi32(riceParamPart128, prediction128); samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); } } else if (order <= 8) { for (i = 0; i < 4; i += 1) { prediction128 = _mm_mullo_epi32(coefficients128_4, samples128_4); prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); /* Horizontal add and shift. */ prediction128 = drflac__mm_hadd_epi32(prediction128); prediction128 = _mm_srai_epi32(prediction128, shift); prediction128 = _mm_add_epi32(riceParamPart128, prediction128); samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); } } else { for (i = 0; i < 4; i += 1) { prediction128 = _mm_mullo_epi32(coefficients128_8, samples128_8); prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_4, samples128_4)); prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); /* Horizontal add and shift. */ prediction128 = drflac__mm_hadd_epi32(prediction128); prediction128 = _mm_srai_epi32(prediction128, shift); prediction128 = _mm_add_epi32(riceParamPart128, prediction128); samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); } } /* We store samples in groups of 4. */ _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); pDecodedSamples += 4; } /* Make sure we process the last few samples. */ i = (count & ~3); while (i < (int)count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { return DRFLAC_FALSE; } /* Rice reconstruction. */ riceParamParts0 &= riceParamMask; riceParamParts0 |= (zeroCountParts0 << riceParam); riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; /* Sample reconstruction. */ pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); i += 1; pDecodedSamples += 1; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { int i; drflac_uint32 riceParamMask; drflac_int32* pDecodedSamples = pSamplesOut; drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); drflac_uint32 zeroCountParts0 = 0; drflac_uint32 zeroCountParts1 = 0; drflac_uint32 zeroCountParts2 = 0; drflac_uint32 zeroCountParts3 = 0; drflac_uint32 riceParamParts0 = 0; drflac_uint32 riceParamParts1 = 0; drflac_uint32 riceParamParts2 = 0; drflac_uint32 riceParamParts3 = 0; __m128i coefficients128_0; __m128i coefficients128_4; __m128i coefficients128_8; __m128i samples128_0; __m128i samples128_4; __m128i samples128_8; __m128i prediction128; __m128i riceParamMask128; const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; DRFLAC_ASSERT(order <= 12); riceParamMask = (drflac_uint32)~((~0UL) << riceParam); riceParamMask128 = _mm_set1_epi32(riceParamMask); prediction128 = _mm_setzero_si128(); /* Pre-load. */ coefficients128_0 = _mm_setzero_si128(); coefficients128_4 = _mm_setzero_si128(); coefficients128_8 = _mm_setzero_si128(); samples128_0 = _mm_setzero_si128(); samples128_4 = _mm_setzero_si128(); samples128_8 = _mm_setzero_si128(); #if 1 { int runningOrder = order; /* 0 - 3. */ if (runningOrder >= 4) { coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); runningOrder -= 4; } else { switch (runningOrder) { case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; } runningOrder = 0; } /* 4 - 7 */ if (runningOrder >= 4) { coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); runningOrder -= 4; } else { switch (runningOrder) { case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; } runningOrder = 0; } /* 8 - 11 */ if (runningOrder == 4) { coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); runningOrder -= 4; } else { switch (runningOrder) { case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; } runningOrder = 0; } /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); } #else switch (order) { case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; } #endif /* For this version we are doing one sample at a time. */ while (pDecodedSamples < pDecodedSamplesEnd) { __m128i zeroCountPart128; __m128i riceParamPart128; if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { return DRFLAC_FALSE; } zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(1))), _mm_set1_epi32(1))); for (i = 0; i < 4; i += 1) { prediction128 = _mm_xor_si128(prediction128, prediction128); /* Reset to 0. */ switch (order) { case 12: case 11: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(1, 1, 0, 0)))); case 10: case 9: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(3, 3, 2, 2)))); case 8: case 7: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(1, 1, 0, 0)))); case 6: case 5: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(3, 3, 2, 2)))); case 4: case 3: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(1, 1, 0, 0)))); case 2: case 1: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(3, 3, 2, 2)))); } /* Horizontal add and shift. */ prediction128 = drflac__mm_hadd_epi64(prediction128); prediction128 = drflac__mm_srai_epi64(prediction128, shift); prediction128 = _mm_add_epi32(riceParamPart128, prediction128); /* Our value should be sitting in prediction128[0]. We need to combine this with our SSE samples. */ samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); } /* We store samples in groups of 4. */ _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); pDecodedSamples += 4; } /* Make sure we process the last few samples. */ i = (count & ~3); while (i < (int)count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { return DRFLAC_FALSE; } /* Rice reconstruction. */ riceParamParts0 &= riceParamMask; riceParamParts0 |= (zeroCountParts0 << riceParam); riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; /* Sample reconstruction. */ pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); i += 1; pDecodedSamples += 1; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */ if (lpcOrder > 0 && lpcOrder <= 12) { if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac__vst2q_s32(drflac_int32* p, int32x4x2_t x) { vst1q_s32(p+0, x.val[0]); vst1q_s32(p+4, x.val[1]); } static DRFLAC_INLINE void drflac__vst2q_u32(drflac_uint32* p, uint32x4x2_t x) { vst1q_u32(p+0, x.val[0]); vst1q_u32(p+4, x.val[1]); } static DRFLAC_INLINE void drflac__vst2q_f32(float* p, float32x4x2_t x) { vst1q_f32(p+0, x.val[0]); vst1q_f32(p+4, x.val[1]); } static DRFLAC_INLINE void drflac__vst2q_s16(drflac_int16* p, int16x4x2_t x) { vst1q_s16(p, vcombine_s16(x.val[0], x.val[1])); } static DRFLAC_INLINE void drflac__vst2q_u16(drflac_uint16* p, uint16x4x2_t x) { vst1q_u16(p, vcombine_u16(x.val[0], x.val[1])); } static DRFLAC_INLINE int32x4_t drflac__vdupq_n_s32x4(drflac_int32 x3, drflac_int32 x2, drflac_int32 x1, drflac_int32 x0) { drflac_int32 x[4]; x[3] = x3; x[2] = x2; x[1] = x1; x[0] = x0; return vld1q_s32(x); } static DRFLAC_INLINE int32x4_t drflac__valignrq_s32_1(int32x4_t a, int32x4_t b) { /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ /* Reference */ /*return drflac__vdupq_n_s32x4( vgetq_lane_s32(a, 0), vgetq_lane_s32(b, 3), vgetq_lane_s32(b, 2), vgetq_lane_s32(b, 1) );*/ return vextq_s32(b, a, 1); } static DRFLAC_INLINE uint32x4_t drflac__valignrq_u32_1(uint32x4_t a, uint32x4_t b) { /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ /* Reference */ /*return drflac__vdupq_n_s32x4( vgetq_lane_s32(a, 0), vgetq_lane_s32(b, 3), vgetq_lane_s32(b, 2), vgetq_lane_s32(b, 1) );*/ return vextq_u32(b, a, 1); } static DRFLAC_INLINE int32x2_t drflac__vhaddq_s32(int32x4_t x) { /* The sum must end up in position 0. */ /* Reference */ /*return vdupq_n_s32( vgetq_lane_s32(x, 3) + vgetq_lane_s32(x, 2) + vgetq_lane_s32(x, 1) + vgetq_lane_s32(x, 0) );*/ int32x2_t r = vadd_s32(vget_high_s32(x), vget_low_s32(x)); return vpadd_s32(r, r); } static DRFLAC_INLINE int64x1_t drflac__vhaddq_s64(int64x2_t x) { return vadd_s64(vget_high_s64(x), vget_low_s64(x)); } static DRFLAC_INLINE int32x4_t drflac__vrevq_s32(int32x4_t x) { /* Reference */ /*return drflac__vdupq_n_s32x4( vgetq_lane_s32(x, 0), vgetq_lane_s32(x, 1), vgetq_lane_s32(x, 2), vgetq_lane_s32(x, 3) );*/ return vrev64q_s32(vcombine_s32(vget_high_s32(x), vget_low_s32(x))); } static DRFLAC_INLINE int32x4_t drflac__vnotq_s32(int32x4_t x) { return veorq_s32(x, vdupq_n_s32(0xFFFFFFFF)); } static DRFLAC_INLINE uint32x4_t drflac__vnotq_u32(uint32x4_t x) { return veorq_u32(x, vdupq_n_u32(0xFFFFFFFF)); } static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { int i; drflac_uint32 riceParamMask; drflac_int32* pDecodedSamples = pSamplesOut; drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); drflac_uint32 zeroCountParts[4]; drflac_uint32 riceParamParts[4]; int32x4_t coefficients128_0; int32x4_t coefficients128_4; int32x4_t coefficients128_8; int32x4_t samples128_0; int32x4_t samples128_4; int32x4_t samples128_8; uint32x4_t riceParamMask128; int32x4_t riceParam128; int32x2_t shift64; uint32x4_t one128; const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; riceParamMask = (drflac_uint32)~((~0UL) << riceParam); riceParamMask128 = vdupq_n_u32(riceParamMask); riceParam128 = vdupq_n_s32(riceParam); shift64 = vdup_n_s32(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ one128 = vdupq_n_u32(1); /* Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted so I think there's opportunity for this to be simplified. */ { int runningOrder = order; drflac_int32 tempC[4] = {0, 0, 0, 0}; drflac_int32 tempS[4] = {0, 0, 0, 0}; /* 0 - 3. */ if (runningOrder >= 4) { coefficients128_0 = vld1q_s32(coefficients + 0); samples128_0 = vld1q_s32(pSamplesOut - 4); runningOrder -= 4; } else { switch (runningOrder) { case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ } coefficients128_0 = vld1q_s32(tempC); samples128_0 = vld1q_s32(tempS); runningOrder = 0; } /* 4 - 7 */ if (runningOrder >= 4) { coefficients128_4 = vld1q_s32(coefficients + 4); samples128_4 = vld1q_s32(pSamplesOut - 8); runningOrder -= 4; } else { switch (runningOrder) { case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ } coefficients128_4 = vld1q_s32(tempC); samples128_4 = vld1q_s32(tempS); runningOrder = 0; } /* 8 - 11 */ if (runningOrder == 4) { coefficients128_8 = vld1q_s32(coefficients + 8); samples128_8 = vld1q_s32(pSamplesOut - 12); runningOrder -= 4; } else { switch (runningOrder) { case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ } coefficients128_8 = vld1q_s32(tempC); samples128_8 = vld1q_s32(tempS); runningOrder = 0; } /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ coefficients128_0 = drflac__vrevq_s32(coefficients128_0); coefficients128_4 = drflac__vrevq_s32(coefficients128_4); coefficients128_8 = drflac__vrevq_s32(coefficients128_8); } /* For this version we are doing one sample at a time. */ while (pDecodedSamples < pDecodedSamplesEnd) { int32x4_t prediction128; int32x2_t prediction64; uint32x4_t zeroCountPart128; uint32x4_t riceParamPart128; if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { return DRFLAC_FALSE; } zeroCountPart128 = vld1q_u32(zeroCountParts); riceParamPart128 = vld1q_u32(riceParamParts); riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); if (order <= 4) { for (i = 0; i < 4; i += 1) { prediction128 = vmulq_s32(coefficients128_0, samples128_0); /* Horizontal add and shift. */ prediction64 = drflac__vhaddq_s32(prediction128); prediction64 = vshl_s32(prediction64, shift64); prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); } } else if (order <= 8) { for (i = 0; i < 4; i += 1) { prediction128 = vmulq_s32(coefficients128_4, samples128_4); prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); /* Horizontal add and shift. */ prediction64 = drflac__vhaddq_s32(prediction128); prediction64 = vshl_s32(prediction64, shift64); prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); } } else { for (i = 0; i < 4; i += 1) { prediction128 = vmulq_s32(coefficients128_8, samples128_8); prediction128 = vmlaq_s32(prediction128, coefficients128_4, samples128_4); prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); /* Horizontal add and shift. */ prediction64 = drflac__vhaddq_s32(prediction128); prediction64 = vshl_s32(prediction64, shift64); prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); } } /* We store samples in groups of 4. */ vst1q_s32(pDecodedSamples, samples128_0); pDecodedSamples += 4; } /* Make sure we process the last few samples. */ i = (count & ~3); while (i < (int)count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { return DRFLAC_FALSE; } /* Rice reconstruction. */ riceParamParts[0] &= riceParamMask; riceParamParts[0] |= (zeroCountParts[0] << riceParam); riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; /* Sample reconstruction. */ pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); i += 1; pDecodedSamples += 1; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { int i; drflac_uint32 riceParamMask; drflac_int32* pDecodedSamples = pSamplesOut; drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); drflac_uint32 zeroCountParts[4]; drflac_uint32 riceParamParts[4]; int32x4_t coefficients128_0; int32x4_t coefficients128_4; int32x4_t coefficients128_8; int32x4_t samples128_0; int32x4_t samples128_4; int32x4_t samples128_8; uint32x4_t riceParamMask128; int32x4_t riceParam128; int64x1_t shift64; uint32x4_t one128; int64x2_t prediction128 = { 0 }; uint32x4_t zeroCountPart128; uint32x4_t riceParamPart128; const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; riceParamMask = (drflac_uint32)~((~0UL) << riceParam); riceParamMask128 = vdupq_n_u32(riceParamMask); riceParam128 = vdupq_n_s32(riceParam); shift64 = vdup_n_s64(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ one128 = vdupq_n_u32(1); /* Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted so I think there's opportunity for this to be simplified. */ { int runningOrder = order; drflac_int32 tempC[4] = {0, 0, 0, 0}; drflac_int32 tempS[4] = {0, 0, 0, 0}; /* 0 - 3. */ if (runningOrder >= 4) { coefficients128_0 = vld1q_s32(coefficients + 0); samples128_0 = vld1q_s32(pSamplesOut - 4); runningOrder -= 4; } else { switch (runningOrder) { case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ } coefficients128_0 = vld1q_s32(tempC); samples128_0 = vld1q_s32(tempS); runningOrder = 0; } /* 4 - 7 */ if (runningOrder >= 4) { coefficients128_4 = vld1q_s32(coefficients + 4); samples128_4 = vld1q_s32(pSamplesOut - 8); runningOrder -= 4; } else { switch (runningOrder) { case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ } coefficients128_4 = vld1q_s32(tempC); samples128_4 = vld1q_s32(tempS); runningOrder = 0; } /* 8 - 11 */ if (runningOrder == 4) { coefficients128_8 = vld1q_s32(coefficients + 8); samples128_8 = vld1q_s32(pSamplesOut - 12); runningOrder -= 4; } else { switch (runningOrder) { case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ } coefficients128_8 = vld1q_s32(tempC); samples128_8 = vld1q_s32(tempS); runningOrder = 0; } /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ coefficients128_0 = drflac__vrevq_s32(coefficients128_0); coefficients128_4 = drflac__vrevq_s32(coefficients128_4); coefficients128_8 = drflac__vrevq_s32(coefficients128_8); } /* For this version we are doing one sample at a time. */ while (pDecodedSamples < pDecodedSamplesEnd) { if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { return DRFLAC_FALSE; } zeroCountPart128 = vld1q_u32(zeroCountParts); riceParamPart128 = vld1q_u32(riceParamParts); riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); for (i = 0; i < 4; i += 1) { int64x1_t prediction64; prediction128 = veorq_s64(prediction128, prediction128); /* Reset to 0. */ switch (order) { case 12: case 11: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_8), vget_low_s32(samples128_8))); case 10: case 9: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_8), vget_high_s32(samples128_8))); case 8: case 7: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_4), vget_low_s32(samples128_4))); case 6: case 5: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_4), vget_high_s32(samples128_4))); case 4: case 3: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_0), vget_low_s32(samples128_0))); case 2: case 1: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_0), vget_high_s32(samples128_0))); } /* Horizontal add and shift. */ prediction64 = drflac__vhaddq_s64(prediction128); prediction64 = vshl_s64(prediction64, shift64); prediction64 = vadd_s64(prediction64, vdup_n_s64(vgetq_lane_u32(riceParamPart128, 0))); /* Our value should be sitting in prediction64[0]. We need to combine this with our SSE samples. */ samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); samples128_0 = drflac__valignrq_s32_1(vcombine_s32(vreinterpret_s32_s64(prediction64), vdup_n_s32(0)), samples128_0); /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); } /* We store samples in groups of 4. */ vst1q_s32(pDecodedSamples, samples128_0); pDecodedSamples += 4; } /* Make sure we process the last few samples. */ i = (count & ~3); while (i < (int)count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { return DRFLAC_FALSE; } /* Rice reconstruction. */ riceParamParts[0] &= riceParamMask; riceParamParts[0] |= (zeroCountParts[0] << riceParam); riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; /* Sample reconstruction. */ pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); i += 1; pDecodedSamples += 1; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */ if (lpcOrder > 0 && lpcOrder <= 12) { if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { #if defined(DRFLAC_SUPPORT_SSE41) if (drflac__gIsSSE41Supported) { return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported) { return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #endif { /* Scalar fallback. */ #if 0 return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #else return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #endif } } /* Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. */ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); for (i = 0; i < count; ++i) { if (!drflac__seek_rice_parts(bs, riceParam)) { return DRFLAC_FALSE; } } return DRFLAC_TRUE; } #if defined(__clang__) __attribute__((no_sanitize("signed-integer-overflow"))) #endif static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(unencodedBitsPerSample <= 31); /* <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. */ DRFLAC_ASSERT(pSamplesOut != NULL); for (i = 0; i < count; ++i) { if (unencodedBitsPerSample > 0) { if (!drflac__read_int32(bs, unencodedBitsPerSample, pSamplesOut + i)) { return DRFLAC_FALSE; } } else { pSamplesOut[i] = 0; } if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } return DRFLAC_TRUE; } /* Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The and parameters are used to determine how many residual values need to be decoded. */ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_uint8 residualMethod; drflac_uint8 partitionOrder; drflac_uint32 samplesInPartition; drflac_uint32 partitionsRemaining; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(blockSize != 0); DRFLAC_ASSERT(pDecodedSamples != NULL); /* <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? */ if (!drflac__read_uint8(bs, 2, &residualMethod)) { return DRFLAC_FALSE; } if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ } /* Ignore the first values. */ pDecodedSamples += lpcOrder; if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; } /* From the FLAC spec: The Rice partition order in a Rice-coded residual section must be less than or equal to 8. */ if (partitionOrder > 8) { return DRFLAC_FALSE; } /* Validation check. */ if ((blockSize / (1 << partitionOrder)) < lpcOrder) { return DRFLAC_FALSE; } samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder; partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { if (!drflac__read_uint8(bs, 4, &riceParam)) { return DRFLAC_FALSE; } if (riceParam == 15) { riceParam = 0xFF; } } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { if (!drflac__read_uint8(bs, 5, &riceParam)) { return DRFLAC_FALSE; } if (riceParam == 31) { riceParam = 0xFF; } } if (riceParam != 0xFF) { if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } else { drflac_uint8 unencodedBitsPerSample = 0; if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { return DRFLAC_FALSE; } if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } pDecodedSamples += samplesInPartition; if (partitionsRemaining == 1) { break; } partitionsRemaining -= 1; if (partitionOrder != 0) { samplesInPartition = blockSize / (1 << partitionOrder); } } return DRFLAC_TRUE; } /* Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The and parameters are used to determine how many residual values need to be decoded. */ static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 order) { drflac_uint8 residualMethod; drflac_uint8 partitionOrder; drflac_uint32 samplesInPartition; drflac_uint32 partitionsRemaining; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(blockSize != 0); if (!drflac__read_uint8(bs, 2, &residualMethod)) { return DRFLAC_FALSE; } if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ } if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; } /* From the FLAC spec: The Rice partition order in a Rice-coded residual section must be less than or equal to 8. */ if (partitionOrder > 8) { return DRFLAC_FALSE; } /* Validation check. */ if ((blockSize / (1 << partitionOrder)) <= order) { return DRFLAC_FALSE; } samplesInPartition = (blockSize / (1 << partitionOrder)) - order; partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { if (!drflac__read_uint8(bs, 4, &riceParam)) { return DRFLAC_FALSE; } if (riceParam == 15) { riceParam = 0xFF; } } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { if (!drflac__read_uint8(bs, 5, &riceParam)) { return DRFLAC_FALSE; } if (riceParam == 31) { riceParam = 0xFF; } } if (riceParam != 0xFF) { if (!drflac__read_and_seek_residual__rice(bs, samplesInPartition, riceParam)) { return DRFLAC_FALSE; } } else { drflac_uint8 unencodedBitsPerSample = 0; if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { return DRFLAC_FALSE; } if (!drflac__seek_bits(bs, unencodedBitsPerSample * samplesInPartition)) { return DRFLAC_FALSE; } } if (partitionsRemaining == 1) { break; } partitionsRemaining -= 1; samplesInPartition = blockSize / (1 << partitionOrder); } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) { drflac_uint32 i; /* Only a single sample needs to be decoded here. */ drflac_int32 sample; if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { return DRFLAC_FALSE; } /* We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) we'll want to look at a more efficient way. */ for (i = 0; i < blockSize; ++i) { pDecodedSamples[i] = sample; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) { drflac_uint32 i; for (i = 0; i < blockSize; ++i) { drflac_int32 sample; if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { return DRFLAC_FALSE; } pDecodedSamples[i] = sample; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { drflac_uint32 i; static drflac_int32 lpcCoefficientsTable[5][4] = { {0, 0, 0, 0}, {1, 0, 0, 0}, {2, -1, 0, 0}, {3, -3, 1, 0}, {4, -6, 4, -1} }; /* Warm up samples and coefficients. */ for (i = 0; i < lpcOrder; ++i) { drflac_int32 sample; if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { return DRFLAC_FALSE; } pDecodedSamples[i] = sample; } if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { drflac_uint8 i; drflac_uint8 lpcPrecision; drflac_int8 lpcShift; drflac_int32 coefficients[32]; /* Warm up samples. */ for (i = 0; i < lpcOrder; ++i) { drflac_int32 sample; if (!drflac__read_int32(bs, bitsPerSample, &sample)) { return DRFLAC_FALSE; } pDecodedSamples[i] = sample; } if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { return DRFLAC_FALSE; } if (lpcPrecision == 15) { return DRFLAC_FALSE; /* Invalid. */ } lpcPrecision += 1; if (!drflac__read_int8(bs, 5, &lpcShift)) { return DRFLAC_FALSE; } /* From the FLAC specification: Quantized linear predictor coefficient shift needed in bits (NOTE: this number is signed two's-complement) Emphasis on the "signed two's-complement". In practice there does not seem to be any encoders nor decoders supporting negative shifts. For now dr_flac is not going to support negative shifts as I don't have any reference files. However, when a reference file comes through I will consider adding support. */ if (lpcShift < 0) { return DRFLAC_FALSE; } DRFLAC_ZERO_MEMORY(coefficients, sizeof(coefficients)); for (i = 0; i < lpcOrder; ++i) { if (!drflac__read_int32(bs, lpcPrecision, coefficients + i)) { return DRFLAC_FALSE; } } if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; } static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_uint8 streaminfoBitsPerSample, drflac_frame_header* header) { const drflac_uint32 sampleRateTable[12] = {0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000}; const drflac_uint8 bitsPerSampleTable[8] = {0, 8, 12, (drflac_uint8)-1, 16, 20, 24, (drflac_uint8)-1}; /* -1 = reserved. */ DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(header != NULL); /* Keep looping until we find a valid sync code. */ for (;;) { drflac_uint8 crc8 = 0xCE; /* 0xCE = drflac_crc8(0, 0x3FFE, 14); */ drflac_uint8 reserved = 0; drflac_uint8 blockingStrategy = 0; drflac_uint8 blockSize = 0; drflac_uint8 sampleRate = 0; drflac_uint8 channelAssignment = 0; drflac_uint8 bitsPerSample = 0; drflac_bool32 isVariableBlockSize; if (!drflac__find_and_seek_to_next_sync_code(bs)) { return DRFLAC_FALSE; } if (!drflac__read_uint8(bs, 1, &reserved)) { return DRFLAC_FALSE; } if (reserved == 1) { continue; } crc8 = drflac_crc8(crc8, reserved, 1); if (!drflac__read_uint8(bs, 1, &blockingStrategy)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, blockingStrategy, 1); if (!drflac__read_uint8(bs, 4, &blockSize)) { return DRFLAC_FALSE; } if (blockSize == 0) { continue; } crc8 = drflac_crc8(crc8, blockSize, 4); if (!drflac__read_uint8(bs, 4, &sampleRate)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, sampleRate, 4); if (!drflac__read_uint8(bs, 4, &channelAssignment)) { return DRFLAC_FALSE; } if (channelAssignment > 10) { continue; } crc8 = drflac_crc8(crc8, channelAssignment, 4); if (!drflac__read_uint8(bs, 3, &bitsPerSample)) { return DRFLAC_FALSE; } if (bitsPerSample == 3 || bitsPerSample == 7) { continue; } crc8 = drflac_crc8(crc8, bitsPerSample, 3); if (!drflac__read_uint8(bs, 1, &reserved)) { return DRFLAC_FALSE; } if (reserved == 1) { continue; } crc8 = drflac_crc8(crc8, reserved, 1); isVariableBlockSize = blockingStrategy == 1; if (isVariableBlockSize) { drflac_uint64 pcmFrameNumber; drflac_result result = drflac__read_utf8_coded_number(bs, &pcmFrameNumber, &crc8); if (result != DRFLAC_SUCCESS) { if (result == DRFLAC_AT_END) { return DRFLAC_FALSE; } else { continue; } } header->flacFrameNumber = 0; header->pcmFrameNumber = pcmFrameNumber; } else { drflac_uint64 flacFrameNumber = 0; drflac_result result = drflac__read_utf8_coded_number(bs, &flacFrameNumber, &crc8); if (result != DRFLAC_SUCCESS) { if (result == DRFLAC_AT_END) { return DRFLAC_FALSE; } else { continue; } } header->flacFrameNumber = (drflac_uint32)flacFrameNumber; /* <-- Safe cast. */ header->pcmFrameNumber = 0; } DRFLAC_ASSERT(blockSize > 0); if (blockSize == 1) { header->blockSizeInPCMFrames = 192; } else if (blockSize <= 5) { DRFLAC_ASSERT(blockSize >= 2); header->blockSizeInPCMFrames = 576 * (1 << (blockSize - 2)); } else if (blockSize == 6) { if (!drflac__read_uint16(bs, 8, &header->blockSizeInPCMFrames)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 8); header->blockSizeInPCMFrames += 1; } else if (blockSize == 7) { if (!drflac__read_uint16(bs, 16, &header->blockSizeInPCMFrames)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); if (header->blockSizeInPCMFrames == 0xFFFF) { return DRFLAC_FALSE; /* Frame is too big. This is the size of the frame minus 1. The STREAMINFO block defines the max block size which is 16-bits. Adding one will make it 17 bits and therefore too big. */ } header->blockSizeInPCMFrames += 1; } else { DRFLAC_ASSERT(blockSize >= 8); header->blockSizeInPCMFrames = 256 * (1 << (blockSize - 8)); } if (sampleRate <= 11) { header->sampleRate = sampleRateTable[sampleRate]; } else if (sampleRate == 12) { if (!drflac__read_uint32(bs, 8, &header->sampleRate)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->sampleRate, 8); header->sampleRate *= 1000; } else if (sampleRate == 13) { if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->sampleRate, 16); } else if (sampleRate == 14) { if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->sampleRate, 16); header->sampleRate *= 10; } else { continue; /* Invalid. Assume an invalid block. */ } header->channelAssignment = channelAssignment; header->bitsPerSample = bitsPerSampleTable[bitsPerSample]; if (header->bitsPerSample == 0) { header->bitsPerSample = streaminfoBitsPerSample; } if (header->bitsPerSample != streaminfoBitsPerSample) { /* If this subframe has a different bitsPerSample then streaminfo or the first frame, reject it */ return DRFLAC_FALSE; } if (!drflac__read_uint8(bs, 8, &header->crc8)) { return DRFLAC_FALSE; } #ifndef DR_FLAC_NO_CRC if (header->crc8 != crc8) { continue; /* CRC mismatch. Loop back to the top and find the next sync code. */ } #endif return DRFLAC_TRUE; } } static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe* pSubframe) { drflac_uint8 header; int type; if (!drflac__read_uint8(bs, 8, &header)) { return DRFLAC_FALSE; } /* First bit should always be 0. */ if ((header & 0x80) != 0) { return DRFLAC_FALSE; } /* Default to 0 for the LPC order. It's important that we always set this to 0 for non LPC and FIXED subframes because we'll be using it in a generic validation check later. */ pSubframe->lpcOrder = 0; type = (header & 0x7E) >> 1; if (type == 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_CONSTANT; } else if (type == 1) { pSubframe->subframeType = DRFLAC_SUBFRAME_VERBATIM; } else { if ((type & 0x20) != 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_LPC; pSubframe->lpcOrder = (drflac_uint8)(type & 0x1F) + 1; } else if ((type & 0x08) != 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_FIXED; pSubframe->lpcOrder = (drflac_uint8)(type & 0x07); if (pSubframe->lpcOrder > 4) { pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; pSubframe->lpcOrder = 0; } } else { pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; } } if (pSubframe->subframeType == DRFLAC_SUBFRAME_RESERVED) { return DRFLAC_FALSE; } /* Wasted bits per sample. */ pSubframe->wastedBitsPerSample = 0; if ((header & 0x01) == 1) { unsigned int wastedBitsPerSample; if (!drflac__seek_past_next_set_bit(bs, &wastedBitsPerSample)) { return DRFLAC_FALSE; } pSubframe->wastedBitsPerSample = (drflac_uint8)wastedBitsPerSample + 1; } return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex, drflac_int32* pDecodedSamplesOut) { drflac_subframe* pSubframe; drflac_uint32 subframeBitsPerSample; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(frame != NULL); pSubframe = frame->subframes + subframeIndex; if (!drflac__read_subframe_header(bs, pSubframe)) { return DRFLAC_FALSE; } /* Side channels require an extra bit per sample. Took a while to figure that one out... */ subframeBitsPerSample = frame->header.bitsPerSample; if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { subframeBitsPerSample += 1; } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { subframeBitsPerSample += 1; } if (subframeBitsPerSample > 32) { /* libFLAC and ffmpeg reject 33-bit subframes as well */ return DRFLAC_FALSE; } /* Need to handle wasted bits per sample. */ if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; } subframeBitsPerSample -= pSubframe->wastedBitsPerSample; pSubframe->pSamplesS32 = pDecodedSamplesOut; /* pDecodedSamplesOut will be pointing to a buffer that was allocated with enough memory to store maxBlockSizeInPCMFrames samples (as specified in the FLAC header). We need to guard against an overflow here. At a higher level we are checking maxBlockSizeInPCMFrames from the header, but here we need to do an additional check to ensure this frame's block size fully encompasses any warmup samples which is determined by the LPC order. For non LPC and FIXED subframes, the LPC order will be have been set to 0 in drflac__read_subframe_header(). */ if (frame->header.blockSizeInPCMFrames < pSubframe->lpcOrder) { return DRFLAC_FALSE; } switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: { drflac__decode_samples__constant(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_VERBATIM: { drflac__decode_samples__verbatim(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_FIXED: { drflac__decode_samples__fixed(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_LPC: { drflac__decode_samples__lpc(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); } break; default: return DRFLAC_FALSE; } return DRFLAC_TRUE; } static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex) { drflac_subframe* pSubframe; drflac_uint32 subframeBitsPerSample; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(frame != NULL); pSubframe = frame->subframes + subframeIndex; if (!drflac__read_subframe_header(bs, pSubframe)) { return DRFLAC_FALSE; } /* Side channels require an extra bit per sample. Took a while to figure that one out... */ subframeBitsPerSample = frame->header.bitsPerSample; if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { subframeBitsPerSample += 1; } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { subframeBitsPerSample += 1; } /* Need to handle wasted bits per sample. */ if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; } subframeBitsPerSample -= pSubframe->wastedBitsPerSample; pSubframe->pSamplesS32 = NULL; switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: { if (!drflac__seek_bits(bs, subframeBitsPerSample)) { return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_VERBATIM: { unsigned int bitsToSeek = frame->header.blockSizeInPCMFrames * subframeBitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_FIXED: { unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_LPC: { drflac_uint8 lpcPrecision; unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { return DRFLAC_FALSE; } if (lpcPrecision == 15) { return DRFLAC_FALSE; /* Invalid. */ } lpcPrecision += 1; bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; /* +5 for shift. */ if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { return DRFLAC_FALSE; } } break; default: return DRFLAC_FALSE; } return DRFLAC_TRUE; } static DRFLAC_INLINE drflac_uint8 drflac__get_channel_count_from_channel_assignment(drflac_int8 channelAssignment) { drflac_uint8 lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; DRFLAC_ASSERT(channelAssignment <= 10); return lookup[channelAssignment]; } static drflac_result drflac__decode_flac_frame(drflac* pFlac) { int channelCount; int i; drflac_uint8 paddingSizeInBits; drflac_uint16 desiredCRC16; #ifndef DR_FLAC_NO_CRC drflac_uint16 actualCRC16; #endif /* This function should be called while the stream is sitting on the first byte after the frame header. */ DRFLAC_ZERO_MEMORY(pFlac->currentFLACFrame.subframes, sizeof(pFlac->currentFLACFrame.subframes)); /* The frame block size must never be larger than the maximum block size defined by the FLAC stream. */ if (pFlac->currentFLACFrame.header.blockSizeInPCMFrames > pFlac->maxBlockSizeInPCMFrames) { return DRFLAC_ERROR; } /* The number of channels in the frame must match the channel count from the STREAMINFO block. */ channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); if (channelCount != (int)pFlac->channels) { return DRFLAC_ERROR; } for (i = 0; i < channelCount; ++i) { if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i, pFlac->pDecodedSamples + (pFlac->currentFLACFrame.header.blockSizeInPCMFrames * i))) { return DRFLAC_ERROR; } } paddingSizeInBits = (drflac_uint8)(DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7); if (paddingSizeInBits > 0) { drflac_uint8 padding = 0; if (!drflac__read_uint8(&pFlac->bs, paddingSizeInBits, &padding)) { return DRFLAC_AT_END; } } #ifndef DR_FLAC_NO_CRC actualCRC16 = drflac__flush_crc16(&pFlac->bs); #endif if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { return DRFLAC_AT_END; } #ifndef DR_FLAC_NO_CRC if (actualCRC16 != desiredCRC16) { return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ } #endif pFlac->currentFLACFrame.pcmFramesRemaining = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; return DRFLAC_SUCCESS; } static drflac_result drflac__seek_flac_frame(drflac* pFlac) { int channelCount; int i; drflac_uint16 desiredCRC16; #ifndef DR_FLAC_NO_CRC drflac_uint16 actualCRC16; #endif channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); for (i = 0; i < channelCount; ++i) { if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i)) { return DRFLAC_ERROR; } } /* Padding. */ if (!drflac__seek_bits(&pFlac->bs, DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7)) { return DRFLAC_ERROR; } /* CRC. */ #ifndef DR_FLAC_NO_CRC actualCRC16 = drflac__flush_crc16(&pFlac->bs); #endif if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { return DRFLAC_AT_END; } #ifndef DR_FLAC_NO_CRC if (actualCRC16 != desiredCRC16) { return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ } #endif return DRFLAC_SUCCESS; } static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) { DRFLAC_ASSERT(pFlac != NULL); for (;;) { drflac_result result; if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } result = drflac__decode_flac_frame(pFlac); if (result != DRFLAC_SUCCESS) { if (result == DRFLAC_CRC_MISMATCH) { continue; /* CRC mismatch. Skip to the next frame. */ } else { return DRFLAC_FALSE; } } return DRFLAC_TRUE; } } static void drflac__get_pcm_frame_range_of_current_flac_frame(drflac* pFlac, drflac_uint64* pFirstPCMFrame, drflac_uint64* pLastPCMFrame) { drflac_uint64 firstPCMFrame; drflac_uint64 lastPCMFrame; DRFLAC_ASSERT(pFlac != NULL); firstPCMFrame = pFlac->currentFLACFrame.header.pcmFrameNumber; if (firstPCMFrame == 0) { firstPCMFrame = ((drflac_uint64)pFlac->currentFLACFrame.header.flacFrameNumber) * pFlac->maxBlockSizeInPCMFrames; } lastPCMFrame = firstPCMFrame + pFlac->currentFLACFrame.header.blockSizeInPCMFrames; if (lastPCMFrame > 0) { lastPCMFrame -= 1; /* Needs to be zero based. */ } if (pFirstPCMFrame) { *pFirstPCMFrame = firstPCMFrame; } if (pLastPCMFrame) { *pLastPCMFrame = lastPCMFrame; } } static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) { drflac_bool32 result; DRFLAC_ASSERT(pFlac != NULL); result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes); DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); pFlac->currentPCMFrame = 0; return result; } static DRFLAC_INLINE drflac_result drflac__seek_to_next_flac_frame(drflac* pFlac) { /* This function should only ever be called while the decoder is sitting on the first byte past the FRAME_HEADER section. */ DRFLAC_ASSERT(pFlac != NULL); return drflac__seek_flac_frame(pFlac); } static drflac_uint64 drflac__seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 pcmFramesToSeek) { drflac_uint64 pcmFramesRead = 0; while (pcmFramesToSeek > 0) { if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { if (pFlac->currentFLACFrame.pcmFramesRemaining > pcmFramesToSeek) { pcmFramesRead += pcmFramesToSeek; pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)pcmFramesToSeek; /* <-- Safe cast. Will always be < currentFrame.pcmFramesRemaining < 65536. */ pcmFramesToSeek = 0; } else { pcmFramesRead += pFlac->currentFLACFrame.pcmFramesRemaining; pcmFramesToSeek -= pFlac->currentFLACFrame.pcmFramesRemaining; pFlac->currentFLACFrame.pcmFramesRemaining = 0; } } } pFlac->currentPCMFrame += pcmFramesRead; return pcmFramesRead; } static drflac_bool32 drflac__seek_to_pcm_frame__brute_force(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_bool32 isMidFrame = DRFLAC_FALSE; drflac_uint64 runningPCMFrameCount; DRFLAC_ASSERT(pFlac != NULL); /* If we are seeking forward we start from the current position. Otherwise we need to start all the way from the start of the file. */ if (pcmFrameIndex >= pFlac->currentPCMFrame) { /* Seeking forward. Need to seek from the current position. */ runningPCMFrameCount = pFlac->currentPCMFrame; /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } else { isMidFrame = DRFLAC_TRUE; } } else { /* Seeking backwards. Need to seek from the start of the file. */ runningPCMFrameCount = 0; /* Move back to the start. */ if (!drflac__seek_to_first_frame(pFlac)) { return DRFLAC_FALSE; } /* Decode the first frame in preparation for sample-exact seeking below. */ if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } /* We need to as quickly as possible find the frame that contains the target sample. To do this, we iterate over each frame and inspect its header. If based on the header we can determine that the frame contains the sample, we do a full decode of that frame. */ for (;;) { drflac_uint64 pcmFrameCountInThisFLACFrame; drflac_uint64 firstPCMFrameInFLACFrame = 0; drflac_uint64 lastPCMFrameInFLACFrame = 0; drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { /* The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend it never existed and keep iterating. */ drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; if (!isMidFrame) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { /* We started seeking mid-frame which means we need to skip the frame decoding part. */ return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; } } else { /* It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this frame never existed and leave the running sample count untouched. */ if (!isMidFrame) { drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { runningPCMFrameCount += pcmFrameCountInThisFLACFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { /* We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. */ runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; pFlac->currentFLACFrame.pcmFramesRemaining = 0; isMidFrame = DRFLAC_FALSE; } /* If we are seeking to the end of the file and we've just hit it, we're done. */ if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { return DRFLAC_TRUE; } } next_iteration: /* Grab the next frame in preparation for the next iteration. */ if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } } #if !defined(DR_FLAC_NO_CRC) /* We use an average compression ratio to determine our approximate start location. FLAC files are generally about 50%-70% the size of their uncompressed counterparts so we'll use this as a basis. I'm going to split the middle and use a factor of 0.6 to determine the starting location. */ #define DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO 0.6f static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFlac, drflac_uint64 targetByte, drflac_uint64 rangeLo, drflac_uint64 rangeHi, drflac_uint64* pLastSuccessfulSeekOffset) { DRFLAC_ASSERT(pFlac != NULL); DRFLAC_ASSERT(pLastSuccessfulSeekOffset != NULL); DRFLAC_ASSERT(targetByte >= rangeLo); DRFLAC_ASSERT(targetByte <= rangeHi); *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; for (;;) { /* After rangeLo == rangeHi == targetByte fails, we need to break out. */ drflac_uint64 lastTargetByte = targetByte; /* When seeking to a byte, failure probably means we've attempted to seek beyond the end of the stream. To counter this we just halve it each attempt. */ if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { /* If we couldn't even seek to the first byte in the stream we have a problem. Just abandon the whole thing. */ if (targetByte == 0) { drflac__seek_to_first_frame(pFlac); /* Try to recover. */ return DRFLAC_FALSE; } /* Halve the byte location and continue. */ targetByte = rangeLo + ((rangeHi - rangeLo)/2); rangeHi = targetByte; } else { /* Getting here should mean that we have seeked to an appropriate byte. */ /* Clear the details of the FLAC frame so we don't misreport data. */ DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); /* Now seek to the next FLAC frame. We need to decode the entire frame (not just the header) because it's possible for the header to incorrectly pass the CRC check and return bad data. We need to decode the entire frame to be more certain. Although this seems unlikely, this has happened to me in testing so it needs to stay this way for now. */ #if 1 if (!drflac__read_and_decode_next_flac_frame(pFlac)) { /* Halve the byte location and continue. */ targetByte = rangeLo + ((rangeHi - rangeLo)/2); rangeHi = targetByte; } else { break; } #else if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { /* Halve the byte location and continue. */ targetByte = rangeLo + ((rangeHi - rangeLo)/2); rangeHi = targetByte; } else { break; } #endif } /* We already tried this byte and there are no more to try, break out. */ if(targetByte == lastTargetByte) { return DRFLAC_FALSE; } } /* The current PCM frame needs to be updated based on the frame we just seeked to. */ drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); DRFLAC_ASSERT(targetByte <= rangeHi); *pLastSuccessfulSeekOffset = targetByte; return DRFLAC_TRUE; } static drflac_bool32 drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 offset) { /* This section of code would be used if we were only decoding the FLAC frame header when calling drflac__seek_to_approximate_flac_frame_to_byte(). */ #if 0 if (drflac__decode_flac_frame(pFlac) != DRFLAC_SUCCESS) { /* We failed to decode this frame which may be due to it being corrupt. We'll just use the next valid FLAC frame. */ if (drflac__read_and_decode_next_flac_frame(pFlac) == DRFLAC_FALSE) { return DRFLAC_FALSE; } } #endif return drflac__seek_forward_by_pcm_frames(pFlac, offset) == offset; } static drflac_bool32 drflac__seek_to_pcm_frame__binary_search_internal(drflac* pFlac, drflac_uint64 pcmFrameIndex, drflac_uint64 byteRangeLo, drflac_uint64 byteRangeHi) { /* This assumes pFlac->currentPCMFrame is sitting on byteRangeLo upon entry. */ drflac_uint64 targetByte; drflac_uint64 pcmRangeLo = pFlac->totalPCMFrameCount; drflac_uint64 pcmRangeHi = 0; drflac_uint64 lastSuccessfulSeekOffset = (drflac_uint64)-1; drflac_uint64 closestSeekOffsetBeforeTargetPCMFrame = byteRangeLo; drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; targetByte = byteRangeLo + (drflac_uint64)(((drflac_int64)((pcmFrameIndex - pFlac->currentPCMFrame) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO); if (targetByte > byteRangeHi) { targetByte = byteRangeHi; } for (;;) { if (drflac__seek_to_approximate_flac_frame_to_byte(pFlac, targetByte, byteRangeLo, byteRangeHi, &lastSuccessfulSeekOffset)) { /* We found a FLAC frame. We need to check if it contains the sample we're looking for. */ drflac_uint64 newPCMRangeLo; drflac_uint64 newPCMRangeHi; drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &newPCMRangeLo, &newPCMRangeHi); /* If we selected the same frame, it means we should be pretty close. Just decode the rest. */ if (pcmRangeLo == newPCMRangeLo) { if (!drflac__seek_to_approximate_flac_frame_to_byte(pFlac, closestSeekOffsetBeforeTargetPCMFrame, closestSeekOffsetBeforeTargetPCMFrame, byteRangeHi, &lastSuccessfulSeekOffset)) { break; /* Failed to seek to closest frame. */ } if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { return DRFLAC_TRUE; } else { break; /* Failed to seek forward. */ } } pcmRangeLo = newPCMRangeLo; pcmRangeHi = newPCMRangeHi; if (pcmRangeLo <= pcmFrameIndex && pcmRangeHi >= pcmFrameIndex) { /* The target PCM frame is in this FLAC frame. */ if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame) ) { return DRFLAC_TRUE; } else { break; /* Failed to seek to FLAC frame. */ } } else { const float approxCompressionRatio = (drflac_int64)(lastSuccessfulSeekOffset - pFlac->firstFLACFramePosInBytes) / ((drflac_int64)(pcmRangeLo * pFlac->channels * pFlac->bitsPerSample)/8.0f); if (pcmRangeLo > pcmFrameIndex) { /* We seeked too far forward. We need to move our target byte backward and try again. */ byteRangeHi = lastSuccessfulSeekOffset; if (byteRangeLo > byteRangeHi) { byteRangeLo = byteRangeHi; } targetByte = byteRangeLo + ((byteRangeHi - byteRangeLo) / 2); if (targetByte < byteRangeLo) { targetByte = byteRangeLo; } } else /*if (pcmRangeHi < pcmFrameIndex)*/ { /* We didn't seek far enough. We need to move our target byte forward and try again. */ /* If we're close enough we can just seek forward. */ if ((pcmFrameIndex - pcmRangeLo) < seekForwardThreshold) { if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { return DRFLAC_TRUE; } else { break; /* Failed to seek to FLAC frame. */ } } else { byteRangeLo = lastSuccessfulSeekOffset; if (byteRangeHi < byteRangeLo) { byteRangeHi = byteRangeLo; } targetByte = lastSuccessfulSeekOffset + (drflac_uint64)(((drflac_int64)((pcmFrameIndex-pcmRangeLo) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * approxCompressionRatio); if (targetByte > byteRangeHi) { targetByte = byteRangeHi; } if (closestSeekOffsetBeforeTargetPCMFrame < lastSuccessfulSeekOffset) { closestSeekOffsetBeforeTargetPCMFrame = lastSuccessfulSeekOffset; } } } } } else { /* Getting here is really bad. We just recover as best we can, but moving to the first frame in the stream, and then abort. */ break; } } drflac__seek_to_first_frame(pFlac); /* <-- Try to recover. */ return DRFLAC_FALSE; } static drflac_bool32 drflac__seek_to_pcm_frame__binary_search(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_uint64 byteRangeLo; drflac_uint64 byteRangeHi; drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; /* Our algorithm currently assumes the FLAC stream is currently sitting at the start. */ if (drflac__seek_to_first_frame(pFlac) == DRFLAC_FALSE) { return DRFLAC_FALSE; } /* If we're close enough to the start, just move to the start and seek forward. */ if (pcmFrameIndex < seekForwardThreshold) { return drflac__seek_forward_by_pcm_frames(pFlac, pcmFrameIndex) == pcmFrameIndex; } /* Our starting byte range is the byte position of the first FLAC frame and the approximate end of the file as if it were completely uncompressed. This ensures the entire file is included, even though most of the time it'll exceed the end of the actual stream. This is OK as the frame searching logic will handle it. */ byteRangeLo = pFlac->firstFLACFramePosInBytes; byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); return drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi); } #endif /* !DR_FLAC_NO_CRC */ static drflac_bool32 drflac__seek_to_pcm_frame__seek_table(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_uint32 iClosestSeekpoint = 0; drflac_bool32 isMidFrame = DRFLAC_FALSE; drflac_uint64 runningPCMFrameCount; drflac_uint32 iSeekpoint; DRFLAC_ASSERT(pFlac != NULL); if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { return DRFLAC_FALSE; } /* Do not use the seektable if pcmFramIndex is not coverd by it. */ if (pFlac->pSeekpoints[0].firstPCMFrame > pcmFrameIndex) { return DRFLAC_FALSE; } for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { if (pFlac->pSeekpoints[iSeekpoint].firstPCMFrame >= pcmFrameIndex) { break; } iClosestSeekpoint = iSeekpoint; } /* There's been cases where the seek table contains only zeros. We need to do some basic validation on the closest seekpoint. */ if (pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount == 0 || pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount > pFlac->maxBlockSizeInPCMFrames) { return DRFLAC_FALSE; } if (pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame > pFlac->totalPCMFrameCount && pFlac->totalPCMFrameCount > 0) { return DRFLAC_FALSE; } #if !defined(DR_FLAC_NO_CRC) /* At this point we should know the closest seek point. We can use a binary search for this. We need to know the total sample count for this. */ if (pFlac->totalPCMFrameCount > 0) { drflac_uint64 byteRangeLo; drflac_uint64 byteRangeHi; byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); byteRangeLo = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset; /* If our closest seek point is not the last one, we only need to search between it and the next one. The section below calculates an appropriate starting value for byteRangeHi which will clamp it appropriately. Note that the next seekpoint must have an offset greater than the closest seekpoint because otherwise our binary search algorithm will break down. There have been cases where a seektable consists of seek points where every byte offset is set to 0 which causes problems. If this happens we need to abort. */ if (iClosestSeekpoint < pFlac->seekpointCount-1) { drflac_uint32 iNextSeekpoint = iClosestSeekpoint + 1; /* Basic validation on the seekpoints to ensure they're usable. */ if (pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset >= pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset || pFlac->pSeekpoints[iNextSeekpoint].pcmFrameCount == 0) { return DRFLAC_FALSE; /* The next seekpoint doesn't look right. The seek table cannot be trusted from here. Abort. */ } if (pFlac->pSeekpoints[iNextSeekpoint].firstPCMFrame != (((drflac_uint64)0xFFFFFFFF << 32) | 0xFFFFFFFF)) { /* Make sure it's not a placeholder seekpoint. */ byteRangeHi = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset - 1; /* byteRangeHi must be zero based. */ } } if (drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { if (drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); if (drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi)) { return DRFLAC_TRUE; } } } } #endif /* !DR_FLAC_NO_CRC */ /* Getting here means we need to use a slower algorithm because the binary search method failed or cannot be used. */ /* If we are seeking forward and the closest seekpoint is _before_ the current sample, we just seek forward from where we are. Otherwise we start seeking from the seekpoint's first sample. */ if (pcmFrameIndex >= pFlac->currentPCMFrame && pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame <= pFlac->currentPCMFrame) { /* Optimized case. Just seek forward from where we are. */ runningPCMFrameCount = pFlac->currentPCMFrame; /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } else { isMidFrame = DRFLAC_TRUE; } } else { /* Slower case. Seek to the start of the seekpoint and then seek forward from there. */ runningPCMFrameCount = pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame; if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { return DRFLAC_FALSE; } /* Grab the frame the seekpoint is sitting on in preparation for the sample-exact seeking below. */ if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } for (;;) { drflac_uint64 pcmFrameCountInThisFLACFrame; drflac_uint64 firstPCMFrameInFLACFrame = 0; drflac_uint64 lastPCMFrameInFLACFrame = 0; drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { /* The sample should be in this frame. We need to fully decode it, but if it's an invalid frame (a CRC mismatch) we need to pretend it never existed and keep iterating. */ drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; if (!isMidFrame) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { /* We started seeking mid-frame which means we need to skip the frame decoding part. */ return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; } } else { /* It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this frame never existed and leave the running sample count untouched. */ if (!isMidFrame) { drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { runningPCMFrameCount += pcmFrameCountInThisFLACFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { /* We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. */ runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; pFlac->currentFLACFrame.pcmFramesRemaining = 0; isMidFrame = DRFLAC_FALSE; } /* If we are seeking to the end of the file and we've just hit it, we're done. */ if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { return DRFLAC_TRUE; } } next_iteration: /* Grab the next frame in preparation for the next iteration. */ if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } } #ifndef DR_FLAC_NO_OGG typedef struct { drflac_uint8 capturePattern[4]; /* Should be "OggS" */ drflac_uint8 structureVersion; /* Always 0. */ drflac_uint8 headerType; drflac_uint64 granulePosition; drflac_uint32 serialNumber; drflac_uint32 sequenceNumber; drflac_uint32 checksum; drflac_uint8 segmentCount; drflac_uint8 segmentTable[255]; } drflac_ogg_page_header; #endif typedef struct { drflac_read_proc onRead; drflac_seek_proc onSeek; drflac_meta_proc onMeta; drflac_container container; void* pUserData; void* pUserDataMD; drflac_uint32 sampleRate; drflac_uint8 channels; drflac_uint8 bitsPerSample; drflac_uint64 totalPCMFrameCount; drflac_uint16 maxBlockSizeInPCMFrames; drflac_uint64 runningFilePos; drflac_bool32 hasStreamInfoBlock; drflac_bool32 hasMetadataBlocks; drflac_bs bs; /* <-- A bit streamer is required for loading data during initialization. */ drflac_frame_header firstFrameHeader; /* <-- The header of the first frame that was read during relaxed initalization. Only set if there is no STREAMINFO block. */ #ifndef DR_FLAC_NO_OGG drflac_uint32 oggSerial; drflac_uint64 oggFirstBytePos; drflac_ogg_page_header oggBosHeader; #endif } drflac_init_info; static DRFLAC_INLINE void drflac__decode_block_header(drflac_uint32 blockHeader, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) { blockHeader = drflac__be2host_32(blockHeader); *isLastBlock = (drflac_uint8)((blockHeader & 0x80000000UL) >> 31); *blockType = (drflac_uint8)((blockHeader & 0x7F000000UL) >> 24); *blockSize = (blockHeader & 0x00FFFFFFUL); } static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_read_proc onRead, void* pUserData, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) { drflac_uint32 blockHeader; *blockSize = 0; if (onRead(pUserData, &blockHeader, 4) != 4) { return DRFLAC_FALSE; } drflac__decode_block_header(blockHeader, isLastBlock, blockType, blockSize); return DRFLAC_TRUE; } static drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) { drflac_uint32 blockSizes; drflac_uint64 frameSizes = 0; drflac_uint64 importantProps; drflac_uint8 md5[16]; /* min/max block size. */ if (onRead(pUserData, &blockSizes, 4) != 4) { return DRFLAC_FALSE; } /* min/max frame size. */ if (onRead(pUserData, &frameSizes, 6) != 6) { return DRFLAC_FALSE; } /* Sample rate, channels, bits per sample and total sample count. */ if (onRead(pUserData, &importantProps, 8) != 8) { return DRFLAC_FALSE; } /* MD5 */ if (onRead(pUserData, md5, sizeof(md5)) != sizeof(md5)) { return DRFLAC_FALSE; } blockSizes = drflac__be2host_32(blockSizes); frameSizes = drflac__be2host_64(frameSizes); importantProps = drflac__be2host_64(importantProps); pStreamInfo->minBlockSizeInPCMFrames = (drflac_uint16)((blockSizes & 0xFFFF0000) >> 16); pStreamInfo->maxBlockSizeInPCMFrames = (drflac_uint16) (blockSizes & 0x0000FFFF); pStreamInfo->minFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 24)) >> 40); pStreamInfo->maxFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 0)) >> 16); pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (((drflac_uint64)0x000FFFFF << 16) << 28)) >> 44); pStreamInfo->channels = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000000E << 16) << 24)) >> 41) + 1; pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000001F << 16) << 20)) >> 36) + 1; pStreamInfo->totalPCMFrameCount = ((importantProps & ((((drflac_uint64)0x0000000F << 16) << 16) | 0xFFFFFFFF))); DRFLAC_COPY_MEMORY(pStreamInfo->md5, md5, sizeof(md5)); return DRFLAC_TRUE; } static void* drflac__malloc_default(size_t sz, void* pUserData) { (void)pUserData; return DRFLAC_MALLOC(sz); } static void* drflac__realloc_default(void* p, size_t sz, void* pUserData) { (void)pUserData; return DRFLAC_REALLOC(p, sz); } static void drflac__free_default(void* p, void* pUserData) { (void)pUserData; DRFLAC_FREE(p); } static void* drflac__malloc_from_callbacks(size_t sz, const drflac_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; } if (pAllocationCallbacks->onMalloc != NULL) { return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); } /* Try using realloc(). */ if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); } return NULL; } static void* drflac__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drflac_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; } if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); } /* Try emulating realloc() in terms of malloc()/free(). */ if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { void* p2; p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); if (p2 == NULL) { return NULL; } if (p != NULL) { DRFLAC_COPY_MEMORY(p2, p, szOld); pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } return p2; } return NULL; } static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) { if (p == NULL || pAllocationCallbacks == NULL) { return; } if (pAllocationCallbacks->onFree != NULL) { pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } } static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) { /* We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that we'll be sitting on byte 42. */ drflac_uint64 runningFilePos = 42; drflac_uint64 seektablePos = 0; drflac_uint32 seektableSize = 0; for (;;) { drflac_metadata metadata; drflac_uint8 isLastBlock = 0; drflac_uint8 blockType = 0; drflac_uint32 blockSize; if (drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == DRFLAC_FALSE) { return DRFLAC_FALSE; } runningFilePos += 4; metadata.type = blockType; metadata.pRawData = NULL; metadata.rawDataSize = 0; switch (blockType) { case DRFLAC_METADATA_BLOCK_TYPE_APPLICATION: { if (blockSize < 4) { return DRFLAC_FALSE; } if (onMeta) { void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; metadata.data.application.id = drflac__be2host_32(*(drflac_uint32*)pRawData); metadata.data.application.pData = (const void*)((drflac_uint8*)pRawData + sizeof(drflac_uint32)); metadata.data.application.dataSize = blockSize - sizeof(drflac_uint32); onMeta(pUserDataMD, &metadata); drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; case DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE: { seektablePos = runningFilePos; seektableSize = blockSize; if (onMeta) { drflac_uint32 seekpointCount; drflac_uint32 iSeekpoint; void* pRawData; seekpointCount = blockSize/DRFLAC_SEEKPOINT_SIZE_IN_BYTES; pRawData = drflac__malloc_from_callbacks(seekpointCount * sizeof(drflac_seekpoint), pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } /* We need to read seekpoint by seekpoint and do some processing. */ for (iSeekpoint = 0; iSeekpoint < seekpointCount; ++iSeekpoint) { drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + iSeekpoint; if (onRead(pUserData, pSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) != DRFLAC_SEEKPOINT_SIZE_IN_BYTES) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } /* Endian swap. */ pSeekpoint->firstPCMFrame = drflac__be2host_64(pSeekpoint->firstPCMFrame); pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset); pSeekpoint->pcmFrameCount = drflac__be2host_16(pSeekpoint->pcmFrameCount); } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; metadata.data.seektable.seekpointCount = seekpointCount; metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData; onMeta(pUserDataMD, &metadata); drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; case DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT: { if (blockSize < 8) { return DRFLAC_FALSE; } if (onMeta) { void* pRawData; const char* pRunningData; const char* pRunningDataEnd; drflac_uint32 i; pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */ if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.vorbis_comment.pComments = pRunningData; /* Check that the comments section is valid before passing it to the callback */ for (i = 0; i < metadata.data.vorbis_comment.commentCount; ++i) { drflac_uint32 commentLength; if (pRunningDataEnd - pRunningData < 4) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } pRunningData += commentLength; } onMeta(pUserDataMD, &metadata); drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; case DRFLAC_METADATA_BLOCK_TYPE_CUESHEET: { if (blockSize < 396) { return DRFLAC_FALSE; } if (onMeta) { void* pRawData; const char* pRunningData; const char* pRunningDataEnd; size_t bufferSize; drflac_uint8 iTrack; drflac_uint8 iIndex; void* pTrackData; /* This needs to be loaded in two passes. The first pass is used to calculate the size of the memory allocation we need for storing the necessary data. The second pass will fill that buffer with usable data. */ pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; DRFLAC_COPY_MEMORY(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8; metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259; metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; metadata.data.cuesheet.pTrackData = NULL; /* Will be filled later. */ /* Pass 1: Calculate the size of the buffer for the track data. */ { const char* pRunningDataSaved = pRunningData; /* Will be restored at the end in preparation for the second pass. */ bufferSize = metadata.data.cuesheet.trackCount * DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES; for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { drflac_uint8 indexCount; drflac_uint32 indexPointSize; if (pRunningDataEnd - pRunningData < DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } /* Skip to the index point count */ pRunningData += 35; indexCount = pRunningData[0]; pRunningData += 1; bufferSize += indexCount * sizeof(drflac_cuesheet_track_index); /* Quick validation check. */ indexPointSize = indexCount * DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES; if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } pRunningData += indexPointSize; } pRunningData = pRunningDataSaved; } /* Pass 2: Allocate a buffer and fill the data. Validation was done in the step above so can be skipped. */ { char* pRunningTrackData; pTrackData = drflac__malloc_from_callbacks(bufferSize, pAllocationCallbacks); if (pTrackData == NULL) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } pRunningTrackData = (char*)pTrackData; for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { drflac_uint8 indexCount; DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES); pRunningData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; /* Skip forward, but not beyond the last byte in the CUESHEET_TRACK block which is the index count. */ pRunningTrackData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; /* Grab the index count for the next part. */ indexCount = pRunningData[0]; pRunningData += 1; pRunningTrackData += 1; /* Extract each track index. */ for (iIndex = 0; iIndex < indexCount; ++iIndex) { drflac_cuesheet_track_index* pTrackIndex = (drflac_cuesheet_track_index*)pRunningTrackData; DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES); pRunningData += DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES; pRunningTrackData += sizeof(drflac_cuesheet_track_index); pTrackIndex->offset = drflac__be2host_64(pTrackIndex->offset); } } metadata.data.cuesheet.pTrackData = pTrackData; } /* The original data is no longer needed. */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); pRawData = NULL; onMeta(pUserDataMD, &metadata); drflac__free_from_callbacks(pTrackData, pAllocationCallbacks); pTrackData = NULL; } } break; case DRFLAC_METADATA_BLOCK_TYPE_PICTURE: { if (blockSize < 32) { return DRFLAC_FALSE; } if (onMeta) { void* pRawData; const char* pRunningData; const char* pRunningDataEnd; pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; /* Need space for the picture after the block */ if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { /* <-- Note the order of operations to avoid overflow to a valid value */ drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } onMeta(pUserDataMD, &metadata); drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; case DRFLAC_METADATA_BLOCK_TYPE_PADDING: { if (onMeta) { metadata.data.padding.unused = 0; /* Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. */ if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } else { onMeta(pUserDataMD, &metadata); } } } break; case DRFLAC_METADATA_BLOCK_TYPE_INVALID: { /* Invalid chunk. Just skip over this one. */ if (onMeta) { if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ } } } break; default: { /* It's an unknown chunk, but not necessarily invalid. There's a chance more metadata blocks might be defined later on, so we can at the very least report the chunk to the application and let it look at the raw data. */ if (onMeta) { void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; onMeta(pUserDataMD, &metadata); drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; } /* If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. */ if (onMeta == NULL && blockSize > 0) { if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { isLastBlock = DRFLAC_TRUE; } } runningFilePos += blockSize; if (isLastBlock) { break; } } *pSeektablePos = seektablePos; *pSeekpointCount = seektableSize / DRFLAC_SEEKPOINT_SIZE_IN_BYTES; *pFirstFramePos = runningFilePos; return DRFLAC_TRUE; } static drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) { /* Pre Condition: The bit stream should be sitting just past the 4-byte id header. */ drflac_uint8 isLastBlock; drflac_uint8 blockType; drflac_uint32 blockSize; (void)onSeek; pInit->container = drflac_container_native; /* The first metadata block should be the STREAMINFO block. */ if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { return DRFLAC_FALSE; } if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { if (!relaxed) { /* We're opening in strict mode and the first block is not the STREAMINFO block. Error. */ return DRFLAC_FALSE; } else { /* Relaxed mode. To open from here we need to just find the first frame and set the sample rate, etc. to whatever is defined for that frame. */ pInit->hasStreamInfoBlock = DRFLAC_FALSE; pInit->hasMetadataBlocks = DRFLAC_FALSE; if (!drflac__read_next_flac_frame_header(&pInit->bs, 0, &pInit->firstFrameHeader)) { return DRFLAC_FALSE; /* Couldn't find a frame. */ } if (pInit->firstFrameHeader.bitsPerSample == 0) { return DRFLAC_FALSE; /* Failed to initialize because the first frame depends on the STREAMINFO block, which does not exist. */ } pInit->sampleRate = pInit->firstFrameHeader.sampleRate; pInit->channels = drflac__get_channel_count_from_channel_assignment(pInit->firstFrameHeader.channelAssignment); pInit->bitsPerSample = pInit->firstFrameHeader.bitsPerSample; pInit->maxBlockSizeInPCMFrames = 65535; /* <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo */ return DRFLAC_TRUE; } } else { drflac_streaminfo streaminfo; if (!drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { return DRFLAC_FALSE; } pInit->hasStreamInfoBlock = DRFLAC_TRUE; pInit->sampleRate = streaminfo.sampleRate; pInit->channels = streaminfo.channels; pInit->bitsPerSample = streaminfo.bitsPerSample; pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; /* Don't care about the min block size - only the max (used for determining the size of the memory allocation). */ pInit->hasMetadataBlocks = !isLastBlock; if (onMeta) { drflac_metadata metadata; metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; metadata.pRawData = NULL; metadata.rawDataSize = 0; metadata.data.streaminfo = streaminfo; onMeta(pUserDataMD, &metadata); } return DRFLAC_TRUE; } } #ifndef DR_FLAC_NO_OGG #define DRFLAC_OGG_MAX_PAGE_SIZE 65307 #define DRFLAC_OGG_CAPTURE_PATTERN_CRC32 1605413199 /* CRC-32 of "OggS". */ typedef enum { drflac_ogg_recover_on_crc_mismatch, drflac_ogg_fail_on_crc_mismatch } drflac_ogg_crc_mismatch_recovery; #ifndef DR_FLAC_NO_CRC static drflac_uint32 drflac__crc32_table[] = { 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L }; #endif static DRFLAC_INLINE drflac_uint32 drflac_crc32_byte(drflac_uint32 crc32, drflac_uint8 data) { #ifndef DR_FLAC_NO_CRC return (crc32 << 8) ^ drflac__crc32_table[(drflac_uint8)((crc32 >> 24) & 0xFF) ^ data]; #else (void)data; return crc32; #endif } #if 0 static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint32(drflac_uint32 crc32, drflac_uint32 data) { crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 24) & 0xFF)); crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 16) & 0xFF)); crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 8) & 0xFF)); crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 0) & 0xFF)); return crc32; } static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint64(drflac_uint32 crc32, drflac_uint64 data) { crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 32) & 0xFFFFFFFF)); crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 0) & 0xFFFFFFFF)); return crc32; } #endif static DRFLAC_INLINE drflac_uint32 drflac_crc32_buffer(drflac_uint32 crc32, drflac_uint8* pData, drflac_uint32 dataSize) { /* This can be optimized. */ drflac_uint32 i; for (i = 0; i < dataSize; ++i) { crc32 = drflac_crc32_byte(crc32, pData[i]); } return crc32; } static DRFLAC_INLINE drflac_bool32 drflac_ogg__is_capture_pattern(drflac_uint8 pattern[4]) { return pattern[0] == 'O' && pattern[1] == 'g' && pattern[2] == 'g' && pattern[3] == 'S'; } static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_header_size(drflac_ogg_page_header* pHeader) { return 27 + pHeader->segmentCount; } static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_page_header* pHeader) { drflac_uint32 pageBodySize = 0; int i; for (i = 0; i < pHeader->segmentCount; ++i) { pageBodySize += pHeader->segmentTable[i]; } return pageBodySize; } static drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) { drflac_uint8 data[23]; drflac_uint32 i; DRFLAC_ASSERT(*pCRC32 == DRFLAC_OGG_CAPTURE_PATTERN_CRC32); if (onRead(pUserData, data, 23) != 23) { return DRFLAC_AT_END; } *pBytesRead += 23; /* It's not actually used, but set the capture pattern to 'OggS' for completeness. Not doing this will cause static analysers to complain about us trying to access uninitialized data. We could alternatively just comment out this member of the drflac_ogg_page_header structure, but I like to have it map to the structure of the underlying data. */ pHeader->capturePattern[0] = 'O'; pHeader->capturePattern[1] = 'g'; pHeader->capturePattern[2] = 'g'; pHeader->capturePattern[3] = 'S'; pHeader->structureVersion = data[0]; pHeader->headerType = data[1]; DRFLAC_COPY_MEMORY(&pHeader->granulePosition, &data[ 2], 8); DRFLAC_COPY_MEMORY(&pHeader->serialNumber, &data[10], 4); DRFLAC_COPY_MEMORY(&pHeader->sequenceNumber, &data[14], 4); DRFLAC_COPY_MEMORY(&pHeader->checksum, &data[18], 4); pHeader->segmentCount = data[22]; /* Calculate the CRC. Note that for the calculation the checksum part of the page needs to be set to 0. */ data[18] = 0; data[19] = 0; data[20] = 0; data[21] = 0; for (i = 0; i < 23; ++i) { *pCRC32 = drflac_crc32_byte(*pCRC32, data[i]); } if (onRead(pUserData, pHeader->segmentTable, pHeader->segmentCount) != pHeader->segmentCount) { return DRFLAC_AT_END; } *pBytesRead += pHeader->segmentCount; for (i = 0; i < pHeader->segmentCount; ++i) { *pCRC32 = drflac_crc32_byte(*pCRC32, pHeader->segmentTable[i]); } return DRFLAC_SUCCESS; } static drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) { drflac_uint8 id[4]; *pBytesRead = 0; if (onRead(pUserData, id, 4) != 4) { return DRFLAC_AT_END; } *pBytesRead += 4; /* We need to read byte-by-byte until we find the OggS capture pattern. */ for (;;) { if (drflac_ogg__is_capture_pattern(id)) { drflac_result result; *pCRC32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; result = drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, pHeader, pBytesRead, pCRC32); if (result == DRFLAC_SUCCESS) { return DRFLAC_SUCCESS; } else { if (result == DRFLAC_CRC_MISMATCH) { continue; } else { return result; } } } else { /* The first 4 bytes did not equal the capture pattern. Read the next byte and try again. */ id[0] = id[1]; id[1] = id[2]; id[2] = id[3]; if (onRead(pUserData, &id[3], 1) != 1) { return DRFLAC_AT_END; } *pBytesRead += 1; } } } /* The main part of the Ogg encapsulation is the conversion from the physical Ogg bitstream to the native FLAC bitstream. It works in three general stages: Ogg Physical Bitstream -> Ogg/FLAC Logical Bitstream -> FLAC Native Bitstream. dr_flac is designed in such a way that the core sections assume everything is delivered in native format. Therefore, for each encapsulation type dr_flac is supporting there needs to be a layer sitting on top of the onRead and onSeek callbacks that ensures the bits read from the physical Ogg bitstream are converted and delivered in native FLAC format. */ typedef struct { drflac_read_proc onRead; /* The original onRead callback from drflac_open() and family. */ drflac_seek_proc onSeek; /* The original onSeek callback from drflac_open() and family. */ void* pUserData; /* The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. */ drflac_uint64 currentBytePos; /* The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. */ drflac_uint64 firstBytePos; /* The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. */ drflac_uint32 serialNumber; /* The serial number of the FLAC audio pages. This is determined by the initial header page that was read during initialization. */ drflac_ogg_page_header bosPageHeader; /* Used for seeking. */ drflac_ogg_page_header currentPageHeader; drflac_uint32 bytesRemainingInPage; drflac_uint32 pageDataSize; drflac_uint8 pageData[DRFLAC_OGG_MAX_PAGE_SIZE]; } drflac_oggbs; /* oggbs = Ogg Bitstream */ static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, size_t bytesToRead) { size_t bytesActuallyRead = oggbs->onRead(oggbs->pUserData, bufferOut, bytesToRead); oggbs->currentBytePos += bytesActuallyRead; return bytesActuallyRead; } static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uint64 offset, drflac_seek_origin origin) { if (origin == drflac_seek_origin_start) { if (offset <= 0x7FFFFFFF) { if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_start)) { return DRFLAC_FALSE; } oggbs->currentBytePos = offset; return DRFLAC_TRUE; } else { if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { return DRFLAC_FALSE; } oggbs->currentBytePos = offset; return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, drflac_seek_origin_current); } } else { while (offset > 0x7FFFFFFF) { if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { return DRFLAC_FALSE; } oggbs->currentBytePos += 0x7FFFFFFF; offset -= 0x7FFFFFFF; } if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { /* <-- Safe cast thanks to the loop above. */ return DRFLAC_FALSE; } oggbs->currentBytePos += offset; return DRFLAC_TRUE; } } static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_ogg_crc_mismatch_recovery recoveryMethod) { drflac_ogg_page_header header; for (;;) { drflac_uint32 crc32 = 0; drflac_uint32 bytesRead; drflac_uint32 pageBodySize; #ifndef DR_FLAC_NO_CRC drflac_uint32 actualCRC32; #endif if (drflac_ogg__read_page_header(oggbs->onRead, oggbs->pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { return DRFLAC_FALSE; } oggbs->currentBytePos += bytesRead; pageBodySize = drflac_ogg__get_page_body_size(&header); if (pageBodySize > DRFLAC_OGG_MAX_PAGE_SIZE) { continue; /* Invalid page size. Assume it's corrupted and just move to the next page. */ } if (header.serialNumber != oggbs->serialNumber) { /* It's not a FLAC page. Skip it. */ if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, drflac_seek_origin_current)) { return DRFLAC_FALSE; } continue; } /* We need to read the entire page and then do a CRC check on it. If there's a CRC mismatch we need to skip this page. */ if (drflac_oggbs__read_physical(oggbs, oggbs->pageData, pageBodySize) != pageBodySize) { return DRFLAC_FALSE; } oggbs->pageDataSize = pageBodySize; #ifndef DR_FLAC_NO_CRC actualCRC32 = drflac_crc32_buffer(crc32, oggbs->pageData, oggbs->pageDataSize); if (actualCRC32 != header.checksum) { if (recoveryMethod == drflac_ogg_recover_on_crc_mismatch) { continue; /* CRC mismatch. Skip this page. */ } else { /* Even though we are failing on a CRC mismatch, we still want our stream to be in a good state. Therefore we go to the next valid page to ensure we're in a good state, but return false to let the caller know that the seek did not fully complete. */ drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch); return DRFLAC_FALSE; } } #else (void)recoveryMethod; /* <-- Silence a warning. */ #endif oggbs->currentPageHeader = header; oggbs->bytesRemainingInPage = pageBodySize; return DRFLAC_TRUE; } } /* Function below is unused at the moment, but I might be re-adding it later. */ #if 0 static drflac_uint8 drflac_oggbs__get_current_segment_index(drflac_oggbs* oggbs, drflac_uint8* pBytesRemainingInSeg) { drflac_uint32 bytesConsumedInPage = drflac_ogg__get_page_body_size(&oggbs->currentPageHeader) - oggbs->bytesRemainingInPage; drflac_uint8 iSeg = 0; drflac_uint32 iByte = 0; while (iByte < bytesConsumedInPage) { drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; if (iByte + segmentSize > bytesConsumedInPage) { break; } else { iSeg += 1; iByte += segmentSize; } } *pBytesRemainingInSeg = oggbs->currentPageHeader.segmentTable[iSeg] - (drflac_uint8)(bytesConsumedInPage - iByte); return iSeg; } static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) { /* The current packet ends when we get to the segment with a lacing value of < 255 which is not at the end of a page. */ for (;;) { drflac_bool32 atEndOfPage = DRFLAC_FALSE; drflac_uint8 bytesRemainingInSeg; drflac_uint8 iFirstSeg = drflac_oggbs__get_current_segment_index(oggbs, &bytesRemainingInSeg); drflac_uint32 bytesToEndOfPacketOrPage = bytesRemainingInSeg; for (drflac_uint8 iSeg = iFirstSeg; iSeg < oggbs->currentPageHeader.segmentCount; ++iSeg) { drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; if (segmentSize < 255) { if (iSeg == oggbs->currentPageHeader.segmentCount-1) { atEndOfPage = DRFLAC_TRUE; } break; } bytesToEndOfPacketOrPage += segmentSize; } /* At this point we will have found either the packet or the end of the page. If were at the end of the page we'll want to load the next page and keep searching for the end of the packet. */ drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, drflac_seek_origin_current); oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; if (atEndOfPage) { /* We're potentially at the next packet, but we need to check the next page first to be sure because the packet may straddle pages. */ if (!drflac_oggbs__goto_next_page(oggbs)) { return DRFLAC_FALSE; } /* If it's a fresh packet it most likely means we're at the next packet. */ if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { return DRFLAC_TRUE; } } else { /* We're at the next packet. */ return DRFLAC_TRUE; } } } static drflac_bool32 drflac_oggbs__seek_to_next_frame(drflac_oggbs* oggbs) { /* The bitstream should be sitting on the first byte just after the header of the frame. */ /* What we're actually doing here is seeking to the start of the next packet. */ return drflac_oggbs__seek_to_next_packet(oggbs); } #endif static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytesToRead) { drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; drflac_uint8* pRunningBufferOut = (drflac_uint8*)bufferOut; size_t bytesRead = 0; DRFLAC_ASSERT(oggbs != NULL); DRFLAC_ASSERT(pRunningBufferOut != NULL); /* Reading is done page-by-page. If we've run out of bytes in the page we need to move to the next one. */ while (bytesRead < bytesToRead) { size_t bytesRemainingToRead = bytesToRead - bytesRead; if (oggbs->bytesRemainingInPage >= bytesRemainingToRead) { DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), bytesRemainingToRead); bytesRead += bytesRemainingToRead; oggbs->bytesRemainingInPage -= (drflac_uint32)bytesRemainingToRead; break; } /* If we get here it means some of the requested data is contained in the next pages. */ if (oggbs->bytesRemainingInPage > 0) { DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), oggbs->bytesRemainingInPage); bytesRead += oggbs->bytesRemainingInPage; pRunningBufferOut += oggbs->bytesRemainingInPage; oggbs->bytesRemainingInPage = 0; } DRFLAC_ASSERT(bytesRemainingToRead > 0); if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { break; /* Failed to go to the next page. Might have simply hit the end of the stream. */ } } return bytesRead; } static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_seek_origin origin) { drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; int bytesSeeked = 0; DRFLAC_ASSERT(oggbs != NULL); DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ /* Seeking is always forward which makes things a lot simpler. */ if (origin == drflac_seek_origin_start) { if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, drflac_seek_origin_start)) { return DRFLAC_FALSE; } if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { return DRFLAC_FALSE; } return drflac__on_seek_ogg(pUserData, offset, drflac_seek_origin_current); } DRFLAC_ASSERT(origin == drflac_seek_origin_current); while (bytesSeeked < offset) { int bytesRemainingToSeek = offset - bytesSeeked; DRFLAC_ASSERT(bytesRemainingToSeek >= 0); if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { bytesSeeked += bytesRemainingToSeek; (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ oggbs->bytesRemainingInPage -= bytesRemainingToSeek; break; } /* If we get here it means some of the requested data is contained in the next pages. */ if (oggbs->bytesRemainingInPage > 0) { bytesSeeked += (int)oggbs->bytesRemainingInPage; oggbs->bytesRemainingInPage = 0; } DRFLAC_ASSERT(bytesRemainingToSeek > 0); if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ return DRFLAC_FALSE; } } return DRFLAC_TRUE; } static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; drflac_uint64 originalBytePos; drflac_uint64 runningGranulePosition; drflac_uint64 runningFrameBytePos; drflac_uint64 runningPCMFrameCount; DRFLAC_ASSERT(oggbs != NULL); originalBytePos = oggbs->currentBytePos; /* For recovery. Points to the OggS identifier. */ /* First seek to the first frame. */ if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes)) { return DRFLAC_FALSE; } oggbs->bytesRemainingInPage = 0; runningGranulePosition = 0; for (;;) { if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { drflac_oggbs__seek_physical(oggbs, originalBytePos, drflac_seek_origin_start); return DRFLAC_FALSE; /* Never did find that sample... */ } runningFrameBytePos = oggbs->currentBytePos - drflac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; if (oggbs->currentPageHeader.granulePosition >= pcmFrameIndex) { break; /* The sample is somewhere in the previous page. */ } /* At this point we know the sample is not in the previous page. It could possibly be in this page. For simplicity we disregard any pages that do not begin a fresh packet. */ if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { /* <-- Is it a fresh page? */ if (oggbs->currentPageHeader.segmentTable[0] >= 2) { drflac_uint8 firstBytesInPage[2]; firstBytesInPage[0] = oggbs->pageData[0]; firstBytesInPage[1] = oggbs->pageData[1]; if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { /* <-- Does the page begin with a frame's sync code? */ runningGranulePosition = oggbs->currentPageHeader.granulePosition; } continue; } } } /* We found the page that that is closest to the sample, so now we need to find it. The first thing to do is seek to the start of that page. In the loop above we checked that it was a fresh page which means this page is also the start of a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until we find the one containing the target sample. */ if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, drflac_seek_origin_start)) { return DRFLAC_FALSE; } if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { return DRFLAC_FALSE; } /* At this point we'll be sitting on the first byte of the frame header of the first frame in the page. We just keep looping over these frames until we find the one containing the sample we're after. */ runningPCMFrameCount = runningGranulePosition; for (;;) { /* There are two ways to find the sample and seek past irrelevant frames: 1) Use the native FLAC decoder. 2) Use Ogg's framing system. Both of these options have their own pros and cons. Using the native FLAC decoder is slower because it needs to do a full decode of the frame. Using Ogg's framing system is faster, but more complicated and involves some code duplication for the decoding of frame headers. Another thing to consider is that using the Ogg framing system will perform direct seeking of the physical Ogg bitstream. This is important to consider because it means we cannot read data from the drflac_bs object using the standard drflac__*() APIs because that will read in extra data for its own internal caching which in turn breaks the positioning of the read pointer of the physical Ogg bitstream. Therefore, anything that would normally be read using the native FLAC decoding APIs, such as drflac__read_next_flac_frame_header(), need to be re-implemented so as to avoid the use of the drflac_bs object. Considering these issues, I have decided to use the slower native FLAC decoding method for the following reasons: 1) Seeking is already partially accelerated using Ogg's paging system in the code block above. 2) Seeking in an Ogg encapsulated FLAC stream is probably quite uncommon. 3) Simplicity. */ drflac_uint64 firstPCMFrameInFLACFrame = 0; drflac_uint64 lastPCMFrameInFLACFrame = 0; drflac_uint64 pcmFrameCountInThisFrame; if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); pcmFrameCountInThisFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; /* If we are seeking to the end of the file and we've just hit it, we're done. */ if (pcmFrameIndex == pFlac->totalPCMFrameCount && (runningPCMFrameCount + pcmFrameCountInThisFrame) == pFlac->totalPCMFrameCount) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { pFlac->currentPCMFrame = pcmFrameIndex; pFlac->currentFLACFrame.pcmFramesRemaining = 0; return DRFLAC_TRUE; } else { return DRFLAC_FALSE; } } if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFrame)) { /* The sample should be in this FLAC frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend it never existed and keep iterating. */ drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ drflac_uint64 pcmFramesToDecode = (size_t)(pcmFrameIndex - runningPCMFrameCount); /* <-- Safe cast because the maximum number of samples in a frame is 65535. */ if (pcmFramesToDecode == 0) { return DRFLAC_TRUE; } pFlac->currentPCMFrame = runningPCMFrameCount; return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ } else { if (result == DRFLAC_CRC_MISMATCH) { continue; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } else { /* It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this frame never existed and leave the running sample count untouched. */ drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { runningPCMFrameCount += pcmFrameCountInThisFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { continue; /* CRC mismatch. Pretend this frame never existed. */ } else { return DRFLAC_FALSE; } } } } } static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) { drflac_ogg_page_header header; drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; drflac_uint32 bytesRead = 0; /* Pre Condition: The bit stream should be sitting just past the 4-byte OggS capture pattern. */ (void)relaxed; pInit->container = drflac_container_ogg; pInit->oggFirstBytePos = 0; /* We'll get here if the first 4 bytes of the stream were the OggS capture pattern, however it doesn't necessarily mean the stream includes FLAC encoded audio. To check for this we need to scan the beginning-of-stream page markers and check if any match the FLAC specification. Important to keep in mind that the stream may be multiplexed. */ if (drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { return DRFLAC_FALSE; } pInit->runningFilePos += bytesRead; for (;;) { int pageBodySize; /* Break if we're past the beginning of stream page. */ if ((header.headerType & 0x02) == 0) { return DRFLAC_FALSE; } /* Check if it's a FLAC header. */ pageBodySize = drflac_ogg__get_page_body_size(&header); if (pageBodySize == 51) { /* 51 = the lacing value of the FLAC header packet. */ /* It could be a FLAC page... */ drflac_uint32 bytesRemainingInPage = pageBodySize; drflac_uint8 packetType; if (onRead(pUserData, &packetType, 1) != 1) { return DRFLAC_FALSE; } bytesRemainingInPage -= 1; if (packetType == 0x7F) { /* Increasingly more likely to be a FLAC page... */ drflac_uint8 sig[4]; if (onRead(pUserData, sig, 4) != 4) { return DRFLAC_FALSE; } bytesRemainingInPage -= 4; if (sig[0] == 'F' && sig[1] == 'L' && sig[2] == 'A' && sig[3] == 'C') { /* Almost certainly a FLAC page... */ drflac_uint8 mappingVersion[2]; if (onRead(pUserData, mappingVersion, 2) != 2) { return DRFLAC_FALSE; } if (mappingVersion[0] != 1) { return DRFLAC_FALSE; /* Only supporting version 1.x of the Ogg mapping. */ } /* The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to be handling it in a generic way based on the serial number and packet types. */ if (!onSeek(pUserData, 2, drflac_seek_origin_current)) { return DRFLAC_FALSE; } /* Expecting the native FLAC signature "fLaC". */ if (onRead(pUserData, sig, 4) != 4) { return DRFLAC_FALSE; } if (sig[0] == 'f' && sig[1] == 'L' && sig[2] == 'a' && sig[3] == 'C') { /* The remaining data in the page should be the STREAMINFO block. */ drflac_streaminfo streaminfo; drflac_uint8 isLastBlock; drflac_uint8 blockType; drflac_uint32 blockSize; if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { return DRFLAC_FALSE; } if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { return DRFLAC_FALSE; /* Invalid block type. First block must be the STREAMINFO block. */ } if (drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { /* Success! */ pInit->hasStreamInfoBlock = DRFLAC_TRUE; pInit->sampleRate = streaminfo.sampleRate; pInit->channels = streaminfo.channels; pInit->bitsPerSample = streaminfo.bitsPerSample; pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; pInit->hasMetadataBlocks = !isLastBlock; if (onMeta) { drflac_metadata metadata; metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; metadata.pRawData = NULL; metadata.rawDataSize = 0; metadata.data.streaminfo = streaminfo; onMeta(pUserDataMD, &metadata); } pInit->runningFilePos += pageBodySize; pInit->oggFirstBytePos = pInit->runningFilePos - 79; /* Subtracting 79 will place us right on top of the "OggS" identifier of the FLAC bos page. */ pInit->oggSerial = header.serialNumber; pInit->oggBosHeader = header; break; } else { /* Failed to read STREAMINFO block. Aww, so close... */ return DRFLAC_FALSE; } } else { /* Invalid file. */ return DRFLAC_FALSE; } } else { /* Not a FLAC header. Skip it. */ if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { return DRFLAC_FALSE; } } } else { /* Not a FLAC header. Seek past the entire page and move on to the next. */ if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { return DRFLAC_FALSE; } } } else { if (!onSeek(pUserData, pageBodySize, drflac_seek_origin_current)) { return DRFLAC_FALSE; } } pInit->runningFilePos += pageBodySize; /* Read the header of the next page. */ if (drflac_ogg__read_page_header(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { return DRFLAC_FALSE; } pInit->runningFilePos += bytesRead; } /* If we get here it means we found a FLAC audio stream. We should be sitting on the first byte of the header of the next page. The next packets in the FLAC logical stream contain the metadata. The only thing left to do in the initialization phase for Ogg is to create the Ogg bistream object. */ pInit->hasMetadataBlocks = DRFLAC_TRUE; /* <-- Always have at least VORBIS_COMMENT metadata block. */ return DRFLAC_TRUE; } #endif static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) { drflac_bool32 relaxed; drflac_uint8 id[4]; if (pInit == NULL || onRead == NULL || onSeek == NULL) { return DRFLAC_FALSE; } DRFLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); pInit->onRead = onRead; pInit->onSeek = onSeek; pInit->onMeta = onMeta; pInit->container = container; pInit->pUserData = pUserData; pInit->pUserDataMD = pUserDataMD; pInit->bs.onRead = onRead; pInit->bs.onSeek = onSeek; pInit->bs.pUserData = pUserData; drflac__reset_cache(&pInit->bs); /* If the container is explicitly defined then we can try opening in relaxed mode. */ relaxed = container != drflac_container_unknown; /* Skip over any ID3 tags. */ for (;;) { if (onRead(pUserData, id, 4) != 4) { return DRFLAC_FALSE; /* Ran out of data. */ } pInit->runningFilePos += 4; if (id[0] == 'I' && id[1] == 'D' && id[2] == '3') { drflac_uint8 header[6]; drflac_uint8 flags; drflac_uint32 headerSize; if (onRead(pUserData, header, 6) != 6) { return DRFLAC_FALSE; /* Ran out of data. */ } pInit->runningFilePos += 6; flags = header[1]; DRFLAC_COPY_MEMORY(&headerSize, header+2, 4); headerSize = drflac__unsynchsafe_32(drflac__be2host_32(headerSize)); if (flags & 0x10) { headerSize += 10; } if (!onSeek(pUserData, headerSize, drflac_seek_origin_current)) { return DRFLAC_FALSE; /* Failed to seek past the tag. */ } pInit->runningFilePos += headerSize; } else { break; } } if (id[0] == 'f' && id[1] == 'L' && id[2] == 'a' && id[3] == 'C') { return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); } #ifndef DR_FLAC_NO_OGG if (id[0] == 'O' && id[1] == 'g' && id[2] == 'g' && id[3] == 'S') { return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); } #endif /* If we get here it means we likely don't have a header. Try opening in relaxed mode, if applicable. */ if (relaxed) { if (container == drflac_container_native) { return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); } #ifndef DR_FLAC_NO_OGG if (container == drflac_container_ogg) { return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); } #endif } /* Unsupported container. */ return DRFLAC_FALSE; } static void drflac__init_from_info(drflac* pFlac, const drflac_init_info* pInit) { DRFLAC_ASSERT(pFlac != NULL); DRFLAC_ASSERT(pInit != NULL); DRFLAC_ZERO_MEMORY(pFlac, sizeof(*pFlac)); pFlac->bs = pInit->bs; pFlac->onMeta = pInit->onMeta; pFlac->pUserDataMD = pInit->pUserDataMD; pFlac->maxBlockSizeInPCMFrames = pInit->maxBlockSizeInPCMFrames; pFlac->sampleRate = pInit->sampleRate; pFlac->channels = (drflac_uint8)pInit->channels; pFlac->bitsPerSample = (drflac_uint8)pInit->bitsPerSample; pFlac->totalPCMFrameCount = pInit->totalPCMFrameCount; pFlac->container = pInit->container; } static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac_init_info init; drflac_uint32 allocationSize; drflac_uint32 wholeSIMDVectorCountPerChannel; drflac_uint32 decodedSamplesAllocationSize; #ifndef DR_FLAC_NO_OGG drflac_oggbs* pOggbs = NULL; #endif drflac_uint64 firstFramePos; drflac_uint64 seektablePos; drflac_uint32 seekpointCount; drflac_allocation_callbacks allocationCallbacks; drflac* pFlac; /* CPU support first. */ drflac__init_cpu_caps(); if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { return NULL; } if (pAllocationCallbacks != NULL) { allocationCallbacks = *pAllocationCallbacks; if (allocationCallbacks.onFree == NULL || (allocationCallbacks.onMalloc == NULL && allocationCallbacks.onRealloc == NULL)) { return NULL; /* Invalid allocation callbacks. */ } } else { allocationCallbacks.pUserData = NULL; allocationCallbacks.onMalloc = drflac__malloc_default; allocationCallbacks.onRealloc = drflac__realloc_default; allocationCallbacks.onFree = drflac__free_default; } /* The size of the allocation for the drflac object needs to be large enough to fit the following: 1) The main members of the drflac structure 2) A block of memory large enough to store the decoded samples of the largest frame in the stream 3) If the container is Ogg, a drflac_oggbs object The complicated part of the allocation is making sure there's enough room the decoded samples, taking into consideration the different SIMD instruction sets. */ allocationSize = sizeof(drflac); /* The allocation size for decoded frames depends on the number of 32-bit integers that fit inside the largest SIMD vector we are supporting. */ if ((init.maxBlockSizeInPCMFrames % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); } else { wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; } decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; allocationSize += decodedSamplesAllocationSize; allocationSize += DRFLAC_MAX_SIMD_VECTOR_SIZE; /* Allocate extra bytes to ensure we have enough for alignment. */ #ifndef DR_FLAC_NO_OGG /* There's additional data required for Ogg streams. */ if (init.container == drflac_container_ogg) { allocationSize += sizeof(drflac_oggbs); pOggbs = (drflac_oggbs*)drflac__malloc_from_callbacks(sizeof(*pOggbs), &allocationCallbacks); if (pOggbs == NULL) { return NULL; /*DRFLAC_OUT_OF_MEMORY;*/ } DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs)); pOggbs->onRead = onRead; pOggbs->onSeek = onSeek; pOggbs->pUserData = pUserData; pOggbs->currentBytePos = init.oggFirstBytePos; pOggbs->firstBytePos = init.oggFirstBytePos; pOggbs->serialNumber = init.oggSerial; pOggbs->bosPageHeader = init.oggBosHeader; pOggbs->bytesRemainingInPage = 0; } #endif /* This part is a bit awkward. We need to load the seektable so that it can be referenced in-memory, but I want the drflac object to consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading and decoding the metadata. */ firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */ seektablePos = 0; seekpointCount = 0; if (init.hasMetadataBlocks) { drflac_read_proc onReadOverride = onRead; drflac_seek_proc onSeekOverride = onSeek; void* pUserDataOverride = pUserData; #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { onReadOverride = drflac__on_read_ogg; onSeekOverride = drflac__on_seek_ogg; pUserDataOverride = (void*)pOggbs; } #endif if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { #ifndef DR_FLAC_NO_OGG drflac__free_from_callbacks(pOggbs, &allocationCallbacks); #endif return NULL; } allocationSize += seekpointCount * sizeof(drflac_seekpoint); } pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks); if (pFlac == NULL) { #ifndef DR_FLAC_NO_OGG drflac__free_from_callbacks(pOggbs, &allocationCallbacks); #endif return NULL; } drflac__init_from_info(pFlac, &init); pFlac->allocationCallbacks = allocationCallbacks; pFlac->pDecodedSamples = (drflac_int32*)drflac_align((size_t)pFlac->pExtraData, DRFLAC_MAX_SIMD_VECTOR_SIZE); #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + (seekpointCount * sizeof(drflac_seekpoint))); DRFLAC_COPY_MEMORY(pInternalOggbs, pOggbs, sizeof(*pOggbs)); /* At this point the pOggbs object has been handed over to pInternalOggbs and can be freed. */ drflac__free_from_callbacks(pOggbs, &allocationCallbacks); pOggbs = NULL; /* The Ogg bistream needs to be layered on top of the original bitstream. */ pFlac->bs.onRead = drflac__on_read_ogg; pFlac->bs.onSeek = drflac__on_seek_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; pFlac->_oggbs = (void*)pInternalOggbs; } #endif pFlac->firstFLACFramePosInBytes = firstFramePos; /* NOTE: Seektables are not currently compatible with Ogg encapsulation (Ogg has its own accelerated seeking system). I may change this later, so I'm leaving this here for now. */ #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { pFlac->pSeekpoints = NULL; pFlac->seekpointCount = 0; } else #endif { /* If we have a seektable we need to load it now, making sure we move back to where we were previously. */ if (seektablePos != 0) { pFlac->seekpointCount = seekpointCount; pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); DRFLAC_ASSERT(pFlac->bs.onSeek != NULL); DRFLAC_ASSERT(pFlac->bs.onRead != NULL); /* Seek to the seektable, then just read directly into our seektable buffer. */ if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) { drflac_uint32 iSeekpoint; for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) { if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints + iSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) == DRFLAC_SEEKPOINT_SIZE_IN_BYTES) { /* Endian swap. */ pFlac->pSeekpoints[iSeekpoint].firstPCMFrame = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstPCMFrame); pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset); pFlac->pSeekpoints[iSeekpoint].pcmFrameCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].pcmFrameCount); } else { /* Failed to read the seektable. Pretend we don't have one. */ pFlac->pSeekpoints = NULL; pFlac->seekpointCount = 0; break; } } /* We need to seek back to where we were. If this fails it's a critical error. */ if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, drflac_seek_origin_start)) { drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } } else { /* Failed to seek to the seektable. Ominous sign, but for now we can just pretend we don't have one. */ pFlac->pSeekpoints = NULL; pFlac->seekpointCount = 0; } } } /* If we get here, but don't have a STREAMINFO block, it means we've opened the stream in relaxed mode and need to decode the first frame. */ if (!init.hasStreamInfoBlock) { pFlac->currentFLACFrame.header = init.firstFrameHeader; for (;;) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { break; } else { if (result == DRFLAC_CRC_MISMATCH) { if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } continue; } else { drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } } } } return pFlac; } #ifndef DR_FLAC_NO_STDIO #include #ifndef DR_FLAC_NO_WCHAR #include /* For wcslen(), wcsrtombs() */ #endif /* Errno */ /* drflac_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ #include static drflac_result drflac_result_from_errno(int e) { switch (e) { case 0: return DRFLAC_SUCCESS; #ifdef EPERM case EPERM: return DRFLAC_INVALID_OPERATION; #endif #ifdef ENOENT case ENOENT: return DRFLAC_DOES_NOT_EXIST; #endif #ifdef ESRCH case ESRCH: return DRFLAC_DOES_NOT_EXIST; #endif #ifdef EINTR case EINTR: return DRFLAC_INTERRUPT; #endif #ifdef EIO case EIO: return DRFLAC_IO_ERROR; #endif #ifdef ENXIO case ENXIO: return DRFLAC_DOES_NOT_EXIST; #endif #ifdef E2BIG case E2BIG: return DRFLAC_INVALID_ARGS; #endif #ifdef ENOEXEC case ENOEXEC: return DRFLAC_INVALID_FILE; #endif #ifdef EBADF case EBADF: return DRFLAC_INVALID_FILE; #endif #ifdef ECHILD case ECHILD: return DRFLAC_ERROR; #endif #ifdef EAGAIN case EAGAIN: return DRFLAC_UNAVAILABLE; #endif #ifdef ENOMEM case ENOMEM: return DRFLAC_OUT_OF_MEMORY; #endif #ifdef EACCES case EACCES: return DRFLAC_ACCESS_DENIED; #endif #ifdef EFAULT case EFAULT: return DRFLAC_BAD_ADDRESS; #endif #ifdef ENOTBLK case ENOTBLK: return DRFLAC_ERROR; #endif #ifdef EBUSY case EBUSY: return DRFLAC_BUSY; #endif #ifdef EEXIST case EEXIST: return DRFLAC_ALREADY_EXISTS; #endif #ifdef EXDEV case EXDEV: return DRFLAC_ERROR; #endif #ifdef ENODEV case ENODEV: return DRFLAC_DOES_NOT_EXIST; #endif #ifdef ENOTDIR case ENOTDIR: return DRFLAC_NOT_DIRECTORY; #endif #ifdef EISDIR case EISDIR: return DRFLAC_IS_DIRECTORY; #endif #ifdef EINVAL case EINVAL: return DRFLAC_INVALID_ARGS; #endif #ifdef ENFILE case ENFILE: return DRFLAC_TOO_MANY_OPEN_FILES; #endif #ifdef EMFILE case EMFILE: return DRFLAC_TOO_MANY_OPEN_FILES; #endif #ifdef ENOTTY case ENOTTY: return DRFLAC_INVALID_OPERATION; #endif #ifdef ETXTBSY case ETXTBSY: return DRFLAC_BUSY; #endif #ifdef EFBIG case EFBIG: return DRFLAC_TOO_BIG; #endif #ifdef ENOSPC case ENOSPC: return DRFLAC_NO_SPACE; #endif #ifdef ESPIPE case ESPIPE: return DRFLAC_BAD_SEEK; #endif #ifdef EROFS case EROFS: return DRFLAC_ACCESS_DENIED; #endif #ifdef EMLINK case EMLINK: return DRFLAC_TOO_MANY_LINKS; #endif #ifdef EPIPE case EPIPE: return DRFLAC_BAD_PIPE; #endif #ifdef EDOM case EDOM: return DRFLAC_OUT_OF_RANGE; #endif #ifdef ERANGE case ERANGE: return DRFLAC_OUT_OF_RANGE; #endif #ifdef EDEADLK case EDEADLK: return DRFLAC_DEADLOCK; #endif #ifdef ENAMETOOLONG case ENAMETOOLONG: return DRFLAC_PATH_TOO_LONG; #endif #ifdef ENOLCK case ENOLCK: return DRFLAC_ERROR; #endif #ifdef ENOSYS case ENOSYS: return DRFLAC_NOT_IMPLEMENTED; #endif #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ case ENOTEMPTY: return DRFLAC_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP case ELOOP: return DRFLAC_TOO_MANY_LINKS; #endif #ifdef ENOMSG case ENOMSG: return DRFLAC_NO_MESSAGE; #endif #ifdef EIDRM case EIDRM: return DRFLAC_ERROR; #endif #ifdef ECHRNG case ECHRNG: return DRFLAC_ERROR; #endif #ifdef EL2NSYNC case EL2NSYNC: return DRFLAC_ERROR; #endif #ifdef EL3HLT case EL3HLT: return DRFLAC_ERROR; #endif #ifdef EL3RST case EL3RST: return DRFLAC_ERROR; #endif #ifdef ELNRNG case ELNRNG: return DRFLAC_OUT_OF_RANGE; #endif #ifdef EUNATCH case EUNATCH: return DRFLAC_ERROR; #endif #ifdef ENOCSI case ENOCSI: return DRFLAC_ERROR; #endif #ifdef EL2HLT case EL2HLT: return DRFLAC_ERROR; #endif #ifdef EBADE case EBADE: return DRFLAC_ERROR; #endif #ifdef EBADR case EBADR: return DRFLAC_ERROR; #endif #ifdef EXFULL case EXFULL: return DRFLAC_ERROR; #endif #ifdef ENOANO case ENOANO: return DRFLAC_ERROR; #endif #ifdef EBADRQC case EBADRQC: return DRFLAC_ERROR; #endif #ifdef EBADSLT case EBADSLT: return DRFLAC_ERROR; #endif #ifdef EBFONT case EBFONT: return DRFLAC_INVALID_FILE; #endif #ifdef ENOSTR case ENOSTR: return DRFLAC_ERROR; #endif #ifdef ENODATA case ENODATA: return DRFLAC_NO_DATA_AVAILABLE; #endif #ifdef ETIME case ETIME: return DRFLAC_TIMEOUT; #endif #ifdef ENOSR case ENOSR: return DRFLAC_NO_DATA_AVAILABLE; #endif #ifdef ENONET case ENONET: return DRFLAC_NO_NETWORK; #endif #ifdef ENOPKG case ENOPKG: return DRFLAC_ERROR; #endif #ifdef EREMOTE case EREMOTE: return DRFLAC_ERROR; #endif #ifdef ENOLINK case ENOLINK: return DRFLAC_ERROR; #endif #ifdef EADV case EADV: return DRFLAC_ERROR; #endif #ifdef ESRMNT case ESRMNT: return DRFLAC_ERROR; #endif #ifdef ECOMM case ECOMM: return DRFLAC_ERROR; #endif #ifdef EPROTO case EPROTO: return DRFLAC_ERROR; #endif #ifdef EMULTIHOP case EMULTIHOP: return DRFLAC_ERROR; #endif #ifdef EDOTDOT case EDOTDOT: return DRFLAC_ERROR; #endif #ifdef EBADMSG case EBADMSG: return DRFLAC_BAD_MESSAGE; #endif #ifdef EOVERFLOW case EOVERFLOW: return DRFLAC_TOO_BIG; #endif #ifdef ENOTUNIQ case ENOTUNIQ: return DRFLAC_NOT_UNIQUE; #endif #ifdef EBADFD case EBADFD: return DRFLAC_ERROR; #endif #ifdef EREMCHG case EREMCHG: return DRFLAC_ERROR; #endif #ifdef ELIBACC case ELIBACC: return DRFLAC_ACCESS_DENIED; #endif #ifdef ELIBBAD case ELIBBAD: return DRFLAC_INVALID_FILE; #endif #ifdef ELIBSCN case ELIBSCN: return DRFLAC_INVALID_FILE; #endif #ifdef ELIBMAX case ELIBMAX: return DRFLAC_ERROR; #endif #ifdef ELIBEXEC case ELIBEXEC: return DRFLAC_ERROR; #endif #ifdef EILSEQ case EILSEQ: return DRFLAC_INVALID_DATA; #endif #ifdef ERESTART case ERESTART: return DRFLAC_ERROR; #endif #ifdef ESTRPIPE case ESTRPIPE: return DRFLAC_ERROR; #endif #ifdef EUSERS case EUSERS: return DRFLAC_ERROR; #endif #ifdef ENOTSOCK case ENOTSOCK: return DRFLAC_NOT_SOCKET; #endif #ifdef EDESTADDRREQ case EDESTADDRREQ: return DRFLAC_NO_ADDRESS; #endif #ifdef EMSGSIZE case EMSGSIZE: return DRFLAC_TOO_BIG; #endif #ifdef EPROTOTYPE case EPROTOTYPE: return DRFLAC_BAD_PROTOCOL; #endif #ifdef ENOPROTOOPT case ENOPROTOOPT: return DRFLAC_PROTOCOL_UNAVAILABLE; #endif #ifdef EPROTONOSUPPORT case EPROTONOSUPPORT: return DRFLAC_PROTOCOL_NOT_SUPPORTED; #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return DRFLAC_SOCKET_NOT_SUPPORTED; #endif #ifdef EOPNOTSUPP case EOPNOTSUPP: return DRFLAC_INVALID_OPERATION; #endif #ifdef EPFNOSUPPORT case EPFNOSUPPORT: return DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED; #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: return DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED; #endif #ifdef EADDRINUSE case EADDRINUSE: return DRFLAC_ALREADY_IN_USE; #endif #ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: return DRFLAC_ERROR; #endif #ifdef ENETDOWN case ENETDOWN: return DRFLAC_NO_NETWORK; #endif #ifdef ENETUNREACH case ENETUNREACH: return DRFLAC_NO_NETWORK; #endif #ifdef ENETRESET case ENETRESET: return DRFLAC_NO_NETWORK; #endif #ifdef ECONNABORTED case ECONNABORTED: return DRFLAC_NO_NETWORK; #endif #ifdef ECONNRESET case ECONNRESET: return DRFLAC_CONNECTION_RESET; #endif #ifdef ENOBUFS case ENOBUFS: return DRFLAC_NO_SPACE; #endif #ifdef EISCONN case EISCONN: return DRFLAC_ALREADY_CONNECTED; #endif #ifdef ENOTCONN case ENOTCONN: return DRFLAC_NOT_CONNECTED; #endif #ifdef ESHUTDOWN case ESHUTDOWN: return DRFLAC_ERROR; #endif #ifdef ETOOMANYREFS case ETOOMANYREFS: return DRFLAC_ERROR; #endif #ifdef ETIMEDOUT case ETIMEDOUT: return DRFLAC_TIMEOUT; #endif #ifdef ECONNREFUSED case ECONNREFUSED: return DRFLAC_CONNECTION_REFUSED; #endif #ifdef EHOSTDOWN case EHOSTDOWN: return DRFLAC_NO_HOST; #endif #ifdef EHOSTUNREACH case EHOSTUNREACH: return DRFLAC_NO_HOST; #endif #ifdef EALREADY case EALREADY: return DRFLAC_IN_PROGRESS; #endif #ifdef EINPROGRESS case EINPROGRESS: return DRFLAC_IN_PROGRESS; #endif #ifdef ESTALE case ESTALE: return DRFLAC_INVALID_FILE; #endif #ifdef EUCLEAN case EUCLEAN: return DRFLAC_ERROR; #endif #ifdef ENOTNAM case ENOTNAM: return DRFLAC_ERROR; #endif #ifdef ENAVAIL case ENAVAIL: return DRFLAC_ERROR; #endif #ifdef EISNAM case EISNAM: return DRFLAC_ERROR; #endif #ifdef EREMOTEIO case EREMOTEIO: return DRFLAC_IO_ERROR; #endif #ifdef EDQUOT case EDQUOT: return DRFLAC_NO_SPACE; #endif #ifdef ENOMEDIUM case ENOMEDIUM: return DRFLAC_DOES_NOT_EXIST; #endif #ifdef EMEDIUMTYPE case EMEDIUMTYPE: return DRFLAC_ERROR; #endif #ifdef ECANCELED case ECANCELED: return DRFLAC_CANCELLED; #endif #ifdef ENOKEY case ENOKEY: return DRFLAC_ERROR; #endif #ifdef EKEYEXPIRED case EKEYEXPIRED: return DRFLAC_ERROR; #endif #ifdef EKEYREVOKED case EKEYREVOKED: return DRFLAC_ERROR; #endif #ifdef EKEYREJECTED case EKEYREJECTED: return DRFLAC_ERROR; #endif #ifdef EOWNERDEAD case EOWNERDEAD: return DRFLAC_ERROR; #endif #ifdef ENOTRECOVERABLE case ENOTRECOVERABLE: return DRFLAC_ERROR; #endif #ifdef ERFKILL case ERFKILL: return DRFLAC_ERROR; #endif #ifdef EHWPOISON case EHWPOISON: return DRFLAC_ERROR; #endif default: return DRFLAC_ERROR; } } /* End Errno */ /* fopen */ static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { #if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ } if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRFLAC_INVALID_ARGS; } #if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drflac_result_from_errno(err); } #else #if defined(_WIN32) || defined(__APPLE__) *ppFile = fopen(pFilePath, pOpenMode); #else #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) *ppFile = fopen64(pFilePath, pOpenMode); #else *ppFile = fopen(pFilePath, pOpenMode); #endif #endif if (*ppFile == NULL) { drflac_result result = drflac_result_from_errno(errno); if (result == DRFLAC_SUCCESS) { result = DRFLAC_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ } return result; } #endif return DRFLAC_SUCCESS; } /* _wfopen() isn't always available in all compilation environments. * Windows only. * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). * MinGW-64 (both 32- and 64-bit) seems to support it. * MinGW wraps it in !defined(__STRICT_ANSI__). * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRFLAC_HAS_WFOPEN #endif #endif #ifndef DR_FLAC_NO_WCHAR static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drflac_allocation_callbacks* pAllocationCallbacks) { if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ } if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRFLAC_INVALID_ARGS; } #if defined(DRFLAC_HAS_WFOPEN) { /* Use _wfopen() on Windows. */ #if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drflac_result_from_errno(err); } #else *ppFile = _wfopen(pFilePath, pOpenMode); if (*ppFile == NULL) { return drflac_result_from_errno(errno); } #endif (void)pAllocationCallbacks; } #else /* Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. */ /* Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just need to abort with an error. If you encounter a compiler lacking such support, add it to this list and submit a bug report and it'll be added to the library upstream. */ #if defined(__DJGPP__) { /* Nothing to do here. This will fall through to the error check below. */ } #else { mbstate_t mbs; size_t lenMB; const wchar_t* pFilePathTemp = pFilePath; char* pFilePathMB = NULL; char pOpenModeMB[32] = {0}; /* Get the length first. */ DRFLAC_ZERO_OBJECT(&mbs); lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); if (lenMB == (size_t)-1) { return drflac_result_from_errno(errno); } pFilePathMB = (char*)drflac__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); if (pFilePathMB == NULL) { return DRFLAC_OUT_OF_MEMORY; } pFilePathTemp = pFilePath; DRFLAC_ZERO_OBJECT(&mbs); wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ { size_t i = 0; for (;;) { if (pOpenMode[i] == 0) { pOpenModeMB[i] = '\0'; break; } pOpenModeMB[i] = (char)pOpenMode[i]; i += 1; } } *ppFile = fopen(pFilePathMB, pOpenModeMB); drflac__free_from_callbacks(pFilePathMB, pAllocationCallbacks); } #endif if (*ppFile == NULL) { return DRFLAC_ERROR; } #endif return DRFLAC_SUCCESS; } #endif /* End fopen */ static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) { return fread(bufferOut, 1, bytesToRead, (FILE*)pUserData); } static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) { DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; FILE* pFile; if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { return NULL; } pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; } return pFlac; } #ifndef DR_FLAC_NO_WCHAR DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; FILE* pFile; if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { return NULL; } pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; } return pFlac; } #endif DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; FILE* pFile; if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { return NULL; } pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; } return pFlac; } #ifndef DR_FLAC_NO_WCHAR DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; FILE* pFile; if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { return NULL; } pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; } return pFlac; } #endif #endif /* DR_FLAC_NO_STDIO */ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; size_t bytesRemaining; DRFLAC_ASSERT(memoryStream != NULL); DRFLAC_ASSERT(memoryStream->dataSize >= memoryStream->currentReadPos); bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } if (bytesToRead > 0) { DRFLAC_COPY_MEMORY(bufferOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); memoryStream->currentReadPos += bytesToRead; } return bytesToRead; } static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; DRFLAC_ASSERT(memoryStream != NULL); DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ if (offset > (drflac_int64)memoryStream->dataSize) { return DRFLAC_FALSE; } if (origin == drflac_seek_origin_current) { if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { memoryStream->currentReadPos += offset; } else { return DRFLAC_FALSE; /* Trying to seek too far forward. */ } } else { if ((drflac_uint32)offset <= memoryStream->dataSize) { memoryStream->currentReadPos = offset; } else { return DRFLAC_FALSE; /* Trying to seek too far forward. */ } } return DRFLAC_TRUE; } DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac__memory_stream memoryStream; drflac* pFlac; memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } pFlac->memoryStream = memoryStream; /* This is an awful hack... */ #ifndef DR_FLAC_NO_OGG if (pFlac->container == drflac_container_ogg) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; oggbs->pUserData = &pFlac->memoryStream; } else #endif { pFlac->bs.pUserData = &pFlac->memoryStream; } return pFlac; } DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac__memory_stream memoryStream; drflac* pFlac; memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } pFlac->memoryStream = memoryStream; /* This is an awful hack... */ #ifndef DR_FLAC_NO_OGG if (pFlac->container == drflac_container_ogg) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; oggbs->pUserData = &pFlac->memoryStream; } else #endif { pFlac->bs.pUserData = &pFlac->memoryStream; } return pFlac; } DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData, pAllocationCallbacks); } DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData, pAllocationCallbacks); } DRFLAC_API void drflac_close(drflac* pFlac) { if (pFlac == NULL) { return; } #ifndef DR_FLAC_NO_STDIO /* If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() was used by looking at the callbacks. */ if (pFlac->bs.onRead == drflac__on_read_stdio) { fclose((FILE*)pFlac->bs.pUserData); } #ifndef DR_FLAC_NO_OGG /* Need to clean up Ogg streams a bit differently due to the way the bit streaming is chained. */ if (pFlac->container == drflac_container_ogg) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; DRFLAC_ASSERT(pFlac->bs.onRead == drflac__on_read_ogg); if (oggbs->onRead == drflac__on_read_stdio) { fclose((FILE*)oggbs->pUserData); } } #endif #endif drflac__free_from_callbacks(pFlac, &pFlac->allocationCallbacks); } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; drflac_uint32 right0 = left0 - side0; drflac_uint32 right1 = left1 - side1; drflac_uint32 right2 = left2 - side2; drflac_uint32 right3 = left3 - side3; pOutputSamples[i*8+0] = (drflac_int32)left0; pOutputSamples[i*8+1] = (drflac_int32)right0; pOutputSamples[i*8+2] = (drflac_int32)left1; pOutputSamples[i*8+3] = (drflac_int32)right1; pOutputSamples[i*8+4] = (drflac_int32)left2; pOutputSamples[i*8+5] = (drflac_int32)right2; pOutputSamples[i*8+6] = (drflac_int32)left3; pOutputSamples[i*8+7] = (drflac_int32)right3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); for (i = 0; i < frameCount4; ++i) { __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i right = _mm_sub_epi32(left, side); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; int32x4_t shift0_4; int32x4_t shift1_4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); shift0_4 = vdupq_n_s32(shift0); shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { uint32x4_t left; uint32x4_t side; uint32x4_t right; left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); right = vsubq_u32(left, side); drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; drflac_uint32 left0 = right0 + side0; drflac_uint32 left1 = right1 + side1; drflac_uint32 left2 = right2 + side2; drflac_uint32 left3 = right3 + side3; pOutputSamples[i*8+0] = (drflac_int32)left0; pOutputSamples[i*8+1] = (drflac_int32)right0; pOutputSamples[i*8+2] = (drflac_int32)left1; pOutputSamples[i*8+3] = (drflac_int32)right1; pOutputSamples[i*8+4] = (drflac_int32)left2; pOutputSamples[i*8+5] = (drflac_int32)right2; pOutputSamples[i*8+6] = (drflac_int32)left3; pOutputSamples[i*8+7] = (drflac_int32)right3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); for (i = 0; i < frameCount4; ++i) { __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i left = _mm_add_epi32(right, side); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; int32x4_t shift0_4; int32x4_t shift1_4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); shift0_4 = vdupq_n_s32(shift0); shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { uint32x4_t side; uint32x4_t right; uint32x4_t left; side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); left = vaddq_u32(right, side); drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left; pOutputSamples[i*2+1] = (drflac_int32)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_int32 shift = unusedBitsPerSample; if (shift > 0) { shift -= 1; for (i = 0; i < frameCount4; ++i) { drflac_uint32 temp0L; drflac_uint32 temp1L; drflac_uint32 temp2L; drflac_uint32 temp3L; drflac_uint32 temp0R; drflac_uint32 temp1R; drflac_uint32 temp2R; drflac_uint32 temp3R; drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid0 = (mid0 << 1) | (side0 & 0x01); mid1 = (mid1 << 1) | (side1 & 0x01); mid2 = (mid2 << 1) | (side2 & 0x01); mid3 = (mid3 << 1) | (side3 & 0x01); temp0L = (mid0 + side0) << shift; temp1L = (mid1 + side1) << shift; temp2L = (mid2 + side2) << shift; temp3L = (mid3 + side3) << shift; temp0R = (mid0 - side0) << shift; temp1R = (mid1 - side1) << shift; temp2R = (mid2 - side2) << shift; temp3R = (mid3 - side3) << shift; pOutputSamples[i*8+0] = (drflac_int32)temp0L; pOutputSamples[i*8+1] = (drflac_int32)temp0R; pOutputSamples[i*8+2] = (drflac_int32)temp1L; pOutputSamples[i*8+3] = (drflac_int32)temp1R; pOutputSamples[i*8+4] = (drflac_int32)temp2L; pOutputSamples[i*8+5] = (drflac_int32)temp2R; pOutputSamples[i*8+6] = (drflac_int32)temp3L; pOutputSamples[i*8+7] = (drflac_int32)temp3R; } } else { for (i = 0; i < frameCount4; ++i) { drflac_uint32 temp0L; drflac_uint32 temp1L; drflac_uint32 temp2L; drflac_uint32 temp3L; drflac_uint32 temp0R; drflac_uint32 temp1R; drflac_uint32 temp2R; drflac_uint32 temp3R; drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid0 = (mid0 << 1) | (side0 & 0x01); mid1 = (mid1 << 1) | (side1 & 0x01); mid2 = (mid2 << 1) | (side2 & 0x01); mid3 = (mid3 << 1) | (side3 & 0x01); temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); pOutputSamples[i*8+0] = (drflac_int32)temp0L; pOutputSamples[i*8+1] = (drflac_int32)temp0R; pOutputSamples[i*8+2] = (drflac_int32)temp1L; pOutputSamples[i*8+3] = (drflac_int32)temp1R; pOutputSamples[i*8+4] = (drflac_int32)temp2L; pOutputSamples[i*8+5] = (drflac_int32)temp2R; pOutputSamples[i*8+6] = (drflac_int32)temp3L; pOutputSamples[i*8+7] = (drflac_int32)temp3R; } } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_int32 shift = unusedBitsPerSample; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); if (shift == 0) { for (i = 0; i < frameCount4; ++i) { __m128i mid; __m128i side; __m128i left; __m128i right; mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; } } else { shift -= 1; for (i = 0; i < frameCount4; ++i) { __m128i mid; __m128i side; __m128i left; __m128i right; mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); } } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_int32 shift = unusedBitsPerSample; int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ uint32x4_t one4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); one4 = vdupq_n_u32(1); if (shift == 0) { for (i = 0; i < frameCount4; ++i) { uint32x4_t mid; uint32x4_t side; int32x4_t left; int32x4_t right; mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; } } else { int32x4_t shift4; shift -= 1; shift4 = vdupq_n_s32(shift); for (i = 0; i < frameCount4; ++i) { uint32x4_t mid; uint32x4_t side; int32x4_t left; int32x4_t right; mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); } } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)); pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; pOutputSamples[i*8+0] = (drflac_int32)tempL0; pOutputSamples[i*8+1] = (drflac_int32)tempR0; pOutputSamples[i*8+2] = (drflac_int32)tempL1; pOutputSamples[i*8+3] = (drflac_int32)tempR1; pOutputSamples[i*8+4] = (drflac_int32)tempL2; pOutputSamples[i*8+5] = (drflac_int32)tempR2; pOutputSamples[i*8+6] = (drflac_int32)tempL3; pOutputSamples[i*8+7] = (drflac_int32)tempR3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; int32x4_t shift4_0 = vdupq_n_s32(shift0); int32x4_t shift4_1 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { int32x4_t left; int32x4_t right; left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift4_0)); right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift4_1)); drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut) { drflac_uint64 framesRead; drflac_uint32 unusedBitsPerSample; if (pFlac == NULL || framesToRead == 0) { return 0; } if (pBufferOut == NULL) { return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); } DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); unusedBitsPerSample = 32 - pFlac->bitsPerSample; framesRead = 0; while (framesToRead > 0) { /* If we've run out of samples in this frame, go to the next. */ if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; drflac_uint64 frameCountThisIteration = framesToRead; if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; } if (channelCount == 2) { const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; switch (pFlac->currentFLACFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { drflac_read_pcm_frames_s32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { drflac_read_pcm_frames_s32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: { drflac_read_pcm_frames_s32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: default: { drflac_read_pcm_frames_s32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; } } else { /* Generic interleaving. */ drflac_uint64 i; for (i = 0; i < frameCountThisIteration; ++i) { unsigned int j; for (j = 0; j < channelCount; ++j) { pBufferOut[(i*channelCount)+j] = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); } } } framesRead += frameCountThisIteration; pBufferOut += frameCountThisIteration * channelCount; framesToRead -= frameCountThisIteration; pFlac->currentPCMFrame += frameCountThisIteration; pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; } } return framesRead; } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); drflac_uint32 right = left - side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; drflac_uint32 right0 = left0 - side0; drflac_uint32 right1 = left1 - side1; drflac_uint32 right2 = left2 - side2; drflac_uint32 right3 = left3 - side3; left0 >>= 16; left1 >>= 16; left2 >>= 16; left3 >>= 16; right0 >>= 16; right1 >>= 16; right2 >>= 16; right3 >>= 16; pOutputSamples[i*8+0] = (drflac_int16)left0; pOutputSamples[i*8+1] = (drflac_int16)right0; pOutputSamples[i*8+2] = (drflac_int16)left1; pOutputSamples[i*8+3] = (drflac_int16)right1; pOutputSamples[i*8+4] = (drflac_int16)left2; pOutputSamples[i*8+5] = (drflac_int16)right2; pOutputSamples[i*8+6] = (drflac_int16)left3; pOutputSamples[i*8+7] = (drflac_int16)right3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); for (i = 0; i < frameCount4; ++i) { __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i right = _mm_sub_epi32(left, side); left = _mm_srai_epi32(left, 16); right = _mm_srai_epi32(right, 16); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; int32x4_t shift0_4; int32x4_t shift1_4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); shift0_4 = vdupq_n_s32(shift0); shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { uint32x4_t left; uint32x4_t side; uint32x4_t right; left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); right = vsubq_u32(left, side); left = vshrq_n_u32(left, 16); right = vshrq_n_u32(right, 16); drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s16__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s16__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); drflac_uint32 left = right + side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; drflac_uint32 left0 = right0 + side0; drflac_uint32 left1 = right1 + side1; drflac_uint32 left2 = right2 + side2; drflac_uint32 left3 = right3 + side3; left0 >>= 16; left1 >>= 16; left2 >>= 16; left3 >>= 16; right0 >>= 16; right1 >>= 16; right2 >>= 16; right3 >>= 16; pOutputSamples[i*8+0] = (drflac_int16)left0; pOutputSamples[i*8+1] = (drflac_int16)right0; pOutputSamples[i*8+2] = (drflac_int16)left1; pOutputSamples[i*8+3] = (drflac_int16)right1; pOutputSamples[i*8+4] = (drflac_int16)left2; pOutputSamples[i*8+5] = (drflac_int16)right2; pOutputSamples[i*8+6] = (drflac_int16)left3; pOutputSamples[i*8+7] = (drflac_int16)right3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); for (i = 0; i < frameCount4; ++i) { __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i left = _mm_add_epi32(right, side); left = _mm_srai_epi32(left, 16); right = _mm_srai_epi32(right, 16); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; int32x4_t shift0_4; int32x4_t shift1_4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); shift0_4 = vdupq_n_s32(shift0); shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { uint32x4_t side; uint32x4_t right; uint32x4_t left; side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); left = vaddq_u32(right, side); left = vshrq_n_u32(left, 16); right = vshrq_n_u32(right, 16); drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; left >>= 16; right >>= 16; pOutputSamples[i*2+0] = (drflac_int16)left; pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s16__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s16__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift = unusedBitsPerSample; if (shift > 0) { shift -= 1; for (i = 0; i < frameCount4; ++i) { drflac_uint32 temp0L; drflac_uint32 temp1L; drflac_uint32 temp2L; drflac_uint32 temp3L; drflac_uint32 temp0R; drflac_uint32 temp1R; drflac_uint32 temp2R; drflac_uint32 temp3R; drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid0 = (mid0 << 1) | (side0 & 0x01); mid1 = (mid1 << 1) | (side1 & 0x01); mid2 = (mid2 << 1) | (side2 & 0x01); mid3 = (mid3 << 1) | (side3 & 0x01); temp0L = (mid0 + side0) << shift; temp1L = (mid1 + side1) << shift; temp2L = (mid2 + side2) << shift; temp3L = (mid3 + side3) << shift; temp0R = (mid0 - side0) << shift; temp1R = (mid1 - side1) << shift; temp2R = (mid2 - side2) << shift; temp3R = (mid3 - side3) << shift; temp0L >>= 16; temp1L >>= 16; temp2L >>= 16; temp3L >>= 16; temp0R >>= 16; temp1R >>= 16; temp2R >>= 16; temp3R >>= 16; pOutputSamples[i*8+0] = (drflac_int16)temp0L; pOutputSamples[i*8+1] = (drflac_int16)temp0R; pOutputSamples[i*8+2] = (drflac_int16)temp1L; pOutputSamples[i*8+3] = (drflac_int16)temp1R; pOutputSamples[i*8+4] = (drflac_int16)temp2L; pOutputSamples[i*8+5] = (drflac_int16)temp2R; pOutputSamples[i*8+6] = (drflac_int16)temp3L; pOutputSamples[i*8+7] = (drflac_int16)temp3R; } } else { for (i = 0; i < frameCount4; ++i) { drflac_uint32 temp0L; drflac_uint32 temp1L; drflac_uint32 temp2L; drflac_uint32 temp3L; drflac_uint32 temp0R; drflac_uint32 temp1R; drflac_uint32 temp2R; drflac_uint32 temp3R; drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid0 = (mid0 << 1) | (side0 & 0x01); mid1 = (mid1 << 1) | (side1 & 0x01); mid2 = (mid2 << 1) | (side2 & 0x01); mid3 = (mid3 << 1) | (side3 & 0x01); temp0L = ((drflac_int32)(mid0 + side0) >> 1); temp1L = ((drflac_int32)(mid1 + side1) >> 1); temp2L = ((drflac_int32)(mid2 + side2) >> 1); temp3L = ((drflac_int32)(mid3 + side3) >> 1); temp0R = ((drflac_int32)(mid0 - side0) >> 1); temp1R = ((drflac_int32)(mid1 - side1) >> 1); temp2R = ((drflac_int32)(mid2 - side2) >> 1); temp3R = ((drflac_int32)(mid3 - side3) >> 1); temp0L >>= 16; temp1L >>= 16; temp2L >>= 16; temp3L >>= 16; temp0R >>= 16; temp1R >>= 16; temp2R >>= 16; temp3R >>= 16; pOutputSamples[i*8+0] = (drflac_int16)temp0L; pOutputSamples[i*8+1] = (drflac_int16)temp0R; pOutputSamples[i*8+2] = (drflac_int16)temp1L; pOutputSamples[i*8+3] = (drflac_int16)temp1R; pOutputSamples[i*8+4] = (drflac_int16)temp2L; pOutputSamples[i*8+5] = (drflac_int16)temp2R; pOutputSamples[i*8+6] = (drflac_int16)temp3L; pOutputSamples[i*8+7] = (drflac_int16)temp3R; } } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift = unusedBitsPerSample; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); if (shift == 0) { for (i = 0; i < frameCount4; ++i) { __m128i mid; __m128i side; __m128i left; __m128i right; mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); left = _mm_srai_epi32(left, 16); right = _mm_srai_epi32(right, 16); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); } } else { shift -= 1; for (i = 0; i < frameCount4; ++i) { __m128i mid; __m128i side; __m128i left; __m128i right; mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); left = _mm_srai_epi32(left, 16); right = _mm_srai_epi32(right, 16); _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); } } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift = unusedBitsPerSample; int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); if (shift == 0) { for (i = 0; i < frameCount4; ++i) { uint32x4_t mid; uint32x4_t side; int32x4_t left; int32x4_t right; mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); left = vshrq_n_s32(left, 16); right = vshrq_n_s32(right, 16); drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); } } else { int32x4_t shift4; shift -= 1; shift4 = vdupq_n_s32(shift); for (i = 0; i < frameCount4; ++i) { uint32x4_t mid; uint32x4_t side; int32x4_t left; int32x4_t right; mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); left = vshrq_n_s32(left, 16); right = vshrq_n_s32(right, 16); drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); } } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s16__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s16__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) >> 16); pOutputSamples[i*2+1] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) >> 16); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; tempL0 >>= 16; tempL1 >>= 16; tempL2 >>= 16; tempL3 >>= 16; tempR0 >>= 16; tempR1 >>= 16; tempR2 >>= 16; tempR3 >>= 16; pOutputSamples[i*8+0] = (drflac_int16)tempL0; pOutputSamples[i*8+1] = (drflac_int16)tempR0; pOutputSamples[i*8+2] = (drflac_int16)tempL1; pOutputSamples[i*8+3] = (drflac_int16)tempR1; pOutputSamples[i*8+4] = (drflac_int16)tempL2; pOutputSamples[i*8+5] = (drflac_int16)tempR2; pOutputSamples[i*8+6] = (drflac_int16)tempL3; pOutputSamples[i*8+7] = (drflac_int16)tempR3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); left = _mm_srai_epi32(left, 16); right = _mm_srai_epi32(right, 16); /* At this point we have results. We can now pack and interleave these into a single __m128i object and then store the in the output buffer. */ _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; int32x4_t shift0_4 = vdupq_n_s32(shift0); int32x4_t shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { int32x4_t left; int32x4_t right; left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); left = vshrq_n_s32(left, 16); right = vshrq_n_s32(right, 16); drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_s16__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_s16__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut) { drflac_uint64 framesRead; drflac_uint32 unusedBitsPerSample; if (pFlac == NULL || framesToRead == 0) { return 0; } if (pBufferOut == NULL) { return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); } DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); unusedBitsPerSample = 32 - pFlac->bitsPerSample; framesRead = 0; while (framesToRead > 0) { /* If we've run out of samples in this frame, go to the next. */ if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; drflac_uint64 frameCountThisIteration = framesToRead; if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; } if (channelCount == 2) { const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; switch (pFlac->currentFLACFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { drflac_read_pcm_frames_s16__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { drflac_read_pcm_frames_s16__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: { drflac_read_pcm_frames_s16__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: default: { drflac_read_pcm_frames_s16__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; } } else { /* Generic interleaving. */ drflac_uint64 i; for (i = 0; i < frameCountThisIteration; ++i) { unsigned int j; for (j = 0; j < channelCount; ++j) { drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); pBufferOut[(i*channelCount)+j] = (drflac_int16)(sampleS32 >> 16); } } } framesRead += frameCountThisIteration; pBufferOut += frameCountThisIteration * channelCount; framesToRead -= frameCountThisIteration; pFlac->currentPCMFrame += frameCountThisIteration; pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; } } return framesRead; } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; float factor = 1 / 2147483648.0; for (i = 0; i < frameCount4; ++i) { drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; drflac_uint32 right0 = left0 - side0; drflac_uint32 right1 = left1 - side1; drflac_uint32 right2 = left2 - side2; drflac_uint32 right3 = left3 - side3; pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left * factor; pOutputSamples[i*2+1] = (drflac_int32)right * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; __m128 factor; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor = _mm_set1_ps(1.0f / 8388608.0f); for (i = 0; i < frameCount4; ++i) { __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i right = _mm_sub_epi32(left, side); __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; float32x4_t factor4; int32x4_t shift0_4; int32x4_t shift1_4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor4 = vdupq_n_f32(1.0f / 8388608.0f); shift0_4 = vdupq_n_s32(shift0); shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { uint32x4_t left; uint32x4_t side; uint32x4_t right; float32x4_t leftf; float32x4_t rightf; left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); right = vsubq_u32(left, side); leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 left = pInputSamples0U32[i] << shift0; drflac_uint32 side = pInputSamples1U32[i] << shift1; drflac_uint32 right = left - side; pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_f32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_f32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; float factor = 1 / 2147483648.0; for (i = 0; i < frameCount4; ++i) { drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; drflac_uint32 left0 = right0 + side0; drflac_uint32 left1 = right1 + side1; drflac_uint32 left2 = right2 + side2; drflac_uint32 left3 = right3 + side3; pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left * factor; pOutputSamples[i*2+1] = (drflac_int32)right * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; __m128 factor; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor = _mm_set1_ps(1.0f / 8388608.0f); for (i = 0; i < frameCount4; ++i) { __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i left = _mm_add_epi32(right, side); __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; float32x4_t factor4; int32x4_t shift0_4; int32x4_t shift1_4; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor4 = vdupq_n_f32(1.0f / 8388608.0f); shift0_4 = vdupq_n_s32(shift0); shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { uint32x4_t side; uint32x4_t right; uint32x4_t left; float32x4_t leftf; float32x4_t rightf; side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); left = vaddq_u32(right, side); leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 side = pInputSamples0U32[i] << shift0; drflac_uint32 right = pInputSamples1U32[i] << shift1; drflac_uint32 left = right + side; pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_f32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_f32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (float)((((drflac_int32)(mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); pOutputSamples[i*2+1] = (float)((((drflac_int32)(mid - side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift = unusedBitsPerSample; float factor = 1 / 2147483648.0; if (shift > 0) { shift -= 1; for (i = 0; i < frameCount4; ++i) { drflac_uint32 temp0L; drflac_uint32 temp1L; drflac_uint32 temp2L; drflac_uint32 temp3L; drflac_uint32 temp0R; drflac_uint32 temp1R; drflac_uint32 temp2R; drflac_uint32 temp3R; drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid0 = (mid0 << 1) | (side0 & 0x01); mid1 = (mid1 << 1) | (side1 & 0x01); mid2 = (mid2 << 1) | (side2 & 0x01); mid3 = (mid3 << 1) | (side3 & 0x01); temp0L = (mid0 + side0) << shift; temp1L = (mid1 + side1) << shift; temp2L = (mid2 + side2) << shift; temp3L = (mid3 + side3) << shift; temp0R = (mid0 - side0) << shift; temp1R = (mid1 - side1) << shift; temp2R = (mid2 - side2) << shift; temp3R = (mid3 - side3) << shift; pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; } } else { for (i = 0; i < frameCount4; ++i) { drflac_uint32 temp0L; drflac_uint32 temp1L; drflac_uint32 temp2L; drflac_uint32 temp3L; drflac_uint32 temp0R; drflac_uint32 temp1R; drflac_uint32 temp2R; drflac_uint32 temp3R; drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid0 = (mid0 << 1) | (side0 & 0x01); mid1 = (mid1 << 1) | (side1 & 0x01); mid2 = (mid2 << 1) | (side2 & 0x01); mid3 = (mid3 << 1) | (side3 & 0x01); temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; } } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) * factor; pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift = unusedBitsPerSample - 8; float factor; __m128 factor128; DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor = 1.0f / 8388608.0f; factor128 = _mm_set1_ps(factor); if (shift == 0) { for (i = 0; i < frameCount4; ++i) { __m128i mid; __m128i side; __m128i tempL; __m128i tempR; __m128 leftf; __m128 rightf; mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); tempL = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); tempR = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; } } else { shift -= 1; for (i = 0; i < frameCount4; ++i) { __m128i mid; __m128i side; __m128i tempL; __m128i tempR; __m128 leftf; __m128 rightf; mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); tempL = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); tempR = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; } } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift = unusedBitsPerSample - 8; float factor; float32x4_t factor4; int32x4_t shift4; int32x4_t wbps0_4; /* Wasted Bits Per Sample */ int32x4_t wbps1_4; /* Wasted Bits Per Sample */ DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor = 1.0f / 8388608.0f; factor4 = vdupq_n_f32(factor); wbps0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); wbps1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); if (shift == 0) { for (i = 0; i < frameCount4; ++i) { int32x4_t lefti; int32x4_t righti; float32x4_t leftf; float32x4_t rightf; uint32x4_t mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); uint32x4_t side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); lefti = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); righti = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; } } else { shift -= 1; shift4 = vdupq_n_s32(shift); for (i = 0; i < frameCount4; ++i) { uint32x4_t mid; uint32x4_t side; int32x4_t lefti; int32x4_t righti; float32x4_t leftf; float32x4_t rightf; mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); lefti = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); righti = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; mid = (mid << 1) | (side & 0x01); pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; } } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_f32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_f32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } #if 0 static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { pOutputSamples[i*2+0] = (float)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) / 2147483648.0); pOutputSamples[i*2+1] = (float)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) / 2147483648.0); } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; float factor = 1 / 2147483648.0; for (i = 0; i < frameCount4; ++i) { drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; pOutputSamples[i*8+0] = (drflac_int32)tempL0 * factor; pOutputSamples[i*8+1] = (drflac_int32)tempR0 * factor; pOutputSamples[i*8+2] = (drflac_int32)tempL1 * factor; pOutputSamples[i*8+3] = (drflac_int32)tempR1 * factor; pOutputSamples[i*8+4] = (drflac_int32)tempL2 * factor; pOutputSamples[i*8+5] = (drflac_int32)tempR2 * factor; pOutputSamples[i*8+6] = (drflac_int32)tempL3 * factor; pOutputSamples[i*8+7] = (drflac_int32)tempR3 * factor; } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; float factor = 1.0f / 8388608.0f; __m128 factor128 = _mm_set1_ps(factor); for (i = 0; i < frameCount4; ++i) { __m128i lefti; __m128i righti; __m128 leftf; __m128 rightf; lefti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); righti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); leftf = _mm_mul_ps(_mm_cvtepi32_ps(lefti), factor128); rightf = _mm_mul_ps(_mm_cvtepi32_ps(righti), factor128); _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; } } #endif #if defined(DRFLAC_SUPPORT_NEON) static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; float factor = 1.0f / 8388608.0f; float32x4_t factor4 = vdupq_n_f32(factor); int32x4_t shift0_4 = vdupq_n_s32(shift0); int32x4_t shift1_4 = vdupq_n_s32(shift1); for (i = 0; i < frameCount4; ++i) { int32x4_t lefti; int32x4_t righti; float32x4_t leftf; float32x4_t rightf; lefti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); righti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; } } #endif static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else #endif { /* Scalar fallback. */ #if 0 drflac_read_pcm_frames_f32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #else drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); #endif } } DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) { drflac_uint64 framesRead; drflac_uint32 unusedBitsPerSample; if (pFlac == NULL || framesToRead == 0) { return 0; } if (pBufferOut == NULL) { return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); } DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); unusedBitsPerSample = 32 - pFlac->bitsPerSample; framesRead = 0; while (framesToRead > 0) { /* If we've run out of samples in this frame, go to the next. */ if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; drflac_uint64 frameCountThisIteration = framesToRead; if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; } if (channelCount == 2) { const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; switch (pFlac->currentFLACFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { drflac_read_pcm_frames_f32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { drflac_read_pcm_frames_f32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: { drflac_read_pcm_frames_f32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: default: { drflac_read_pcm_frames_f32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; } } else { /* Generic interleaving. */ drflac_uint64 i; for (i = 0; i < frameCountThisIteration; ++i) { unsigned int j; for (j = 0; j < channelCount; ++j) { drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); pBufferOut[(i*channelCount)+j] = (float)(sampleS32 / 2147483648.0); } } } framesRead += frameCountThisIteration; pBufferOut += frameCountThisIteration * channelCount; framesToRead -= frameCountThisIteration; pFlac->currentPCMFrame += frameCountThisIteration; pFlac->currentFLACFrame.pcmFramesRemaining -= (unsigned int)frameCountThisIteration; } } return framesRead; } DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { if (pFlac == NULL) { return DRFLAC_FALSE; } /* Don't do anything if we're already on the seek point. */ if (pFlac->currentPCMFrame == pcmFrameIndex) { return DRFLAC_TRUE; } /* If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present when the decoder was opened. */ if (pFlac->firstFLACFramePosInBytes == 0) { return DRFLAC_FALSE; } if (pcmFrameIndex == 0) { pFlac->currentPCMFrame = 0; return drflac__seek_to_first_frame(pFlac); } else { drflac_bool32 wasSuccessful = DRFLAC_FALSE; drflac_uint64 originalPCMFrame = pFlac->currentPCMFrame; /* Clamp the sample to the end. */ if (pcmFrameIndex > pFlac->totalPCMFrameCount) { pcmFrameIndex = pFlac->totalPCMFrameCount; } /* If the target sample and the current sample are in the same frame we just move the position forward. */ if (pcmFrameIndex > pFlac->currentPCMFrame) { /* Forward. */ drflac_uint32 offset = (drflac_uint32)(pcmFrameIndex - pFlac->currentPCMFrame); if (pFlac->currentFLACFrame.pcmFramesRemaining > offset) { pFlac->currentFLACFrame.pcmFramesRemaining -= offset; pFlac->currentPCMFrame = pcmFrameIndex; return DRFLAC_TRUE; } } else { /* Backward. */ drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentPCMFrame - pcmFrameIndex); drflac_uint32 currentFLACFramePCMFrameCount = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; drflac_uint32 currentFLACFramePCMFramesConsumed = currentFLACFramePCMFrameCount - pFlac->currentFLACFrame.pcmFramesRemaining; if (currentFLACFramePCMFramesConsumed > offsetAbs) { pFlac->currentFLACFrame.pcmFramesRemaining += offsetAbs; pFlac->currentPCMFrame = pcmFrameIndex; return DRFLAC_TRUE; } } /* Different techniques depending on encapsulation. Using the native FLAC seektable with Ogg encapsulation is a bit awkward so we'll instead use Ogg's natural seeking facility. */ #ifndef DR_FLAC_NO_OGG if (pFlac->container == drflac_container_ogg) { wasSuccessful = drflac_ogg__seek_to_pcm_frame(pFlac, pcmFrameIndex); } else #endif { /* First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. */ if (/*!wasSuccessful && */!pFlac->_noSeekTableSeek) { wasSuccessful = drflac__seek_to_pcm_frame__seek_table(pFlac, pcmFrameIndex); } #if !defined(DR_FLAC_NO_CRC) /* Fall back to binary search if seek table seeking fails. This requires the length of the stream to be known. */ if (!wasSuccessful && !pFlac->_noBinarySearchSeek && pFlac->totalPCMFrameCount > 0) { wasSuccessful = drflac__seek_to_pcm_frame__binary_search(pFlac, pcmFrameIndex); } #endif /* Fall back to brute force if all else fails. */ if (!wasSuccessful && !pFlac->_noBruteForceSeek) { wasSuccessful = drflac__seek_to_pcm_frame__brute_force(pFlac, pcmFrameIndex); } } if (wasSuccessful) { pFlac->currentPCMFrame = pcmFrameIndex; } else { /* Seek failed. Try putting the decoder back to it's original state. */ if (drflac_seek_to_pcm_frame(pFlac, originalPCMFrame) == DRFLAC_FALSE) { /* Failed to seek back to the original PCM frame. Fall back to 0. */ drflac_seek_to_pcm_frame(pFlac, 0); } } return wasSuccessful; } } /* High Level APIs */ /* SIZE_MAX */ #if defined(SIZE_MAX) #define DRFLAC_SIZE_MAX SIZE_MAX #else #if defined(DRFLAC_64BIT) #define DRFLAC_SIZE_MAX ((drflac_uint64)0xFFFFFFFFFFFFFFFF) #else #define DRFLAC_SIZE_MAX 0xFFFFFFFF #endif #endif /* End SIZE_MAX */ /* Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. */ #define DRFLAC_DEFINE_FULL_READ_AND_CLOSE(extension, type) \ static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut)\ { \ type* pSampleData = NULL; \ drflac_uint64 totalPCMFrameCount; \ \ DRFLAC_ASSERT(pFlac != NULL); \ \ totalPCMFrameCount = pFlac->totalPCMFrameCount; \ \ if (totalPCMFrameCount == 0) { \ type buffer[4096]; \ drflac_uint64 pcmFramesRead; \ size_t sampleDataBufferSize = sizeof(buffer); \ \ pSampleData = (type*)drflac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ if (pSampleData == NULL) { \ goto on_error; \ } \ \ while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ type* pNewSampleData; \ size_t newSampleDataBufferSize; \ \ newSampleDataBufferSize = sampleDataBufferSize * 2; \ pNewSampleData = (type*)drflac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ if (pNewSampleData == NULL) { \ drflac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ goto on_error; \ } \ \ sampleDataBufferSize = newSampleDataBufferSize; \ pSampleData = pNewSampleData; \ } \ \ DRFLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ totalPCMFrameCount += pcmFramesRead; \ } \ \ /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ protect those ears from random noise! */ \ DRFLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ } else { \ drflac_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ if (dataSize > (drflac_uint64)DRFLAC_SIZE_MAX) { \ goto on_error; /* The decoded data is too big. */ \ } \ \ pSampleData = (type*)drflac__malloc_from_callbacks((size_t)dataSize, &pFlac->allocationCallbacks); /* <-- Safe cast as per the check above. */ \ if (pSampleData == NULL) { \ goto on_error; \ } \ \ totalPCMFrameCount = drflac_read_pcm_frames_##extension(pFlac, pFlac->totalPCMFrameCount, pSampleData); \ } \ \ if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ if (channelsOut) *channelsOut = pFlac->channels; \ if (totalPCMFrameCountOut) *totalPCMFrameCountOut = totalPCMFrameCount; \ \ drflac_close(pFlac); \ return pSampleData; \ \ on_error: \ drflac_close(pFlac); \ return NULL; \ } DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalPCMFrameCountOut) { *totalPCMFrameCountOut = 0; } pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalPCMFrameCountOut) { *totalPCMFrameCountOut = 0; } pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalPCMFrameCountOut) { *totalPCMFrameCountOut = 0; } pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_f32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } #ifndef DR_FLAC_NO_STDIO DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (sampleRate) { *sampleRate = 0; } if (channels) { *channels = 0; } if (totalPCMFrameCount) { *totalPCMFrameCount = 0; } pFlac = drflac_open_file(filename, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); } DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (sampleRate) { *sampleRate = 0; } if (channels) { *channels = 0; } if (totalPCMFrameCount) { *totalPCMFrameCount = 0; } pFlac = drflac_open_file(filename, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); } DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (sampleRate) { *sampleRate = 0; } if (channels) { *channels = 0; } if (totalPCMFrameCount) { *totalPCMFrameCount = 0; } pFlac = drflac_open_file(filename, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); } #endif DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (sampleRate) { *sampleRate = 0; } if (channels) { *channels = 0; } if (totalPCMFrameCount) { *totalPCMFrameCount = 0; } pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); } DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (sampleRate) { *sampleRate = 0; } if (channels) { *channels = 0; } if (totalPCMFrameCount) { *totalPCMFrameCount = 0; } pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); } DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; if (sampleRate) { *sampleRate = 0; } if (channels) { *channels = 0; } if (totalPCMFrameCount) { *totalPCMFrameCount = 0; } pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); } DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { drflac__free_from_callbacks(p, pAllocationCallbacks); } else { drflac__free_default(p, NULL); } } DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) { if (pIter == NULL) { return; } pIter->countRemaining = commentCount; pIter->pRunningData = (const char*)pComments; } DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) { drflac_int32 length; const char* pComment; /* Safety. */ if (pCommentLengthOut) { *pCommentLengthOut = 0; } if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { return NULL; } length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData); pIter->pRunningData += 4; pComment = pIter->pRunningData; pIter->pRunningData += length; pIter->countRemaining -= 1; if (pCommentLengthOut) { *pCommentLengthOut = length; } return pComment; } DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) { if (pIter == NULL) { return; } pIter->countRemaining = trackCount; pIter->pRunningData = (const char*)pTrackData; } DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) { drflac_cuesheet_track cuesheetTrack; const char* pRunningData; drflac_uint64 offsetHi; drflac_uint64 offsetLo; if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { return DRFLAC_FALSE; } pRunningData = pIter->pRunningData; offsetHi = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; offsetLo = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; cuesheetTrack.offset = offsetLo | (offsetHi << 32); cuesheetTrack.trackNumber = pRunningData[0]; pRunningData += 1; DRFLAC_COPY_MEMORY(cuesheetTrack.ISRC, pRunningData, sizeof(cuesheetTrack.ISRC)); pRunningData += 12; cuesheetTrack.isAudio = (pRunningData[0] & 0x80) != 0; cuesheetTrack.preEmphasis = (pRunningData[0] & 0x40) != 0; pRunningData += 14; cuesheetTrack.indexCount = pRunningData[0]; pRunningData += 1; cuesheetTrack.pIndexPoints = (const drflac_cuesheet_track_index*)pRunningData; pRunningData += cuesheetTrack.indexCount * sizeof(drflac_cuesheet_track_index); pIter->pRunningData = pRunningData; pIter->countRemaining -= 1; if (pCuesheetTrack) { *pCuesheetTrack = cuesheetTrack; } return DRFLAC_TRUE; } #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif /* dr_flac_c */ #endif /* DR_FLAC_IMPLEMENTATION */ /* REVISION HISTORY ================ v0.12.44 - TBD - Fix compilation for AIX OS. v0.12.43 - 2024-12-17 - Fix a possible buffer overflow during decoding. - Improve detection of ARM64EC v0.12.42 - 2023-11-02 - Fix build for ARMv6-M. - Fix a compilation warning with GCC. v0.12.41 - 2023-06-17 - Fix an incorrect date in revision history. No functional change. v0.12.40 - 2023-05-22 - Minor code restructure. No functional change. v0.12.39 - 2022-09-17 - Fix compilation with DJGPP. - Fix compilation error with Visual Studio 2019 and the ARM build. - Fix an error with SSE 4.1 detection. - Add support for disabling wchar_t with DR_WAV_NO_WCHAR. - Improve compatibility with compilers which lack support for explicit struct packing. - Improve compatibility with low-end and embedded hardware by reducing the amount of stack allocation when loading an Ogg encapsulated file. v0.12.38 - 2022-04-10 - Fix compilation error on older versions of GCC. v0.12.37 - 2022-02-12 - Improve ARM detection. v0.12.36 - 2022-02-07 - Fix a compilation error with the ARM build. v0.12.35 - 2022-02-06 - Fix a bug due to underestimating the amount of precision required for the prediction stage. - Fix some bugs found from fuzz testing. v0.12.34 - 2022-01-07 - Fix some misalignment bugs when reading metadata. v0.12.33 - 2021-12-22 - Fix a bug with seeking when the seek table does not start at PCM frame 0. v0.12.32 - 2021-12-11 - Fix a warning with Clang. v0.12.31 - 2021-08-16 - Silence some warnings. v0.12.30 - 2021-07-31 - Fix platform detection for ARM64. v0.12.29 - 2021-04-02 - Fix a bug where the running PCM frame index is set to an invalid value when over-seeking. - Fix a decoding error due to an incorrect validation check. v0.12.28 - 2021-02-21 - Fix a warning due to referencing _MSC_VER when it is undefined. v0.12.27 - 2021-01-31 - Fix a static analysis warning. v0.12.26 - 2021-01-17 - Fix a compilation warning due to _BSD_SOURCE being deprecated. v0.12.25 - 2020-12-26 - Update documentation. v0.12.24 - 2020-11-29 - Fix ARM64/NEON detection when compiling with MSVC. v0.12.23 - 2020-11-21 - Fix compilation with OpenWatcom. v0.12.22 - 2020-11-01 - Fix an error with the previous release. v0.12.21 - 2020-11-01 - Fix a possible deadlock when seeking. - Improve compiler support for older versions of GCC. v0.12.20 - 2020-09-08 - Fix a compilation error on older compilers. v0.12.19 - 2020-08-30 - Fix a bug due to an undefined 32-bit shift. v0.12.18 - 2020-08-14 - Fix a crash when compiling with clang-cl. v0.12.17 - 2020-08-02 - Simplify sized types. v0.12.16 - 2020-07-25 - Fix a compilation warning. v0.12.15 - 2020-07-06 - Check for negative LPC shifts and return an error. v0.12.14 - 2020-06-23 - Add include guard for the implementation section. v0.12.13 - 2020-05-16 - Add compile-time and run-time version querying. - DRFLAC_VERSION_MINOR - DRFLAC_VERSION_MAJOR - DRFLAC_VERSION_REVISION - DRFLAC_VERSION_STRING - drflac_version() - drflac_version_string() v0.12.12 - 2020-04-30 - Fix compilation errors with VC6. v0.12.11 - 2020-04-19 - Fix some pedantic warnings. - Fix some undefined behaviour warnings. v0.12.10 - 2020-04-10 - Fix some bugs when trying to seek with an invalid seek table. v0.12.9 - 2020-04-05 - Fix warnings. v0.12.8 - 2020-04-04 - Add drflac_open_file_w() and drflac_open_file_with_metadata_w(). - Fix some static analysis warnings. - Minor documentation updates. v0.12.7 - 2020-03-14 - Fix compilation errors with VC6. v0.12.6 - 2020-03-07 - Fix compilation error with Visual Studio .NET 2003. v0.12.5 - 2020-01-30 - Silence some static analysis warnings. v0.12.4 - 2020-01-29 - Silence some static analysis warnings. v0.12.3 - 2019-12-02 - Fix some warnings when compiling with GCC and the -Og flag. - Fix a crash in out-of-memory situations. - Fix potential integer overflow bug. - Fix some static analysis warnings. - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. - Fix a bug with binary search seeking where the bits per sample is not a multiple of 8. v0.12.2 - 2019-10-07 - Internal code clean up. v0.12.1 - 2019-09-29 - Fix some Clang Static Analyzer warnings. - Fix an unused variable warning. v0.12.0 - 2019-09-23 - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: - drflac_open() - drflac_open_relaxed() - drflac_open_with_metadata() - drflac_open_with_metadata_relaxed() - drflac_open_file() - drflac_open_file_with_metadata() - drflac_open_memory() - drflac_open_memory_with_metadata() - drflac_open_and_read_pcm_frames_s32() - drflac_open_and_read_pcm_frames_s16() - drflac_open_and_read_pcm_frames_f32() - drflac_open_file_and_read_pcm_frames_s32() - drflac_open_file_and_read_pcm_frames_s16() - drflac_open_file_and_read_pcm_frames_f32() - drflac_open_memory_and_read_pcm_frames_s32() - drflac_open_memory_and_read_pcm_frames_s16() - drflac_open_memory_and_read_pcm_frames_f32() Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. - Remove deprecated APIs: - drflac_read_s32() - drflac_read_s16() - drflac_read_f32() - drflac_seek_to_sample() - drflac_open_and_decode_s32() - drflac_open_and_decode_s16() - drflac_open_and_decode_f32() - drflac_open_and_decode_file_s32() - drflac_open_and_decode_file_s16() - drflac_open_and_decode_file_f32() - drflac_open_and_decode_memory_s32() - drflac_open_and_decode_memory_s16() - drflac_open_and_decode_memory_f32() - Remove drflac.totalSampleCount which is now replaced with drflac.totalPCMFrameCount. You can emulate drflac.totalSampleCount by doing pFlac->totalPCMFrameCount*pFlac->channels. - Rename drflac.currentFrame to drflac.currentFLACFrame to remove ambiguity with PCM frames. - Fix errors when seeking to the end of a stream. - Optimizations to seeking. - SSE improvements and optimizations. - ARM NEON optimizations. - Optimizations to drflac_read_pcm_frames_s16(). - Optimizations to drflac_read_pcm_frames_s32(). v0.11.10 - 2019-06-26 - Fix a compiler error. v0.11.9 - 2019-06-16 - Silence some ThreadSanitizer warnings. v0.11.8 - 2019-05-21 - Fix warnings. v0.11.7 - 2019-05-06 - C89 fixes. v0.11.6 - 2019-05-05 - Add support for C89. - Fix a compiler warning when CRC is disabled. - Change license to choice of public domain or MIT-0. v0.11.5 - 2019-04-19 - Fix a compiler error with GCC. v0.11.4 - 2019-04-17 - Fix some warnings with GCC when compiling with -std=c99. v0.11.3 - 2019-04-07 - Silence warnings with GCC. v0.11.2 - 2019-03-10 - Fix a warning. v0.11.1 - 2019-02-17 - Fix a potential bug with seeking. v0.11.0 - 2018-12-16 - API CHANGE: Deprecated drflac_read_s32(), drflac_read_s16() and drflac_read_f32() and replaced them with drflac_read_pcm_frames_s32(), drflac_read_pcm_frames_s16() and drflac_read_pcm_frames_f32(). The new APIs take and return PCM frame counts instead of sample counts. To upgrade you will need to change the input count by dividing it by the channel count, and then do the same with the return value. - API_CHANGE: Deprecated drflac_seek_to_sample() and replaced with drflac_seek_to_pcm_frame(). Same rules as the changes to drflac_read_*() apply. - API CHANGE: Deprecated drflac_open_and_decode_*() and replaced with drflac_open_*_and_read_*(). Same rules as the changes to drflac_read_*() apply. - Optimizations. v0.10.0 - 2018-09-11 - Remove the DR_FLAC_NO_WIN32_IO option and the Win32 file IO functionality. If you need to use Win32 file IO you need to do it yourself via the callback API. - Fix the clang build. - Fix undefined behavior. - Fix errors with CUESHEET metdata blocks. - Add an API for iterating over each cuesheet track in the CUESHEET metadata block. This works the same way as the Vorbis comment API. - Other miscellaneous bug fixes, mostly relating to invalid FLAC streams. - Minor optimizations. v0.9.11 - 2018-08-29 - Fix a bug with sample reconstruction. v0.9.10 - 2018-08-07 - Improve 64-bit detection. v0.9.9 - 2018-08-05 - Fix C++ build on older versions of GCC. v0.9.8 - 2018-07-24 - Fix compilation errors. v0.9.7 - 2018-07-05 - Fix a warning. v0.9.6 - 2018-06-29 - Fix some typos. v0.9.5 - 2018-06-23 - Fix some warnings. v0.9.4 - 2018-06-14 - Optimizations to seeking. - Clean up. v0.9.3 - 2018-05-22 - Bug fix. v0.9.2 - 2018-05-12 - Fix a compilation error due to a missing break statement. v0.9.1 - 2018-04-29 - Fix compilation error with Clang. v0.9 - 2018-04-24 - Fix Clang build. - Start using major.minor.revision versioning. v0.8g - 2018-04-19 - Fix build on non-x86/x64 architectures. v0.8f - 2018-02-02 - Stop pretending to support changing rate/channels mid stream. v0.8e - 2018-02-01 - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. - Fix a crash the the Rice partition order is invalid. v0.8d - 2017-09-22 - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. v0.8c - 2017-09-07 - Fix warning on non-x86/x64 architectures. v0.8b - 2017-08-19 - Fix build on non-x86/x64 architectures. v0.8a - 2017-08-13 - A small optimization for the Clang build. v0.8 - 2017-08-12 - API CHANGE: Rename dr_* types to drflac_*. - Optimizations. This brings dr_flac back to about the same class of efficiency as the reference implementation. - Add support for custom implementations of malloc(), realloc(), etc. - Add CRC checking to Ogg encapsulated streams. - Fix VC++ 6 build. This is only for the C++ compiler. The C compiler is not currently supported. - Bug fixes. v0.7 - 2017-07-23 - Add support for opening a stream without a header block. To do this, use drflac_open_relaxed() / drflac_open_with_metadata_relaxed(). v0.6 - 2017-07-22 - Add support for recovering from invalid frames. With this change, dr_flac will simply skip over invalid frames as if they never existed. Frames are checked against their sync code, the CRC-8 of the frame header and the CRC-16 of the whole frame. v0.5 - 2017-07-16 - Fix typos. - Change drflac_bool* types to unsigned. - Add CRC checking. This makes dr_flac slower, but can be disabled with #define DR_FLAC_NO_CRC. v0.4f - 2017-03-10 - Fix a couple of bugs with the bitstreaming code. v0.4e - 2017-02-17 - Fix some warnings. v0.4d - 2016-12-26 - Add support for 32-bit floating-point PCM decoding. - Use drflac_int* and drflac_uint* sized types to improve compiler support. - Minor improvements to documentation. v0.4c - 2016-12-26 - Add support for signed 16-bit integer PCM decoding. v0.4b - 2016-10-23 - A minor change to drflac_bool8 and drflac_bool32 types. v0.4a - 2016-10-11 - Rename drBool32 to drflac_bool32 for styling consistency. v0.4 - 2016-09-29 - API/ABI CHANGE: Use fixed size 32-bit booleans instead of the built-in bool type. - API CHANGE: Rename drflac_open_and_decode*() to drflac_open_and_decode*_s32(). - API CHANGE: Swap the order of "channels" and "sampleRate" parameters in drflac_open_and_decode*(). Rationale for this is to keep it consistent with drflac_audio. v0.3f - 2016-09-21 - Fix a warning with GCC. v0.3e - 2016-09-18 - Fixed a bug where GCC 4.3+ was not getting properly identified. - Fixed a few typos. - Changed date formats to ISO 8601 (YYYY-MM-DD). v0.3d - 2016-06-11 - Minor clean up. v0.3c - 2016-05-28 - Fixed compilation error. v0.3b - 2016-05-16 - Fixed Linux/GCC build. - Updated documentation. v0.3a - 2016-05-15 - Minor fixes to documentation. v0.3 - 2016-05-11 - Optimizations. Now at about parity with the reference implementation on 32-bit builds. - Lots of clean up. v0.2b - 2016-05-10 - Bug fixes. v0.2a - 2016-05-10 - Made drflac_open_and_decode() more robust. - Removed an unused debugging variable v0.2 - 2016-05-09 - Added support for Ogg encapsulation. - API CHANGE. Have the onSeek callback take a third argument which specifies whether or not the seek should be relative to the start or the current position. Also changes the seeking rules such that seeking offsets will never be negative. - Have drflac_open_and_decode() fail gracefully if the stream has an unknown total sample count. v0.1b - 2016-05-07 - Properly close the file handle in drflac_open_file() and family when the decoder fails to initialize. - Removed a stale comment. v0.1a - 2016-05-05 - Minor formatting changes. - Fixed a warning on the GCC build. v0.1 - 2016-05-03 - Initial versioned release. */ /* This software is available as a choice of the following licenses. Choose whichever you prefer. =============================================================================== ALTERNATIVE 1 - Public Domain (www.unlicense.org) =============================================================================== This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== Copyright 2023 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/dr_libs/dr_mp3.h000066400000000000000000006177341501405355700242350ustar00rootroot00000000000000/* MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. dr_mp3 - v0.7.0 - TBD David Reid - mackron@gmail.com GitHub: https://github.com/mackron/dr_libs Based on minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for differences between minimp3 and dr_mp3. */ /* Introduction ============= dr_mp3 is a single file library. To use it, do something like the following in one .c file. ```c #define DR_MP3_IMPLEMENTATION #include "dr_mp3.h" ``` You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: ```c drmp3 mp3; if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { // Failed to open file } ... drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToRead, pFrames); ``` The drmp3 object is transparent so you can get access to the channel count and sample rate like so: ``` drmp3_uint32 channels = mp3.channels; drmp3_uint32 sampleRate = mp3.sampleRate; ``` The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek callbacks with `drmp3_init_memory()` and `drmp3_init()` respectively. You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request any number of PCM frames in each call to `drmp3_read_pcm_frames_f32()` and it will return as many PCM frames as it can, up to the requested amount. You can also decode an entire file in one go with `drmp3_open_and_read_pcm_frames_f32()`, `drmp3_open_memory_and_read_pcm_frames_f32()` and `drmp3_open_file_and_read_pcm_frames_f32()`. Build Options ============= #define these options before including this file. #define DR_MP3_NO_STDIO Disable drmp3_init_file(), etc. #define DR_MP3_NO_SIMD Disable SIMD optimizations. */ #ifndef dr_mp3_h #define dr_mp3_h #ifdef __cplusplus extern "C" { #endif #define DRMP3_STRINGIFY(x) #x #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 7 #define DRMP3_VERSION_REVISION 0 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include /* For size_t. */ /* Sized Types */ typedef signed char drmp3_int8; typedef unsigned char drmp3_uint8; typedef signed short drmp3_int16; typedef unsigned short drmp3_uint16; typedef signed int drmp3_int32; typedef unsigned int drmp3_uint32; #if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) #pragma GCC diagnostic ignored "-Wc++11-long-long" #endif #endif typedef signed long long drmp3_int64; typedef unsigned long long drmp3_uint64; #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__powerpc64__) typedef drmp3_uint64 drmp3_uintptr; #else typedef drmp3_uint32 drmp3_uintptr; #endif typedef drmp3_uint8 drmp3_bool8; typedef drmp3_uint32 drmp3_bool32; #define DRMP3_TRUE 1 #define DRMP3_FALSE 0 /* Weird shifting syntax is for VC6 compatibility. */ #define DRMP3_UINT64_MAX (((drmp3_uint64)0xFFFFFFFF << 32) | (drmp3_uint64)0xFFFFFFFF) /* End Sized Types */ /* Decorations */ #if !defined(DRMP3_API) #if defined(DRMP3_DLL) #if defined(_WIN32) #define DRMP3_DLL_IMPORT __declspec(dllimport) #define DRMP3_DLL_EXPORT __declspec(dllexport) #define DRMP3_DLL_PRIVATE static #else #if defined(__GNUC__) && __GNUC__ >= 4 #define DRMP3_DLL_IMPORT __attribute__((visibility("default"))) #define DRMP3_DLL_EXPORT __attribute__((visibility("default"))) #define DRMP3_DLL_PRIVATE __attribute__((visibility("hidden"))) #else #define DRMP3_DLL_IMPORT #define DRMP3_DLL_EXPORT #define DRMP3_DLL_PRIVATE static #endif #endif #if defined(DR_MP3_IMPLEMENTATION) #define DRMP3_API DRMP3_DLL_EXPORT #else #define DRMP3_API DRMP3_DLL_IMPORT #endif #define DRMP3_PRIVATE DRMP3_DLL_PRIVATE #else #define DRMP3_API extern #define DRMP3_PRIVATE static #endif #endif /* End Decorations */ /* Result Codes */ typedef drmp3_int32 drmp3_result; #define DRMP3_SUCCESS 0 #define DRMP3_ERROR -1 /* A generic error. */ #define DRMP3_INVALID_ARGS -2 #define DRMP3_INVALID_OPERATION -3 #define DRMP3_OUT_OF_MEMORY -4 #define DRMP3_OUT_OF_RANGE -5 #define DRMP3_ACCESS_DENIED -6 #define DRMP3_DOES_NOT_EXIST -7 #define DRMP3_ALREADY_EXISTS -8 #define DRMP3_TOO_MANY_OPEN_FILES -9 #define DRMP3_INVALID_FILE -10 #define DRMP3_TOO_BIG -11 #define DRMP3_PATH_TOO_LONG -12 #define DRMP3_NAME_TOO_LONG -13 #define DRMP3_NOT_DIRECTORY -14 #define DRMP3_IS_DIRECTORY -15 #define DRMP3_DIRECTORY_NOT_EMPTY -16 #define DRMP3_END_OF_FILE -17 #define DRMP3_NO_SPACE -18 #define DRMP3_BUSY -19 #define DRMP3_IO_ERROR -20 #define DRMP3_INTERRUPT -21 #define DRMP3_UNAVAILABLE -22 #define DRMP3_ALREADY_IN_USE -23 #define DRMP3_BAD_ADDRESS -24 #define DRMP3_BAD_SEEK -25 #define DRMP3_BAD_PIPE -26 #define DRMP3_DEADLOCK -27 #define DRMP3_TOO_MANY_LINKS -28 #define DRMP3_NOT_IMPLEMENTED -29 #define DRMP3_NO_MESSAGE -30 #define DRMP3_BAD_MESSAGE -31 #define DRMP3_NO_DATA_AVAILABLE -32 #define DRMP3_INVALID_DATA -33 #define DRMP3_TIMEOUT -34 #define DRMP3_NO_NETWORK -35 #define DRMP3_NOT_UNIQUE -36 #define DRMP3_NOT_SOCKET -37 #define DRMP3_NO_ADDRESS -38 #define DRMP3_BAD_PROTOCOL -39 #define DRMP3_PROTOCOL_UNAVAILABLE -40 #define DRMP3_PROTOCOL_NOT_SUPPORTED -41 #define DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED -42 #define DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED -43 #define DRMP3_SOCKET_NOT_SUPPORTED -44 #define DRMP3_CONNECTION_RESET -45 #define DRMP3_ALREADY_CONNECTED -46 #define DRMP3_NOT_CONNECTED -47 #define DRMP3_CONNECTION_REFUSED -48 #define DRMP3_NO_HOST -49 #define DRMP3_IN_PROGRESS -50 #define DRMP3_CANCELLED -51 #define DRMP3_MEMORY_ALREADY_MAPPED -52 #define DRMP3_AT_END -53 /* End Result Codes */ #define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 #define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) /* Inline */ #ifdef _MSC_VER #define DRMP3_INLINE __forceinline #elif defined(__GNUC__) /* I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) #define DRMP3_GNUC_INLINE_HINT __inline__ #else #define DRMP3_GNUC_INLINE_HINT inline #endif #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT __attribute__((always_inline)) #else #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRMP3_INLINE __inline #else #define DRMP3_INLINE #endif /* End Inline */ DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision); DRMP3_API const char* drmp3_version_string(void); /* Allocation Callbacks */ typedef struct { void* pUserData; void* (* onMalloc)(size_t sz, void* pUserData); void* (* onRealloc)(void* p, size_t sz, void* pUserData); void (* onFree)(void* p, void* pUserData); } drmp3_allocation_callbacks; /* End Allocation Callbacks */ /* Low Level Push API ================== */ typedef struct { int frame_bytes, channels, sample_rate, layer, bitrate_kbps; } drmp3dec_frame_info; typedef struct { float mdct_overlap[2][9*32], qmf_state[15*2*32]; int reserv, free_format_bytes; drmp3_uint8 header[4], reserv_buf[511]; } drmp3dec; /* Initializes a low level decoder. */ DRMP3_API void drmp3dec_init(drmp3dec *dec); /* Reads a frame from a low level decoder. */ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); /* Helper for converting between f32 and s16. */ DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples); /* Main API (Pull API) =================== */ typedef enum { drmp3_seek_origin_start, drmp3_seek_origin_current, drmp3_seek_origin_end } drmp3_seek_origin; typedef struct { drmp3_uint64 seekPosInBytes; /* Points to the first byte of an MP3 frame. */ drmp3_uint64 pcmFrameIndex; /* The index of the PCM frame this seek point targets. */ drmp3_uint16 mp3FramesToDiscard; /* The number of whole MP3 frames to be discarded before pcmFramesToDiscard. */ drmp3_uint16 pcmFramesToDiscard; /* The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard. */ } drmp3_seek_point; typedef enum { DRMP3_METADATA_TYPE_ID3V1, DRMP3_METADATA_TYPE_ID3V2, DRMP3_METADATA_TYPE_APE, DRMP3_METADATA_TYPE_XING, DRMP3_METADATA_TYPE_VBRI } drmp3_metadata_type; typedef struct { drmp3_metadata_type type; const void* pRawData; /* A pointer to the raw data. */ size_t rawDataSize; } drmp3_metadata; /* Callback for when data is read. Return value is the number of bytes actually read. pUserData [in] The user data that was passed to drmp3_init(), and family. pBufferOut [out] The output buffer. bytesToRead [in] The number of bytes to read. Returns the number of bytes actually read. A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until either the entire bytesToRead is filled or you have reached the end of the stream. */ typedef size_t (* drmp3_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); /* Callback for when data needs to be seeked. pUserData [in] The user data that was passed to drmp3_init(), and family. offset [in] The number of bytes to move, relative to the origin. Can be negative. origin [in] The origin of the seek. Returns whether or not the seek was successful. */ typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin); /* Callback for retrieving the current cursor position. pUserData [in] The user data that was passed to drmp3_init(), and family. pCursor [out] The cursor position in bytes from the start of the stream. Returns whether or not the cursor position was successfully retrieved. */ typedef drmp3_bool32 (* drmp3_tell_proc)(void* pUserData, drmp3_int64* pCursor); /* Callback for when metadata is read. Only the raw data is provided. The client is responsible for parsing the contents of the data themsevles. */ typedef void (* drmp3_meta_proc)(void* pUserData, const drmp3_metadata* pMetadata); typedef struct { drmp3_uint32 channels; drmp3_uint32 sampleRate; } drmp3_config; typedef struct { drmp3dec decoder; drmp3_uint32 channels; drmp3_uint32 sampleRate; drmp3_read_proc onRead; drmp3_seek_proc onSeek; drmp3_meta_proc onMeta; void* pUserData; void* pUserDataMeta; drmp3_allocation_callbacks allocationCallbacks; drmp3_uint32 mp3FrameChannels; /* The number of channels in the currently loaded MP3 frame. Internal use only. */ drmp3_uint32 mp3FrameSampleRate; /* The sample rate of the currently loaded MP3 frame. Internal use only. */ drmp3_uint32 pcmFramesConsumedInMP3Frame; drmp3_uint32 pcmFramesRemainingInMP3Frame; drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */ drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally. */ drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */ drmp3_uint64 streamLength; /* The length of the stream in bytes. dr_mp3 will not read beyond this. If a ID3v1 or APE tag is present, this will be set to the first byte of the tag. */ drmp3_uint64 streamStartOffset; /* The offset of the start of the MP3 data. This is used for skipping ID3v2 and VBR tags. */ drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */ drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */ drmp3_uint32 delayInPCMFrames; drmp3_uint32 paddingInPCMFrames; drmp3_uint64 totalPCMFrameCount; /* Set to DRMP3_UINT64_MAX if the length is unknown. Includes delay and padding. */ drmp3_bool32 isVBR; drmp3_bool32 isCBR; size_t dataSize; size_t dataCapacity; size_t dataConsumed; drmp3_uint8* pData; drmp3_bool32 atEnd; struct { const drmp3_uint8* pData; size_t dataSize; size_t currentReadPos; } memory; /* Only used for decoders that were opened against a block of memory. */ } drmp3; /* Initializes an MP3 decoder. onRead [in] The function to call when data needs to be read from the client. onSeek [in] The function to call when the read position of the client data needs to move. onTell [in] The function to call when the read position of the client data needs to be retrieved. pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. Returns true if successful; false otherwise. Close the loader with drmp3_uninit(). See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() */ DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); /* Initializes an MP3 decoder from a block of memory. This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for the lifetime of the drmp3 object. The buffer should contain the contents of the entire MP3 file. */ DRMP3_API drmp3_bool32 drmp3_init_memory_with_metadata(drmp3* pMP3, const void* pData, size_t dataSize, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO /* Initializes an MP3 decoder from a file. This holds the internal FILE object until drmp3_uninit() is called. Keep this in mind if you're caching drmp3 objects because the operating system may restrict the number of file handles an application can have open at any given time. */ DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata(drmp3* pMP3, const char* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata_w(drmp3* pMP3, const wchar_t* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif /* Uninitializes an MP3 decoder. */ DRMP3_API void drmp3_uninit(drmp3* pMP3); /* Reads PCM frames as interleaved 32-bit IEEE floating point PCM. Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. */ DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); /* Reads PCM frames as interleaved signed 16-bit integer PCM. Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. */ DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut); /* Seeks to a specific frame. Note that this is _not_ an MP3 frame, but rather a PCM frame. */ DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); /* Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. */ DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); /* Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. */ DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); /* Calculates the total number of MP3 and PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. This is equivalent to calling drmp3_get_mp3_frame_count() and drmp3_get_pcm_frame_count() except that it's more efficient. */ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount); /* Calculates the seekpoints based on PCM frames. This is slow. pSeekpoint count is a pointer to a uint32 containing the seekpoint count. On input it contains the desired count. On output it contains the actual count. The reason for this design is that the client may request too many seekpoints, in which case dr_mp3 will return a corrected count. Note that seektable seeking is not quite sample exact when the MP3 stream contains inconsistent sample rates. */ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); /* Binds a seek table to the decoder. This does _not_ make a copy of pSeekPoints - it only references it. It is up to the application to ensure this remains valid while it is bound to the decoder. Use drmp3_calculate_seek_points() to calculate the seek points. */ DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); /* Opens an decodes an entire MP3 stream as a single operation. On output pConfig will receive the channel count and sample rate of the stream. Free the returned pointer with drmp3_free(). */ DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif /* Allocates a block of memory on the heap. */ DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks); /* Frees any memory that was allocated by a public drmp3 API. */ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifdef __cplusplus } #endif #endif /* dr_mp3_h */ /************************************************************************************************************************************************************ ************************************************************************************************************************************************************ IMPLEMENTATION ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ #if defined(DR_MP3_IMPLEMENTATION) #ifndef dr_mp3_c #define dr_mp3_c #include #include #include /* For INT_MAX */ DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision) { if (pMajor) { *pMajor = DRMP3_VERSION_MAJOR; } if (pMinor) { *pMinor = DRMP3_VERSION_MINOR; } if (pRevision) { *pRevision = DRMP3_VERSION_REVISION; } } DRMP3_API const char* drmp3_version_string(void) { return DRMP3_VERSION_STRING; } /* Disable SIMD when compiling with TCC for now. */ #if defined(__TINYC__) #define DR_MP3_NO_SIMD #endif #define DRMP3_OFFSET_PTR(p, offset) ((void*)((drmp3_uint8*)(p) + (offset))) #define DRMP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */ #ifndef DRMP3_MAX_FRAME_SYNC_MATCHES #define DRMP3_MAX_FRAME_SYNC_MATCHES 10 #endif #define DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES DRMP3_MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ #define DRMP3_MAX_BITRESERVOIR_BYTES 511 #define DRMP3_SHORT_BLOCK_TYPE 2 #define DRMP3_STOP_BLOCK_TYPE 3 #define DRMP3_MODE_MONO 3 #define DRMP3_MODE_JOINT_STEREO 1 #define DRMP3_HDR_SIZE 4 #define DRMP3_HDR_IS_MONO(h) (((h[3]) & 0xC0) == 0xC0) #define DRMP3_HDR_IS_MS_STEREO(h) (((h[3]) & 0xE0) == 0x60) #define DRMP3_HDR_IS_FREE_FORMAT(h) (((h[2]) & 0xF0) == 0) #define DRMP3_HDR_IS_CRC(h) (!((h[1]) & 1)) #define DRMP3_HDR_TEST_PADDING(h) ((h[2]) & 0x2) #define DRMP3_HDR_TEST_MPEG1(h) ((h[1]) & 0x8) #define DRMP3_HDR_TEST_NOT_MPEG25(h) ((h[1]) & 0x10) #define DRMP3_HDR_TEST_I_STEREO(h) ((h[3]) & 0x10) #define DRMP3_HDR_TEST_MS_STEREO(h) ((h[3]) & 0x20) #define DRMP3_HDR_GET_STEREO_MODE(h) (((h[3]) >> 6) & 3) #define DRMP3_HDR_GET_STEREO_MODE_EXT(h) (((h[3]) >> 4) & 3) #define DRMP3_HDR_GET_LAYER(h) (((h[1]) >> 1) & 3) #define DRMP3_HDR_GET_BITRATE(h) ((h[2]) >> 4) #define DRMP3_HDR_GET_SAMPLE_RATE(h) (((h[2]) >> 2) & 3) #define DRMP3_HDR_GET_MY_SAMPLE_RATE(h) (DRMP3_HDR_GET_SAMPLE_RATE(h) + (((h[1] >> 3) & 1) + ((h[1] >> 4) & 1))*3) #define DRMP3_HDR_IS_FRAME_576(h) ((h[1] & 14) == 2) #define DRMP3_HDR_IS_LAYER_1(h) ((h[1] & 6) == 6) #define DRMP3_BITS_DEQUANTIZER_OUT -1 #define DRMP3_MAX_SCF (255 + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210) #define DRMP3_MAX_SCFI ((DRMP3_MAX_SCF + 3) & ~3) #define DRMP3_MIN(a, b) ((a) > (b) ? (b) : (a)) #define DRMP3_MAX(a, b) ((a) < (b) ? (b) : (a)) #if !defined(DR_MP3_NO_SIMD) #if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) /* x64 always have SSE2, arm64 always have neon, no need for generic code */ #define DR_MP3_ONLY_SIMD #endif #if ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) #if defined(_MSC_VER) #include #endif #include #define DRMP3_HAVE_SSE 1 #define DRMP3_HAVE_SIMD 1 #define DRMP3_VSTORE _mm_storeu_ps #define DRMP3_VLD _mm_loadu_ps #define DRMP3_VSET _mm_set1_ps #define DRMP3_VADD _mm_add_ps #define DRMP3_VSUB _mm_sub_ps #define DRMP3_VMUL _mm_mul_ps #define DRMP3_VMAC(a, x, y) _mm_add_ps(a, _mm_mul_ps(x, y)) #define DRMP3_VMSB(a, x, y) _mm_sub_ps(a, _mm_mul_ps(x, y)) #define DRMP3_VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s)) #define DRMP3_VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3)) typedef __m128 drmp3_f4; #if defined(_MSC_VER) || defined(DR_MP3_ONLY_SIMD) #define drmp3_cpuid __cpuid #else static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], const int InfoType) { #if defined(__PIC__) __asm__ __volatile__( #if defined(__x86_64__) "push %%rbx\n" "cpuid\n" "xchgl %%ebx, %1\n" "pop %%rbx\n" #else "xchgl %%ebx, %1\n" "cpuid\n" "xchgl %%ebx, %1\n" #endif : "=a" (CPUInfo[0]), "=r" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) : "a" (InfoType)); #else __asm__ __volatile__( "cpuid" : "=a" (CPUInfo[0]), "=b" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) : "a" (InfoType)); #endif } #endif static int drmp3_have_simd(void) { #ifdef DR_MP3_ONLY_SIMD return 1; #else static int g_have_simd; int CPUInfo[4]; #ifdef MINIMP3_TEST static int g_counter; if (g_counter++ > 100) return 0; #endif if (g_have_simd) goto end; drmp3_cpuid(CPUInfo, 0); if (CPUInfo[0] > 0) { drmp3_cpuid(CPUInfo, 1); g_have_simd = (CPUInfo[3] & (1 << 26)) + 1; /* SSE2 */ return g_have_simd - 1; } end: return g_have_simd - 1; #endif } #elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #include #define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 1 #define DRMP3_VSTORE vst1q_f32 #define DRMP3_VLD vld1q_f32 #define DRMP3_VSET vmovq_n_f32 #define DRMP3_VADD vaddq_f32 #define DRMP3_VSUB vsubq_f32 #define DRMP3_VMUL vmulq_f32 #define DRMP3_VMAC(a, x, y) vmlaq_f32(a, x, y) #define DRMP3_VMSB(a, x, y) vmlsq_f32(a, x, y) #define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) #define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) typedef float32x4_t drmp3_f4; static int drmp3_have_simd(void) { /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ return 1; } #else #define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 0 #ifdef DR_MP3_ONLY_SIMD #error DR_MP3_ONLY_SIMD used, but SSE/NEON not enabled #endif #endif #else #define DRMP3_HAVE_SIMD 0 #endif #if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(_M_ARM64EC) && !defined(__ARM_ARCH_6M__) #define DRMP3_HAVE_ARMV6 1 static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(drmp3_int32 a) { drmp3_int32 x = 0; __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); return x; } #else #define DRMP3_HAVE_ARMV6 0 #endif /* Standard library stuff. */ #ifndef DRMP3_ASSERT #include #define DRMP3_ASSERT(expression) assert(expression) #endif #ifndef DRMP3_COPY_MEMORY #define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) #endif #ifndef DRMP3_MOVE_MEMORY #define DRMP3_MOVE_MEMORY(dst, src, sz) memmove((dst), (src), (sz)) #endif #ifndef DRMP3_ZERO_MEMORY #define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) #endif #define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) #ifndef DRMP3_MALLOC #define DRMP3_MALLOC(sz) malloc((sz)) #endif #ifndef DRMP3_REALLOC #define DRMP3_REALLOC(p, sz) realloc((p), (sz)) #endif #ifndef DRMP3_FREE #define DRMP3_FREE(p) free((p)) #endif typedef struct { const drmp3_uint8 *buf; int pos, limit; } drmp3_bs; typedef struct { float scf[3*64]; drmp3_uint8 total_bands, stereo_bands, bitalloc[64], scfcod[64]; } drmp3_L12_scale_info; typedef struct { drmp3_uint8 tab_offset, code_tab_width, band_count; } drmp3_L12_subband_alloc; typedef struct { const drmp3_uint8 *sfbtab; drmp3_uint16 part_23_length, big_values, scalefac_compress; drmp3_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; drmp3_uint8 table_select[3], region_count[3], subblock_gain[3]; drmp3_uint8 preflag, scalefac_scale, count1_table, scfsi; } drmp3_L3_gr_info; typedef struct { drmp3_bs bs; drmp3_uint8 maindata[DRMP3_MAX_BITRESERVOIR_BYTES + DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES]; drmp3_L3_gr_info gr_info[4]; float grbuf[2][576], scf[40], syn[18 + 15][2*32]; drmp3_uint8 ist_pos[2][39]; } drmp3dec_scratch; static void drmp3_bs_init(drmp3_bs *bs, const drmp3_uint8 *data, int bytes) { bs->buf = data; bs->pos = 0; bs->limit = bytes*8; } static drmp3_uint32 drmp3_bs_get_bits(drmp3_bs *bs, int n) { drmp3_uint32 next, cache = 0, s = bs->pos & 7; int shl = n + s; const drmp3_uint8 *p = bs->buf + (bs->pos >> 3); if ((bs->pos += n) > bs->limit) return 0; next = *p++ & (255 >> s); while ((shl -= 8) > 0) { cache |= next << shl; next = *p++; } return cache | (next >> -shl); } static int drmp3_hdr_valid(const drmp3_uint8 *h) { return h[0] == 0xff && ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) && (DRMP3_HDR_GET_LAYER(h) != 0) && (DRMP3_HDR_GET_BITRATE(h) != 15) && (DRMP3_HDR_GET_SAMPLE_RATE(h) != 3); } static int drmp3_hdr_compare(const drmp3_uint8 *h1, const drmp3_uint8 *h2) { return drmp3_hdr_valid(h2) && ((h1[1] ^ h2[1]) & 0xFE) == 0 && ((h1[2] ^ h2[2]) & 0x0C) == 0 && !(DRMP3_HDR_IS_FREE_FORMAT(h1) ^ DRMP3_HDR_IS_FREE_FORMAT(h2)); } static unsigned drmp3_hdr_bitrate_kbps(const drmp3_uint8 *h) { static const drmp3_uint8 halfrate[2][3][15] = { { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,16,24,28,32,40,48,56,64,72,80,88,96,112,128 } }, { { 0,16,20,24,28,32,40,48,56,64,80,96,112,128,160 }, { 0,16,24,28,32,40,48,56,64,80,96,112,128,160,192 }, { 0,16,32,48,64,80,96,112,128,144,160,176,192,208,224 } }, }; return 2*halfrate[!!DRMP3_HDR_TEST_MPEG1(h)][DRMP3_HDR_GET_LAYER(h) - 1][DRMP3_HDR_GET_BITRATE(h)]; } static unsigned drmp3_hdr_sample_rate_hz(const drmp3_uint8 *h) { static const unsigned g_hz[3] = { 44100, 48000, 32000 }; return g_hz[DRMP3_HDR_GET_SAMPLE_RATE(h)] >> (int)!DRMP3_HDR_TEST_MPEG1(h) >> (int)!DRMP3_HDR_TEST_NOT_MPEG25(h); } static unsigned drmp3_hdr_frame_samples(const drmp3_uint8 *h) { return DRMP3_HDR_IS_LAYER_1(h) ? 384 : (1152 >> (int)DRMP3_HDR_IS_FRAME_576(h)); } static int drmp3_hdr_frame_bytes(const drmp3_uint8 *h, int free_format_size) { int frame_bytes = drmp3_hdr_frame_samples(h)*drmp3_hdr_bitrate_kbps(h)*125/drmp3_hdr_sample_rate_hz(h); if (DRMP3_HDR_IS_LAYER_1(h)) { frame_bytes &= ~3; /* slot align */ } return frame_bytes ? frame_bytes : free_format_size; } static int drmp3_hdr_padding(const drmp3_uint8 *h) { return DRMP3_HDR_TEST_PADDING(h) ? (DRMP3_HDR_IS_LAYER_1(h) ? 4 : 1) : 0; } #ifndef DR_MP3_ONLY_MP3 static const drmp3_L12_subband_alloc *drmp3_L12_subband_alloc_table(const drmp3_uint8 *hdr, drmp3_L12_scale_info *sci) { const drmp3_L12_subband_alloc *alloc; int mode = DRMP3_HDR_GET_STEREO_MODE(hdr); int nbands, stereo_bands = (mode == DRMP3_MODE_MONO) ? 0 : (mode == DRMP3_MODE_JOINT_STEREO) ? (DRMP3_HDR_GET_STEREO_MODE_EXT(hdr) << 2) + 4 : 32; if (DRMP3_HDR_IS_LAYER_1(hdr)) { static const drmp3_L12_subband_alloc g_alloc_L1[] = { { 76, 4, 32 } }; alloc = g_alloc_L1; nbands = 32; } else if (!DRMP3_HDR_TEST_MPEG1(hdr)) { static const drmp3_L12_subband_alloc g_alloc_L2M2[] = { { 60, 4, 4 }, { 44, 3, 7 }, { 44, 2, 19 } }; alloc = g_alloc_L2M2; nbands = 30; } else { static const drmp3_L12_subband_alloc g_alloc_L2M1[] = { { 0, 4, 3 }, { 16, 4, 8 }, { 32, 3, 12 }, { 40, 2, 7 } }; int sample_rate_idx = DRMP3_HDR_GET_SAMPLE_RATE(hdr); unsigned kbps = drmp3_hdr_bitrate_kbps(hdr) >> (int)(mode != DRMP3_MODE_MONO); if (!kbps) /* free-format */ { kbps = 192; } alloc = g_alloc_L2M1; nbands = 27; if (kbps < 56) { static const drmp3_L12_subband_alloc g_alloc_L2M1_lowrate[] = { { 44, 4, 2 }, { 44, 3, 10 } }; alloc = g_alloc_L2M1_lowrate; nbands = sample_rate_idx == 2 ? 12 : 8; } else if (kbps >= 96 && sample_rate_idx != 1) { nbands = 30; } } sci->total_bands = (drmp3_uint8)nbands; sci->stereo_bands = (drmp3_uint8)DRMP3_MIN(stereo_bands, nbands); return alloc; } static void drmp3_L12_read_scalefactors(drmp3_bs *bs, drmp3_uint8 *pba, drmp3_uint8 *scfcod, int bands, float *scf) { static const float g_deq_L12[18*3] = { #define DRMP3_DQ(x) 9.53674316e-07f/x, 7.56931807e-07f/x, 6.00777173e-07f/x DRMP3_DQ(3),DRMP3_DQ(7),DRMP3_DQ(15),DRMP3_DQ(31),DRMP3_DQ(63),DRMP3_DQ(127),DRMP3_DQ(255),DRMP3_DQ(511),DRMP3_DQ(1023),DRMP3_DQ(2047),DRMP3_DQ(4095),DRMP3_DQ(8191),DRMP3_DQ(16383),DRMP3_DQ(32767),DRMP3_DQ(65535),DRMP3_DQ(3),DRMP3_DQ(5),DRMP3_DQ(9) }; int i, m; for (i = 0; i < bands; i++) { float s = 0; int ba = *pba++; int mask = ba ? 4 + ((19 >> scfcod[i]) & 3) : 0; for (m = 4; m; m >>= 1) { if (mask & m) { int b = drmp3_bs_get_bits(bs, 6); s = g_deq_L12[ba*3 - 6 + b % 3]*(int)(1 << 21 >> b/3); } *scf++ = s; } } } static void drmp3_L12_read_scale_info(const drmp3_uint8 *hdr, drmp3_bs *bs, drmp3_L12_scale_info *sci) { static const drmp3_uint8 g_bitalloc_code_tab[] = { 0,17, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16, 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,16, 0,17,18, 3,19,4,5,16, 0,17,18,16, 0,17,18,19, 4,5,6, 7,8, 9,10,11,12,13,14,15, 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,14, 0, 2, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16 }; const drmp3_L12_subband_alloc *subband_alloc = drmp3_L12_subband_alloc_table(hdr, sci); int i, k = 0, ba_bits = 0; const drmp3_uint8 *ba_code_tab = g_bitalloc_code_tab; for (i = 0; i < sci->total_bands; i++) { drmp3_uint8 ba; if (i == k) { k += subband_alloc->band_count; ba_bits = subband_alloc->code_tab_width; ba_code_tab = g_bitalloc_code_tab + subband_alloc->tab_offset; subband_alloc++; } ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; sci->bitalloc[2*i] = ba; if (i < sci->stereo_bands) { ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; } sci->bitalloc[2*i + 1] = sci->stereo_bands ? ba : 0; } for (i = 0; i < 2*sci->total_bands; i++) { sci->scfcod[i] = (drmp3_uint8)(sci->bitalloc[i] ? DRMP3_HDR_IS_LAYER_1(hdr) ? 2 : drmp3_bs_get_bits(bs, 2) : 6); } drmp3_L12_read_scalefactors(bs, sci->bitalloc, sci->scfcod, sci->total_bands*2, sci->scf); for (i = sci->stereo_bands; i < sci->total_bands; i++) { sci->bitalloc[2*i + 1] = 0; } } static int drmp3_L12_dequantize_granule(float *grbuf, drmp3_bs *bs, drmp3_L12_scale_info *sci, int group_size) { int i, j, k, choff = 576; for (j = 0; j < 4; j++) { float *dst = grbuf + group_size*j; for (i = 0; i < 2*sci->total_bands; i++) { int ba = sci->bitalloc[i]; if (ba != 0) { if (ba < 17) { int half = (1 << (ba - 1)) - 1; for (k = 0; k < group_size; k++) { dst[k] = (float)((int)drmp3_bs_get_bits(bs, ba) - half); } } else { unsigned mod = (2 << (ba - 17)) + 1; /* 3, 5, 9 */ unsigned code = drmp3_bs_get_bits(bs, mod + 2 - (mod >> 3)); /* 5, 7, 10 */ for (k = 0; k < group_size; k++, code /= mod) { dst[k] = (float)((int)(code % mod - mod/2)); } } } dst += choff; choff = 18 - choff; } } return group_size*4; } static void drmp3_L12_apply_scf_384(drmp3_L12_scale_info *sci, const float *scf, float *dst) { int i, k; DRMP3_COPY_MEMORY(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) { for (k = 0; k < 12; k++) { dst[k + 0] *= scf[0]; dst[k + 576] *= scf[3]; } } } #endif static int drmp3_L3_read_side_info(drmp3_bs *bs, drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) { static const drmp3_uint8 g_scf_long[8][23] = { { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, { 12,12,12,12,12,12,16,20,24,28,32,40,48,56,64,76,90,2,2,2,2,2,0 }, { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, { 6,6,6,6,6,6,8,10,12,14,16,18,22,26,32,38,46,54,62,70,76,36,0 }, { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, { 4,4,4,4,4,4,6,6,8,8,10,12,16,20,24,28,34,42,50,54,76,158,0 }, { 4,4,4,4,4,4,6,6,6,8,10,12,16,18,22,28,34,40,46,54,54,192,0 }, { 4,4,4,4,4,4,6,6,8,10,12,16,20,24,30,38,46,56,68,84,102,26,0 } }; static const drmp3_uint8 g_scf_short[8][40] = { { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, { 8,8,8,8,8,8,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, { 4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } }; static const drmp3_uint8 g_scf_mixed[8][40] = { { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, { 12,12,12,4,4,4,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, { 6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } }; unsigned tables, scfsi = 0; int main_data_begin, part_23_sum = 0; int gr_count = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; int sr_idx = DRMP3_HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); if (DRMP3_HDR_TEST_MPEG1(hdr)) { gr_count *= 2; main_data_begin = drmp3_bs_get_bits(bs, 9); scfsi = drmp3_bs_get_bits(bs, 7 + gr_count); } else { main_data_begin = drmp3_bs_get_bits(bs, 8 + gr_count) >> gr_count; } do { if (DRMP3_HDR_IS_MONO(hdr)) { scfsi <<= 4; } gr->part_23_length = (drmp3_uint16)drmp3_bs_get_bits(bs, 12); part_23_sum += gr->part_23_length; gr->big_values = (drmp3_uint16)drmp3_bs_get_bits(bs, 9); if (gr->big_values > 288) { return -1; } gr->global_gain = (drmp3_uint8)drmp3_bs_get_bits(bs, 8); gr->scalefac_compress = (drmp3_uint16)drmp3_bs_get_bits(bs, DRMP3_HDR_TEST_MPEG1(hdr) ? 4 : 9); gr->sfbtab = g_scf_long[sr_idx]; gr->n_long_sfb = 22; gr->n_short_sfb = 0; if (drmp3_bs_get_bits(bs, 1)) { gr->block_type = (drmp3_uint8)drmp3_bs_get_bits(bs, 2); if (!gr->block_type) { return -1; } gr->mixed_block_flag = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); gr->region_count[0] = 7; gr->region_count[1] = 255; if (gr->block_type == DRMP3_SHORT_BLOCK_TYPE) { scfsi &= 0x0F0F; if (!gr->mixed_block_flag) { gr->region_count[0] = 8; gr->sfbtab = g_scf_short[sr_idx]; gr->n_long_sfb = 0; gr->n_short_sfb = 39; } else { gr->sfbtab = g_scf_mixed[sr_idx]; gr->n_long_sfb = DRMP3_HDR_TEST_MPEG1(hdr) ? 8 : 6; gr->n_short_sfb = 30; } } tables = drmp3_bs_get_bits(bs, 10); tables <<= 5; gr->subblock_gain[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); gr->subblock_gain[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); gr->subblock_gain[2] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); } else { gr->block_type = 0; gr->mixed_block_flag = 0; tables = drmp3_bs_get_bits(bs, 15); gr->region_count[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 4); gr->region_count[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); gr->region_count[2] = 255; } gr->table_select[0] = (drmp3_uint8)(tables >> 10); gr->table_select[1] = (drmp3_uint8)((tables >> 5) & 31); gr->table_select[2] = (drmp3_uint8)((tables) & 31); gr->preflag = (drmp3_uint8)(DRMP3_HDR_TEST_MPEG1(hdr) ? drmp3_bs_get_bits(bs, 1) : (gr->scalefac_compress >= 500)); gr->scalefac_scale = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); gr->count1_table = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); gr->scfsi = (drmp3_uint8)((scfsi >> 12) & 15); scfsi <<= 4; gr++; } while(--gr_count); if (part_23_sum + bs->pos > bs->limit + main_data_begin*8) { return -1; } return main_data_begin; } static void drmp3_L3_read_scalefactors(drmp3_uint8 *scf, drmp3_uint8 *ist_pos, const drmp3_uint8 *scf_size, const drmp3_uint8 *scf_count, drmp3_bs *bitbuf, int scfsi) { int i, k; for (i = 0; i < 4 && scf_count[i]; i++, scfsi *= 2) { int cnt = scf_count[i]; if (scfsi & 8) { DRMP3_COPY_MEMORY(scf, ist_pos, cnt); } else { int bits = scf_size[i]; if (!bits) { DRMP3_ZERO_MEMORY(scf, cnt); DRMP3_ZERO_MEMORY(ist_pos, cnt); } else { int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; for (k = 0; k < cnt; k++) { int s = drmp3_bs_get_bits(bitbuf, bits); ist_pos[k] = (drmp3_uint8)(s == max_scf ? -1 : s); scf[k] = (drmp3_uint8)s; } } } ist_pos += cnt; scf += cnt; } scf[0] = scf[1] = scf[2] = 0; } static float drmp3_L3_ldexp_q2(float y, int exp_q2) { static const float g_expfrac[4] = { 9.31322575e-10f,7.83145814e-10f,6.58544508e-10f,5.53767716e-10f }; int e; do { e = DRMP3_MIN(30*4, exp_q2); y *= g_expfrac[e & 3]*(1 << 30 >> (e >> 2)); } while ((exp_q2 -= e) > 0); return y; } static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *ist_pos, drmp3_bs *bs, const drmp3_L3_gr_info *gr, float *scf, int ch) { static const drmp3_uint8 g_scf_partitions[3][28] = { { 6,5,5, 5,6,5,5,5,6,5, 7,3,11,10,0,0, 7, 7, 7,0, 6, 6,6,3, 8, 8,5,0 }, { 8,9,6,12,6,9,9,9,6,9,12,6,15,18,0,0, 6,15,12,0, 6,12,9,6, 6,18,9,0 }, { 9,9,6,12,9,9,9,9,9,9,12,6,18,18,0,0,12,12,12,0,12, 9,9,6,15,12,9,0 } }; const drmp3_uint8 *scf_partition = g_scf_partitions[!!gr->n_short_sfb + !gr->n_long_sfb]; drmp3_uint8 scf_size[4], iscf[40]; int i, scf_shift = gr->scalefac_scale + 1, gain_exp, scfsi = gr->scfsi; float gain; if (DRMP3_HDR_TEST_MPEG1(hdr)) { static const drmp3_uint8 g_scfc_decode[16] = { 0,1,2,3, 12,5,6,7, 9,10,11,13, 14,15,18,19 }; int part = g_scfc_decode[gr->scalefac_compress]; scf_size[1] = scf_size[0] = (drmp3_uint8)(part >> 2); scf_size[3] = scf_size[2] = (drmp3_uint8)(part & 3); } else { static const drmp3_uint8 g_mod[6*4] = { 5,5,4,4,5,5,4,1,4,3,1,1,5,6,6,1,4,4,4,1,4,3,1,1 }; int k, modprod, sfc, ist = DRMP3_HDR_TEST_I_STEREO(hdr) && ch; sfc = gr->scalefac_compress >> ist; for (k = ist*3*4; sfc >= 0; sfc -= modprod, k += 4) { for (modprod = 1, i = 3; i >= 0; i--) { scf_size[i] = (drmp3_uint8)(sfc / modprod % g_mod[k + i]); modprod *= g_mod[k + i]; } } scf_partition += k; scfsi = -16; } drmp3_L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi); if (gr->n_short_sfb) { int sh = 3 - scf_shift; for (i = 0; i < gr->n_short_sfb; i += 3) { iscf[gr->n_long_sfb + i + 0] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 0] + (gr->subblock_gain[0] << sh)); iscf[gr->n_long_sfb + i + 1] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 1] + (gr->subblock_gain[1] << sh)); iscf[gr->n_long_sfb + i + 2] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 2] + (gr->subblock_gain[2] << sh)); } } else if (gr->preflag) { static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; for (i = 0; i < 10; i++) { iscf[11 + i] = (drmp3_uint8)(iscf[11 + i] + g_preamp[i]); } } gain_exp = gr->global_gain + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210 - (DRMP3_HDR_IS_MS_STEREO(hdr) ? 2 : 0); gain = drmp3_L3_ldexp_q2(1 << (DRMP3_MAX_SCFI/4), DRMP3_MAX_SCFI - gain_exp); for (i = 0; i < (int)(gr->n_long_sfb + gr->n_short_sfb); i++) { scf[i] = drmp3_L3_ldexp_q2(gain, iscf[i] << scf_shift); } } static const float g_drmp3_pow43[129 + 16] = { 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f }; static float drmp3_L3_pow_43(int x) { float frac; int sign, mult = 256; if (x < 129) { return g_drmp3_pow43[16 + x]; } if (x < 1024) { mult = 16; x <<= 3; } sign = 2*x & 64; frac = (float)((x & 63) - sign) / ((x & ~63) + sign); return g_drmp3_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; } static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *gr_info, const float *scf, int layer3gr_limit) { static const drmp3_int16 tabs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 785,785,785,785,784,784,784,784,513,513,513,513,513,513,513,513,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256, -255,1313,1298,1282,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,290,288, -255,1313,1298,1282,769,769,769,769,529,529,529,529,529,529,529,529,528,528,528,528,528,528,528,528,512,512,512,512,512,512,512,512,290,288, -253,-318,-351,-367,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,819,818,547,547,275,275,275,275,561,560,515,546,289,274,288,258, -254,-287,1329,1299,1314,1312,1057,1057,1042,1042,1026,1026,784,784,784,784,529,529,529,529,529,529,529,529,769,769,769,769,768,768,768,768,563,560,306,306,291,259, -252,-413,-477,-542,1298,-575,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-383,-399,1107,1092,1106,1061,849,849,789,789,1104,1091,773,773,1076,1075,341,340,325,309,834,804,577,577,532,532,516,516,832,818,803,816,561,561,531,531,515,546,289,289,288,258, -252,-429,-493,-559,1057,1057,1042,1042,529,529,529,529,529,529,529,529,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,-382,1077,-415,1106,1061,1104,849,849,789,789,1091,1076,1029,1075,834,834,597,581,340,340,339,324,804,833,532,532,832,772,818,803,817,787,816,771,290,290,290,290,288,258, -253,-349,-414,-447,-463,1329,1299,-479,1314,1312,1057,1057,1042,1042,1026,1026,785,785,785,785,784,784,784,784,769,769,769,769,768,768,768,768,-319,851,821,-335,836,850,805,849,341,340,325,336,533,533,579,579,564,564,773,832,578,548,563,516,321,276,306,291,304,259, -251,-572,-733,-830,-863,-879,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,1396,1351,1381,1366,1395,1335,1380,-559,1334,1138,1138,1063,1063,1350,1392,1031,1031,1062,1062,1364,1363,1120,1120,1333,1348,881,881,881,881,375,374,359,373,343,358,341,325,791,791,1123,1122,-703,1105,1045,-719,865,865,790,790,774,774,1104,1029,338,293,323,308,-799,-815,833,788,772,818,803,816,322,292,307,320,561,531,515,546,289,274,288,258, -251,-525,-605,-685,-765,-831,-846,1298,1057,1057,1312,1282,785,785,785,785,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,1399,1398,1383,1367,1382,1396,1351,-511,1381,1366,1139,1139,1079,1079,1124,1124,1364,1349,1363,1333,882,882,882,882,807,807,807,807,1094,1094,1136,1136,373,341,535,535,881,775,867,822,774,-591,324,338,-671,849,550,550,866,864,609,609,293,336,534,534,789,835,773,-751,834,804,308,307,833,788,832,772,562,562,547,547,305,275,560,515,290,290, -252,-397,-477,-557,-622,-653,-719,-735,-750,1329,1299,1314,1057,1057,1042,1042,1312,1282,1024,1024,785,785,785,785,784,784,784,784,769,769,769,769,-383,1127,1141,1111,1126,1140,1095,1110,869,869,883,883,1079,1109,882,882,375,374,807,868,838,881,791,-463,867,822,368,263,852,837,836,-543,610,610,550,550,352,336,534,534,865,774,851,821,850,805,593,533,579,564,773,832,578,578,548,548,577,577,307,276,306,291,516,560,259,259, -250,-2107,-2507,-2764,-2909,-2974,-3007,-3023,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-767,-1052,-1213,-1277,-1358,-1405,-1469,-1535,-1550,-1582,-1614,-1647,-1662,-1694,-1726,-1759,-1774,-1807,-1822,-1854,-1886,1565,-1919,-1935,-1951,-1967,1731,1730,1580,1717,-1983,1729,1564,-1999,1548,-2015,-2031,1715,1595,-2047,1714,-2063,1610,-2079,1609,-2095,1323,1323,1457,1457,1307,1307,1712,1547,1641,1700,1699,1594,1685,1625,1442,1442,1322,1322,-780,-973,-910,1279,1278,1277,1262,1276,1261,1275,1215,1260,1229,-959,974,974,989,989,-943,735,478,478,495,463,506,414,-1039,1003,958,1017,927,942,987,957,431,476,1272,1167,1228,-1183,1256,-1199,895,895,941,941,1242,1227,1212,1135,1014,1014,490,489,503,487,910,1013,985,925,863,894,970,955,1012,847,-1343,831,755,755,984,909,428,366,754,559,-1391,752,486,457,924,997,698,698,983,893,740,740,908,877,739,739,667,667,953,938,497,287,271,271,683,606,590,712,726,574,302,302,738,736,481,286,526,725,605,711,636,724,696,651,589,681,666,710,364,467,573,695,466,466,301,465,379,379,709,604,665,679,316,316,634,633,436,436,464,269,424,394,452,332,438,363,347,408,393,448,331,422,362,407,392,421,346,406,391,376,375,359,1441,1306,-2367,1290,-2383,1337,-2399,-2415,1426,1321,-2431,1411,1336,-2447,-2463,-2479,1169,1169,1049,1049,1424,1289,1412,1352,1319,-2495,1154,1154,1064,1064,1153,1153,416,390,360,404,403,389,344,374,373,343,358,372,327,357,342,311,356,326,1395,1394,1137,1137,1047,1047,1365,1392,1287,1379,1334,1364,1349,1378,1318,1363,792,792,792,792,1152,1152,1032,1032,1121,1121,1046,1046,1120,1120,1030,1030,-2895,1106,1061,1104,849,849,789,789,1091,1076,1029,1090,1060,1075,833,833,309,324,532,532,832,772,818,803,561,561,531,560,515,546,289,274,288,258, -250,-1179,-1579,-1836,-1996,-2124,-2253,-2333,-2413,-2477,-2542,-2574,-2607,-2622,-2655,1314,1313,1298,1312,1282,785,785,785,785,1040,1040,1025,1025,768,768,768,768,-766,-798,-830,-862,-895,-911,-927,-943,-959,-975,-991,-1007,-1023,-1039,-1055,-1070,1724,1647,-1103,-1119,1631,1767,1662,1738,1708,1723,-1135,1780,1615,1779,1599,1677,1646,1778,1583,-1151,1777,1567,1737,1692,1765,1722,1707,1630,1751,1661,1764,1614,1736,1676,1763,1750,1645,1598,1721,1691,1762,1706,1582,1761,1566,-1167,1749,1629,767,766,751,765,494,494,735,764,719,749,734,763,447,447,748,718,477,506,431,491,446,476,461,505,415,430,475,445,504,399,460,489,414,503,383,474,429,459,502,502,746,752,488,398,501,473,413,472,486,271,480,270,-1439,-1455,1357,-1471,-1487,-1503,1341,1325,-1519,1489,1463,1403,1309,-1535,1372,1448,1418,1476,1356,1462,1387,-1551,1475,1340,1447,1402,1386,-1567,1068,1068,1474,1461,455,380,468,440,395,425,410,454,364,467,466,464,453,269,409,448,268,432,1371,1473,1432,1417,1308,1460,1355,1446,1459,1431,1083,1083,1401,1416,1458,1445,1067,1067,1370,1457,1051,1051,1291,1430,1385,1444,1354,1415,1400,1443,1082,1082,1173,1113,1186,1066,1185,1050,-1967,1158,1128,1172,1097,1171,1081,-1983,1157,1112,416,266,375,400,1170,1142,1127,1065,793,793,1169,1033,1156,1096,1141,1111,1155,1080,1126,1140,898,898,808,808,897,897,792,792,1095,1152,1032,1125,1110,1139,1079,1124,882,807,838,881,853,791,-2319,867,368,263,822,852,837,866,806,865,-2399,851,352,262,534,534,821,836,594,594,549,549,593,593,533,533,848,773,579,579,564,578,548,563,276,276,577,576,306,291,516,560,305,305,275,259, -251,-892,-2058,-2620,-2828,-2957,-3023,-3039,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,-559,1530,-575,-591,1528,1527,1407,1526,1391,1023,1023,1023,1023,1525,1375,1268,1268,1103,1103,1087,1087,1039,1039,1523,-604,815,815,815,815,510,495,509,479,508,463,507,447,431,505,415,399,-734,-782,1262,-815,1259,1244,-831,1258,1228,-847,-863,1196,-879,1253,987,987,748,-767,493,493,462,477,414,414,686,669,478,446,461,445,474,429,487,458,412,471,1266,1264,1009,1009,799,799,-1019,-1276,-1452,-1581,-1677,-1757,-1821,-1886,-1933,-1997,1257,1257,1483,1468,1512,1422,1497,1406,1467,1496,1421,1510,1134,1134,1225,1225,1466,1451,1374,1405,1252,1252,1358,1480,1164,1164,1251,1251,1238,1238,1389,1465,-1407,1054,1101,-1423,1207,-1439,830,830,1248,1038,1237,1117,1223,1148,1236,1208,411,426,395,410,379,269,1193,1222,1132,1235,1221,1116,976,976,1192,1162,1177,1220,1131,1191,963,963,-1647,961,780,-1663,558,558,994,993,437,408,393,407,829,978,813,797,947,-1743,721,721,377,392,844,950,828,890,706,706,812,859,796,960,948,843,934,874,571,571,-1919,690,555,689,421,346,539,539,944,779,918,873,932,842,903,888,570,570,931,917,674,674,-2575,1562,-2591,1609,-2607,1654,1322,1322,1441,1441,1696,1546,1683,1593,1669,1624,1426,1426,1321,1321,1639,1680,1425,1425,1305,1305,1545,1668,1608,1623,1667,1592,1638,1666,1320,1320,1652,1607,1409,1409,1304,1304,1288,1288,1664,1637,1395,1395,1335,1335,1622,1636,1394,1394,1319,1319,1606,1621,1392,1392,1137,1137,1137,1137,345,390,360,375,404,373,1047,-2751,-2767,-2783,1062,1121,1046,-2799,1077,-2815,1106,1061,789,789,1105,1104,263,355,310,340,325,354,352,262,339,324,1091,1076,1029,1090,1060,1075,833,833,788,788,1088,1028,818,818,803,803,561,561,531,531,816,771,546,546,289,274,288,258, -253,-317,-381,-446,-478,-509,1279,1279,-811,-1179,-1451,-1756,-1900,-2028,-2189,-2253,-2333,-2414,-2445,-2511,-2526,1313,1298,-2559,1041,1041,1040,1040,1025,1025,1024,1024,1022,1007,1021,991,1020,975,1019,959,687,687,1018,1017,671,671,655,655,1016,1015,639,639,758,758,623,623,757,607,756,591,755,575,754,559,543,543,1009,783,-575,-621,-685,-749,496,-590,750,749,734,748,974,989,1003,958,988,973,1002,942,987,957,972,1001,926,986,941,971,956,1000,910,985,925,999,894,970,-1071,-1087,-1102,1390,-1135,1436,1509,1451,1374,-1151,1405,1358,1480,1420,-1167,1507,1494,1389,1342,1465,1435,1450,1326,1505,1310,1493,1373,1479,1404,1492,1464,1419,428,443,472,397,736,526,464,464,486,457,442,471,484,482,1357,1449,1434,1478,1388,1491,1341,1490,1325,1489,1463,1403,1309,1477,1372,1448,1418,1433,1476,1356,1462,1387,-1439,1475,1340,1447,1402,1474,1324,1461,1371,1473,269,448,1432,1417,1308,1460,-1711,1459,-1727,1441,1099,1099,1446,1386,1431,1401,-1743,1289,1083,1083,1160,1160,1458,1445,1067,1067,1370,1457,1307,1430,1129,1129,1098,1098,268,432,267,416,266,400,-1887,1144,1187,1082,1173,1113,1186,1066,1050,1158,1128,1143,1172,1097,1171,1081,420,391,1157,1112,1170,1142,1127,1065,1169,1049,1156,1096,1141,1111,1155,1080,1126,1154,1064,1153,1140,1095,1048,-2159,1125,1110,1137,-2175,823,823,1139,1138,807,807,384,264,368,263,868,838,853,791,867,822,852,837,866,806,865,790,-2319,851,821,836,352,262,850,805,849,-2399,533,533,835,820,336,261,578,548,563,577,532,532,832,772,562,562,547,547,305,275,560,515,290,290,288,258 }; static const drmp3_uint8 tab32[] = { 130,162,193,209,44,28,76,140,9,9,9,9,9,9,9,9,190,254,222,238,126,94,157,157,109,61,173,205}; static const drmp3_uint8 tab33[] = { 252,236,220,204,188,172,156,140,124,108,92,76,60,44,28,12 }; static const drmp3_int16 tabindex[2*16] = { 0,32,64,98,0,132,180,218,292,364,426,538,648,746,0,1126,1460,1460,1460,1460,1460,1460,1460,1460,1842,1842,1842,1842,1842,1842,1842,1842 }; static const drmp3_uint8 g_linbits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,6,8,10,13,4,5,6,7,8,9,11,13 }; #define DRMP3_PEEK_BITS(n) (bs_cache >> (32 - (n))) #define DRMP3_FLUSH_BITS(n) { bs_cache <<= (n); bs_sh += (n); } #define DRMP3_CHECK_BITS while (bs_sh >= 0) { bs_cache |= (drmp3_uint32)*bs_next_ptr++ << bs_sh; bs_sh -= 8; } #define DRMP3_BSPOS ((bs_next_ptr - bs->buf)*8 - 24 + bs_sh) float one = 0.0f; int ireg = 0, big_val_cnt = gr_info->big_values; const drmp3_uint8 *sfb = gr_info->sfbtab; const drmp3_uint8 *bs_next_ptr = bs->buf + bs->pos/8; drmp3_uint32 bs_cache = (((bs_next_ptr[0]*256u + bs_next_ptr[1])*256u + bs_next_ptr[2])*256u + bs_next_ptr[3]) << (bs->pos & 7); int pairs_to_decode, np, bs_sh = (bs->pos & 7) - 8; bs_next_ptr += 4; while (big_val_cnt > 0) { int tab_num = gr_info->table_select[ireg]; int sfb_cnt = gr_info->region_count[ireg++]; const drmp3_int16 *codebook = tabs + tabindex[tab_num]; int linbits = g_linbits[tab_num]; if (linbits) { do { np = *sfb++ / 2; pairs_to_decode = DRMP3_MIN(big_val_cnt, np); one = *scf++; do { int j, w = 5; int leaf = codebook[DRMP3_PEEK_BITS(w)]; while (leaf < 0) { DRMP3_FLUSH_BITS(w); w = leaf & 7; leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; } DRMP3_FLUSH_BITS(leaf >> 8); for (j = 0; j < 2; j++, dst++, leaf >>= 4) { int lsb = leaf & 0x0F; if (lsb == 15) { lsb += DRMP3_PEEK_BITS(linbits); DRMP3_FLUSH_BITS(linbits); DRMP3_CHECK_BITS; *dst = one*drmp3_L3_pow_43(lsb)*((drmp3_int32)bs_cache < 0 ? -1: 1); } else { *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; } DRMP3_FLUSH_BITS(lsb ? 1 : 0); } DRMP3_CHECK_BITS; } while (--pairs_to_decode); } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); } else { do { np = *sfb++ / 2; pairs_to_decode = DRMP3_MIN(big_val_cnt, np); one = *scf++; do { int j, w = 5; int leaf = codebook[DRMP3_PEEK_BITS(w)]; while (leaf < 0) { DRMP3_FLUSH_BITS(w); w = leaf & 7; leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; } DRMP3_FLUSH_BITS(leaf >> 8); for (j = 0; j < 2; j++, dst++, leaf >>= 4) { int lsb = leaf & 0x0F; *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; DRMP3_FLUSH_BITS(lsb ? 1 : 0); } DRMP3_CHECK_BITS; } while (--pairs_to_decode); } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); } } for (np = 1 - big_val_cnt;; dst += 4) { const drmp3_uint8 *codebook_count1 = (gr_info->count1_table) ? tab33 : tab32; int leaf = codebook_count1[DRMP3_PEEK_BITS(4)]; if (!(leaf & 8)) { leaf = codebook_count1[(leaf >> 3) + (bs_cache << 4 >> (32 - (leaf & 3)))]; } DRMP3_FLUSH_BITS(leaf & 7); if (DRMP3_BSPOS > layer3gr_limit) { break; } #define DRMP3_RELOAD_SCALEFACTOR if (!--np) { np = *sfb++/2; if (!np) break; one = *scf++; } #define DRMP3_DEQ_COUNT1(s) if (leaf & (128 >> s)) { dst[s] = ((drmp3_int32)bs_cache < 0) ? -one : one; DRMP3_FLUSH_BITS(1) } DRMP3_RELOAD_SCALEFACTOR; DRMP3_DEQ_COUNT1(0); DRMP3_DEQ_COUNT1(1); DRMP3_RELOAD_SCALEFACTOR; DRMP3_DEQ_COUNT1(2); DRMP3_DEQ_COUNT1(3); DRMP3_CHECK_BITS; } bs->pos = layer3gr_limit; } static void drmp3_L3_midside_stereo(float *left, int n) { int i = 0; float *right = left + 576; #if DRMP3_HAVE_SIMD if (drmp3_have_simd()) { for (; i < n - 3; i += 4) { drmp3_f4 vl = DRMP3_VLD(left + i); drmp3_f4 vr = DRMP3_VLD(right + i); DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); } #ifdef __GNUC__ /* Workaround for spurious -Waggressive-loop-optimizations warning from gcc. * For more info see: https://github.com/lieff/minimp3/issues/88 */ if (__builtin_constant_p(n % 4 == 0) && n % 4 == 0) return; #endif } #endif for (; i < n; i++) { float a = left[i]; float b = right[i]; left[i] = a + b; right[i] = a - b; } } static void drmp3_L3_intensity_stereo_band(float *left, int n, float kl, float kr) { int i; for (i = 0; i < n; i++) { left[i + 576] = left[i]*kr; left[i] = left[i]*kl; } } static void drmp3_L3_stereo_top_band(const float *right, const drmp3_uint8 *sfb, int nbands, int max_band[3]) { int i, k; max_band[0] = max_band[1] = max_band[2] = -1; for (i = 0; i < nbands; i++) { for (k = 0; k < sfb[i]; k += 2) { if (right[k] != 0 || right[k + 1] != 0) { max_band[i % 3] = i; break; } } right += sfb[i]; } } static void drmp3_L3_stereo_process(float *left, const drmp3_uint8 *ist_pos, const drmp3_uint8 *sfb, const drmp3_uint8 *hdr, int max_band[3], int mpeg2_sh) { static const float g_pan[7*2] = { 0,1,0.21132487f,0.78867513f,0.36602540f,0.63397460f,0.5f,0.5f,0.63397460f,0.36602540f,0.78867513f,0.21132487f,1,0 }; unsigned i, max_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 7 : 64; for (i = 0; sfb[i]; i++) { unsigned ipos = ist_pos[i]; if ((int)i > max_band[i % 3] && ipos < max_pos) { float kl, kr, s = DRMP3_HDR_TEST_MS_STEREO(hdr) ? 1.41421356f : 1; if (DRMP3_HDR_TEST_MPEG1(hdr)) { kl = g_pan[2*ipos]; kr = g_pan[2*ipos + 1]; } else { kl = 1; kr = drmp3_L3_ldexp_q2(1, (ipos + 1) >> 1 << mpeg2_sh); if (ipos & 1) { kl = kr; kr = 1; } } drmp3_L3_intensity_stereo_band(left, sfb[i], kl*s, kr*s); } else if (DRMP3_HDR_TEST_MS_STEREO(hdr)) { drmp3_L3_midside_stereo(left, sfb[i]); } left += sfb[i]; } } static void drmp3_L3_intensity_stereo(float *left, drmp3_uint8 *ist_pos, const drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) { int max_band[3], n_sfb = gr->n_long_sfb + gr->n_short_sfb; int i, max_blocks = gr->n_short_sfb ? 3 : 1; drmp3_L3_stereo_top_band(left + 576, gr->sfbtab, n_sfb, max_band); if (gr->n_long_sfb) { max_band[0] = max_band[1] = max_band[2] = DRMP3_MAX(DRMP3_MAX(max_band[0], max_band[1]), max_band[2]); } for (i = 0; i < max_blocks; i++) { int default_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 3 : 0; int itop = n_sfb - max_blocks + i; int prev = itop - max_blocks; ist_pos[itop] = (drmp3_uint8)(max_band[i] >= prev ? default_pos : ist_pos[prev]); } drmp3_L3_stereo_process(left, ist_pos, gr->sfbtab, hdr, max_band, gr[1].scalefac_compress & 1); } static void drmp3_L3_reorder(float *grbuf, float *scratch, const drmp3_uint8 *sfb) { int i, len; float *src = grbuf, *dst = scratch; for (;0 != (len = *sfb); sfb += 3, src += 2*len) { for (i = 0; i < len; i++, src++) { *dst++ = src[0*len]; *dst++ = src[1*len]; *dst++ = src[2*len]; } } DRMP3_COPY_MEMORY(grbuf, scratch, (dst - scratch)*sizeof(float)); } static void drmp3_L3_antialias(float *grbuf, int nbands) { static const float g_aa[2][8] = { {0.85749293f,0.88174200f,0.94962865f,0.98331459f,0.99551782f,0.99916056f,0.99989920f,0.99999316f}, {0.51449576f,0.47173197f,0.31337745f,0.18191320f,0.09457419f,0.04096558f,0.01419856f,0.00369997f} }; for (; nbands > 0; nbands--, grbuf += 18) { int i = 0; #if DRMP3_HAVE_SIMD if (drmp3_have_simd()) for (; i < 8; i += 4) { drmp3_f4 vu = DRMP3_VLD(grbuf + 18 + i); drmp3_f4 vd = DRMP3_VLD(grbuf + 14 - i); drmp3_f4 vc0 = DRMP3_VLD(g_aa[0] + i); drmp3_f4 vc1 = DRMP3_VLD(g_aa[1] + i); vd = DRMP3_VREV(vd); DRMP3_VSTORE(grbuf + 18 + i, DRMP3_VSUB(DRMP3_VMUL(vu, vc0), DRMP3_VMUL(vd, vc1))); vd = DRMP3_VADD(DRMP3_VMUL(vu, vc1), DRMP3_VMUL(vd, vc0)); DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vd)); } #endif #ifndef DR_MP3_ONLY_SIMD for(; i < 8; i++) { float u = grbuf[18 + i]; float d = grbuf[17 - i]; grbuf[18 + i] = u*g_aa[0][i] - d*g_aa[1][i]; grbuf[17 - i] = u*g_aa[1][i] + d*g_aa[0][i]; } #endif } } static void drmp3_L3_dct3_9(float *y) { float s0, s1, s2, s3, s4, s5, s6, s7, s8, t0, t2, t4; s0 = y[0]; s2 = y[2]; s4 = y[4]; s6 = y[6]; s8 = y[8]; t0 = s0 + s6*0.5f; s0 -= s6; t4 = (s4 + s2)*0.93969262f; t2 = (s8 + s2)*0.76604444f; s6 = (s4 - s8)*0.17364818f; s4 += s8 - s2; s2 = s0 - s4*0.5f; y[4] = s4 + s0; s8 = t0 - t2 + s6; s0 = t0 - t4 + t2; s4 = t0 + t4 - s6; s1 = y[1]; s3 = y[3]; s5 = y[5]; s7 = y[7]; s3 *= 0.86602540f; t0 = (s5 + s1)*0.98480775f; t4 = (s5 - s7)*0.34202014f; t2 = (s1 + s7)*0.64278761f; s1 = (s1 - s5 - s7)*0.86602540f; s5 = t0 - s3 - t2; s7 = t4 - s3 - t0; s3 = t4 + s3 - t2; y[0] = s4 - s7; y[1] = s2 + s1; y[2] = s0 - s3; y[3] = s8 + s5; y[5] = s8 - s5; y[6] = s0 + s3; y[7] = s2 - s1; y[8] = s4 + s7; } static void drmp3_L3_imdct36(float *grbuf, float *overlap, const float *window, int nbands) { int i, j; static const float g_twid9[18] = { 0.73727734f,0.79335334f,0.84339145f,0.88701083f,0.92387953f,0.95371695f,0.97629601f,0.99144486f,0.99904822f,0.67559021f,0.60876143f,0.53729961f,0.46174861f,0.38268343f,0.30070580f,0.21643961f,0.13052619f,0.04361938f }; for (j = 0; j < nbands; j++, grbuf += 18, overlap += 9) { float co[9], si[9]; co[0] = -grbuf[0]; si[0] = grbuf[17]; for (i = 0; i < 4; i++) { si[8 - 2*i] = grbuf[4*i + 1] - grbuf[4*i + 2]; co[1 + 2*i] = grbuf[4*i + 1] + grbuf[4*i + 2]; si[7 - 2*i] = grbuf[4*i + 4] - grbuf[4*i + 3]; co[2 + 2*i] = -(grbuf[4*i + 3] + grbuf[4*i + 4]); } drmp3_L3_dct3_9(co); drmp3_L3_dct3_9(si); si[1] = -si[1]; si[3] = -si[3]; si[5] = -si[5]; si[7] = -si[7]; i = 0; #if DRMP3_HAVE_SIMD if (drmp3_have_simd()) for (; i < 8; i += 4) { drmp3_f4 vovl = DRMP3_VLD(overlap + i); drmp3_f4 vc = DRMP3_VLD(co + i); drmp3_f4 vs = DRMP3_VLD(si + i); drmp3_f4 vr0 = DRMP3_VLD(g_twid9 + i); drmp3_f4 vr1 = DRMP3_VLD(g_twid9 + 9 + i); drmp3_f4 vw0 = DRMP3_VLD(window + i); drmp3_f4 vw1 = DRMP3_VLD(window + 9 + i); drmp3_f4 vsum = DRMP3_VADD(DRMP3_VMUL(vc, vr1), DRMP3_VMUL(vs, vr0)); DRMP3_VSTORE(overlap + i, DRMP3_VSUB(DRMP3_VMUL(vc, vr0), DRMP3_VMUL(vs, vr1))); DRMP3_VSTORE(grbuf + i, DRMP3_VSUB(DRMP3_VMUL(vovl, vw0), DRMP3_VMUL(vsum, vw1))); vsum = DRMP3_VADD(DRMP3_VMUL(vovl, vw1), DRMP3_VMUL(vsum, vw0)); DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vsum)); } #endif for (; i < 9; i++) { float ovl = overlap[i]; float sum = co[i]*g_twid9[9 + i] + si[i]*g_twid9[0 + i]; overlap[i] = co[i]*g_twid9[0 + i] - si[i]*g_twid9[9 + i]; grbuf[i] = ovl*window[0 + i] - sum*window[9 + i]; grbuf[17 - i] = ovl*window[9 + i] + sum*window[0 + i]; } } } static void drmp3_L3_idct3(float x0, float x1, float x2, float *dst) { float m1 = x1*0.86602540f; float a1 = x0 - x2*0.5f; dst[1] = x0 + x2; dst[0] = a1 + m1; dst[2] = a1 - m1; } static void drmp3_L3_imdct12(float *x, float *dst, float *overlap) { static const float g_twid3[6] = { 0.79335334f,0.92387953f,0.99144486f, 0.60876143f,0.38268343f,0.13052619f }; float co[3], si[3]; int i; drmp3_L3_idct3(-x[0], x[6] + x[3], x[12] + x[9], co); drmp3_L3_idct3(x[15], x[12] - x[9], x[6] - x[3], si); si[1] = -si[1]; for (i = 0; i < 3; i++) { float ovl = overlap[i]; float sum = co[i]*g_twid3[3 + i] + si[i]*g_twid3[0 + i]; overlap[i] = co[i]*g_twid3[0 + i] - si[i]*g_twid3[3 + i]; dst[i] = ovl*g_twid3[2 - i] - sum*g_twid3[5 - i]; dst[5 - i] = ovl*g_twid3[5 - i] + sum*g_twid3[2 - i]; } } static void drmp3_L3_imdct_short(float *grbuf, float *overlap, int nbands) { for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) { float tmp[18]; DRMP3_COPY_MEMORY(tmp, grbuf, sizeof(tmp)); DRMP3_COPY_MEMORY(grbuf, overlap, 6*sizeof(float)); drmp3_L3_imdct12(tmp, grbuf + 6, overlap + 6); drmp3_L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); drmp3_L3_imdct12(tmp + 2, overlap, overlap + 6); } } static void drmp3_L3_change_sign(float *grbuf) { int b, i; for (b = 0, grbuf += 18; b < 32; b += 2, grbuf += 36) for (i = 1; i < 18; i += 2) grbuf[i] = -grbuf[i]; } static void drmp3_L3_imdct_gr(float *grbuf, float *overlap, unsigned block_type, unsigned n_long_bands) { static const float g_mdct_window[2][18] = { { 0.99904822f,0.99144486f,0.97629601f,0.95371695f,0.92387953f,0.88701083f,0.84339145f,0.79335334f,0.73727734f,0.04361938f,0.13052619f,0.21643961f,0.30070580f,0.38268343f,0.46174861f,0.53729961f,0.60876143f,0.67559021f }, { 1,1,1,1,1,1,0.99144486f,0.92387953f,0.79335334f,0,0,0,0,0,0,0.13052619f,0.38268343f,0.60876143f } }; if (n_long_bands) { drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[0], n_long_bands); grbuf += 18*n_long_bands; overlap += 9*n_long_bands; } if (block_type == DRMP3_SHORT_BLOCK_TYPE) drmp3_L3_imdct_short(grbuf, overlap, 32 - n_long_bands); else drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[block_type == DRMP3_STOP_BLOCK_TYPE], 32 - n_long_bands); } static void drmp3_L3_save_reservoir(drmp3dec *h, drmp3dec_scratch *s) { int pos = (s->bs.pos + 7)/8u; int remains = s->bs.limit/8u - pos; if (remains > DRMP3_MAX_BITRESERVOIR_BYTES) { pos += remains - DRMP3_MAX_BITRESERVOIR_BYTES; remains = DRMP3_MAX_BITRESERVOIR_BYTES; } if (remains > 0) { DRMP3_MOVE_MEMORY(h->reserv_buf, s->maindata + pos, remains); } h->reserv = remains; } static int drmp3_L3_restore_reservoir(drmp3dec *h, drmp3_bs *bs, drmp3dec_scratch *s, int main_data_begin) { int frame_bytes = (bs->limit - bs->pos)/8; int bytes_have = DRMP3_MIN(h->reserv, main_data_begin); DRMP3_COPY_MEMORY(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); DRMP3_COPY_MEMORY(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); drmp3_bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); return h->reserv >= main_data_begin; } static void drmp3_L3_decode(drmp3dec *h, drmp3dec_scratch *s, drmp3_L3_gr_info *gr_info, int nch) { int ch; for (ch = 0; ch < nch; ch++) { int layer3gr_limit = s->bs.pos + gr_info[ch].part_23_length; drmp3_L3_decode_scalefactors(h->header, s->ist_pos[ch], &s->bs, gr_info + ch, s->scf, ch); drmp3_L3_huffman(s->grbuf[ch], &s->bs, gr_info + ch, s->scf, layer3gr_limit); } if (DRMP3_HDR_TEST_I_STEREO(h->header)) { drmp3_L3_intensity_stereo(s->grbuf[0], s->ist_pos[1], gr_info, h->header); } else if (DRMP3_HDR_IS_MS_STEREO(h->header)) { drmp3_L3_midside_stereo(s->grbuf[0], 576); } for (ch = 0; ch < nch; ch++, gr_info++) { int aa_bands = 31; int n_long_bands = (gr_info->mixed_block_flag ? 2 : 0) << (int)(DRMP3_HDR_GET_MY_SAMPLE_RATE(h->header) == 2); if (gr_info->n_short_sfb) { aa_bands = n_long_bands - 1; drmp3_L3_reorder(s->grbuf[ch] + n_long_bands*18, s->syn[0], gr_info->sfbtab + gr_info->n_long_sfb); } drmp3_L3_antialias(s->grbuf[ch], aa_bands); drmp3_L3_imdct_gr(s->grbuf[ch], h->mdct_overlap[ch], gr_info->block_type, n_long_bands); drmp3_L3_change_sign(s->grbuf[ch]); } } static void drmp3d_DCT_II(float *grbuf, int n) { static const float g_sec[24] = { 10.19000816f,0.50060302f,0.50241929f,3.40760851f,0.50547093f,0.52249861f,2.05778098f,0.51544732f,0.56694406f,1.48416460f,0.53104258f,0.64682180f,1.16943991f,0.55310392f,0.78815460f,0.97256821f,0.58293498f,1.06067765f,0.83934963f,0.62250412f,1.72244716f,0.74453628f,0.67480832f,5.10114861f }; int i, k = 0; #if DRMP3_HAVE_SIMD if (drmp3_have_simd()) for (; k < n; k += 4) { drmp3_f4 t[4][8], *x; float *y = grbuf + k; for (x = t[0], i = 0; i < 8; i++, x++) { drmp3_f4 x0 = DRMP3_VLD(&y[i*18]); drmp3_f4 x1 = DRMP3_VLD(&y[(15 - i)*18]); drmp3_f4 x2 = DRMP3_VLD(&y[(16 + i)*18]); drmp3_f4 x3 = DRMP3_VLD(&y[(31 - i)*18]); drmp3_f4 t0 = DRMP3_VADD(x0, x3); drmp3_f4 t1 = DRMP3_VADD(x1, x2); drmp3_f4 t2 = DRMP3_VMUL_S(DRMP3_VSUB(x1, x2), g_sec[3*i + 0]); drmp3_f4 t3 = DRMP3_VMUL_S(DRMP3_VSUB(x0, x3), g_sec[3*i + 1]); x[0] = DRMP3_VADD(t0, t1); x[8] = DRMP3_VMUL_S(DRMP3_VSUB(t0, t1), g_sec[3*i + 2]); x[16] = DRMP3_VADD(t3, t2); x[24] = DRMP3_VMUL_S(DRMP3_VSUB(t3, t2), g_sec[3*i + 2]); } for (x = t[0], i = 0; i < 4; i++, x += 8) { drmp3_f4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; xt = DRMP3_VSUB(x0, x7); x0 = DRMP3_VADD(x0, x7); x7 = DRMP3_VSUB(x1, x6); x1 = DRMP3_VADD(x1, x6); x6 = DRMP3_VSUB(x2, x5); x2 = DRMP3_VADD(x2, x5); x5 = DRMP3_VSUB(x3, x4); x3 = DRMP3_VADD(x3, x4); x4 = DRMP3_VSUB(x0, x3); x0 = DRMP3_VADD(x0, x3); x3 = DRMP3_VSUB(x1, x2); x1 = DRMP3_VADD(x1, x2); x[0] = DRMP3_VADD(x0, x1); x[4] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x1), 0.70710677f); x5 = DRMP3_VADD(x5, x6); x6 = DRMP3_VMUL_S(DRMP3_VADD(x6, x7), 0.70710677f); x7 = DRMP3_VADD(x7, xt); x3 = DRMP3_VMUL_S(DRMP3_VADD(x3, x4), 0.70710677f); x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); /* rotate by PI/8 */ x7 = DRMP3_VADD(x7, DRMP3_VMUL_S(x5, 0.382683432f)); x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); x0 = DRMP3_VSUB(xt, x6); xt = DRMP3_VADD(xt, x6); x[1] = DRMP3_VMUL_S(DRMP3_VADD(xt, x7), 0.50979561f); x[2] = DRMP3_VMUL_S(DRMP3_VADD(x4, x3), 0.54119611f); x[3] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x5), 0.60134488f); x[5] = DRMP3_VMUL_S(DRMP3_VADD(x0, x5), 0.89997619f); x[6] = DRMP3_VMUL_S(DRMP3_VSUB(x4, x3), 1.30656302f); x[7] = DRMP3_VMUL_S(DRMP3_VSUB(xt, x7), 2.56291556f); } if (k > n - 3) { #if DRMP3_HAVE_SSE #define DRMP3_VSAVE2(i, v) _mm_storel_pi((__m64 *)(void*)&y[i*18], v) #else #define DRMP3_VSAVE2(i, v) vst1_f32((float32_t *)&y[(i)*18], vget_low_f32(v)) #endif for (i = 0; i < 7; i++, y += 4*18) { drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); DRMP3_VSAVE2(0, t[0][i]); DRMP3_VSAVE2(1, DRMP3_VADD(t[2][i], s)); DRMP3_VSAVE2(2, DRMP3_VADD(t[1][i], t[1][i + 1])); DRMP3_VSAVE2(3, DRMP3_VADD(t[2][1 + i], s)); } DRMP3_VSAVE2(0, t[0][7]); DRMP3_VSAVE2(1, DRMP3_VADD(t[2][7], t[3][7])); DRMP3_VSAVE2(2, t[1][7]); DRMP3_VSAVE2(3, t[3][7]); } else { #define DRMP3_VSAVE4(i, v) DRMP3_VSTORE(&y[(i)*18], v) for (i = 0; i < 7; i++, y += 4*18) { drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); DRMP3_VSAVE4(0, t[0][i]); DRMP3_VSAVE4(1, DRMP3_VADD(t[2][i], s)); DRMP3_VSAVE4(2, DRMP3_VADD(t[1][i], t[1][i + 1])); DRMP3_VSAVE4(3, DRMP3_VADD(t[2][1 + i], s)); } DRMP3_VSAVE4(0, t[0][7]); DRMP3_VSAVE4(1, DRMP3_VADD(t[2][7], t[3][7])); DRMP3_VSAVE4(2, t[1][7]); DRMP3_VSAVE4(3, t[3][7]); } } else #endif #ifdef DR_MP3_ONLY_SIMD {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ #else for (; k < n; k++) { float t[4][8], *x, *y = grbuf + k; for (x = t[0], i = 0; i < 8; i++, x++) { float x0 = y[i*18]; float x1 = y[(15 - i)*18]; float x2 = y[(16 + i)*18]; float x3 = y[(31 - i)*18]; float t0 = x0 + x3; float t1 = x1 + x2; float t2 = (x1 - x2)*g_sec[3*i + 0]; float t3 = (x0 - x3)*g_sec[3*i + 1]; x[0] = t0 + t1; x[8] = (t0 - t1)*g_sec[3*i + 2]; x[16] = t3 + t2; x[24] = (t3 - t2)*g_sec[3*i + 2]; } for (x = t[0], i = 0; i < 4; i++, x += 8) { float x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; xt = x0 - x7; x0 += x7; x7 = x1 - x6; x1 += x6; x6 = x2 - x5; x2 += x5; x5 = x3 - x4; x3 += x4; x4 = x0 - x3; x0 += x3; x3 = x1 - x2; x1 += x2; x[0] = x0 + x1; x[4] = (x0 - x1)*0.70710677f; x5 = x5 + x6; x6 = (x6 + x7)*0.70710677f; x7 = x7 + xt; x3 = (x3 + x4)*0.70710677f; x5 -= x7*0.198912367f; /* rotate by PI/8 */ x7 += x5*0.382683432f; x5 -= x7*0.198912367f; x0 = xt - x6; xt += x6; x[1] = (xt + x7)*0.50979561f; x[2] = (x4 + x3)*0.54119611f; x[3] = (x0 - x5)*0.60134488f; x[5] = (x0 + x5)*0.89997619f; x[6] = (x4 - x3)*1.30656302f; x[7] = (xt - x7)*2.56291556f; } for (i = 0; i < 7; i++, y += 4*18) { y[0*18] = t[0][i]; y[1*18] = t[2][i] + t[3][i] + t[3][i + 1]; y[2*18] = t[1][i] + t[1][i + 1]; y[3*18] = t[2][i + 1] + t[3][i] + t[3][i + 1]; } y[0*18] = t[0][7]; y[1*18] = t[2][7] + t[3][7]; y[2*18] = t[1][7]; y[3*18] = t[3][7]; } #endif } #ifndef DR_MP3_FLOAT_OUTPUT typedef drmp3_int16 drmp3d_sample_t; static drmp3_int16 drmp3d_scale_pcm(float sample) { drmp3_int16 s; #if DRMP3_HAVE_ARMV6 drmp3_int32 s32 = (drmp3_int32)(sample + .5f); s32 -= (s32 < 0); s = (drmp3_int16)drmp3_clip_int16_arm(s32); #else if (sample >= 32766.5f) return (drmp3_int16) 32767; if (sample <= -32767.5f) return (drmp3_int16)-32768; s = (drmp3_int16)(sample + .5f); s -= (s < 0); /* away from zero, to be compliant */ #endif return s; } #else typedef float drmp3d_sample_t; static float drmp3d_scale_pcm(float sample) { return sample*(1.f/32768.f); } #endif static void drmp3d_synth_pair(drmp3d_sample_t *pcm, int nch, const float *z) { float a; a = (z[14*64] - z[ 0]) * 29; a += (z[ 1*64] + z[13*64]) * 213; a += (z[12*64] - z[ 2*64]) * 459; a += (z[ 3*64] + z[11*64]) * 2037; a += (z[10*64] - z[ 4*64]) * 5153; a += (z[ 5*64] + z[ 9*64]) * 6574; a += (z[ 8*64] - z[ 6*64]) * 37489; a += z[ 7*64] * 75038; pcm[0] = drmp3d_scale_pcm(a); z += 2; a = z[14*64] * 104; a += z[12*64] * 1567; a += z[10*64] * 9727; a += z[ 8*64] * 64019; a += z[ 6*64] * -9975; a += z[ 4*64] * -45; a += z[ 2*64] * 146; a += z[ 0*64] * -5; pcm[16*nch] = drmp3d_scale_pcm(a); } static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) { int i; float *xr = xl + 576*(nch - 1); drmp3d_sample_t *dstr = dstl + (nch - 1); static const float g_win[] = { -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992, -1,24,-35,202,222,347,-581,2080,1952,4425,-5879,7640,5288,33791,-41176,74856, -1,21,-38,196,225,294,-645,2087,1893,4063,-6237,8092,4561,31947,-43006,74630, -1,19,-41,190,227,244,-711,2085,1822,3705,-6589,8492,3776,30112,-44821,74313, -1,17,-45,183,228,197,-779,2075,1739,3351,-6935,8840,2935,28289,-46617,73908, -1,16,-49,176,228,153,-848,2057,1644,3004,-7271,9139,2037,26482,-48390,73415, -2,14,-53,169,227,111,-919,2032,1535,2663,-7597,9389,1082,24694,-50137,72835, -2,13,-58,161,224,72,-991,2001,1414,2330,-7910,9592,70,22929,-51853,72169, -2,11,-63,154,221,36,-1064,1962,1280,2006,-8209,9750,-998,21189,-53534,71420, -2,10,-68,147,215,2,-1137,1919,1131,1692,-8491,9863,-2122,19478,-55178,70590, -3,9,-73,139,208,-29,-1210,1870,970,1388,-8755,9935,-3300,17799,-56778,69679, -3,8,-79,132,200,-57,-1283,1817,794,1095,-8998,9966,-4533,16155,-58333,68692, -4,7,-85,125,189,-83,-1356,1759,605,814,-9219,9959,-5818,14548,-59838,67629, -4,7,-91,117,177,-106,-1428,1698,402,545,-9416,9916,-7154,12980,-61289,66494, -5,6,-97,111,163,-127,-1498,1634,185,288,-9585,9838,-8540,11455,-62684,65290 }; float *zlin = lins + 15*64; const float *w = g_win; zlin[4*15] = xl[18*16]; zlin[4*15 + 1] = xr[18*16]; zlin[4*15 + 2] = xl[0]; zlin[4*15 + 3] = xr[0]; zlin[4*31] = xl[1 + 18*16]; zlin[4*31 + 1] = xr[1 + 18*16]; zlin[4*31 + 2] = xl[1]; zlin[4*31 + 3] = xr[1]; drmp3d_synth_pair(dstr, nch, lins + 4*15 + 1); drmp3d_synth_pair(dstr + 32*nch, nch, lins + 4*15 + 64 + 1); drmp3d_synth_pair(dstl, nch, lins + 4*15); drmp3d_synth_pair(dstl + 32*nch, nch, lins + 4*15 + 64); #if DRMP3_HAVE_SIMD if (drmp3_have_simd()) for (i = 14; i >= 0; i--) { #define DRMP3_VLOAD(k) drmp3_f4 w0 = DRMP3_VSET(*w++); drmp3_f4 w1 = DRMP3_VSET(*w++); drmp3_f4 vz = DRMP3_VLD(&zlin[4*i - 64*k]); drmp3_f4 vy = DRMP3_VLD(&zlin[4*i - 64*(15 - k)]); #define DRMP3_V0(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0)) ; a = DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1)); } #define DRMP3_V1(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1))); } #define DRMP3_V2(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vy, w1), DRMP3_VMUL(vz, w0))); } drmp3_f4 a, b; zlin[4*i] = xl[18*(31 - i)]; zlin[4*i + 1] = xr[18*(31 - i)]; zlin[4*i + 2] = xl[1 + 18*(31 - i)]; zlin[4*i + 3] = xr[1 + 18*(31 - i)]; zlin[4*i + 64] = xl[1 + 18*(1 + i)]; zlin[4*i + 64 + 1] = xr[1 + 18*(1 + i)]; zlin[4*i - 64 + 2] = xl[18*(1 + i)]; zlin[4*i - 64 + 3] = xr[18*(1 + i)]; DRMP3_V0(0) DRMP3_V2(1) DRMP3_V1(2) DRMP3_V2(3) DRMP3_V1(4) DRMP3_V2(5) DRMP3_V1(6) DRMP3_V2(7) { #ifndef DR_MP3_FLOAT_OUTPUT #if DRMP3_HAVE_SSE static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); dstr[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); dstr[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); dstl[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); dstl[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); dstr[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); dstr[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); dstl[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); dstl[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); #else int16x4_t pcma, pcmb; a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); vst1_lane_s16(dstr + (15 - i)*nch, pcma, 1); vst1_lane_s16(dstr + (17 + i)*nch, pcmb, 1); vst1_lane_s16(dstl + (15 - i)*nch, pcma, 0); vst1_lane_s16(dstl + (17 + i)*nch, pcmb, 0); vst1_lane_s16(dstr + (47 - i)*nch, pcma, 3); vst1_lane_s16(dstr + (49 + i)*nch, pcmb, 3); vst1_lane_s16(dstl + (47 - i)*nch, pcma, 2); vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); #endif #else #if DRMP3_HAVE_SSE static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; #else const drmp3_f4 g_scale = vdupq_n_f32(1.0f/32768.0f); #endif a = DRMP3_VMUL(a, g_scale); b = DRMP3_VMUL(b, g_scale); #if DRMP3_HAVE_SSE _mm_store_ss(dstr + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1))); _mm_store_ss(dstr + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1))); _mm_store_ss(dstl + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0))); _mm_store_ss(dstl + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0))); _mm_store_ss(dstr + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3))); _mm_store_ss(dstr + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3))); _mm_store_ss(dstl + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2))); _mm_store_ss(dstl + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2))); #else vst1q_lane_f32(dstr + (15 - i)*nch, a, 1); vst1q_lane_f32(dstr + (17 + i)*nch, b, 1); vst1q_lane_f32(dstl + (15 - i)*nch, a, 0); vst1q_lane_f32(dstl + (17 + i)*nch, b, 0); vst1q_lane_f32(dstr + (47 - i)*nch, a, 3); vst1q_lane_f32(dstr + (49 + i)*nch, b, 3); vst1q_lane_f32(dstl + (47 - i)*nch, a, 2); vst1q_lane_f32(dstl + (49 + i)*nch, b, 2); #endif #endif /* DR_MP3_FLOAT_OUTPUT */ } } else #endif #ifdef DR_MP3_ONLY_SIMD {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ #else for (i = 14; i >= 0; i--) { #define DRMP3_LOAD(k) float w0 = *w++; float w1 = *w++; float *vz = &zlin[4*i - k*64]; float *vy = &zlin[4*i - (15 - k)*64]; #define DRMP3_S0(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] = vz[j]*w1 + vy[j]*w0, a[j] = vz[j]*w0 - vy[j]*w1; } #define DRMP3_S1(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vz[j]*w0 - vy[j]*w1; } #define DRMP3_S2(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vy[j]*w1 - vz[j]*w0; } float a[4], b[4]; zlin[4*i] = xl[18*(31 - i)]; zlin[4*i + 1] = xr[18*(31 - i)]; zlin[4*i + 2] = xl[1 + 18*(31 - i)]; zlin[4*i + 3] = xr[1 + 18*(31 - i)]; zlin[4*(i + 16)] = xl[1 + 18*(1 + i)]; zlin[4*(i + 16) + 1] = xr[1 + 18*(1 + i)]; zlin[4*(i - 16) + 2] = xl[18*(1 + i)]; zlin[4*(i - 16) + 3] = xr[18*(1 + i)]; DRMP3_S0(0) DRMP3_S2(1) DRMP3_S1(2) DRMP3_S2(3) DRMP3_S1(4) DRMP3_S2(5) DRMP3_S1(6) DRMP3_S2(7) dstr[(15 - i)*nch] = drmp3d_scale_pcm(a[1]); dstr[(17 + i)*nch] = drmp3d_scale_pcm(b[1]); dstl[(15 - i)*nch] = drmp3d_scale_pcm(a[0]); dstl[(17 + i)*nch] = drmp3d_scale_pcm(b[0]); dstr[(47 - i)*nch] = drmp3d_scale_pcm(a[3]); dstr[(49 + i)*nch] = drmp3d_scale_pcm(b[3]); dstl[(47 - i)*nch] = drmp3d_scale_pcm(a[2]); dstl[(49 + i)*nch] = drmp3d_scale_pcm(b[2]); } #endif } static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, drmp3d_sample_t *pcm, float *lins) { int i; for (i = 0; i < nch; i++) { drmp3d_DCT_II(grbuf + 576*i, nbands); } DRMP3_COPY_MEMORY(lins, qmf_state, sizeof(float)*15*64); for (i = 0; i < nbands; i += 2) { drmp3d_synth(grbuf + i, pcm + 32*nch*i, nch, lins + i*64); } #ifndef DR_MP3_NONSTANDARD_BUT_LOGICAL if (nch == 1) { for (i = 0; i < 15*64; i += 2) { qmf_state[i] = lins[nbands*64 + i]; } } else #endif { DRMP3_COPY_MEMORY(qmf_state, lins + nbands*64, sizeof(float)*15*64); } } static int drmp3d_match_frame(const drmp3_uint8 *hdr, int mp3_bytes, int frame_bytes) { int i, nmatch; for (i = 0, nmatch = 0; nmatch < DRMP3_MAX_FRAME_SYNC_MATCHES; nmatch++) { i += drmp3_hdr_frame_bytes(hdr + i, frame_bytes) + drmp3_hdr_padding(hdr + i); if (i + DRMP3_HDR_SIZE > mp3_bytes) return nmatch > 0; if (!drmp3_hdr_compare(hdr, hdr + i)) return 0; } return 1; } static int drmp3d_find_frame(const drmp3_uint8 *mp3, int mp3_bytes, int *free_format_bytes, int *ptr_frame_bytes) { int i, k; for (i = 0; i < mp3_bytes - DRMP3_HDR_SIZE; i++, mp3++) { if (drmp3_hdr_valid(mp3)) { int frame_bytes = drmp3_hdr_frame_bytes(mp3, *free_format_bytes); int frame_and_padding = frame_bytes + drmp3_hdr_padding(mp3); for (k = DRMP3_HDR_SIZE; !frame_bytes && k < DRMP3_MAX_FREE_FORMAT_FRAME_SIZE && i + 2*k < mp3_bytes - DRMP3_HDR_SIZE; k++) { if (drmp3_hdr_compare(mp3, mp3 + k)) { int fb = k - drmp3_hdr_padding(mp3); int nextfb = fb + drmp3_hdr_padding(mp3 + k); if (i + k + nextfb + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + k + nextfb)) continue; frame_and_padding = k; frame_bytes = fb; *free_format_bytes = fb; } } if ((frame_bytes && i + frame_and_padding <= mp3_bytes && drmp3d_match_frame(mp3, mp3_bytes - i, frame_bytes)) || (!i && frame_and_padding == mp3_bytes)) { *ptr_frame_bytes = frame_and_padding; return i; } *free_format_bytes = 0; } } *ptr_frame_bytes = 0; return mp3_bytes; } DRMP3_API void drmp3dec_init(drmp3dec *dec) { dec->header[0] = 0; } DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) { int i = 0, igr, frame_size = 0, success = 1; const drmp3_uint8 *hdr; drmp3_bs bs_frame[1]; drmp3dec_scratch scratch; if (mp3_bytes > 4 && dec->header[0] == 0xff && drmp3_hdr_compare(dec->header, mp3)) { frame_size = drmp3_hdr_frame_bytes(mp3, dec->free_format_bytes) + drmp3_hdr_padding(mp3); if (frame_size != mp3_bytes && (frame_size + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + frame_size))) { frame_size = 0; } } if (!frame_size) { DRMP3_ZERO_MEMORY(dec, sizeof(drmp3dec)); i = drmp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); if (!frame_size || i + frame_size > mp3_bytes) { info->frame_bytes = i; return 0; } } hdr = mp3 + i; DRMP3_COPY_MEMORY(dec->header, hdr, DRMP3_HDR_SIZE); info->frame_bytes = i + frame_size; info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; info->sample_rate = drmp3_hdr_sample_rate_hz(hdr); info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE); if (DRMP3_HDR_IS_CRC(hdr)) { drmp3_bs_get_bits(bs_frame, 16); } if (info->layer == 3) { int main_data_begin = drmp3_L3_read_side_info(bs_frame, scratch.gr_info, hdr); if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) { drmp3dec_init(dec); return 0; } success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); if (success && pcm != NULL) { for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) { DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); } } drmp3_L3_save_reservoir(dec, &scratch); } else { #ifdef DR_MP3_ONLY_MP3 return 0; #else drmp3_L12_scale_info sci[1]; if (pcm == NULL) { return drmp3_hdr_frame_samples(hdr); } drmp3_L12_read_scale_info(hdr, bs_frame, sci); DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); for (i = 0, igr = 0; igr < 3; igr++) { if (12 == (i += drmp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) { i = 0; drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); } if (bs_frame->pos > bs_frame->limit) { drmp3dec_init(dec); return 0; } } #endif } return success*drmp3_hdr_frame_samples(dec->header); } DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples) { size_t i = 0; #if DRMP3_HAVE_SIMD size_t aligned_count = num_samples & ~7; for(; i < aligned_count; i+=8) { drmp3_f4 scale = DRMP3_VSET(32768.0f); drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), scale); drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), scale); #if DRMP3_HAVE_SSE drmp3_f4 s16max = DRMP3_VSET( 32767.0f); drmp3_f4 s16min = DRMP3_VSET(-32768.0f); __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, s16max), s16min)), _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, s16max), s16min))); out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); #else int16x4_t pcma, pcmb; a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); vst1_lane_s16(out+i , pcma, 0); vst1_lane_s16(out+i+1, pcma, 1); vst1_lane_s16(out+i+2, pcma, 2); vst1_lane_s16(out+i+3, pcma, 3); vst1_lane_s16(out+i+4, pcmb, 0); vst1_lane_s16(out+i+5, pcmb, 1); vst1_lane_s16(out+i+6, pcmb, 2); vst1_lane_s16(out+i+7, pcmb, 3); #endif } #endif for(; i < num_samples; i++) { float sample = in[i] * 32768.0f; if (sample >= 32766.5f) out[i] = (drmp3_int16) 32767; else if (sample <= -32767.5f) out[i] = (drmp3_int16)-32768; else { short s = (drmp3_int16)(sample + .5f); s -= (s < 0); /* away from zero, to be compliant */ out[i] = s; } } } /************************************************************************************************************************************************************ Main Public API ************************************************************************************************************************************************************/ /* SIZE_MAX */ #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX #else #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) #define DRMP3_SIZE_MAX ((drmp3_uint64)0xFFFFFFFFFFFFFFFF) #else #define DRMP3_SIZE_MAX 0xFFFFFFFF #endif #endif /* End SIZE_MAX */ /* Options. */ #ifndef DRMP3_SEEK_LEADING_MP3_FRAMES #define DRMP3_SEEK_LEADING_MP3_FRAMES 2 #endif #define DRMP3_MIN_DATA_CHUNK_SIZE 16384 /* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends at least 16K, but in an attempt to reduce data movement I'm making this slightly larger. */ #ifndef DRMP3_DATA_CHUNK_SIZE #define DRMP3_DATA_CHUNK_SIZE (DRMP3_MIN_DATA_CHUNK_SIZE*4) #endif #define DRMP3_COUNTOF(x) (sizeof(x) / sizeof(x[0])) #define DRMP3_CLAMP(x, lo, hi) (DRMP3_MAX(lo, DRMP3_MIN(x, hi))) #ifndef DRMP3_PI_D #define DRMP3_PI_D 3.14159265358979323846264 #endif #define DRMP3_DEFAULT_RESAMPLER_LPF_ORDER 2 static DRMP3_INLINE float drmp3_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; } static DRMP3_INLINE float drmp3_mix_f32_fast(float x, float y, float a) { float r0 = (y - x); float r1 = r0*a; return x + r1; /*return x + (y - x)*a;*/ } /* Greatest common factor using Euclid's algorithm iteratively. */ static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) { for (;;) { if (b == 0) { break; } else { drmp3_uint32 t = a; a = b; b = t % a; } } return a; } static void* drmp3__malloc_default(size_t sz, void* pUserData) { (void)pUserData; return DRMP3_MALLOC(sz); } static void* drmp3__realloc_default(void* p, size_t sz, void* pUserData) { (void)pUserData; return DRMP3_REALLOC(p, sz); } static void drmp3__free_default(void* p, void* pUserData) { (void)pUserData; DRMP3_FREE(p); } static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; } if (pAllocationCallbacks->onMalloc != NULL) { return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); } /* Try using realloc(). */ if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); } return NULL; } static void* drmp3__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; } if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); } /* Try emulating realloc() in terms of malloc()/free(). */ if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { void* p2; p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); if (p2 == NULL) { return NULL; } if (p != NULL) { DRMP3_COPY_MEMORY(p2, p, szOld); pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } return p2; } return NULL; } static void drmp3__free_from_callbacks(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (p == NULL || pAllocationCallbacks == NULL) { return; } if (pAllocationCallbacks->onFree != NULL) { pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } } static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { /* Copy. */ return *pAllocationCallbacks; } else { /* Defaults. */ drmp3_allocation_callbacks allocationCallbacks; allocationCallbacks.pUserData = NULL; allocationCallbacks.onMalloc = drmp3__malloc_default; allocationCallbacks.onRealloc = drmp3__realloc_default; allocationCallbacks.onFree = drmp3__free_default; return allocationCallbacks; } } static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { size_t bytesRead; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->onRead != NULL); /* Don't try reading 0 bytes from the callback. This can happen when the stream is clamped against ID3v1 or APE tags at the end of the stream. */ if (bytesToRead == 0) { return 0; } bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); pMP3->streamCursor += bytesRead; return bytesRead; } static size_t drmp3__on_read_clamped(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->onRead != NULL); if (pMP3->streamLength == DRMP3_UINT64_MAX) { return drmp3__on_read(pMP3, pBufferOut, bytesToRead); } else { drmp3_uint64 bytesRemaining; bytesRemaining = (pMP3->streamLength - pMP3->streamCursor); if (bytesToRead > bytesRemaining) { bytesToRead = (size_t)bytesRemaining; } return drmp3__on_read(pMP3, pBufferOut, bytesToRead); } } static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) { DRMP3_ASSERT(offset >= 0); DRMP3_ASSERT(origin == drmp3_seek_origin_start || origin == drmp3_seek_origin_current); if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { return DRMP3_FALSE; } if (origin == drmp3_seek_origin_start) { pMP3->streamCursor = (drmp3_uint64)offset; } else{ pMP3->streamCursor += offset; } return DRMP3_TRUE; } static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_seek_origin origin) { if (offset <= 0x7FFFFFFF) { return drmp3__on_seek(pMP3, (int)offset, origin); } /* Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset. */ if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) { return DRMP3_FALSE; } offset -= 0x7FFFFFFF; while (offset > 0) { if (offset <= 0x7FFFFFFF) { if (!drmp3__on_seek(pMP3, (int)offset, drmp3_seek_origin_current)) { return DRMP3_FALSE; } offset = 0; } else { if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_current)) { return DRMP3_FALSE; } offset -= 0x7FFFFFFF; } } return DRMP3_TRUE; } static void drmp3__on_meta(drmp3* pMP3, drmp3_metadata_type type, const void* pRawData, size_t rawDataSize) { if (pMP3->onMeta) { drmp3_metadata metadata; DRMP3_ZERO_OBJECT(&metadata); metadata.type = type; metadata.pRawData = pRawData; metadata.rawDataSize = rawDataSize; pMP3->onMeta(pMP3->pUserDataMeta, &metadata); } } static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData) { drmp3_uint32 pcmFramesRead = 0; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->onRead != NULL); if (pMP3->atEnd) { return 0; } for (;;) { drmp3dec_frame_info info; /* minimp3 recommends doing data submission in chunks of at least 16K. If we don't have at least 16K bytes available, get more. */ if (pMP3->dataSize < DRMP3_MIN_DATA_CHUNK_SIZE) { size_t bytesRead; /* First we need to move the data down. */ if (pMP3->pData != NULL) { DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); } pMP3->dataConsumed = 0; if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { drmp3_uint8* pNewData; size_t newDataCap; newDataCap = DRMP3_DATA_CHUNK_SIZE; pNewData = (drmp3_uint8*)drmp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks); if (pNewData == NULL) { return 0; /* Out of memory. */ } pMP3->pData = pNewData; pMP3->dataCapacity = newDataCap; } bytesRead = drmp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { if (pMP3->dataSize == 0) { pMP3->atEnd = DRMP3_TRUE; return 0; /* No data. */ } } pMP3->dataSize += bytesRead; } if (pMP3->dataSize > INT_MAX) { pMP3->atEnd = DRMP3_TRUE; return 0; /* File too big. */ } DRMP3_ASSERT(pMP3->pData != NULL); DRMP3_ASSERT(pMP3->dataCapacity > 0); /* Do a runtime check here to try silencing a false-positive from clang-analyzer. */ if (pMP3->pData == NULL) { return 0; } pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ /* Consume the data. */ pMP3->dataConsumed += (size_t)info.frame_bytes; pMP3->dataSize -= (size_t)info.frame_bytes; /* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */ if (pcmFramesRead > 0) { pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; pMP3->mp3FrameSampleRate = info.sample_rate; if (pMP3FrameInfo != NULL) { *pMP3FrameInfo = info; } if (ppMP3FrameData != NULL) { *ppMP3FrameData = pMP3->pData + pMP3->dataConsumed - (size_t)info.frame_bytes; } break; } else if (info.frame_bytes == 0) { /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ size_t bytesRead; /* First we need to move the data down. */ DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); pMP3->dataConsumed = 0; if (pMP3->dataCapacity == pMP3->dataSize) { /* No room. Expand. */ drmp3_uint8* pNewData; size_t newDataCap; newDataCap = pMP3->dataCapacity + DRMP3_DATA_CHUNK_SIZE; pNewData = (drmp3_uint8*)drmp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks); if (pNewData == NULL) { return 0; /* Out of memory. */ } pMP3->pData = pNewData; pMP3->dataCapacity = newDataCap; } /* Fill in a chunk. */ bytesRead = drmp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); if (bytesRead == 0) { pMP3->atEnd = DRMP3_TRUE; return 0; /* Error reading more data. */ } pMP3->dataSize += bytesRead; } }; return pcmFramesRead; } static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData) { drmp3_uint32 pcmFramesRead = 0; drmp3dec_frame_info info; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->memory.pData != NULL); if (pMP3->atEnd) { return 0; } for (;;) { pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->memory.pData + pMP3->memory.currentReadPos, (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos), pPCMFrames, &info); if (pcmFramesRead > 0) { pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; pMP3->mp3FrameSampleRate = info.sample_rate; if (pMP3FrameInfo != NULL) { *pMP3FrameInfo = info; } if (ppMP3FrameData != NULL) { *ppMP3FrameData = pMP3->memory.pData + pMP3->memory.currentReadPos; } break; } else if (info.frame_bytes > 0) { /* No frames were read, but it looks like we skipped past one. Read the next MP3 frame. */ pMP3->memory.currentReadPos += (size_t)info.frame_bytes; pMP3->streamCursor += (size_t)info.frame_bytes; } else { /* Nothing at all was read. Abort. */ break; } } /* Consume the data. */ pMP3->memory.currentReadPos += (size_t)info.frame_bytes; pMP3->streamCursor += (size_t)info.frame_bytes; return pcmFramesRead; } static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3dec_frame_info* pMP3FrameInfo, const drmp3_uint8** ppMP3FrameData) { if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData); } else { return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames, pMP3FrameInfo, ppMP3FrameData); } } static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, NULL, NULL); } #if 0 static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) { drmp3_uint32 pcmFrameCount; DRMP3_ASSERT(pMP3 != NULL); pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFrameCount == 0) { return 0; } /* We have essentially just skipped past the frame, so just set the remaining samples to 0. */ pMP3->currentPCMFrame += pcmFrameCount; pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; pMP3->pcmFramesRemainingInMP3Frame = 0; return pcmFrameCount; } #endif static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3dec_frame_info firstFrameInfo; const drmp3_uint8* pFirstFrameData; drmp3_uint32 firstFramePCMFrameCount; drmp3_uint32 detectedMP3FrameCount = 0xFFFFFFFF; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(onRead != NULL); /* This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. */ drmp3dec_init(&pMP3->decoder); pMP3->onRead = onRead; pMP3->onSeek = onSeek; pMP3->onMeta = onMeta; pMP3->pUserData = pUserData; pMP3->pUserDataMeta = pUserDataMeta; pMP3->allocationCallbacks = drmp3_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pMP3->allocationCallbacks.onFree == NULL || (pMP3->allocationCallbacks.onMalloc == NULL && pMP3->allocationCallbacks.onRealloc == NULL)) { return DRMP3_FALSE; /* Invalid allocation callbacks. */ } pMP3->streamCursor = 0; pMP3->streamLength = DRMP3_UINT64_MAX; pMP3->streamStartOffset = 0; pMP3->delayInPCMFrames = 0; pMP3->paddingInPCMFrames = 0; pMP3->totalPCMFrameCount = DRMP3_UINT64_MAX; /* We'll first check for any ID3v1 or APE tags. */ #if 1 if (onSeek != NULL && onTell != NULL) { if (onSeek(pUserData, 0, drmp3_seek_origin_end)) { drmp3_int64 streamLen; int streamEndOffset = 0; /* First get the length of the stream. We need this so we can ensure the stream is big enough to store the tags. */ if (onTell(pUserData, &streamLen)) { /* ID3v1 */ if (streamLen > 128) { char id3[3]; if (onSeek(pUserData, streamEndOffset - 128, drmp3_seek_origin_end)) { if (onRead(pUserData, id3, 3) == 3 && id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') { /* We have an ID3v1 tag. */ streamEndOffset -= 128; streamLen -= 128; /* Fire a metadata callback for the TAG data. */ if (onMeta != NULL) { drmp3_uint8 tag[128]; tag[0] = 'T'; tag[1] = 'A'; tag[2] = 'G'; if (onRead(pUserData, tag + 3, 125) == 125) { drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_ID3V1, tag, 128); } } } else { /* No ID3v1 tag. */ } } else { /* Failed to seek to the ID3v1 tag. */ } } else { /* Stream too short. No ID3v1 tag. */ } /* APE */ if (streamLen > 32) { char ape[32]; /* The footer. */ if (onSeek(pUserData, streamEndOffset - 32, drmp3_seek_origin_end)) { if (onRead(pUserData, ape, 32) == 32 && ape[0] == 'A' && ape[1] == 'P' && ape[2] == 'E' && ape[3] == 'T' && ape[4] == 'A' && ape[5] == 'G' && ape[6] == 'E' && ape[7] == 'X') { /* We have an APE tag. */ drmp3_uint32 tagSize = ((drmp3_uint32)ape[24] << 0) | ((drmp3_uint32)ape[25] << 8) | ((drmp3_uint32)ape[26] << 16) | ((drmp3_uint32)ape[27] << 24); streamEndOffset -= 32 + tagSize; streamLen -= 32 + tagSize; /* Fire a metadata callback for the APE data. Must include both the main content and footer. */ if (onMeta != NULL) { /* We first need to seek to the start of the APE tag. */ if (onSeek(pUserData, streamEndOffset, drmp3_seek_origin_end)) { size_t apeTagSize = (size_t)tagSize + 32; drmp3_uint8* pTagData = (drmp3_uint8*)drmp3_malloc(apeTagSize, pAllocationCallbacks); if (pTagData != NULL) { if (onRead(pUserData, pTagData, apeTagSize) == apeTagSize) { drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_APE, pTagData, apeTagSize); } drmp3_free(pTagData, pAllocationCallbacks); } } } } } } else { /* Stream too short. No APE tag. */ } /* Seek back to the start. */ if (!onSeek(pUserData, 0, drmp3_seek_origin_start)) { return DRMP3_FALSE; /* Failed to seek back to the start. */ } pMP3->streamLength = (drmp3_uint64)streamLen; if (pMP3->memory.pData != NULL) { pMP3->memory.dataSize = (size_t)pMP3->streamLength; } } else { /* Failed to get the length of the stream. ID3v1 and APE tags cannot be skipped. */ if (!onSeek(pUserData, 0, drmp3_seek_origin_start)) { return DRMP3_FALSE; /* Failed to seek back to the start. */ } } } else { /* Failed to seek to the end. Cannot skip ID3v1 or APE tags. */ } } else { /* No onSeek or onTell callback. Cannot skip ID3v1 or APE tags. */ } #endif /* ID3v2 tags */ #if 1 { char header[10]; if (onRead(pUserData, header, 10) == 10) { if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') { drmp3_uint32 tagSize = (((drmp3_uint32)header[6] & 0x7F) << 21) | (((drmp3_uint32)header[7] & 0x7F) << 14) | (((drmp3_uint32)header[8] & 0x7F) << 7) | (((drmp3_uint32)header[9] & 0x7F) << 0); /* Account for the footer. */ if (header[5] & 0x10) { tagSize += 10; } /* Read the tag content and fire a metadata callback. */ if (onMeta != NULL) { size_t tagSizeWithHeader = 10 + tagSize; drmp3_uint8* pTagData = (drmp3_uint8*)drmp3_malloc(tagSizeWithHeader, pAllocationCallbacks); if (pTagData != NULL) { DRMP3_COPY_MEMORY(pTagData, header, 10); if (onRead(pUserData, pTagData + 10, tagSize) == tagSize) { drmp3__on_meta(pMP3, DRMP3_METADATA_TYPE_ID3V2, pTagData, tagSizeWithHeader); } drmp3_free(pTagData, pAllocationCallbacks); } } else { /* Don't have a metadata callback, so just skip the tag. */ if (onSeek != NULL) { if (!onSeek(pUserData, tagSize, drmp3_seek_origin_current)) { return DRMP3_FALSE; /* Failed to seek past the ID3v2 tag. */ } } else { /* Don't have a seek callback. Read and discard. */ char discard[1024]; while (tagSize > 0) { size_t bytesToRead = tagSize; if (bytesToRead > sizeof(discard)) { bytesToRead = sizeof(discard); } if (onRead(pUserData, discard, bytesToRead) != bytesToRead) { return DRMP3_FALSE; /* Failed to read data. */ } tagSize -= (drmp3_uint32)bytesToRead; } } } pMP3->streamStartOffset += 10 + tagSize; /* +10 for the header. */ pMP3->streamCursor = pMP3->streamStartOffset; } else { /* Not an ID3v2 tag. Seek back to the start. */ if (onSeek != NULL) { if (!onSeek(pUserData, 0, drmp3_seek_origin_start)) { return DRMP3_FALSE; /* Failed to seek back to the start. */ } } else { /* Don't have a seek callback to move backwards. We'll just fall through and let the decoding process re-sync. The ideal solution here would be to read into the cache. */ /* TODO: Copy the header into the cache. Will need to allocate space. See drmp3_decode_next_frame_ex__callbacks. There is not need to handle the memory case because that will always have a seek implementation and will never hit this code path. */ } } } else { /* Failed to read the header. We can return false here. If we couldn't read 10 bytes there's no way we'll have a valid MP3 stream. */ return DRMP3_FALSE; } } #endif /* Decode the first frame to confirm that it is indeed a valid MP3 stream. Note that it's possible the first frame is actually a Xing/LAME/VBRI header. If this is the case we need to skip over it. */ firstFramePCMFrameCount = drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, &firstFrameInfo, &pFirstFrameData); if (firstFramePCMFrameCount > 0) { DRMP3_ASSERT(pFirstFrameData != NULL); /* It might be a header. If so, we need to clear out the cached PCM frames in order to trigger a reload of fresh data when decoding starts. We can assume all validation has already been performed to check if this is a valid MP3 frame and that there is more than 0 bytes making up the frame. We're going to be basing this parsing code off the minimp3_ex implementation. */ #if 1 DRMP3_ASSERT(firstFrameInfo.frame_bytes > 0); { drmp3_bs bs; drmp3_L3_gr_info grInfo[4]; const drmp3_uint8* pTagData = pFirstFrameData; drmp3_bs_init(&bs, pFirstFrameData + DRMP3_HDR_SIZE, firstFrameInfo.frame_bytes - DRMP3_HDR_SIZE); if (DRMP3_HDR_IS_CRC(pFirstFrameData)) { drmp3_bs_get_bits(&bs, 16); /* CRC. */ } if (drmp3_L3_read_side_info(&bs, grInfo, pFirstFrameData) >= 0) { drmp3_bool32 isXing = DRMP3_FALSE; drmp3_bool32 isInfo = DRMP3_FALSE; const drmp3_uint8* pTagDataBeg; pTagDataBeg = pFirstFrameData + DRMP3_HDR_SIZE + (bs.pos/8); pTagData = pTagDataBeg; /* Check for both "Xing" and "Info" identifiers. */ isXing = (pTagData[0] == 'X' && pTagData[1] == 'i' && pTagData[2] == 'n' && pTagData[3] == 'g'); isInfo = (pTagData[0] == 'I' && pTagData[1] == 'n' && pTagData[2] == 'f' && pTagData[3] == 'o'); if (isXing || isInfo) { drmp3_uint32 bytes = 0; drmp3_uint32 flags = pTagData[7]; pTagData += 8; /* Skip past the ID and flags. */ if (flags & 0x01) { /* FRAMES flag. */ detectedMP3FrameCount = (drmp3_uint32)pTagData[0] << 24 | (drmp3_uint32)pTagData[1] << 16 | (drmp3_uint32)pTagData[2] << 8 | (drmp3_uint32)pTagData[3]; pTagData += 4; } if (flags & 0x02) { /* BYTES flag. */ bytes = (drmp3_uint32)pTagData[0] << 24 | (drmp3_uint32)pTagData[1] << 16 | (drmp3_uint32)pTagData[2] << 8 | (drmp3_uint32)pTagData[3]; (void)bytes; /* <-- Just to silence a warning about `bytes` being assigned but unused. Want to leave this here in case I want to make use of it later. */ pTagData += 4; } if (flags & 0x04) { /* TOC flag. */ /* TODO: Extract and bind seek points. */ pTagData += 100; } if (flags & 0x08) { /* SCALE flag. */ pTagData += 4; } /* At this point we're done with the Xing/Info header. Now we can look at the LAME data. */ if (pTagData[0]) { pTagData += 21; if (pTagData - pFirstFrameData + 14 < firstFrameInfo.frame_bytes) { int delayInPCMFrames; int paddingInPCMFrames; delayInPCMFrames = (( (drmp3_uint32)pTagData[0] << 4) | ((drmp3_uint32)pTagData[1] >> 4)) + (528 + 1); paddingInPCMFrames = ((((drmp3_uint32)pTagData[1] & 0xF) << 8) | ((drmp3_uint32)pTagData[2] )) - (528 + 1); if (paddingInPCMFrames < 0) { paddingInPCMFrames = 0; /* Padding cannot be negative. Probably a malformed file. Ignore. */ } pMP3->delayInPCMFrames = (drmp3_uint32)delayInPCMFrames; pMP3->paddingInPCMFrames = (drmp3_uint32)paddingInPCMFrames; } } /* My understanding is that if the "Xing" header is present we can consider this to be a VBR stream and if the "Info" header is present it's a CBR stream. If this is not the case let me know! I'm just tracking this for the time being in case I want to look at doing some CBR optimizations later on, such as faster seeking. */ if (isXing) { pMP3->isVBR = DRMP3_TRUE; } else if (isInfo) { pMP3->isCBR = DRMP3_TRUE; } /* Post the raw data of the tag to the metadata callback. */ if (onMeta != NULL) { drmp3_metadata_type metadataType = isXing ? DRMP3_METADATA_TYPE_XING : DRMP3_METADATA_TYPE_VBRI; size_t tagDataSize; tagDataSize = (size_t)firstFrameInfo.frame_bytes; tagDataSize -= (size_t)(pTagDataBeg - pFirstFrameData); drmp3__on_meta(pMP3, metadataType, pTagDataBeg, tagDataSize); } /* Since this was identified as a tag, we don't want to treat it as audio. We need to clear out the PCM cache. */ pMP3->pcmFramesRemainingInMP3Frame = 0; /* The start offset needs to be moved to the end of this frame so it's not included in any audio processing after seeking. */ pMP3->streamStartOffset += (drmp3_uint32)(firstFrameInfo.frame_bytes); pMP3->streamCursor = pMP3->streamStartOffset; } } else { /* Failed to read the side info. */ } } #endif } else { /* Not a valid MP3 stream. */ drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); /* The call above may have allocated memory. Need to make sure it's freed before aborting. */ return DRMP3_FALSE; } if (detectedMP3FrameCount != 0xFFFFFFFF) { pMP3->totalPCMFrameCount = detectedMP3FrameCount * firstFramePCMFrameCount; } pMP3->channels = pMP3->mp3FrameChannels; pMP3->sampleRate = pMP3->mp3FrameSampleRate; return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, drmp3_meta_proc onMeta, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL || onRead == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); return drmp3_init_internal(pMP3, onRead, onSeek, onTell, onMeta, pUserData, pUserData, pAllocationCallbacks); } static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { drmp3* pMP3 = (drmp3*)pUserData; size_t bytesRemaining; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } if (bytesToRead > 0) { DRMP3_COPY_MEMORY(pBufferOut, pMP3->memory.pData + pMP3->memory.currentReadPos, bytesToRead); pMP3->memory.currentReadPos += bytesToRead; } return bytesToRead; } static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3_seek_origin origin) { drmp3* pMP3 = (drmp3*)pUserData; DRMP3_ASSERT(pMP3 != NULL); if (origin == drmp3_seek_origin_current) { if (byteOffset > 0) { if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { return DRMP3_FALSE; /* Trying to seek too far forward. */ } } else { if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { return DRMP3_FALSE; /* Trying to seek too far backwards. */ } } /* This will never underflow thanks to the clamps above. */ pMP3->memory.currentReadPos += byteOffset; } else if (origin == drmp3_seek_origin_start) { if ((drmp3_uint32)byteOffset <= pMP3->memory.dataSize) { pMP3->memory.currentReadPos = byteOffset; } else { return DRMP3_FALSE; /* Trying to seek too far forward. */ } } else if (origin == drmp3_seek_origin_end) { if (byteOffset > 0) { return DRMP3_FALSE; /* Trying to seek beyond the end of the buffer. */ } if ((size_t)(-byteOffset) > pMP3->memory.dataSize) { return DRMP3_FALSE; /* Trying to seek too far back. */ } pMP3->memory.currentReadPos = pMP3->memory.dataSize - (size_t)(-byteOffset); } return DRMP3_TRUE; } static drmp3_bool32 drmp3__on_tell_memory(void* pUserData, drmp3_int64* pCursor) { drmp3* pMP3 = (drmp3*)pUserData; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pCursor != NULL); *pCursor = (drmp3_int64)pMP3->memory.currentReadPos; return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init_memory_with_metadata(drmp3* pMP3, const void* pData, size_t dataSize, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3_bool32 result; if (pMP3 == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); if (pData == NULL || dataSize == 0) { return DRMP3_FALSE; } pMP3->memory.pData = (const drmp3_uint8*)pData; pMP3->memory.dataSize = dataSize; pMP3->memory.currentReadPos = 0; result = drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, drmp3__on_tell_memory, onMeta, pMP3, pUserDataMeta, pAllocationCallbacks); if (result == DRMP3_FALSE) { return DRMP3_FALSE; } /* Adjust the length of the memory stream to account for ID3v1 and APE tags. */ if (pMP3->streamLength <= (drmp3_uint64)DRMP3_SIZE_MAX) { pMP3->memory.dataSize = (size_t)pMP3->streamLength; /* Safe cast. */ } if (pMP3->streamStartOffset > (drmp3_uint64)DRMP3_SIZE_MAX) { return DRMP3_FALSE; /* Tags too big. */ } return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) { return drmp3_init_memory_with_metadata(pMP3, pData, dataSize, NULL, NULL, pAllocationCallbacks); } #ifndef DR_MP3_NO_STDIO #include #include /* For wcslen(), wcsrtombs() */ /* Errno */ /* drmp3_result_from_errno() is only used inside DR_MP3_NO_STDIO for now. Move this out if it's ever used elsewhere. */ #include static drmp3_result drmp3_result_from_errno(int e) { switch (e) { case 0: return DRMP3_SUCCESS; #ifdef EPERM case EPERM: return DRMP3_INVALID_OPERATION; #endif #ifdef ENOENT case ENOENT: return DRMP3_DOES_NOT_EXIST; #endif #ifdef ESRCH case ESRCH: return DRMP3_DOES_NOT_EXIST; #endif #ifdef EINTR case EINTR: return DRMP3_INTERRUPT; #endif #ifdef EIO case EIO: return DRMP3_IO_ERROR; #endif #ifdef ENXIO case ENXIO: return DRMP3_DOES_NOT_EXIST; #endif #ifdef E2BIG case E2BIG: return DRMP3_INVALID_ARGS; #endif #ifdef ENOEXEC case ENOEXEC: return DRMP3_INVALID_FILE; #endif #ifdef EBADF case EBADF: return DRMP3_INVALID_FILE; #endif #ifdef ECHILD case ECHILD: return DRMP3_ERROR; #endif #ifdef EAGAIN case EAGAIN: return DRMP3_UNAVAILABLE; #endif #ifdef ENOMEM case ENOMEM: return DRMP3_OUT_OF_MEMORY; #endif #ifdef EACCES case EACCES: return DRMP3_ACCESS_DENIED; #endif #ifdef EFAULT case EFAULT: return DRMP3_BAD_ADDRESS; #endif #ifdef ENOTBLK case ENOTBLK: return DRMP3_ERROR; #endif #ifdef EBUSY case EBUSY: return DRMP3_BUSY; #endif #ifdef EEXIST case EEXIST: return DRMP3_ALREADY_EXISTS; #endif #ifdef EXDEV case EXDEV: return DRMP3_ERROR; #endif #ifdef ENODEV case ENODEV: return DRMP3_DOES_NOT_EXIST; #endif #ifdef ENOTDIR case ENOTDIR: return DRMP3_NOT_DIRECTORY; #endif #ifdef EISDIR case EISDIR: return DRMP3_IS_DIRECTORY; #endif #ifdef EINVAL case EINVAL: return DRMP3_INVALID_ARGS; #endif #ifdef ENFILE case ENFILE: return DRMP3_TOO_MANY_OPEN_FILES; #endif #ifdef EMFILE case EMFILE: return DRMP3_TOO_MANY_OPEN_FILES; #endif #ifdef ENOTTY case ENOTTY: return DRMP3_INVALID_OPERATION; #endif #ifdef ETXTBSY case ETXTBSY: return DRMP3_BUSY; #endif #ifdef EFBIG case EFBIG: return DRMP3_TOO_BIG; #endif #ifdef ENOSPC case ENOSPC: return DRMP3_NO_SPACE; #endif #ifdef ESPIPE case ESPIPE: return DRMP3_BAD_SEEK; #endif #ifdef EROFS case EROFS: return DRMP3_ACCESS_DENIED; #endif #ifdef EMLINK case EMLINK: return DRMP3_TOO_MANY_LINKS; #endif #ifdef EPIPE case EPIPE: return DRMP3_BAD_PIPE; #endif #ifdef EDOM case EDOM: return DRMP3_OUT_OF_RANGE; #endif #ifdef ERANGE case ERANGE: return DRMP3_OUT_OF_RANGE; #endif #ifdef EDEADLK case EDEADLK: return DRMP3_DEADLOCK; #endif #ifdef ENAMETOOLONG case ENAMETOOLONG: return DRMP3_PATH_TOO_LONG; #endif #ifdef ENOLCK case ENOLCK: return DRMP3_ERROR; #endif #ifdef ENOSYS case ENOSYS: return DRMP3_NOT_IMPLEMENTED; #endif #if defined(ENOTEMPTY) && ENOTEMPTY != EEXIST /* In AIX, ENOTEMPTY and EEXIST use the same value. */ case ENOTEMPTY: return DRMP3_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP case ELOOP: return DRMP3_TOO_MANY_LINKS; #endif #ifdef ENOMSG case ENOMSG: return DRMP3_NO_MESSAGE; #endif #ifdef EIDRM case EIDRM: return DRMP3_ERROR; #endif #ifdef ECHRNG case ECHRNG: return DRMP3_ERROR; #endif #ifdef EL2NSYNC case EL2NSYNC: return DRMP3_ERROR; #endif #ifdef EL3HLT case EL3HLT: return DRMP3_ERROR; #endif #ifdef EL3RST case EL3RST: return DRMP3_ERROR; #endif #ifdef ELNRNG case ELNRNG: return DRMP3_OUT_OF_RANGE; #endif #ifdef EUNATCH case EUNATCH: return DRMP3_ERROR; #endif #ifdef ENOCSI case ENOCSI: return DRMP3_ERROR; #endif #ifdef EL2HLT case EL2HLT: return DRMP3_ERROR; #endif #ifdef EBADE case EBADE: return DRMP3_ERROR; #endif #ifdef EBADR case EBADR: return DRMP3_ERROR; #endif #ifdef EXFULL case EXFULL: return DRMP3_ERROR; #endif #ifdef ENOANO case ENOANO: return DRMP3_ERROR; #endif #ifdef EBADRQC case EBADRQC: return DRMP3_ERROR; #endif #ifdef EBADSLT case EBADSLT: return DRMP3_ERROR; #endif #ifdef EBFONT case EBFONT: return DRMP3_INVALID_FILE; #endif #ifdef ENOSTR case ENOSTR: return DRMP3_ERROR; #endif #ifdef ENODATA case ENODATA: return DRMP3_NO_DATA_AVAILABLE; #endif #ifdef ETIME case ETIME: return DRMP3_TIMEOUT; #endif #ifdef ENOSR case ENOSR: return DRMP3_NO_DATA_AVAILABLE; #endif #ifdef ENONET case ENONET: return DRMP3_NO_NETWORK; #endif #ifdef ENOPKG case ENOPKG: return DRMP3_ERROR; #endif #ifdef EREMOTE case EREMOTE: return DRMP3_ERROR; #endif #ifdef ENOLINK case ENOLINK: return DRMP3_ERROR; #endif #ifdef EADV case EADV: return DRMP3_ERROR; #endif #ifdef ESRMNT case ESRMNT: return DRMP3_ERROR; #endif #ifdef ECOMM case ECOMM: return DRMP3_ERROR; #endif #ifdef EPROTO case EPROTO: return DRMP3_ERROR; #endif #ifdef EMULTIHOP case EMULTIHOP: return DRMP3_ERROR; #endif #ifdef EDOTDOT case EDOTDOT: return DRMP3_ERROR; #endif #ifdef EBADMSG case EBADMSG: return DRMP3_BAD_MESSAGE; #endif #ifdef EOVERFLOW case EOVERFLOW: return DRMP3_TOO_BIG; #endif #ifdef ENOTUNIQ case ENOTUNIQ: return DRMP3_NOT_UNIQUE; #endif #ifdef EBADFD case EBADFD: return DRMP3_ERROR; #endif #ifdef EREMCHG case EREMCHG: return DRMP3_ERROR; #endif #ifdef ELIBACC case ELIBACC: return DRMP3_ACCESS_DENIED; #endif #ifdef ELIBBAD case ELIBBAD: return DRMP3_INVALID_FILE; #endif #ifdef ELIBSCN case ELIBSCN: return DRMP3_INVALID_FILE; #endif #ifdef ELIBMAX case ELIBMAX: return DRMP3_ERROR; #endif #ifdef ELIBEXEC case ELIBEXEC: return DRMP3_ERROR; #endif #ifdef EILSEQ case EILSEQ: return DRMP3_INVALID_DATA; #endif #ifdef ERESTART case ERESTART: return DRMP3_ERROR; #endif #ifdef ESTRPIPE case ESTRPIPE: return DRMP3_ERROR; #endif #ifdef EUSERS case EUSERS: return DRMP3_ERROR; #endif #ifdef ENOTSOCK case ENOTSOCK: return DRMP3_NOT_SOCKET; #endif #ifdef EDESTADDRREQ case EDESTADDRREQ: return DRMP3_NO_ADDRESS; #endif #ifdef EMSGSIZE case EMSGSIZE: return DRMP3_TOO_BIG; #endif #ifdef EPROTOTYPE case EPROTOTYPE: return DRMP3_BAD_PROTOCOL; #endif #ifdef ENOPROTOOPT case ENOPROTOOPT: return DRMP3_PROTOCOL_UNAVAILABLE; #endif #ifdef EPROTONOSUPPORT case EPROTONOSUPPORT: return DRMP3_PROTOCOL_NOT_SUPPORTED; #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return DRMP3_SOCKET_NOT_SUPPORTED; #endif #ifdef EOPNOTSUPP case EOPNOTSUPP: return DRMP3_INVALID_OPERATION; #endif #ifdef EPFNOSUPPORT case EPFNOSUPPORT: return DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED; #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: return DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED; #endif #ifdef EADDRINUSE case EADDRINUSE: return DRMP3_ALREADY_IN_USE; #endif #ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: return DRMP3_ERROR; #endif #ifdef ENETDOWN case ENETDOWN: return DRMP3_NO_NETWORK; #endif #ifdef ENETUNREACH case ENETUNREACH: return DRMP3_NO_NETWORK; #endif #ifdef ENETRESET case ENETRESET: return DRMP3_NO_NETWORK; #endif #ifdef ECONNABORTED case ECONNABORTED: return DRMP3_NO_NETWORK; #endif #ifdef ECONNRESET case ECONNRESET: return DRMP3_CONNECTION_RESET; #endif #ifdef ENOBUFS case ENOBUFS: return DRMP3_NO_SPACE; #endif #ifdef EISCONN case EISCONN: return DRMP3_ALREADY_CONNECTED; #endif #ifdef ENOTCONN case ENOTCONN: return DRMP3_NOT_CONNECTED; #endif #ifdef ESHUTDOWN case ESHUTDOWN: return DRMP3_ERROR; #endif #ifdef ETOOMANYREFS case ETOOMANYREFS: return DRMP3_ERROR; #endif #ifdef ETIMEDOUT case ETIMEDOUT: return DRMP3_TIMEOUT; #endif #ifdef ECONNREFUSED case ECONNREFUSED: return DRMP3_CONNECTION_REFUSED; #endif #ifdef EHOSTDOWN case EHOSTDOWN: return DRMP3_NO_HOST; #endif #ifdef EHOSTUNREACH case EHOSTUNREACH: return DRMP3_NO_HOST; #endif #ifdef EALREADY case EALREADY: return DRMP3_IN_PROGRESS; #endif #ifdef EINPROGRESS case EINPROGRESS: return DRMP3_IN_PROGRESS; #endif #ifdef ESTALE case ESTALE: return DRMP3_INVALID_FILE; #endif #ifdef EUCLEAN case EUCLEAN: return DRMP3_ERROR; #endif #ifdef ENOTNAM case ENOTNAM: return DRMP3_ERROR; #endif #ifdef ENAVAIL case ENAVAIL: return DRMP3_ERROR; #endif #ifdef EISNAM case EISNAM: return DRMP3_ERROR; #endif #ifdef EREMOTEIO case EREMOTEIO: return DRMP3_IO_ERROR; #endif #ifdef EDQUOT case EDQUOT: return DRMP3_NO_SPACE; #endif #ifdef ENOMEDIUM case ENOMEDIUM: return DRMP3_DOES_NOT_EXIST; #endif #ifdef EMEDIUMTYPE case EMEDIUMTYPE: return DRMP3_ERROR; #endif #ifdef ECANCELED case ECANCELED: return DRMP3_CANCELLED; #endif #ifdef ENOKEY case ENOKEY: return DRMP3_ERROR; #endif #ifdef EKEYEXPIRED case EKEYEXPIRED: return DRMP3_ERROR; #endif #ifdef EKEYREVOKED case EKEYREVOKED: return DRMP3_ERROR; #endif #ifdef EKEYREJECTED case EKEYREJECTED: return DRMP3_ERROR; #endif #ifdef EOWNERDEAD case EOWNERDEAD: return DRMP3_ERROR; #endif #ifdef ENOTRECOVERABLE case ENOTRECOVERABLE: return DRMP3_ERROR; #endif #ifdef ERFKILL case ERFKILL: return DRMP3_ERROR; #endif #ifdef EHWPOISON case EHWPOISON: return DRMP3_ERROR; #endif default: return DRMP3_ERROR; } } /* End Errno */ /* fopen */ static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { #if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ } if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRMP3_INVALID_ARGS; } #if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drmp3_result_from_errno(err); } #else #if defined(_WIN32) || defined(__APPLE__) *ppFile = fopen(pFilePath, pOpenMode); #else #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) *ppFile = fopen64(pFilePath, pOpenMode); #else *ppFile = fopen(pFilePath, pOpenMode); #endif #endif if (*ppFile == NULL) { drmp3_result result = drmp3_result_from_errno(errno); if (result == DRMP3_SUCCESS) { result = DRMP3_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ } return result; } #endif return DRMP3_SUCCESS; } /* _wfopen() isn't always available in all compilation environments. * Windows only. * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). * MinGW-64 (both 32- and 64-bit) seems to support it. * MinGW wraps it in !defined(__STRICT_ANSI__). * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRMP3_HAS_WFOPEN #endif #endif static drmp3_result drmp3_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ } if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRMP3_INVALID_ARGS; } #if defined(DRMP3_HAS_WFOPEN) { /* Use _wfopen() on Windows. */ #if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drmp3_result_from_errno(err); } #else *ppFile = _wfopen(pFilePath, pOpenMode); if (*ppFile == NULL) { return drmp3_result_from_errno(errno); } #endif (void)pAllocationCallbacks; } #else /* Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. */ /* Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just need to abort with an error. If you encounter a compiler lacking such support, add it to this list and submit a bug report and it'll be added to the library upstream. */ #if defined(__DJGPP__) { /* Nothing to do here. This will fall through to the error check below. */ } #else { mbstate_t mbs; size_t lenMB; const wchar_t* pFilePathTemp = pFilePath; char* pFilePathMB = NULL; char pOpenModeMB[32] = {0}; /* Get the length first. */ DRMP3_ZERO_OBJECT(&mbs); lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); if (lenMB == (size_t)-1) { return drmp3_result_from_errno(errno); } pFilePathMB = (char*)drmp3__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); if (pFilePathMB == NULL) { return DRMP3_OUT_OF_MEMORY; } pFilePathTemp = pFilePath; DRMP3_ZERO_OBJECT(&mbs); wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ { size_t i = 0; for (;;) { if (pOpenMode[i] == 0) { pOpenModeMB[i] = '\0'; break; } pOpenModeMB[i] = (char)pOpenMode[i]; i += 1; } } *ppFile = fopen(pFilePathMB, pOpenModeMB); drmp3__free_from_callbacks(pFilePathMB, pAllocationCallbacks); } #endif if (*ppFile == NULL) { return DRMP3_ERROR; } #endif return DRMP3_SUCCESS; } /* End fopen */ static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) { return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); } static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek_origin origin) { int whence = SEEK_SET; if (origin == drmp3_seek_origin_current) { whence = SEEK_CUR; } else if (origin == drmp3_seek_origin_end) { whence = SEEK_END; } return fseek((FILE*)pUserData, offset, whence) == 0; } static drmp3_bool32 drmp3__on_tell_stdio(void* pUserData, drmp3_int64* pCursor) { FILE* pFileStdio = (FILE*)pUserData; drmp3_int64 result; /* These were all validated at a higher level. */ DRMP3_ASSERT(pFileStdio != NULL); DRMP3_ASSERT(pCursor != NULL); #if defined(_WIN32) #if defined(_MSC_VER) && _MSC_VER > 1200 result = _ftelli64(pFileStdio); #else result = ftell(pFileStdio); #endif #else result = ftell(pFileStdio); #endif *pCursor = result; return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata(drmp3* pMP3, const char* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3_bool32 result; FILE* pFile; if (pMP3 == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { return DRMP3_FALSE; } result = drmp3_init_internal(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, drmp3__on_tell_stdio, onMeta, (void*)pFile, pUserDataMeta, pAllocationCallbacks); if (result != DRMP3_TRUE) { fclose(pFile); return result; } return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init_file_with_metadata_w(drmp3* pMP3, const wchar_t* pFilePath, drmp3_meta_proc onMeta, void* pUserDataMeta, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3_bool32 result; FILE* pFile; if (pMP3 == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { return DRMP3_FALSE; } result = drmp3_init_internal(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, drmp3__on_tell_stdio, onMeta, (void*)pFile, pUserDataMeta, pAllocationCallbacks); if (result != DRMP3_TRUE) { fclose(pFile); return result; } return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) { return drmp3_init_file_with_metadata(pMP3, pFilePath, NULL, NULL, pAllocationCallbacks); } DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) { return drmp3_init_file_with_metadata_w(pMP3, pFilePath, NULL, NULL, pAllocationCallbacks); } #endif DRMP3_API void drmp3_uninit(drmp3* pMP3) { if (pMP3 == NULL) { return; } #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { FILE* pFile = (FILE*)pMP3->pUserData; if (pFile != NULL) { fclose(pFile); pMP3->pUserData = NULL; /* Make sure the file handle is cleared to NULL to we don't attempt to close it a second time. */ } } #endif drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); } #if defined(DR_MP3_FLOAT_OUTPUT) static void drmp3_f32_to_s16(drmp3_int16* dst, const float* src, drmp3_uint64 sampleCount) { drmp3_uint64 i; drmp3_uint64 i4; drmp3_uint64 sampleCount4; /* Unrolled. */ i = 0; sampleCount4 = sampleCount >> 2; for (i4 = 0; i4 < sampleCount4; i4 += 1) { float x0 = src[i+0]; float x1 = src[i+1]; float x2 = src[i+2]; float x3 = src[i+3]; x0 = ((x0 < -1) ? -1 : ((x0 > 1) ? 1 : x0)); x1 = ((x1 < -1) ? -1 : ((x1 > 1) ? 1 : x1)); x2 = ((x2 < -1) ? -1 : ((x2 > 1) ? 1 : x2)); x3 = ((x3 < -1) ? -1 : ((x3 > 1) ? 1 : x3)); x0 = x0 * 32767.0f; x1 = x1 * 32767.0f; x2 = x2 * 32767.0f; x3 = x3 * 32767.0f; dst[i+0] = (drmp3_int16)x0; dst[i+1] = (drmp3_int16)x1; dst[i+2] = (drmp3_int16)x2; dst[i+3] = (drmp3_int16)x3; i += 4; } /* Leftover. */ for (; i < sampleCount; i += 1) { float x = src[i]; x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ x = x * 32767.0f; /* -1..1 to -32767..32767 */ dst[i] = (drmp3_int16)x; } } #endif #if !defined(DR_MP3_FLOAT_OUTPUT) static void drmp3_s16_to_f32(float* dst, const drmp3_int16* src, drmp3_uint64 sampleCount) { drmp3_uint64 i; for (i = 0; i < sampleCount; i += 1) { float x = (float)src[i]; x = x * 0.000030517578125f; /* -32768..32767 to -1..0.999969482421875 */ dst[i] = x; } } #endif static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesToRead, void* pBufferOut) { drmp3_uint64 totalFramesRead = 0; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->onRead != NULL); while (framesToRead > 0) { drmp3_uint32 framesToConsume; /* Skip frames if necessary. */ if (pMP3->currentPCMFrame < pMP3->delayInPCMFrames) { drmp3_uint32 framesToSkip = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, pMP3->delayInPCMFrames - pMP3->currentPCMFrame); pMP3->currentPCMFrame += framesToSkip; pMP3->pcmFramesConsumedInMP3Frame += framesToSkip; pMP3->pcmFramesRemainingInMP3Frame -= framesToSkip; } framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); /* Clamp the number of frames to read to the padding. */ if (pMP3->totalPCMFrameCount != DRMP3_UINT64_MAX && pMP3->totalPCMFrameCount > pMP3->paddingInPCMFrames) { if (pMP3->currentPCMFrame < (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames)) { drmp3_uint64 framesRemainigToPadding = (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames) - pMP3->currentPCMFrame; if (framesToConsume > framesRemainigToPadding) { framesToConsume = (drmp3_uint32)framesRemainigToPadding; } } else { /* We're into the padding. Abort. */ break; } } if (pBufferOut != NULL) { #if defined(DR_MP3_FLOAT_OUTPUT) { /* f32 */ float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); } #else { /* s16 */ drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); } #endif } pMP3->currentPCMFrame += framesToConsume; pMP3->pcmFramesConsumedInMP3Frame += framesToConsume; pMP3->pcmFramesRemainingInMP3Frame -= framesToConsume; totalFramesRead += framesToConsume; framesToRead -= framesToConsume; if (framesToRead == 0) { break; } /* If the cursor is already at the padding we need to abort. */ if (pMP3->totalPCMFrameCount != DRMP3_UINT64_MAX && pMP3->totalPCMFrameCount > pMP3->paddingInPCMFrames && pMP3->currentPCMFrame >= (pMP3->totalPCMFrameCount - pMP3->paddingInPCMFrames)) { break; } DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); /* At this point we have exhausted our in-memory buffer so we need to re-fill. */ if (drmp3_decode_next_frame(pMP3) == 0) { break; } } return totalFramesRead; } DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) { if (pMP3 == NULL || pMP3->onRead == NULL) { return 0; } #if defined(DR_MP3_FLOAT_OUTPUT) /* Fast path. No conversion required. */ return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); #else /* Slow path. Convert from s16 to f32. */ { drmp3_int16 pTempS16[8192]; drmp3_uint64 totalPCMFramesRead = 0; while (totalPCMFramesRead < framesToRead) { drmp3_uint64 framesJustRead; drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempS16) / pMP3->channels; if (framesToReadNow > framesRemaining) { framesToReadNow = framesRemaining; } framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempS16); if (framesJustRead == 0) { break; } drmp3_s16_to_f32((float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalPCMFramesRead * pMP3->channels), pTempS16, framesJustRead * pMP3->channels); totalPCMFramesRead += framesJustRead; } return totalPCMFramesRead; } #endif } DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) { if (pMP3 == NULL || pMP3->onRead == NULL) { return 0; } #if !defined(DR_MP3_FLOAT_OUTPUT) /* Fast path. No conversion required. */ return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); #else /* Slow path. Convert from f32 to s16. */ { float pTempF32[4096]; drmp3_uint64 totalPCMFramesRead = 0; while (totalPCMFramesRead < framesToRead) { drmp3_uint64 framesJustRead; drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempF32) / pMP3->channels; if (framesToReadNow > framesRemaining) { framesToReadNow = framesRemaining; } framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempF32); if (framesJustRead == 0) { break; } drmp3_f32_to_s16((drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalPCMFramesRead * pMP3->channels), pTempF32, framesJustRead * pMP3->channels); totalPCMFramesRead += framesJustRead; } return totalPCMFramesRead; } #endif } static void drmp3_reset(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = 0; pMP3->currentPCMFrame = 0; pMP3->dataSize = 0; pMP3->atEnd = DRMP3_FALSE; drmp3dec_init(&pMP3->decoder); } static drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->onSeek != NULL); /* Seek to the start of the stream to begin with. */ if (!drmp3__on_seek_64(pMP3, pMP3->streamStartOffset, drmp3_seek_origin_start)) { return DRMP3_FALSE; } /* Clear any cached data. */ drmp3_reset(pMP3); return DRMP3_TRUE; } static drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) { drmp3_uint64 framesRead; /* Just using a dumb read-and-discard for now. What would be nice is to parse only the header of the MP3 frame, and then skip over leading frames without spending the time doing a full decode. I cannot see an easy way to do this in minimp3, however, so it may involve some kind of manual processing. */ #if defined(DR_MP3_FLOAT_OUTPUT) framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); #else framesRead = drmp3_read_pcm_frames_s16(pMP3, frameOffset, NULL); #endif if (framesRead != frameOffset) { return DRMP3_FALSE; } return DRMP3_TRUE; } static drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) { DRMP3_ASSERT(pMP3 != NULL); if (frameIndex == pMP3->currentPCMFrame) { return DRMP3_TRUE; } /* If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of the stream and read from the beginning. */ if (frameIndex < pMP3->currentPCMFrame) { /* Moving backward. Move to the start of the stream and then move forward. */ if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } } DRMP3_ASSERT(frameIndex >= pMP3->currentPCMFrame); return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame)); } static drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) { drmp3_uint32 iSeekPoint; DRMP3_ASSERT(pSeekPointIndex != NULL); *pSeekPointIndex = 0; if (frameIndex < pMP3->pSeekPoints[0].pcmFrameIndex) { return DRMP3_FALSE; } /* Linear search for simplicity to begin with while I'm getting this thing working. Once it's all working change this to a binary search. */ for (iSeekPoint = 0; iSeekPoint < pMP3->seekPointCount; ++iSeekPoint) { if (pMP3->pSeekPoints[iSeekPoint].pcmFrameIndex > frameIndex) { break; /* Found it. */ } *pSeekPointIndex = iSeekPoint; } return DRMP3_TRUE; } static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) { drmp3_seek_point seekPoint; drmp3_uint32 priorSeekPointIndex; drmp3_uint16 iMP3Frame; drmp3_uint64 leftoverFrames; DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(pMP3->pSeekPoints != NULL); DRMP3_ASSERT(pMP3->seekPointCount > 0); /* If there is no prior seekpoint it means the target PCM frame comes before the first seek point. Just assume a seekpoint at the start of the file in this case. */ if (drmp3_find_closest_seek_point(pMP3, frameIndex, &priorSeekPointIndex)) { seekPoint = pMP3->pSeekPoints[priorSeekPointIndex]; } else { seekPoint.seekPosInBytes = 0; seekPoint.pcmFrameIndex = 0; seekPoint.mp3FramesToDiscard = 0; seekPoint.pcmFramesToDiscard = 0; } /* First thing to do is seek to the first byte of the relevant MP3 frame. */ if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, drmp3_seek_origin_start)) { return DRMP3_FALSE; /* Failed to seek. */ } /* Clear any cached data. */ drmp3_reset(pMP3); /* Whole MP3 frames need to be discarded first. */ for (iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { drmp3_uint32 pcmFramesRead; drmp3d_sample_t* pPCMFrames; /* Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. */ pPCMFrames = NULL; if (iMP3Frame == seekPoint.mp3FramesToDiscard-1) { pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames; } /* We first need to decode the next frame. */ pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, NULL, NULL); if (pcmFramesRead == 0) { return DRMP3_FALSE; } } /* We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly. */ pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard; /* Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then read-and-discard at least 2 whole MP3 frames. */ leftoverFrames = frameIndex - pMP3->currentPCMFrame; return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames); } DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) { if (pMP3 == NULL || pMP3->onSeek == NULL) { return DRMP3_FALSE; } if (frameIndex == 0) { return drmp3_seek_to_start_of_stream(pMP3); } /* Use the seek table if we have one. */ if (pMP3->pSeekPoints != NULL && pMP3->seekPointCount > 0) { return drmp3_seek_to_pcm_frame__seek_table(pMP3, frameIndex); } else { return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); } } DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) { drmp3_uint64 currentPCMFrame; drmp3_uint64 totalPCMFrameCount; drmp3_uint64 totalMP3FrameCount; if (pMP3 == NULL) { return DRMP3_FALSE; } /* The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. */ /* The stream must support seeking for this to work. */ if (pMP3->onSeek == NULL) { return DRMP3_FALSE; } /* We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. */ currentPCMFrame = pMP3->currentPCMFrame; if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } totalPCMFrameCount = 0; totalMP3FrameCount = 0; for (;;) { drmp3_uint32 pcmFramesInCurrentMP3Frame; pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3Frame == 0) { break; } totalPCMFrameCount += pcmFramesInCurrentMP3Frame; totalMP3FrameCount += 1; } /* Finally, we need to seek back to where we were. */ if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { return DRMP3_FALSE; } if (pMP3FrameCount != NULL) { *pMP3FrameCount = totalMP3FrameCount; } if (pPCMFrameCount != NULL) { *pPCMFrameCount = totalPCMFrameCount; } return DRMP3_TRUE; } DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) { drmp3_uint64 totalPCMFrameCount; if (pMP3 == NULL) { return 0; } if (pMP3->totalPCMFrameCount != DRMP3_UINT64_MAX) { totalPCMFrameCount = pMP3->totalPCMFrameCount; if (totalPCMFrameCount >= pMP3->delayInPCMFrames) { totalPCMFrameCount -= pMP3->delayInPCMFrames; } else { /* The delay is greater than the frame count reported by the Xing/Info tag. Assume it's invalid and ignore. */ } if (totalPCMFrameCount >= pMP3->paddingInPCMFrames) { totalPCMFrameCount -= pMP3->paddingInPCMFrames; } else { /* The padding is greater than the frame count reported by the Xing/Info tag. Assume it's invalid and ignore. */ } return totalPCMFrameCount; } else { /* Unknown frame count. Need to calculate it. */ if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { return 0; } return totalPCMFrameCount; } } DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) { drmp3_uint64 totalMP3FrameCount; if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) { return 0; } return totalMP3FrameCount; } static void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) { float srcRatio; float pcmFrameCountOutF; drmp3_uint32 pcmFrameCountOut; srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; DRMP3_ASSERT(srcRatio > 0); pcmFrameCountOutF = *pRunningPCMFrameCountFractionalPart + (pcmFrameCountIn / srcRatio); pcmFrameCountOut = (drmp3_uint32)pcmFrameCountOutF; *pRunningPCMFrameCountFractionalPart = pcmFrameCountOutF - pcmFrameCountOut; *pRunningPCMFrameCount += pcmFrameCountOut; } typedef struct { drmp3_uint64 bytePos; drmp3_uint64 pcmFrameIndex; /* <-- After sample rate conversion. */ } drmp3__seeking_mp3_frame_info; DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) { drmp3_uint32 seekPointCount; drmp3_uint64 currentPCMFrame; drmp3_uint64 totalMP3FrameCount; drmp3_uint64 totalPCMFrameCount; if (pMP3 == NULL || pSeekPointCount == NULL || pSeekPoints == NULL) { return DRMP3_FALSE; /* Invalid args. */ } seekPointCount = *pSeekPointCount; if (seekPointCount == 0) { return DRMP3_FALSE; /* The client has requested no seek points. Consider this to be invalid arguments since the client has probably not intended this. */ } /* We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top. */ currentPCMFrame = pMP3->currentPCMFrame; /* We never do more than the total number of MP3 frames and we limit it to 32-bits. */ if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) { return DRMP3_FALSE; } /* If there's less than DRMP3_SEEK_LEADING_MP3_FRAMES+1 frames we just report 1 seek point which will be the very start of the stream. */ if (totalMP3FrameCount < DRMP3_SEEK_LEADING_MP3_FRAMES+1) { seekPointCount = 1; pSeekPoints[0].seekPosInBytes = 0; pSeekPoints[0].pcmFrameIndex = 0; pSeekPoints[0].mp3FramesToDiscard = 0; pSeekPoints[0].pcmFramesToDiscard = 0; } else { drmp3_uint64 pcmFramesBetweenSeekPoints; drmp3__seeking_mp3_frame_info mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES+1]; drmp3_uint64 runningPCMFrameCount = 0; float runningPCMFrameCountFractionalPart = 0; drmp3_uint64 nextTargetPCMFrame; drmp3_uint32 iMP3Frame; drmp3_uint32 iSeekPoint; if (seekPointCount > totalMP3FrameCount-1) { seekPointCount = (drmp3_uint32)totalMP3FrameCount-1; } pcmFramesBetweenSeekPoints = totalPCMFrameCount / (seekPointCount+1); /* Here is where we actually calculate the seek points. We need to start by moving the start of the stream. We then enumerate over each MP3 frame. */ if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } /* We need to cache the byte positions of the previous MP3 frames. As a new MP3 frame is iterated, we cycle the byte positions in this array. The value in the first item in this array is the byte position that will be reported in the next seek point. */ /* We need to initialize the array of MP3 byte positions for the leading MP3 frames. */ for (iMP3Frame = 0; iMP3Frame < DRMP3_SEEK_LEADING_MP3_FRAMES+1; ++iMP3Frame) { drmp3_uint32 pcmFramesInCurrentMP3FrameIn; /* The byte position of the next frame will be the stream's cursor position, minus whatever is sitting in the buffer. */ DRMP3_ASSERT(pMP3->streamCursor >= pMP3->dataSize); mp3FrameInfo[iMP3Frame].bytePos = pMP3->streamCursor - pMP3->dataSize; mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; /* We need to get information about this frame so we can know how many samples it contained. */ pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { return DRMP3_FALSE; /* This should never happen. */ } drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); } /* At this point we will have extracted the byte positions of the leading MP3 frames. We can now start iterating over each seek point and calculate them. */ nextTargetPCMFrame = 0; for (iSeekPoint = 0; iSeekPoint < seekPointCount; ++iSeekPoint) { nextTargetPCMFrame += pcmFramesBetweenSeekPoints; for (;;) { if (nextTargetPCMFrame < runningPCMFrameCount) { /* The next seek point is in the current MP3 frame. */ pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); break; } else { size_t i; drmp3_uint32 pcmFramesInCurrentMP3FrameIn; /* The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached MP3 frame info. */ for (i = 0; i < DRMP3_COUNTOF(mp3FrameInfo)-1; ++i) { mp3FrameInfo[i] = mp3FrameInfo[i+1]; } /* Cache previous MP3 frame info. */ mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; /* Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it should only ever do it for the last seek point. */ pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, NULL, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); break; } drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); } } } /* Finally, we need to seek back to where we were. */ if (!drmp3_seek_to_start_of_stream(pMP3)) { return DRMP3_FALSE; } if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { return DRMP3_FALSE; } } *pSeekPointCount = seekPointCount; return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) { if (pMP3 == NULL) { return DRMP3_FALSE; } if (seekPointCount == 0 || pSeekPoints == NULL) { /* Unbinding. */ pMP3->seekPointCount = 0; pMP3->pSeekPoints = NULL; } else { /* Binding. */ pMP3->seekPointCount = seekPointCount; pMP3->pSeekPoints = pSeekPoints; } return DRMP3_TRUE; } static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; float* pFrames = NULL; float temp[4096]; DRMP3_ASSERT(pMP3 != NULL); for (;;) { drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; } /* Reallocate the output buffer if there's not enough room. */ if (framesCapacity < totalFramesRead + framesJustRead) { drmp3_uint64 oldFramesBufferSize; drmp3_uint64 newFramesBufferSize; drmp3_uint64 newFramesCap; float* pNewFrames; newFramesCap = framesCapacity * 2; if (newFramesCap < totalFramesRead + framesJustRead) { newFramesCap = totalFramesRead + framesJustRead; } oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(float); newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(float); if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { break; } pNewFrames = (float*)drmp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); if (pNewFrames == NULL) { drmp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); break; } pFrames = pNewFrames; framesCapacity = newFramesCap; } DRMP3_COPY_MEMORY(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(float))); totalFramesRead += framesJustRead; /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ if (framesJustRead != framesToReadRightNow) { break; } } if (pConfig != NULL) { pConfig->channels = pMP3->channels; pConfig->sampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); if (pTotalFrameCount) { *pTotalFrameCount = totalFramesRead; } return pFrames; } static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; drmp3_int16* pFrames = NULL; drmp3_int16 temp[4096]; DRMP3_ASSERT(pMP3 != NULL); for (;;) { drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_s16(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; } /* Reallocate the output buffer if there's not enough room. */ if (framesCapacity < totalFramesRead + framesJustRead) { drmp3_uint64 newFramesBufferSize; drmp3_uint64 oldFramesBufferSize; drmp3_uint64 newFramesCap; drmp3_int16* pNewFrames; newFramesCap = framesCapacity * 2; if (newFramesCap < totalFramesRead + framesJustRead) { newFramesCap = totalFramesRead + framesJustRead; } oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(drmp3_int16); newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(drmp3_int16); if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { break; } pNewFrames = (drmp3_int16*)drmp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); if (pNewFrames == NULL) { drmp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); break; } pFrames = pNewFrames; framesCapacity = newFramesCap; } DRMP3_COPY_MEMORY(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(drmp3_int16))); totalFramesRead += framesJustRead; /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ if (framesJustRead != framesToReadRightNow) { break; } } if (pConfig != NULL) { pConfig->channels = pMP3->channels; pConfig->sampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); if (pTotalFrameCount) { *pTotalFrameCount = totalFramesRead; } return pFrames; } DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; if (!drmp3_init(&mp3, onRead, onSeek, onTell, NULL, pUserData, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, drmp3_tell_proc onTell, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; if (!drmp3_init(&mp3, onRead, onSeek, onTell, NULL, pUserData, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); } DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); } #ifndef DR_MP3_NO_STDIO DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); } #endif DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { return drmp3__malloc_from_callbacks(sz, pAllocationCallbacks); } else { return drmp3__malloc_default(sz, NULL); } } DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { drmp3__free_from_callbacks(p, pAllocationCallbacks); } else { drmp3__free_default(p, NULL); } } #endif /* dr_mp3_c */ #endif /*DR_MP3_IMPLEMENTATION*/ /* DIFFERENCES BETWEEN minimp3 AND dr_mp3 ====================================== - First, keep in mind that minimp3 (https://github.com/lieff/minimp3) is where all the real work was done. All of the code relating to the actual decoding remains mostly unmodified, apart from some namespacing changes. - dr_mp3 adds a pulling style API which allows you to deliver raw data via callbacks. So, rather than pushing data to the decoder, the decoder _pulls_ data from your callbacks. - In addition to callbacks, a decoder can be initialized from a block of memory and a file. - The dr_mp3 pull API reads PCM frames rather than whole MP3 frames. - dr_mp3 adds convenience APIs for opening and decoding entire files in one go. - dr_mp3 is fully namespaced, including the implementation section, which is more suitable when compiling projects as a single translation unit (aka unity builds). At the time of writing this, a unity build is not possible when using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. */ /* REVISION HISTORY ================ v0.7.0 - TBD - The old `DRMP3_IMPLEMENTATION` has been removed. Use `DR_MP3_IMPLEMENTATION` instead. The reason for this change is that in the future everything will eventually be using the underscored naming convention in the future, so `drmp3` will become `dr_mp3`. - API CHANGE: Add drmp3_seek_origin_end as a seek origin for the seek callback. This is required for detection of ID3v1 and APE tags. - API CHANGE: Add onTell callback to `drmp3_init()`. This is needed in order to track the location of ID3v1 and APE tags. - API CHANGE: Add onMeta callback to `drmp3_init()`. This is used for reporting tag data back to the caller. Currently this only reports the raw tag data which means applications need to parse the data themselves. - API CHANGE: Rename `drmp3dec_frame_info.hz` to `drmp3dec_frame_info.sample_rate`. - Add detection of ID3v2, ID3v1, APE and Xing/VBRI tags. This should fix errors with some files where the decoder was reading tags as audio data. - Delay and padding samples from LAME tags are now handled. - Fix compilation for AIX OS. v0.6.40 - 2024-12-17 - Improve detection of ARM64EC v0.6.39 - 2024-02-27 - Fix a Wdouble-promotion warning. v0.6.38 - 2023-11-02 - Fix build for ARMv6-M. v0.6.37 - 2023-07-07 - Silence a static analysis warning. v0.6.36 - 2023-06-17 - Fix an incorrect date in revision history. No functional change. v0.6.35 - 2023-05-22 - Minor code restructure. No functional change. v0.6.34 - 2022-09-17 - Fix compilation with DJGPP. - Fix compilation when compiling with x86 with no SSE2. - Remove an unnecessary variable from the drmp3 structure. v0.6.33 - 2022-04-10 - Fix compilation error with the MSVC ARM64 build. - Fix compilation error on older versions of GCC. - Remove some unused functions. v0.6.32 - 2021-12-11 - Fix a warning with Clang. v0.6.31 - 2021-08-22 - Fix a bug when loading from memory. v0.6.30 - 2021-08-16 - Silence some warnings. - Replace memory operations with DRMP3_* macros. v0.6.29 - 2021-08-08 - Bring up to date with minimp3. v0.6.28 - 2021-07-31 - Fix platform detection for ARM64. - Fix a compilation error with C89. v0.6.27 - 2021-02-21 - Fix a warning due to referencing _MSC_VER when it is undefined. v0.6.26 - 2021-01-31 - Bring up to date with minimp3. v0.6.25 - 2020-12-26 - Remove DRMP3_DEFAULT_CHANNELS and DRMP3_DEFAULT_SAMPLE_RATE which are leftovers from some removed APIs. v0.6.24 - 2020-12-07 - Fix a typo in version date for 0.6.23. v0.6.23 - 2020-12-03 - Fix an error where a file can be closed twice when initialization of the decoder fails. v0.6.22 - 2020-12-02 - Fix an error where it's possible for a file handle to be left open when initialization of the decoder fails. v0.6.21 - 2020-11-28 - Bring up to date with minimp3. v0.6.20 - 2020-11-21 - Fix compilation with OpenWatcom. v0.6.19 - 2020-11-13 - Minor code clean up. v0.6.18 - 2020-11-01 - Improve compiler support for older versions of GCC. v0.6.17 - 2020-09-28 - Bring up to date with minimp3. v0.6.16 - 2020-08-02 - Simplify sized types. v0.6.15 - 2020-07-25 - Fix a compilation warning. v0.6.14 - 2020-07-23 - Fix undefined behaviour with memmove(). v0.6.13 - 2020-07-06 - Fix a bug when converting from s16 to f32 in drmp3_read_pcm_frames_f32(). v0.6.12 - 2020-06-23 - Add include guard for the implementation section. v0.6.11 - 2020-05-26 - Fix use of uninitialized variable error. v0.6.10 - 2020-05-16 - Add compile-time and run-time version querying. - DRMP3_VERSION_MINOR - DRMP3_VERSION_MAJOR - DRMP3_VERSION_REVISION - DRMP3_VERSION_STRING - drmp3_version() - drmp3_version_string() v0.6.9 - 2020-04-30 - Change the `pcm` parameter of drmp3dec_decode_frame() to a `const drmp3_uint8*` for consistency with internal APIs. v0.6.8 - 2020-04-26 - Optimizations to decoding when initializing from memory. v0.6.7 - 2020-04-25 - Fix a compilation error with DR_MP3_NO_STDIO - Optimization to decoding by reducing some data movement. v0.6.6 - 2020-04-23 - Fix a minor bug with the running PCM frame counter. v0.6.5 - 2020-04-19 - Fix compilation error on ARM builds. v0.6.4 - 2020-04-19 - Bring up to date with changes to minimp3. v0.6.3 - 2020-04-13 - Fix some pedantic warnings. v0.6.2 - 2020-04-10 - Fix a crash in drmp3_open_*_and_read_pcm_frames_*() if the output config object is NULL. v0.6.1 - 2020-04-05 - Fix warnings. v0.6.0 - 2020-04-04 - API CHANGE: Remove the pConfig parameter from the following APIs: - drmp3_init() - drmp3_init_memory() - drmp3_init_file() - Add drmp3_init_file_w() for opening a file from a wchar_t encoded path. v0.5.6 - 2020-02-12 - Bring up to date with minimp3. v0.5.5 - 2020-01-29 - Fix a memory allocation bug in high level s16 decoding APIs. v0.5.4 - 2019-12-02 - Fix a possible null pointer dereference when using custom memory allocators for realloc(). v0.5.3 - 2019-11-14 - Fix typos in documentation. v0.5.2 - 2019-11-02 - Bring up to date with minimp3. v0.5.1 - 2019-10-08 - Fix a warning with GCC. v0.5.0 - 2019-10-07 - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: - drmp3_init() - drmp3_init_file() - drmp3_init_memory() - drmp3_open_and_read_pcm_frames_f32() - drmp3_open_and_read_pcm_frames_s16() - drmp3_open_memory_and_read_pcm_frames_f32() - drmp3_open_memory_and_read_pcm_frames_s16() - drmp3_open_file_and_read_pcm_frames_f32() - drmp3_open_file_and_read_pcm_frames_s16() - API CHANGE: Renamed the following APIs: - drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() - drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() - drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() - drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() - drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() - drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() v0.4.7 - 2019-07-28 - Fix a compiler error. v0.4.6 - 2019-06-14 - Fix a compiler error. v0.4.5 - 2019-06-06 - Bring up to date with minimp3. v0.4.4 - 2019-05-06 - Fixes to the VC6 build. v0.4.3 - 2019-05-05 - Use the channel count and/or sample rate of the first MP3 frame instead of DRMP3_DEFAULT_CHANNELS and DRMP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to DRMP3_DEFAULT_CHANNELS or DRMP3_DEFAULT_SAMPLE_RATE. - Add s16 reading APIs - drmp3_read_pcm_frames_s16 - drmp3_open_memory_and_read_pcm_frames_s16 - drmp3_open_and_read_pcm_frames_s16 - drmp3_open_file_and_read_pcm_frames_s16 - Add drmp3_get_mp3_and_pcm_frame_count() to the public header section. - Add support for C89. - Change license to choice of public domain or MIT-0. v0.4.2 - 2019-02-21 - Fix a warning. v0.4.1 - 2018-12-30 - Fix a warning. v0.4.0 - 2018-12-16 - API CHANGE: Rename some APIs: - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32 - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame - drmp3_open_and_decode_f32 -> drmp3_open_and_read_pcm_frames_f32 - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_pcm_frames_f32 - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_pcm_frames_f32 - Add drmp3_get_pcm_frame_count(). - Add drmp3_get_mp3_frame_count(). - Improve seeking performance. v0.3.2 - 2018-09-11 - Fix a couple of memory leaks. - Bring up to date with minimp3. v0.3.1 - 2018-08-25 - Fix C++ build. v0.3.0 - 2018-08-25 - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or not the DR_MP3_FLOAT_OUTPUT option is set. v0.2.11 - 2018-08-08 - Fix a bug where the last part of a file is not read. v0.2.10 - 2018-08-07 - Improve 64-bit detection. v0.2.9 - 2018-08-05 - Fix C++ build on older versions of GCC. - Bring up to date with minimp3. v0.2.8 - 2018-08-02 - Fix compilation errors with older versions of GCC. v0.2.7 - 2018-07-13 - Bring up to date with minimp3. v0.2.6 - 2018-07-12 - Bring up to date with minimp3. v0.2.5 - 2018-06-22 - Bring up to date with minimp3. v0.2.4 - 2018-05-12 - Bring up to date with minimp3. v0.2.3 - 2018-04-29 - Fix TCC build. v0.2.2 - 2018-04-28 - Fix bug when opening a decoder from memory. v0.2.1 - 2018-04-27 - Efficiency improvements when the decoder reaches the end of the stream. v0.2 - 2018-04-21 - Bring up to date with minimp3. - Start using major.minor.revision versioning. v0.1d - 2018-03-30 - Bring up to date with minimp3. v0.1c - 2018-03-11 - Fix C++ build error. v0.1b - 2018-03-07 - Bring up to date with minimp3. v0.1a - 2018-02-28 - Fix compilation error on GCC/Clang. - Fix some warnings. v0.1 - 2018-02-xx - Initial versioned release. */ /* This software is available as a choice of the following licenses. Choose whichever you prefer. =============================================================================== ALTERNATIVE 1 - Public Domain (www.unlicense.org) =============================================================================== This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== Copyright 2023 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* https://github.com/lieff/minimp3 To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See . */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/load_aiff.c000066400000000000000000000203361501405355700233140ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This is the source needed to decode an AIFF file into a waveform. It's pretty straightforward once you get going. The only externally-callable function is Mix_LoadAIFF_IO(), which is meant to act as identically to SDL_LoadWAV_IO() as possible. This file by Torbjörn Andersson (torbjorn.andersson@eurotime.se) 8SVX file support added by Marc Le Douarain (mavati@club-internet.fr) in december 2002. */ #include #include #include "load_aiff.h" /*********************************************/ /* Define values for AIFF (IFF audio) format */ /*********************************************/ #define FORM 0x4d524f46 /* "FORM" */ #define AIFF 0x46464941 /* "AIFF" */ #define SSND 0x444e5353 /* "SSND" */ #define COMM 0x4d4d4f43 /* "COMM" */ #define _8SVX 0x58565338 /* "8SVX" */ #define VHDR 0x52444856 /* "VHDR" */ #define BODY 0x59444F42 /* "BODY" */ /* This function was taken from libsndfile. I don't pretend to fully * understand it. */ static Uint32 SANE_to_Uint32 (Uint8 *sanebuf) { /* Is the frequency outside of what we can represent with Uint32? */ if ((sanebuf[0] & 0x80) || (sanebuf[0] <= 0x3F) || (sanebuf[0] > 0x40) || (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C)) return 0; return ((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7) | (sanebuf[5] >> 1)) >> (29 - sanebuf[1]); } /* This function is based on SDL_LoadWAV_IO(). */ SDL_AudioSpec *Mix_LoadAIFF_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { int found_SSND; int found_COMM; int found_VHDR; int found_BODY; bool was_error = true; Sint64 start = 0; Uint32 chunk_type; Uint32 chunk_length; Sint64 next_chunk; /* AIFF magic header */ Uint32 FORMchunk; Uint32 AIFFmagic; /* SSND chunk */ Uint32 offset; Uint32 blocksize; /* COMM format chunk */ Uint16 channels = 0; Uint32 numsamples = 0; Uint16 samplesize = 0; Uint8 sane_freq[10]; Uint32 frequency = 0; /* VHDR chunk */ Uint16 frequency16 = 0; /* Sanity checks */ if (audio_buf) { *audio_buf = NULL; } if (!src) { SDL_InvalidParamError("src"); goto done; } if (!spec) { SDL_InvalidParamError("spec"); goto done; } if (!audio_buf) { SDL_InvalidParamError("audio_buf"); goto done; } if (!audio_len) { SDL_InvalidParamError("audio_len"); goto done; } if (!SDL_ReadU32LE(src, &FORMchunk) || !SDL_ReadU32BE(src, &chunk_length)) { goto done; } if (chunk_length == AIFF) { /* The FORMchunk has already been read */ AIFFmagic = chunk_length; chunk_length = FORMchunk; FORMchunk = FORM; } else { if (!SDL_ReadU32LE(src, &AIFFmagic)) { goto done; } } if ((FORMchunk != FORM) || ((AIFFmagic != AIFF) && (AIFFmagic != _8SVX))) { SDL_SetError("Unrecognized file type (not AIFF nor 8SVX)"); goto done; } /* TODO: Better santity-checking. */ found_SSND = 0; found_COMM = 0; found_VHDR = 0; found_BODY = 0; do { if (!SDL_ReadU32LE(src, &chunk_type) || !SDL_ReadU32BE(src, &chunk_length)) { goto done; } next_chunk = SDL_TellIO(src) + chunk_length; /* Paranoia to avoid infinite loops */ if (chunk_length == 0) { break; } switch (chunk_type) { case SSND: found_SSND = 1; if (!SDL_ReadU32BE(src, &offset) || !SDL_ReadU32BE(src, &blocksize)) { goto done; } start = SDL_TellIO(src) + offset; (void)blocksize; /* unused. */ break; case COMM: found_COMM = 1; if (!SDL_ReadU16BE(src, &channels) || !SDL_ReadU32BE(src, &numsamples) || !SDL_ReadU16BE(src, &samplesize)) { goto done; } if (SDL_ReadIO(src, sane_freq, sizeof(sane_freq)) != sizeof(sane_freq)) { SDL_SetError("Bad AIFF sample frequency"); goto done; } frequency = SANE_to_Uint32(sane_freq); if (frequency == 0) { SDL_SetError("Bad AIFF sample frequency"); goto done; } break; case VHDR: found_VHDR = 1; if (!SDL_ReadU32BE(src, NULL) || !SDL_ReadU32BE(src, NULL) || !SDL_ReadU32BE(src, NULL) || !SDL_ReadU16BE(src, &frequency16)) { goto done; } channels = 1; samplesize = 8; frequency = frequency16; break; case BODY: found_BODY = 1; numsamples = chunk_length; start = SDL_TellIO(src); break; default: break; } /* a 0 pad byte can be stored for any odd-length chunk */ if (chunk_length&1) { next_chunk++; } } while ((((AIFFmagic == AIFF) && (!found_SSND || !found_COMM)) || ((AIFFmagic == _8SVX) && (!found_VHDR || !found_BODY))) && SDL_SeekIO(src, next_chunk, SDL_IO_SEEK_SET) != 1); if ((AIFFmagic == AIFF) && !found_SSND) { SDL_SetError("Bad AIFF (no SSND chunk)"); goto done; } if ((AIFFmagic == AIFF) && !found_COMM) { SDL_SetError("Bad AIFF (no COMM chunk)"); goto done; } if ((AIFFmagic == _8SVX) && !found_VHDR) { SDL_SetError("Bad 8SVX (no VHDR chunk)"); goto done; } if ((AIFFmagic == _8SVX) && !found_BODY) { SDL_SetError("Bad 8SVX (no BODY chunk)"); goto done; } /* Decode the audio data format */ SDL_zerop(spec); spec->freq = frequency; switch (samplesize) { case 8: spec->format = SDL_AUDIO_S8; break; case 16: spec->format = SDL_AUDIO_S16BE; break; default: SDL_SetError("Unsupported AIFF samplesize"); goto done; } spec->channels = (Uint8) channels; *audio_len = channels * numsamples * (samplesize / 8); *audio_buf = (Uint8 *)SDL_malloc(*audio_len); if (*audio_buf == NULL) { goto done; } SDL_SeekIO(src, start, SDL_IO_SEEK_SET); if (SDL_ReadIO(src, *audio_buf, *audio_len) != *audio_len) { SDL_SetError("Unable to read audio data"); goto done; } /* Don't return a buffer that isn't a multiple of samplesize */ *audio_len &= ~((samplesize / 8) - 1); was_error = false; done: if (closeio && src) { SDL_CloseIO(src); } if (was_error) { if (audio_buf && *audio_buf) { SDL_free(*audio_buf); *audio_buf = NULL; } if (audio_len) { *audio_len = 0; } spec = NULL; } return spec; } /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/load_aiff.h000066400000000000000000000027761501405355700233310ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This is the source needed to decode an AIFF file into a waveform. It's pretty straightforward once you get going. The only externally-callable function is Mix_LoadAIFF_IO(), which is meant to act as identically to SDL_LoadWAV_IO() as possible. This file by Torbjörn Andersson (torbjorn.andersson@eurotime.se) */ /* Don't call this directly; use Mix_LoadWAV_IO() for now. */ SDL_AudioSpec *Mix_LoadAIFF_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len); /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/load_sndfile.c000066400000000000000000000146441501405355700240400ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This is the source needed to decode a file in any format supported by libsndfile. The only externally-callable function is Mix_LoadSndFile_IO(), which is meant to act as identically to SDL_LoadWAV_IO() as possible. This file by Fabian Greffrath (fabian@greffrath.com). */ #include #include "load_sndfile.h" #ifdef LOAD_SNDFILE #include #include static SNDFILE* (*SF_sf_open_virtual) (SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data); static int (*SF_sf_close) (SNDFILE *sndfile); static sf_count_t (*SF_sf_readf_short) (SNDFILE *sndfile, short *ptr, sf_count_t frames); static const char* (*SF_sf_strerror) (SNDFILE *sndfile); static int SNDFILE_loaded; static void *SNDFILE_lib; static int SNDFILE_init (void) { if (SNDFILE_loaded == 0) { #ifdef SNDFILE_DYNAMIC SNDFILE_lib = SDL_LoadObject(SNDFILE_DYNAMIC); if (SNDFILE_lib == NULL) { return -1; } /* *INDENT-OFF* */ /* clang-format off */ SF_sf_open_virtual = (SNDFILE* (*)(SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data))SDL_LoadFunction(SNDFILE_lib, "sf_open_virtual"); SF_sf_close = (int (*)(SNDFILE *sndfile))SDL_LoadFunction(SNDFILE_lib, "sf_close"); SF_sf_readf_short = (sf_count_t(*)(SNDFILE *sndfile, short *ptr, sf_count_t frames))SDL_LoadFunction(SNDFILE_lib, "sf_readf_short"); SF_sf_strerror = (const char* (*)(SNDFILE *sndfile))SDL_LoadFunction(SNDFILE_lib, "sf_strerror"); /* *INDENT-ON* */ /* clang-format on */ if (SF_sf_open_virtual == NULL || SF_sf_close == NULL || SF_sf_readf_short == NULL || SF_sf_strerror == NULL) { SDL_UnloadObject(SNDFILE_lib); SNDFILE_lib = NULL; return -1; } #else SF_sf_open_virtual = sf_open_virtual; SF_sf_close = sf_close; SF_sf_readf_short = sf_readf_short; SF_sf_strerror = sf_strerror; #endif SNDFILE_loaded = 1; } return 0; } void SNDFILE_uninit (void) { if (SNDFILE_lib != NULL) { SDL_UnloadObject(SNDFILE_lib); SNDFILE_lib = NULL; SF_sf_open_virtual = NULL; SF_sf_close = NULL; SF_sf_readf_short = NULL; SF_sf_strerror = NULL; SNDFILE_loaded = 0; } } static sf_count_t sfvio_size(void *user_data) { SDL_IOStream *io = user_data; return SDL_GetIOSize(io); } static sf_count_t sfvio_seek(sf_count_t offset, int whence, void *user_data) { SDL_IOStream *io = user_data; return SDL_SeekIO(io, offset, whence); } static sf_count_t sfvio_read(void *ptr, sf_count_t count, void *user_data) { SDL_IOStream *io = user_data; return SDL_ReadIO(io, ptr, count); } static sf_count_t sfvio_tell(void *user_data) { SDL_IOStream *io = user_data; return SDL_TellIO(io); } SDL_AudioSpec *Mix_LoadSndFile_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { bool was_error = true; SNDFILE *sndfile = NULL; SF_INFO sfinfo; SF_VIRTUAL_IO sfvio = { sfvio_size, sfvio_seek, sfvio_read, NULL, sfvio_tell }; Uint32 len; short *buf = NULL; /* Sanity checks */ if (audio_buf) { *audio_buf = NULL; } if (!src) { SDL_InvalidParamError("src"); goto done; } if (!spec) { SDL_InvalidParamError("spec"); goto done; } if (!audio_buf) { SDL_InvalidParamError("audio_buf"); goto done; } if (!audio_len) { SDL_InvalidParamError("audio_len"); goto done; } if (SNDFILE_loaded == 0) { if (SNDFILE_init() != 0) { goto done; } } SDL_memset(&sfinfo, 0, sizeof(sfinfo)); sndfile = SF_sf_open_virtual(&sfvio, SFM_READ, &sfinfo, src); if (sndfile == NULL) { SDL_SetError("sf_open_virtual: %s", SF_sf_strerror(sndfile)); goto done; } if (sfinfo.frames <= 0) { SDL_SetError("Invalid number of frames: %ld", (long)sfinfo.frames); goto done; } if (sfinfo.channels <= 0) { SDL_SetError("Invalid number of channels: %d", sfinfo.channels); goto done; } len = sfinfo.frames * sfinfo.channels * sizeof(short); buf = SDL_malloc(len); if (buf == NULL) { goto done; } if (SF_sf_readf_short(sndfile, buf, sfinfo.frames) < sfinfo.frames) { SDL_free(buf); SDL_SetError("sf_readf_short: %s", SF_sf_strerror(sndfile)); goto done; } SDL_zerop(spec); spec->channels = sfinfo.channels; spec->freq = sfinfo.samplerate; spec->format = SDL_AUDIO_S16; *audio_buf = (Uint8 *)buf; *audio_len = len; was_error = false; done: if (sndfile) { SF_sf_close(sndfile); } if (closeio && src) { SDL_CloseIO(src); } if (was_error) { if (audio_buf && *audio_buf) { SDL_free(*audio_buf); *audio_buf = NULL; } if (audio_len) { *audio_len = 0; } spec = NULL; } return spec; } #else SDL_AudioSpec *Mix_LoadSndFile_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { (void) src; (void) closeio; (void) spec; (void) audio_buf; (void) audio_len; return NULL; } void SNDFILE_uninit (void) { /* no-op */ } #endif /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/load_sndfile.h000066400000000000000000000031061501405355700240340ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This is the source needed to decode a file in any format supported by libsndfile. The only externally-callable function is Mix_LoadSndFile_IO(), which is meant to act as identically to SDL_LoadWAV_IO() as possible. This file by Fabian Greffrath (fabian@greffrath.com). */ #ifndef LOAD_SNDFILE_H #define LOAD_SNDFILE_H #include /* Don't call this directly; use Mix_LoadWAV_IO() for now. */ SDL_AudioSpec *Mix_LoadSndFile_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len); void SNDFILE_uninit (void); #endif /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/load_voc.c000066400000000000000000000351131501405355700231750ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This is the source needed to decode a Creative Labs VOC file into a waveform. It's pretty straightforward once you get going. The only externally-callable function is Mix_LoadVOC_IO(), which is meant to act as identically to SDL_LoadWAV_IO() as possible. This file by Ryan C. Gordon (icculus@icculus.org). Heavily borrowed from sox v12.17.1's voc.c. (http://www.freshmeat.net/projects/sox/) */ #include #include "load_voc.h" /* Private data for VOC file */ typedef struct vocstuff { Uint32 rest; /* bytes remaining in current block */ Uint32 rate; /* rate code (byte) of this chunk */ int silent; /* sound or silence? */ Uint32 srate; /* rate code (byte) of silence */ Uint32 blockseek; /* start of current output block */ Uint32 samples; /* number of samples output */ Uint32 size; /* word length of data */ Uint8 channels; /* number of sound channels */ int has_extended; /* Has an extended block been read? */ } vs_t; /* Size field */ /* SJB: note that the 1st 3 are sometimes used as sizeof(type) */ #define ST_SIZE_BYTE 1 #define ST_SIZE_8BIT 1 #define ST_SIZE_WORD 2 #define ST_SIZE_16BIT 2 #define ST_SIZE_DWORD 4 #define ST_SIZE_32BIT 4 #define ST_SIZE_FLOAT 5 #define ST_SIZE_DOUBLE 6 #define ST_SIZE_IEEE 7 /* IEEE 80-bit floats. */ /* Style field */ #define ST_ENCODING_UNSIGNED 1 /* unsigned linear: Sound Blaster */ #define ST_ENCODING_SIGN2 2 /* signed linear 2's comp: Mac */ #define ST_ENCODING_ULAW 3 /* U-law signed logs: US telephony, SPARC */ #define ST_ENCODING_ALAW 4 /* A-law signed logs: non-US telephony */ #define ST_ENCODING_ADPCM 5 /* Compressed PCM */ #define ST_ENCODING_IMA_ADPCM 6 /* Compressed PCM */ #define ST_ENCODING_GSM 7 /* GSM 6.10 33-byte frame lossy compression */ #define VOC_TERM 0 #define VOC_DATA 1 #define VOC_CONT 2 #define VOC_SILENCE 3 #define VOC_MARKER 4 #define VOC_TEXT 5 #define VOC_LOOP 6 #define VOC_LOOPEND 7 #define VOC_EXTENDED 8 #define VOC_DATA_16 9 #define VOC_BAD_RATE ~((Uint32)0) static int voc_check_header(SDL_IOStream *src) { /* VOC magic header */ Uint8 signature[20]; /* "Creative Voice File\032" */ Uint16 datablockofs; SDL_SeekIO(src, 0, SDL_IO_SEEK_SET); if (SDL_ReadIO(src, signature, sizeof(signature)) != sizeof(signature)) { return 0; } if (SDL_memcmp(signature, "Creative Voice File\032", sizeof(signature)) != 0) { SDL_SetError("Unrecognized file type (not VOC)"); return 0; } /* get the offset where the first datablock is located */ if (SDL_ReadIO(src, &datablockofs, sizeof(Uint16)) != sizeof(Uint16)) { return 0; } datablockofs = SDL_Swap16LE(datablockofs); if (SDL_SeekIO(src, datablockofs, SDL_IO_SEEK_SET) != datablockofs) { return 0; } return 1; /* success! */ } /* voc_check_header */ /* Read next block header, save info, leave position at start of data */ static int voc_get_block(SDL_IOStream *src, vs_t *v, SDL_AudioSpec *spec) { Uint8 bits24[3]; Uint8 uc, block; Uint32 sblen; Uint16 new_rate_short; Uint32 new_rate_long; Uint8 trash[6]; Uint16 period; unsigned int i; v->silent = 0; while (v->rest == 0) { if (SDL_ReadIO(src, &block, sizeof(block)) != sizeof(block)) { return 1; /* assume that's the end of the file. */ } if (block == VOC_TERM) { return 1; } if (SDL_ReadIO(src, bits24, sizeof(bits24)) != sizeof(bits24)) { return 1; /* assume that's the end of the file. */ } /* Size is an 24-bit value. Ugh. */ sblen = (Uint32)((bits24[0]) | (bits24[1] << 8) | (bits24[2] << 16)); switch(block) { case VOC_DATA: if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) { return 0; } /* When DATA block preceeded by an EXTENDED */ /* block, the DATA blocks rate value is invalid */ if (!v->has_extended) { if (uc == 0) { SDL_SetError("VOC Sample rate is zero?"); return 0; } if ((v->rate != VOC_BAD_RATE) && (uc != v->rate)) { SDL_SetError("VOC sample rate codes differ"); return 0; } v->rate = uc; spec->freq = (Uint16)(1000000.0/(256 - v->rate)); v->channels = 1; } if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) { return 0; } if (uc != 0) { SDL_SetError("VOC decoder only interprets 8-bit data"); return 0; } v->has_extended = 0; v->rest = sblen - 2; v->size = ST_SIZE_BYTE; return 1; case VOC_DATA_16: if (SDL_ReadIO(src, &new_rate_long, sizeof(new_rate_long)) != sizeof(new_rate_long)) { return 0; } new_rate_long = SDL_Swap32LE(new_rate_long); if (new_rate_long == 0) { SDL_SetError("VOC Sample rate is zero?"); return 0; } if ((v->rate != VOC_BAD_RATE) && (new_rate_long != v->rate)) { SDL_SetError("VOC sample rate codes differ"); return 0; } v->rate = new_rate_long; spec->freq = (int)new_rate_long; if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) { return 0; } switch (uc) { case 8: v->size = ST_SIZE_BYTE; break; case 16: v->size = ST_SIZE_WORD; break; default: SDL_SetError("VOC with unknown data size"); return 0; } if (SDL_ReadIO(src, &v->channels, sizeof(Uint8)) != sizeof(Uint8)) { return 0; } if (SDL_ReadIO(src, trash, 6) != 6) { return 0; } v->rest = sblen - 12; return 1; case VOC_CONT: v->rest = sblen; return 1; case VOC_SILENCE: if (SDL_ReadIO(src, &period, sizeof(period)) != sizeof(period)) { return 0; } period = SDL_Swap16LE(period); if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) { return 0; } if (uc == 0) { SDL_SetError("VOC silence sample rate is zero"); return 0; } /* * Some silence-packed files have gratuitously * different sample rate codes in silence. * Adjust period. */ if ((v->rate != VOC_BAD_RATE) && (uc != v->rate)) period = (Uint16)((period * (256 - uc))/(256 - v->rate)); else v->rate = uc; v->rest = period; v->silent = 1; return 1; case VOC_LOOP: case VOC_LOOPEND: for (i = 0; i < sblen; i++) { /* skip repeat loops. */ if (SDL_ReadIO(src, trash, sizeof(Uint8)) != sizeof(Uint8)) { return 0; } } break; case VOC_EXTENDED: /* An Extended block is followed by a data block */ /* Set this byte so we know to use the rate */ /* value from the extended block and not the */ /* data block. */ v->has_extended = 1; if (SDL_ReadIO(src, &new_rate_short, sizeof(new_rate_short)) != sizeof(new_rate_short)) { return 0; } new_rate_short = SDL_Swap16LE(new_rate_short); if (new_rate_short == 0) { SDL_SetError("VOC sample rate is zero"); return 0; } if ((v->rate != VOC_BAD_RATE) && (new_rate_short != v->rate)) { SDL_SetError("VOC sample rate codes differ"); return 0; } v->rate = new_rate_short; if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) { return 0; } if (uc != 0) { SDL_SetError("VOC decoder only interprets 8-bit data"); return 0; } if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) { return 0; } if (uc) /* Stereo */ spec->channels = 2; /* VOC_EXTENDED may be read before spec->channels inited: */ else spec->channels = 1; /* Needed number of channels before finishing compute for rate */ spec->freq = (256000000L / (65536L - v->rate)) / spec->channels; /* An extended block must be followed by a data */ /* block to be valid so loop back to top so it */ /* can be grabed. */ continue; case VOC_MARKER: if (SDL_ReadIO(src, trash, 2) != 2) { return 0; } /* fallthrough */ default: /* text block or other krapola. */ for (i = 0; i < sblen; i++) { if (SDL_ReadIO(src, trash, sizeof(Uint8)) != sizeof(Uint8)) { return 0; } } if (block == VOC_TEXT) { continue; /* get next block */ } } } return 1; } static Uint32 voc_read(SDL_IOStream *src, vs_t *v, Uint8 *buf, SDL_AudioSpec *spec) { Sint64 done = 0; Uint8 silence = 0x80; if (v->rest == 0) { if (!voc_get_block(src, v, spec)) { return 0; } } if (v->rest == 0) { return 0; } if (v->silent) { if (v->size == ST_SIZE_WORD) { silence = 0x00; } /* Fill in silence */ SDL_memset(buf, silence, v->rest); done = v->rest; v->rest = 0; } else { done = SDL_ReadIO(src, buf, v->rest); if (done <= 0) { return 0; } v->rest = (Uint32)(v->rest - done); if (v->size == ST_SIZE_WORD) { #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) Uint16 *samples = (Uint16 *)buf; for (; v->rest > 0; v->rest -= 2) { *samples = SDL_Swap16LE(*samples); samples++; } #endif done >>= 1; } } return (Uint32)done; } /* voc_read */ /* don't call this directly; use Mix_LoadWAV_IO() for now. */ SDL_AudioSpec *Mix_LoadVOC_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { bool was_error = true; vs_t v; int samplesize; Uint8 *fillptr; void *ptr; /* Sanity checks */ if (audio_buf) { *audio_buf = NULL; } if (!src) { SDL_InvalidParamError("src"); goto done; } if (!spec) { SDL_InvalidParamError("spec"); goto done; } if (!audio_buf) { SDL_InvalidParamError("audio_buf"); goto done; } if (!audio_len) { SDL_InvalidParamError("audio_len"); goto done; } if (!voc_check_header(src)) { goto done; } SDL_zero(v); v.rate = VOC_BAD_RATE; v.rest = 0; v.has_extended = 0; SDL_zerop(spec); if (!voc_get_block(src, &v, spec)) { goto done; } if (v.rate == VOC_BAD_RATE) { SDL_SetError("VOC data had no sound!"); goto done; } if (v.size == 0) { SDL_SetError("VOC data had invalid word size!"); goto done; } spec->format = ((v.size == ST_SIZE_WORD) ? SDL_AUDIO_S16 : SDL_AUDIO_U8); if (spec->channels == 0) { spec->channels = v.channels; } *audio_len = v.rest; *audio_buf = (v.rest == 0) ? NULL : SDL_malloc(v.rest); if (*audio_buf == NULL) { goto done; } fillptr = *audio_buf; while (voc_read(src, &v, fillptr, spec)) { if (!voc_get_block(src, &v, spec)) { goto done; } *audio_len += v.rest; ptr = SDL_realloc(*audio_buf, *audio_len); if (ptr == NULL) { goto done; } *audio_buf = ptr; fillptr = ((Uint8 *) ptr) + (*audio_len - v.rest); } /* Don't return a buffer that isn't a multiple of samplesize */ samplesize = ((spec->format & 0xFF)/8)*spec->channels; *audio_len &= (Uint32) ~(samplesize-1); was_error = false; done: if (closeio && src) { SDL_CloseIO(src); } if (was_error) { if (audio_buf && *audio_buf) { SDL_free(*audio_buf); *audio_buf = NULL; } if (audio_len) { *audio_len = 0; } spec = NULL; } return spec; } /* Mix_LoadVOC_IO */ /* end of load_voc.c ... */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/load_voc.h000066400000000000000000000031251501405355700232000ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This is the source needed to decode a Creative Labs VOC file into a waveform. It's pretty straightforward once you get going. The only externally-callable function is Mix_LoadVOC_IO(), which is meant to act as identically to SDL_LoadWAV_IO() as possible. This file by Ryan C. Gordon (icculus@icculus.org). Heavily borrowed from sox v12.17.1's voc.c. (http://www.freshmeat.net/projects/sox/) */ /* Don't call this directly; use Mix_LoadWAV_IO() for now. */ SDL_AudioSpec *Mix_LoadVOC_IO (SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len); /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/mp3utils.c000066400000000000000000001200011501405355700231560ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* Functions to discard MP3 tags - * written by O.Sezer , put into public domain. */ /* Functions to parse some of MP3 tags - * written by V.Novichkov , put into public domain. */ #include #include "mp3utils.h" #ifdef ENABLE_ID3V2_TAG /******************** SDL_IOStream with bookkeeping *********************/ int MP3_IOinit(struct mp3file_t *fil, SDL_IOStream *src) { /* Don't use SDL_IOsize() here -- see SDL bug #5509 */ fil->src = src; fil->start = SDL_TellIO(src); fil->length = SDL_SeekIO(src, 0, SDL_IO_SEEK_END) - fil->start; fil->pos = 0; if (fil->start < 0 || fil->length < 0) { SDL_SetError("Error seeking in datastream"); return -1; } SDL_SeekIO(src, fil->start, SDL_IO_SEEK_SET); return 0; } size_t MP3_IOread(struct mp3file_t *fil, void *ptr, size_t size, size_t maxnum) { size_t remaining = (size_t)(fil->length - fil->pos); size_t ret; maxnum *= size; if (maxnum > remaining) maxnum = remaining; ret = SDL_ReadIO(fil->src, ptr, maxnum); fil->pos += ret; return ret; } Sint64 MP3_IOseek(struct mp3file_t *fil, Sint64 offset, int whence) { Sint64 ret; switch (whence) { case SDL_IO_SEEK_CUR: offset += fil->pos; break; case SDL_IO_SEEK_END: offset += fil->length; break; } if (offset < 0) return -1; if (offset > fil->length) offset = fil->length; ret = SDL_SeekIO(fil->src, fil->start + offset, SDL_IO_SEEK_SET); if (ret < 0) return ret; fil->pos = offset; return offset; } Sint64 MP3_IOtell(struct mp3file_t *fil) { return fil->pos; } #endif /* ENABLE_ID3V2_TAG */ #ifdef ENABLE_ALL_MP3_TAGS static SDL_INLINE Sint32 read_sint32le(const Uint8 *data) { Uint32 result = (Uint32)data[0]; result |= (Uint32)data[1] << 8; result |= (Uint32)data[2] << 16; result |= (Uint32)data[3] << 24; return (Sint32)result; } #endif /* ENABLE_ALL_MP3_TAGS */ #ifdef ENABLE_ID3V2_TAG static SDL_INLINE Sint32 read_sint24be(const Uint8 *data) { Uint32 result = (Uint32)data[2]; result |= (Uint32)data[1] << 8; result |= (Uint32)data[0] << 16; return (Sint32)result; } static SDL_INLINE Sint32 read_sint32be(const Uint8 *data) { Uint32 result = (Uint32)data[3]; result |= (Uint32)data[2] << 8; result |= (Uint32)data[1] << 16; result |= (Uint32)data[0] << 24; return (Sint32)result; } #endif /* ENABLE_ID3V2_TAG */ #ifdef ENABLE_ALL_MP3_TAGS #define TAGS_INPUT_BUFFER_SIZE 128 /******************************************************** * ID3v1 * ********************************************************/ #define ID3v1_TAG_SIZE 128 #define ID3v1_SIZE_OF_FIELD 30 #define ID3v1_FIELD_TITLE 3 #define ID3v1_FIELD_ARTIST 33 #define ID3v1_FIELD_ALBUM 63 #define ID3v1_FIELD_COPYRIGHT 97 static SDL_INLINE bool is_id3v1(const Uint8 *data, size_t length) { /* http://id3.org/ID3v1 : 3 bytes "TAG" identifier and 125 bytes tag data */ if (length < ID3v1_TAG_SIZE || SDL_memcmp(data,"TAG", 3) != 0) { return false; } return true; } #endif #ifdef ENABLE_ID3V2_TAG /* Parse ISO-8859-1 string and convert it into UTF-8 */ static char *parse_id3v1_ansi_string(const Uint8 *buffer, size_t src_len) { char *src_buffer = (char*)SDL_malloc(src_len + 1); char *ret; if (!src_buffer) { return NULL; /* Out of memory */ } SDL_memset(src_buffer, 0, src_len + 1); SDL_memcpy(src_buffer, buffer, src_len); ret = SDL_iconv_string("UTF-8", "ISO-8859-1", src_buffer, src_len + 1); SDL_free(src_buffer); return ret; } #endif /* ENABLE_ID3V2_TAG */ #ifdef ENABLE_ALL_MP3_TAGS static void id3v1_set_tag(Mix_MusicMetaTags *out_tags, Mix_MusicMetaTag tag, const Uint8 *buffer, size_t len) { char *src_buf = parse_id3v1_ansi_string(buffer, len); if (src_buf) { meta_tags_set(out_tags, tag, src_buf); SDL_free(src_buf); } } /* Parse content of ID3v1 tag */ static void parse_id3v1(Mix_MusicMetaTags *out_tags, const Uint8 *buffer) { id3v1_set_tag(out_tags, MIX_META_TITLE, buffer + ID3v1_FIELD_TITLE, ID3v1_SIZE_OF_FIELD); id3v1_set_tag(out_tags, MIX_META_ARTIST, buffer + ID3v1_FIELD_ARTIST, ID3v1_SIZE_OF_FIELD); id3v1_set_tag(out_tags, MIX_META_ALBUM, buffer + ID3v1_FIELD_ALBUM, ID3v1_SIZE_OF_FIELD); id3v1_set_tag(out_tags, MIX_META_COPYRIGHT, buffer + ID3v1_FIELD_COPYRIGHT, ID3v1_SIZE_OF_FIELD); } #endif /* ENABLE_ALL_MP3_TAGS */ #ifdef ENABLE_ID3V2_TAG /******************************************************** * ID3v2 * ********************************************************/ #define ID3v2_BUFFER_SIZE 1024 #define ID3v2_HEADER_SIZE 10 #define ID3v2_FIELD_VERSION_MAJOR 3 #define ID3v2_FIELD_VERSION_MINOR 4 #define ID3v2_FIELD_HEAD_FLAGS 5 #define ID3v2_FIELD_TAG_LENGTH 6 #define ID3v2_FIELD_EXTRA_HEADER_LENGTH 10 #define ID3v2_FLAG_HAS_FOOTER 0x10 #define ID3v2_FLAG_HAS_EXTRA_HEAD 0x40 #define ID3v2_3_FRAME_HEADER_SIZE 10 #define ID3v2_2_FRAME_HEADER_SIZE 6 #define ID3v2_FIELD_FRAME_SIZE 4 #define ID3v2_FIELD_FRAME_SIZEv2 3 #define ID3v2_FIELD_FLAGS 8 static bool is_id3v2(const Uint8 *data, size_t length) { /* ID3v2 header is 10 bytes: http://id3.org/id3v2.4.0-structure */ /* bytes 0-2: "ID3" identifier */ if (length < ID3v2_HEADER_SIZE || SDL_memcmp(data, "ID3",3) != 0) { return false; } /* bytes 3-4: version num (major,revision), each byte always less than 0xff. */ if (data[3] == 0xff || data[4] == 0xff) { return false; } /* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the * highest bit 7 in each byte zeroed. i.e.: 7 bit information in each byte -> * effectively a 28 bit value. */ if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) { return false; } return true; } static SDL_INLINE Sint32 id3v2_synchsafe_decode(const Uint8 *data) { return ((data[0] << 21) + (data[1] << 14) + (data[2] << 7) + data[3]); } static long get_id3v2_len(const Uint8 *data, long length) { /* size is a 'synchsafe' integer (see above) */ long size = id3v2_synchsafe_decode(data + 6); size += ID3v2_HEADER_SIZE; /* header size */ /* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero). * bit 4 set: footer is present (a copy of the header but * with "3DI" as ident.) */ if (data[5] & 0x10) { size += ID3v2_HEADER_SIZE; /* footer size */ } /* optional padding (always zeroes) */ while (size < length && data[size] == 0) { ++size; } return size; } /* Decode a string in the frame according to an encoding marker */ static char *id3v2_decode_string(const Uint8 *string, size_t size) { char *str_buffer = NULL; char *src_buffer = NULL; size_t copy_size = size; if (size == 0) { SDL_Log("id3v2_decode_string: Bad string size: a string should have at least 1 byte"); return NULL; } if (size < 2) { return NULL; } if (string[0] == '\x01') { /* UTF-16 string with a BOM */ if (size <= 5) { if (size < 5) { SDL_Log("id3v2_decode_string: Bad BOM-UTF16 string size: %u < 5", (unsigned int)size); } return NULL; } copy_size = size - 3 + 2; /* exclude 3 bytes of encoding hint, append 2 bytes for a NULL termination */ src_buffer = (char*)SDL_malloc(copy_size); if (!src_buffer) { return NULL; /* Out of memory */ } SDL_memset(src_buffer, 0, copy_size); SDL_memcpy(src_buffer, (string + 3), copy_size - 2); if (SDL_memcmp(string, "\x01\xFE\xFF", 3) == 0) { /* UTF-16BE*/ str_buffer = SDL_iconv_string("UTF-8", "UCS-2BE", src_buffer, copy_size); } else if (SDL_memcmp(string, "\x01\xFF\xFE", 3) == 0) { /* UTF-16LE*/ str_buffer = SDL_iconv_string("UTF-8", "UCS-2LE", src_buffer, copy_size); } SDL_free(src_buffer); } else if (string[0] == '\x02') { /* UTF-16BEstring without a BOM */ if (size <= 3) { if (size < 3) { SDL_Log("id3v2_decode_string: Bad UTF16BE string size: %u < 3", (unsigned int)size); } return NULL; /* Blank string*/ } copy_size = size - 1 + 2; /* exclude 1 byte of encoding hint, append 2 bytes for a NULL termination */ src_buffer = (char*)SDL_malloc(copy_size); if (!src_buffer) { return NULL; /* Out of memory */ } SDL_memset(src_buffer, 0, copy_size); SDL_memcpy(src_buffer, (string + 1), copy_size - 2); str_buffer = SDL_iconv_string("UTF-8", "UCS-2BE", src_buffer, copy_size); SDL_free(src_buffer); } else if (string[0] == '\x03') { /* UTF-8 string */ if (size <= 2) { return NULL; /* Blank string*/ } str_buffer = (char*)SDL_malloc(size); if (!str_buffer) { return NULL; /* Out of memory */ } SDL_strlcpy(str_buffer, (const char*)(string + 1), size); } else if (string[0] == '\x00') { /* Latin-1 string */ if (size <= 2) { return NULL; /* Blank string*/ } str_buffer = parse_id3v1_ansi_string((string + 1), size - 1); } return str_buffer; } /* Write a tag string into internal meta-tags storage */ static void write_id3v2_string(Mix_MusicMetaTags *out_tags, Mix_MusicMetaTag tag, const Uint8 *string, size_t size) { char *str_buffer = id3v2_decode_string(string, size); if (str_buffer) { meta_tags_set(out_tags, tag, str_buffer); SDL_free(str_buffer); } } /* Identify a meta-key and decode the string (Note: input buffer should have at least 4 characters!) */ static void handle_id3v2_string(Mix_MusicMetaTags *out_tags, const char *key, const Uint8 *string, size_t size) { if (SDL_memcmp(key, "TIT2", 4) == 0) { write_id3v2_string(out_tags, MIX_META_TITLE, string, size); } else if (SDL_memcmp(key, "TPE1", 4) == 0) { write_id3v2_string(out_tags, MIX_META_ARTIST, string, size); } else if (SDL_memcmp(key, "TALB", 4) == 0) { write_id3v2_string(out_tags, MIX_META_ALBUM, string, size); } else if (SDL_memcmp(key, "TCOP", 4) == 0) { write_id3v2_string(out_tags, MIX_META_COPYRIGHT, string, size); } /* TODO: Extract "Copyright message" from TXXX value: a KEY=VALUE string divided by a zero byte:*/ /* else if (SDL_memcmp(key, "TXXX", 4) == 0) { write_id3v2_string(out_tags, MIX_META_COPYRIGHT, string, size); } */ } /* Identify a meta-key and decode the string (Note: input buffer should have at least 4 characters!) */ static void handle_id3v2x2_string(Mix_MusicMetaTags *out_tags, const char *key, const Uint8 *string, size_t size) { if (SDL_memcmp(key, "TT2", 3) == 0) { write_id3v2_string(out_tags, MIX_META_TITLE, string, size); } else if (SDL_memcmp(key, "TP1", 3) == 0) { write_id3v2_string(out_tags, MIX_META_ARTIST, string, size); } else if (SDL_memcmp(key, "TAL", 3) == 0) { write_id3v2_string(out_tags, MIX_META_ALBUM, string, size); } else if (SDL_memcmp(key, "TCR", 3) == 0) { write_id3v2_string(out_tags, MIX_META_COPYRIGHT, string, size); } } /* Parse a frame in ID3v2.2 format */ static size_t id3v22_parse_frame(Mix_MusicMetaTags *out_tags, struct mp3file_t *src, Uint8 *buffer) { size_t size; char key[4]; size_t read_size; Sint64 frame_begin = MP3_IOtell(src); read_size = MP3_IOread(src, buffer, 1, ID3v2_2_FRAME_HEADER_SIZE); if (read_size < ID3v2_2_FRAME_HEADER_SIZE) { SDL_Log("id3v22_parse_frame (1): Unexpected end of the file while frame header reading (had to read %u bytes, %u bytes wanted)", (unsigned int)read_size, (unsigned int)ID3v2_2_FRAME_HEADER_SIZE); MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; /* Buffer size that left is too small */ } if (SDL_memcmp(buffer, "\0\0\0", 3) == 0) { MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; } SDL_memcpy(key, buffer, 3); /* Tag title (key) */ size = (size_t)read_sint24be(buffer + ID3v2_FIELD_FRAME_SIZEv2); if (size < ID3v2_BUFFER_SIZE) { read_size = MP3_IOread(src, buffer, 1, size); if (read_size < size) { SDL_Log("id3v22_parse_frame (2): Unexpected end of the file while frame data reading (had to read %u bytes, %u bytes wanted)", (unsigned int)read_size, (unsigned int)size); MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; /* Can't read frame data, possibly, a file size was reached */ } } else { read_size = MP3_IOread(src, buffer, 1, ID3v2_BUFFER_SIZE); if (read_size < ID3v2_BUFFER_SIZE) { SDL_Log("id3v22_parse_frame (3): Unexpected end of the file while frame data reading (had to read %u bytes, %u bytes wanted)", (unsigned int)read_size, (unsigned int)ID3v2_BUFFER_SIZE); MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; /* Can't read frame data, possibly, a file size was reached */ } MP3_IOseek(src, frame_begin + (Sint64)size, SDL_IO_SEEK_SET); } handle_id3v2x2_string(out_tags, key, buffer, read_size); return (size_t)(size + ID3v2_2_FRAME_HEADER_SIZE); /* data size + size of the header */ } /* Parse a frame in ID3v2.3 and ID3v2.4 formats */ static size_t id3v2x_parse_frame(Mix_MusicMetaTags *out_tags, struct mp3file_t *src, Uint8 *buffer, Uint8 version) { Uint32 size; char key[4]; Uint8 flags[2]; size_t read_size; Sint64 frame_begin = MP3_IOtell(src); read_size = MP3_IOread(src, buffer, 1, ID3v2_3_FRAME_HEADER_SIZE); if (read_size < ID3v2_3_FRAME_HEADER_SIZE) { SDL_Log("id3v2x_parse_frame (1): Unexpected end of the file while frame header reading (had to read %u bytes, %u bytes wanted)", (unsigned int)read_size, (unsigned int)ID3v2_3_FRAME_HEADER_SIZE); MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; /* Can't read frame header, possibly, a file size was reached */ } if (SDL_memcmp(buffer, "\0\0\0\0", 4) == 0) { MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; } SDL_memcpy(key, buffer, 4); /* Tag title (key) */ if (version == 4) { size = (Uint32)id3v2_synchsafe_decode(buffer + ID3v2_FIELD_FRAME_SIZE); } else { size = (Uint32)read_sint32be(buffer + ID3v2_FIELD_FRAME_SIZE); } SDL_memcpy(flags, buffer + ID3v2_FIELD_FLAGS, 2); if (size < ID3v2_BUFFER_SIZE) { read_size = MP3_IOread(src, buffer, 1, size); if (read_size < size) { SDL_Log("id3v2x_parse_frame (2): Unexpected end of the file while frame data reading (had to read %u bytes, %u bytes wanted)", (unsigned int)read_size, (unsigned int)size); MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; /* Can't read frame data, possibly, a file size was reached */ } } else { read_size = MP3_IOread(src, buffer, 1, ID3v2_BUFFER_SIZE); if (read_size < ID3v2_BUFFER_SIZE) { SDL_Log("id3v2x_parse_frame (3): Unexpected end of the file while frame data reading (had to read %u bytes, %u bytes wanted)", (unsigned int)read_size, (unsigned int)ID3v2_BUFFER_SIZE); MP3_IOseek(src, frame_begin, SDL_IO_SEEK_SET); return 0; /* Can't read frame data, possibly, a file size was reached */ } MP3_IOseek(src, frame_begin + (Sint64)size, SDL_IO_SEEK_SET); } handle_id3v2_string(out_tags, key, buffer, size); return (size_t)(size + ID3v2_3_FRAME_HEADER_SIZE); /* data size + size of the header */ } /* Parse content of ID3v2 */ static bool parse_id3v2(Mix_MusicMetaTags *out_tags, struct mp3file_t *src) { Uint8 version_major, flags; long total_length, tag_len, tag_extended_len = 0; Uint8 buffer[ID3v2_BUFFER_SIZE]; size_t read_size; size_t frame_length; Sint64 file_size; total_length = 0; file_size = src->length; MP3_IOseek(src, 0, SDL_IO_SEEK_SET); read_size = MP3_IOread(src, buffer, 1, ID3v2_HEADER_SIZE); /* Retrieve the header */ if (read_size < ID3v2_HEADER_SIZE) { SDL_Log("parse_id3v2: fail to read a header (%u < 10)", (unsigned int)read_size); return false; /* Unsupported version of the tag */ } total_length += ID3v2_HEADER_SIZE; version_major = buffer[ID3v2_FIELD_VERSION_MAJOR]; /* Major version */ /* version_minor = buffer[ID3v2_VERSION_MINOR]; * Minor version, UNUSED */ flags = buffer[ID3v2_FIELD_HEAD_FLAGS]; /* Flags */ tag_len = id3v2_synchsafe_decode(buffer + ID3v2_FIELD_TAG_LENGTH); /* Length of a tag */ if (version_major != 2 && version_major != 3 && version_major != 4) { SDL_Log("parse_id3v2: Unsupported version %d", version_major); return false; /* Unsupported version of the tag */ } if ((version_major > 2) && ((flags & ID3v2_FLAG_HAS_EXTRA_HEAD) == ID3v2_FLAG_HAS_EXTRA_HEAD)) { MP3_IOread(src, buffer + ID3v2_FIELD_EXTRA_HEADER_LENGTH, 1, 4); MP3_IOseek(src, -4, SDL_IO_SEEK_CUR); tag_extended_len = id3v2_synchsafe_decode(buffer + ID3v2_FIELD_EXTRA_HEADER_LENGTH); /* Length of an extended header */ } if (tag_extended_len) { tag_len -= tag_extended_len; /* Subtract the size of extended header */ MP3_IOseek(src, tag_extended_len, SDL_IO_SEEK_CUR); /* Skip extended header and it's size value */ } total_length += tag_len; if (flags & ID3v2_FLAG_HAS_FOOTER) { total_length += ID3v2_HEADER_SIZE; /* footer size */ } if ((MP3_IOtell(src) + tag_len) > file_size) { SDL_Log("parse_id3v2: Tag size bigger than actual file size"); return false; /* Tag size is bigger than actual buffer data */ } while ((MP3_IOtell(src) >= 0) && (MP3_IOtell(src) < (total_length))) { if (version_major == 2) { frame_length = id3v22_parse_frame(out_tags, src, buffer); } else { frame_length = id3v2x_parse_frame(out_tags, src, buffer, version_major); } if (!frame_length) { break; } } return true; } #endif /* ENABLE_ID3V2_TAG */ #ifdef ENABLE_ALL_MP3_TAGS /******************************************************** * APE v1 and v2 * ********************************************************/ #define APE_BUFFER_SIZE 256 #define APE_V1 1000U #define APE_V2 2000U #define APE_HEADER_SIZE 32 #define APE_HEAD_FIELD_VERSION 8 #define APE_HEAD_FIELD_TAGSIZE 12 #define APE_HEAD_FIELD_ITEMS_COUNT 16 #define APE_HEAD_FIELD_FLAGS 20 #define APE_HEAD_FIELD_RESERVED 24 #define APE_FRAME_TAG_KEY 4 static bool is_apetag(const Uint8 *data, size_t length) { /* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification * Header/footer is 32 bytes: bytes 0-7 ident, bytes 8-11 version, * bytes 12-17 size. bytes 24-31 are reserved: must be all zeroes. */ Uint32 v; if (length < 32 || SDL_memcmp(data,"APETAGEX",8) != 0) { return false; } v = (Uint32) read_sint32le(data + 8); /* version */ if (v != APE_V2 && v != APE_V1) { return false; } v = 0; /* reserved bits : */ if (SDL_memcmp(&data[24],&v,4) != 0 || SDL_memcmp(&data[28],&v,4) != 0) { return false; } return true; } static long get_ape_len(const Uint8 *data, Uint32 *version) { Uint32 flags; long size = (long) read_sint32le(data + APE_HEAD_FIELD_TAGSIZE); *version = (Uint32) read_sint32le(data + APE_HEAD_FIELD_VERSION); flags = (Uint32) read_sint32le(data + APE_HEAD_FIELD_FLAGS); if (*version == APE_V2 && (flags & (1U<<31))) { size += APE_HEADER_SIZE; /* header present. */ } return size; } static char *ape_find_value(char *key) { char *end = (key + APE_BUFFER_SIZE - 4); while (*key && key != end) { key++; } if (key >= end) { return NULL; } return key + 1; } static Uint32 ape_handle_tag(Mix_MusicMetaTags *out_tags, Uint8 *data, size_t valsize) { /* http://wiki.hydrogenaud.io/index.php?title=APE_Tag_Item * Tag entry has unclear size because of no size value for a key field * However, we only know next sizes: * - 4 bytes is a [length] of value field * - 4 bytes of value-specific flags * - unknown lenght of a key field. To detect it's size * it's need to find a zero byte looking at begin of the key field * - 1 byte of a null-terminator * - [length] bytes a value content */ char *key = (char*)(data + APE_FRAME_TAG_KEY); char *value = NULL; Uint32 key_len; /* Length of the key field */ value = ape_find_value(key); if (!value) { return 0; } key_len = (Uint32)(value - key); if (valsize > APE_BUFFER_SIZE - key_len) { data[APE_BUFFER_SIZE] = '\0'; } else { value[valsize] = '\0'; } if (SDL_strncasecmp(key, "Title", 6) == 0) { meta_tags_set(out_tags, MIX_META_TITLE, (const char*)(value)); } else if (SDL_strncasecmp(key, "Album", 6) == 0) { meta_tags_set(out_tags, MIX_META_ALBUM, (const char*)(value)); } else if (SDL_strncasecmp(key, "Artist", 7) == 0) { meta_tags_set(out_tags, MIX_META_ARTIST, (const char*)(value)); } else if (SDL_strncasecmp(key, "Copyright", 10) == 0) { meta_tags_set(out_tags, MIX_META_COPYRIGHT, (const char*)(value)); } return 4 + (Uint32)valsize + key_len; } /* Parse content of APE tag */ static bool parse_ape(Mix_MusicMetaTags *out_tags, struct mp3file_t *src, Sint64 ape_head_pos, Uint32 version) { Uint8 buffer[APE_BUFFER_SIZE + 1]; Uint32 v, i, tag_size, tag_items_count, tag_item_size; Uint32 zero8[2] = {0, 0}; Sint64 file_size, cur_tag; size_t read_size; file_size = src->length; MP3_IOseek(src, ape_head_pos, SDL_IO_SEEK_SET); read_size = MP3_IOread(src, buffer, 1, APE_HEADER_SIZE); /* Retrieve the header */ if (read_size < APE_HEADER_SIZE) { MP3_IOseek(src, ape_head_pos, SDL_IO_SEEK_SET); return false; } v = (Uint32)read_sint32le(buffer + APE_HEAD_FIELD_VERSION); /* version */ if (v != APE_V2 && v != APE_V1) { return false; } tag_size = (Uint32)read_sint32le(buffer + APE_HEAD_FIELD_TAGSIZE); /* tag size */ if (version == APE_V1) { /* If version 1, we are at footer */ if (ape_head_pos - (tag_size - APE_HEADER_SIZE) < 0) { MP3_IOseek(src, ape_head_pos, SDL_IO_SEEK_SET); return false; } MP3_IOseek(src, ape_head_pos - (tag_size - APE_HEADER_SIZE), SDL_IO_SEEK_SET); } else if ((ape_head_pos + tag_size + APE_HEADER_SIZE) > file_size) { MP3_IOseek(src, ape_head_pos, SDL_IO_SEEK_SET); return false; } tag_items_count = (Uint32)read_sint32le(buffer + APE_HEAD_FIELD_ITEMS_COUNT); /* count tag items */ /*flags = (Uint32)read_sint32be(buffer + APE_HEAD_FIELD_FLAGS);*/ /* global flags, unused */ /* reserved bits : */ if (SDL_memcmp(buffer + APE_HEAD_FIELD_RESERVED, zero8, 8) != 0) { return false; } for(i = 0; i < tag_items_count; i++) { cur_tag = MP3_IOtell(src); if (cur_tag < 0) { break; } read_size = MP3_IOread(src, buffer, 1, 4); /* Retrieve the size */ if (read_size < 4) { MP3_IOseek(src, ape_head_pos, SDL_IO_SEEK_SET); return false; } v = (Uint32)read_sint32le(buffer); /* size of the tag's value field */ /* (we still need to find key size by a null termination) */ /* Retrieve the tag's data with an aproximal size as we can */ if (v + 40 < APE_BUFFER_SIZE) { read_size = MP3_IOread(src, buffer, 1, v + 40); } else { read_size = MP3_IOread(src, buffer, 1, APE_BUFFER_SIZE); } buffer[read_size] = '\0'; tag_item_size = ape_handle_tag(out_tags, buffer, v); if (tag_item_size == 0) { break; } MP3_IOseek(src, cur_tag + tag_item_size + 4, SDL_IO_SEEK_SET); } MP3_IOseek(src, ape_head_pos, SDL_IO_SEEK_SET); return true; } /******************************************************** * Lyrics3 skip * ********************************************************/ /* Header : "LYRICSBEGIN" -- 11 bytes * Size field: (decimal) (v2 only) 6 bytes * End marker: "LYRICS200" (v2) - 9 bytes * End marker: "LYRICSEND" (v1) - 9 bytes * * The maximum length of Lyrics3v1 is 5100 bytes. */ #define LYRICS3v1_SEARCH_BUFFER 5120 /* 5100 + 20 of tag begin and end keywords */ #define LYRICS3v1_HEAD_SIZE 11 #define LYRICS3v1_TAIL_SIZE 9 #define LYRICS3v2_TAG_SIZE_VALUE 6 #define LYRICS3_FOOTER_SIZE 15 static SDL_INLINE int is_lyrics3tag(const Uint8 *data, size_t length) { /* http://id3.org/Lyrics3 * http://id3.org/Lyrics3v2 */ if (length < LYRICS3_FOOTER_SIZE) return 0; if (SDL_memcmp(data+LYRICS3v2_TAG_SIZE_VALUE,"LYRICS200",9) == 0) return 2; /* v2 */ if (SDL_memcmp(data+LYRICS3v2_TAG_SIZE_VALUE,"LYRICSEND",9) == 0) return 1; /* v1 */ return 0; } static long get_lyrics3v1_len(struct mp3file_t *m) { const char *p; long i, len; char buf[LYRICS3v1_SEARCH_BUFFER + 1]; /* needs manual search: http://id3.org/Lyrics3 */ if (m->length < 20) return -1; len = (m->length > LYRICS3v1_SEARCH_BUFFER)? LYRICS3v1_SEARCH_BUFFER : (long)m->length; MP3_IOseek(m, -len, SDL_IO_SEEK_END); MP3_IOread(m, buf, 1, (size_t)(len -= LYRICS3v1_TAIL_SIZE)); /* exclude footer */ /* strstr() won't work here. */ p = buf; for (i = len - LYRICS3v1_HEAD_SIZE; i >= 0; --i, ++p) { if (SDL_memcmp(p, "LYRICSBEGIN", LYRICS3v1_HEAD_SIZE) == 0) break; } if (i < 0) return -1; return len - (long)(p - buf) + LYRICS3v1_TAIL_SIZE /* footer */; } static SDL_INLINE long get_lyrics3v2_len(const Uint8 *data, size_t length) { /* 6 bytes before the end marker is size in decimal format - * does not include the 9 bytes end marker and size field. */ if (length != LYRICS3v2_TAG_SIZE_VALUE) return 0; return SDL_strtol((const char *)data, NULL, 10) + LYRICS3_FOOTER_SIZE; } static SDL_INLINE bool verify_lyrics3v2(const Uint8 *data, size_t length) { if (length < LYRICS3v1_HEAD_SIZE) return false; if (SDL_memcmp(data,"LYRICSBEGIN",LYRICS3v1_HEAD_SIZE) == 0) return true; return false; } /******************************************************** * MusicMatch * ********************************************************/ #define MUSICMATCH_HEADER_SIZE 256 #define MUSICMATCH_VERSION_INFO_SIZE 256 #define MUSICMATCH_FOOTER_SIZE 48 #define MUSICMATCH_OFFSETS_SIZE 20 #define MMTAG_PARANOID static bool is_musicmatch(const Uint8 *data, long length) { /* From docs/musicmatch.txt in id3lib: https://sourceforge.net/projects/id3lib/ Overall tag structure: +-----------------------------+ | Header | | (256 bytes, OPTIONAL) | +-----------------------------+ | Image extension (4 bytes) | +-----------------------------+ | Image binary | | (var. length >= 4 bytes) | +-----------------------------+ | Unused (4 bytes) | +-----------------------------+ | Version info (256 bytes) | +-----------------------------+ | Audio meta-data | | (var. length >= 7868 bytes) | +-----------------------------+ | Data offsets (20 bytes) | +-----------------------------+ | Footer (48 bytes) | +-----------------------------+ */ if (length < MUSICMATCH_FOOTER_SIZE) return false; /* sig: 19 bytes company name + 13 bytes space */ if (SDL_memcmp(data,"Brava Software Inc. ",32) != 0) { return false; } /* 4 bytes version: x.xx */ if (!SDL_isdigit(data[32]) || data[33] != '.' || !SDL_isdigit(data[34]) ||!SDL_isdigit(data[35])) { return false; } #ifdef MMTAG_PARANOID /* [36..47]: 12 bytes trailing space */ for (length = 36; length < MUSICMATCH_FOOTER_SIZE; ++length) { if (data[length] != ' ') return false; } #endif return true; } static long get_musicmatch_len(struct mp3file_t *m) { const Sint32 metasizes[4] = { 7868, 7936, 8004, 8132 }; const unsigned char syncstr[10] = {'1','8','2','7','3','6','4','5',0,0}; unsigned char buf[256]; Sint32 i, j, imgext_ofs, version_ofs; long len; MP3_IOseek(m, -68, SDL_IO_SEEK_END); MP3_IOread(m, buf, 1, 20); imgext_ofs = (Sint32)((buf[3] <<24) | (buf[2] <<16) | (buf[1] <<8) | buf[0] ); version_ofs = (Sint32)((buf[15]<<24) | (buf[14]<<16) | (buf[13]<<8) | buf[12]); if (version_ofs <= imgext_ofs) return -1; if (version_ofs <= 0 || imgext_ofs <= 0) return -1; /* Try finding the version info section: * Because metadata section comes after it, and because metadata section * has different sizes across versions (format ver. <= 3.00: always 7868 * bytes), we can _not_ directly calculate using deltas from the offsets * section. */ for (i = 0; i < 4; ++i) { /* 48: footer, 20: offsets, 256: version info */ len = metasizes[i] + MUSICMATCH_FOOTER_SIZE + MUSICMATCH_OFFSETS_SIZE + MUSICMATCH_VERSION_INFO_SIZE; if (m->length < len) return -1; MP3_IOseek(m, -len, SDL_IO_SEEK_END); MP3_IOread(m, buf, 1, MUSICMATCH_VERSION_INFO_SIZE); /* [0..9]: sync string, [30..255]: 0x20 */ #ifdef MMTAG_PARANOID for (j = 30; j < MUSICMATCH_VERSION_INFO_SIZE; ++j) { if (buf[j] != ' ') break; } if (j < MUSICMATCH_VERSION_INFO_SIZE) continue; #endif if (SDL_memcmp(buf, syncstr, 10) == 0) { break; } } if (i == 4) return -1; /* no luck. */ #ifdef MMTAG_PARANOID /* unused section: (4 bytes of 0x00) */ MP3_IOseek(m, -(len + 4), SDL_IO_SEEK_END); MP3_IOread(m, buf, 1, 4); j = 0; if (SDL_memcmp(buf, &j, 4) != 0) return -1; #endif len += (version_ofs - imgext_ofs); if (m->length < len) return -1; MP3_IOseek(m, -len, SDL_IO_SEEK_END); MP3_IOread(m, buf, 1, 8); j = (Sint32)((buf[7] <<24) | (buf[6] <<16) | (buf[5] <<8) | buf[4]); if (j < 0) return -1; /* verify image size: */ /* without this, we may land at a wrong place. */ if (j + 12 != version_ofs - imgext_ofs) return -1; /* try finding the optional header */ if (m->length < len + MUSICMATCH_HEADER_SIZE) return len; MP3_IOseek(m, -(len + MUSICMATCH_HEADER_SIZE), SDL_IO_SEEK_END); MP3_IOread(m, buf, 1, MUSICMATCH_HEADER_SIZE); /* [0..9]: sync string, [30..255]: 0x20 */ if (SDL_memcmp(buf, syncstr, 10) != 0) { return len; } #ifdef MMTAG_PARANOID for (j = 30; j < MUSICMATCH_HEADER_SIZE; ++j) { if (buf[j] != ' ') return len; } #endif return len + MUSICMATCH_HEADER_SIZE; /* header is present. */ } #define TAG_FOUND 1 #define TAG_INVALID -1 #define TAG_NOT_FOUND 0 static int probe_id3v1(Mix_MusicMetaTags *out_tags, struct mp3file_t *fil, Uint8 *buf, bool tag_handled, int atend) { if (fil->length >= ID3v1_TAG_SIZE) { MP3_IOseek(fil, -ID3v1_TAG_SIZE, SDL_IO_SEEK_END); if (MP3_IOread(fil, buf, 1, ID3v1_TAG_SIZE) != ID3v1_TAG_SIZE) return TAG_INVALID; if (is_id3v1(buf, ID3v1_TAG_SIZE)) { if (!atend) { /* possible false positive? */ if (is_musicmatch(buf + 128 - 48, 48) || is_apetag (buf + 128 - 32, 32) || is_lyrics3tag(buf + 128 - 15, 15)) { return TAG_NOT_FOUND; } } if (!tag_handled) { parse_id3v1(out_tags, buf); } fil->length -= ID3v1_TAG_SIZE; return TAG_FOUND; /* FIXME: handle possible double-ID3v1 tags?? */ } } return TAG_NOT_FOUND; } static int probe_mmtag(Mix_MusicMetaTags *out_tags, struct mp3file_t *fil, Uint8 *buf) { long len; (void)out_tags; /* TODO: Implement reading tag contents. */ if (fil->length >= 68) { MP3_IOseek(fil, -MUSICMATCH_FOOTER_SIZE, SDL_IO_SEEK_END); if (MP3_IOread(fil, buf, 1, MUSICMATCH_FOOTER_SIZE) != MUSICMATCH_FOOTER_SIZE) return TAG_INVALID; if (is_musicmatch(buf, MUSICMATCH_FOOTER_SIZE)) { len = get_musicmatch_len(fil); if (len < 0) return TAG_INVALID; if (len >= fil->length) return TAG_INVALID; fil->length -= len; return TAG_FOUND; } } return TAG_NOT_FOUND; } static int probe_apetag(Mix_MusicMetaTags *out_tags, struct mp3file_t *fil, Uint8 *buf, bool tag_handled) { Sint64 ape_tag_pos; size_t readsize; long len; Uint32 v; /* APE tag may be at the end: read the footer */ if (fil->length >= APE_HEADER_SIZE) { MP3_IOseek(fil, -APE_HEADER_SIZE, SDL_IO_SEEK_END); readsize = MP3_IOread(fil, buf, 1, APE_HEADER_SIZE); if (readsize != APE_HEADER_SIZE) { return TAG_INVALID; } /* APE tag may be at end or before ID3v1 tag */ if (is_apetag(buf, APE_HEADER_SIZE)) { len = get_ape_len(buf, &v); if (len >= fil->length) { return TAG_INVALID; } if (v == APE_V2) { /* verify header : */ MP3_IOseek(fil, -len, SDL_IO_SEEK_END); ape_tag_pos = MP3_IOtell(fil); readsize = MP3_IOread(fil, buf, 1, APE_HEADER_SIZE); if (readsize != APE_HEADER_SIZE) { return TAG_INVALID; } if (!is_apetag(buf, APE_HEADER_SIZE)) { fil->length -= len; return TAG_NOT_FOUND; } if (!tag_handled) { parse_ape(out_tags, fil, ape_tag_pos, APE_V2); } } else if (!tag_handled) { bool ape_tag_valid; MP3_IOseek(fil, -APE_HEADER_SIZE, SDL_IO_SEEK_END); ape_tag_pos = MP3_IOtell(fil); ape_tag_valid = parse_ape(out_tags, fil, ape_tag_pos, APE_V1); if (!ape_tag_valid) { fil->length -= len; return TAG_NOT_FOUND; } } fil->length -= len; return TAG_FOUND; } } return TAG_NOT_FOUND; } static int probe_lyrics3(struct mp3file_t *fil, Uint8 *buf) { long len; int ver; if (fil->length >= LYRICS3_FOOTER_SIZE) { MP3_IOseek(fil, -LYRICS3_FOOTER_SIZE, SDL_IO_SEEK_END); if (MP3_IOread(fil, buf, 1, LYRICS3_FOOTER_SIZE) != LYRICS3_FOOTER_SIZE) return TAG_INVALID; ver = is_lyrics3tag(buf, LYRICS3_FOOTER_SIZE); if (ver == 2) { len = get_lyrics3v2_len(buf, LYRICS3v2_TAG_SIZE_VALUE); if (len >= fil->length) return TAG_INVALID; if (len < LYRICS3_FOOTER_SIZE) return TAG_INVALID; MP3_IOseek(fil, -len, SDL_IO_SEEK_END); if (MP3_IOread(fil, buf, 1, LYRICS3v1_HEAD_SIZE) != LYRICS3v1_HEAD_SIZE) return TAG_INVALID; if (!verify_lyrics3v2(buf, LYRICS3v1_HEAD_SIZE)) return -1; fil->length -= len; return TAG_FOUND; } else if (ver == 1) { len = get_lyrics3v1_len(fil); if (len < 0) return TAG_INVALID; fil->length -= len; return TAG_FOUND; } } return TAG_NOT_FOUND; } int mp3_read_tags(Mix_MusicMetaTags *out_tags, struct mp3file_t *fil, bool keep_id3v2) { Uint8 buf[TAGS_INPUT_BUFFER_SIZE]; long len; size_t readsize; int c_id3, c_ape, c_lyr, c_mm; int rc = -1; bool tag_handled = false; /* MP3 standard has no metadata format, so everyone invented * their own thing, even with extensions, until ID3v2 became * dominant: Hence the impossible mess here. * * Note: I don't yet care about freaky broken mp3 files with * double tags. -- O.S. */ MP3_IOseek(fil, 0, SDL_IO_SEEK_SET); readsize = MP3_IOread(fil, buf, 1, TAGS_INPUT_BUFFER_SIZE); if (!readsize) goto fail; /* ID3v2 tag is at the start */ if (is_id3v2(buf, readsize)) { len = get_id3v2_len(buf, (long)readsize); if (len >= fil->length) goto fail; tag_handled = parse_id3v2(out_tags, fil); if (!keep_id3v2) { fil->start += len; fil->length -= len; } } /* APE tag _might_ be at the start (discouraged * but not forbidden, either.) read the header. */ else if (is_apetag(buf, readsize)) { Uint32 v; len = get_ape_len(buf, &v); if (len >= fil->length) goto fail; if (v == APE_V1 || v == APE_V2) { tag_handled = parse_ape(out_tags, fil, 0, v); } fil->start += len; fil->length -= len; } /* it's not impossible that _old_ MusicMatch tag * placing itself after ID3v1. */ if ((c_mm = probe_mmtag(out_tags, fil, buf)) < 0) { goto fail; } /* ID3v1 tag is at the end */ if ((c_id3 = probe_id3v1(out_tags, fil, buf, tag_handled, !c_mm)) < 0) { goto fail; } /* we do not know the order of ape or lyrics3 * or musicmatch tags, hence the loop here.. */ c_ape = 0; c_lyr = 0; for (;;) { if (!c_lyr) { /* care about mp3s with double Lyrics3 tags? */ if ((c_lyr = probe_lyrics3(fil, buf)) == TAG_INVALID) goto fail; if (c_lyr) continue; } if (!c_mm) { if ((c_mm = probe_mmtag(out_tags, fil, buf)) == TAG_INVALID) goto fail; if (c_mm) continue; } if (!c_ape) { if ((c_ape = probe_apetag(out_tags, fil, buf, tag_handled)) == TAG_INVALID) goto fail; if (c_ape) continue; } break; } /* for (;;) */ rc = (fil->length > 0)? 0 : -1; fail: MP3_IOseek(fil, 0, SDL_IO_SEEK_SET); return rc; } #endif /* ENABLE_ALL_MP3_TAGS */ #ifdef ENABLE_ID3V2_TAG int read_id3v2_from_mem(Mix_MusicMetaTags *out_tags, Uint8 *data, size_t length) { SDL_IOStream *src = SDL_IOFromConstMem(data, (int)length); bool is_valid; struct mp3file_t fil; if (src) { fil.src = src; fil.start = 0; fil.length = (Sint64)length; fil.pos = 0; if (!is_id3v2(data, length)) { SDL_CloseIO(src); return -1; } if (get_id3v2_len(data, (long)length) > (long)length) { SDL_CloseIO(src); return -1; } is_valid = parse_id3v2(out_tags, &fil); SDL_CloseIO(src); return is_valid ? 0 : -1; } return -1; } #endif /* ENABLE_ID3V2_TAG */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/mp3utils.h000066400000000000000000000037311501405355700231750ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file provides utility functions to work with MP3 files including reading of tags. */ #ifndef MIX_MP3UTILS_H #define MIX_MP3UTILS_H #include "music.h" #define ENABLE_ALL_MP3_TAGS #if defined(MUSIC_WAV) || defined(ENABLE_ALL_MP3_TAGS) #define ENABLE_ID3V2_TAG struct mp3file_t { SDL_IOStream *src; Sint64 start, length, pos; }; #endif #ifdef ENABLE_ALL_MP3_TAGS extern int mp3_read_tags(Mix_MusicMetaTags *out_tags, struct mp3file_t *fil, bool keep_id3v2); #endif /* ENABLE_ALL_MP3_TAGS */ #ifdef ENABLE_ID3V2_TAG extern int read_id3v2_from_mem(Mix_MusicMetaTags *out_tags, Uint8 *data, size_t length); #endif #ifdef ENABLE_ALL_MP3_TAGS extern int MP3_IOinit(struct mp3file_t *fil, SDL_IOStream *src); extern size_t MP3_IOread(struct mp3file_t *fil, void *ptr, size_t size, size_t maxnum); extern Sint64 MP3_IOseek(struct mp3file_t *fil, Sint64 offset, int whence); extern Sint64 MP3_IOtell(struct mp3file_t *fil); #endif /* ENABLE_ALL_MP3_TAGS */ #endif /* MIX_MP3UTILS_H */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_drflac.c000066400000000000000000000311641501405355700240440ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_FLAC_DRFLAC #include "music_drflac.h" #include "mp3utils.h" #include "../utils.h" #include #define DR_FLAC_IMPLEMENTATION #if defined(__GNUC__) && (__GNUC__ >= 4) && \ !(defined(_WIN32) || defined(__EMX__)) #define DRFLAC_API __attribute__((visibility("hidden"))) #elif defined(__APPLE__) #define DRFLAC_API __private_extern__ #else #define DRFLAC_API /* just in case.. */ #endif #define DR_FLAC_NO_STDIO #define DRFLAC_ASSERT(expression) #define DRFLAC_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz)) #define DRFLAC_MOVE_MEMORY(dst, src, sz) SDL_memmove((dst), (src), (sz)) #define DRFLAC_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz)) #define DRFLAC_MALLOC(sz) SDL_malloc((sz)) #define DRFLAC_REALLOC(p, sz) SDL_realloc((p), (sz)) #define DRFLAC_FREE(p) SDL_free((p)) #include "dr_libs/dr_flac.h" typedef struct { struct mp3file_t file; drflac *dec; int play_count; bool closeio; int volume; int status; int sample_rate; int channels; SDL_AudioStream *stream; drflac_int16 *buffer; int buffer_size; int loop; bool loop_flag; Sint64 loop_start; Sint64 loop_end; Sint64 loop_len; Mix_MusicMetaTags tags; } DRFLAC_Music; static size_t DRFLAC_ReadCB(void *context, void *buf, size_t size) { DRFLAC_Music *music = (DRFLAC_Music *)context; return MP3_IOread(&music->file, buf, 1, size); } static drflac_bool32 DRFLAC_SeekCB(void *context, int offset, drflac_seek_origin origin) { DRFLAC_Music *music = (DRFLAC_Music *)context; int whence = (origin == drflac_seek_origin_start) ? SDL_IO_SEEK_SET : SDL_IO_SEEK_CUR; if (MP3_IOseek(&music->file, offset, whence) < 0) { return DRFLAC_FALSE; } return DRFLAC_TRUE; } static void DRFLAC_MetaCB(void *context, drflac_metadata *metadata) { DRFLAC_Music *music = (DRFLAC_Music *)context; if (metadata->type == DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO) { music->sample_rate = metadata->data.streaminfo.sampleRate; music->channels = metadata->data.streaminfo.channels; } else if (metadata->type == DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT) { drflac_uint32 i; char *param, *argument, *value; bool is_loop_length = false; const char *pRunningData = (const char *)metadata->data.vorbis_comment.pComments; for (i = 0; i < metadata->data.vorbis_comment.commentCount; ++i) { drflac_uint32 commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; param = (char *)SDL_malloc(commentLength + 1); if (param) { SDL_memcpy(param, pRunningData, commentLength); param[commentLength] = '\0'; argument = param; value = SDL_strchr(param, '='); if (value == NULL) { value = param + SDL_strlen(param); } else { *(value++) = '\0'; } /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from * string if it is present at position 4. */ if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) { SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4); } if (SDL_strcasecmp(argument, "LOOPSTART") == 0) music->loop_start = _Mix_ParseTime(value, music->sample_rate); else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) { music->loop_len = SDL_strtoll(value, NULL, 10); is_loop_length = true; } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) { music->loop_end = _Mix_ParseTime(value, music->sample_rate); is_loop_length = false; } else if (SDL_strcasecmp(argument, "TITLE") == 0) { meta_tags_set(&music->tags, MIX_META_TITLE, value); } else if (SDL_strcasecmp(argument, "ARTIST") == 0) { meta_tags_set(&music->tags, MIX_META_ARTIST, value); } else if (SDL_strcasecmp(argument, "ALBUM") == 0) { meta_tags_set(&music->tags, MIX_META_ALBUM, value); } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) { meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value); } SDL_free(param); } pRunningData += commentLength; } if (is_loop_length) { music->loop_end = music->loop_start + music->loop_len; } else { music->loop_len = music->loop_end - music->loop_start; } /* Ignore invalid loop tag */ if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) { music->loop_start = 0; music->loop_len = 0; music->loop_end = 0; } } } static int DRFLAC_Seek(void *context, double position); static void *DRFLAC_CreateFromIO(SDL_IOStream *src, bool closeio) { DRFLAC_Music *music; SDL_AudioSpec srcspec; music = (DRFLAC_Music *)SDL_calloc(1, sizeof(DRFLAC_Music)); if (!music) { return NULL; } music->volume = MIX_MAX_VOLUME; if (MP3_IOinit(&music->file, src) < 0) { SDL_free(music); return NULL; } meta_tags_init(&music->tags); music->dec = drflac_open_with_metadata(DRFLAC_ReadCB, DRFLAC_SeekCB, DRFLAC_MetaCB, music, NULL); if (!music->dec) { SDL_free(music); SDL_SetError("music_drflac: corrupt flac file (bad stream)."); return NULL; } /* We should have channels and sample rate set up here */ SDL_zero(srcspec); srcspec.format = SDL_AUDIO_S16; srcspec.channels = music->channels; srcspec.freq = music->sample_rate; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { drflac_close(music->dec); SDL_free(music); return NULL; } music->buffer_size = 4096/*music_spec.samples*/ * sizeof(drflac_int16) * music->channels; music->buffer = (drflac_int16*)SDL_calloc(1, music->buffer_size); if (!music->buffer) { drflac_close(music->dec); SDL_free(music); return NULL; } /* loop_start, loop_end and loop_len get set by metadata callback if tags * are present in metadata. */ if ((music->loop_end > 0) && (music->loop_end <= (Sint64)music->dec->totalPCMFrameCount) && (music->loop_start < music->loop_end)) { music->loop = 1; } music->closeio = closeio; return music; } static void DRFLAC_SetVolume(void *context, int volume) { DRFLAC_Music *music = (DRFLAC_Music *)context; music->volume = volume; } static int DRFLAC_GetVolume(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; return music->volume; } /* Starts the playback. */ static int DRFLAC_Play(void *context, int play_count) { DRFLAC_Music *music = (DRFLAC_Music *)context; music->play_count = play_count; return DRFLAC_Seek(music, 0.0); } static void DRFLAC_Stop(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; SDL_ClearAudioStream(music->stream); } static int DRFLAC_GetSome(void *context, void *data, int bytes, bool *done) { DRFLAC_Music *music = (DRFLAC_Music *)context; int filled; drflac_uint64 amount; if (music->stream) { filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } } if (!music->play_count) { /* All done */ *done = true; return 0; } if (music->loop_flag) { if (!drflac_seek_to_pcm_frame(music->dec, music->loop_start)) { SDL_SetError("drflac_seek_to_pcm_frame() failed"); return -1; } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } music->play_count = play_count; music->loop_flag = false; } } amount = drflac_read_pcm_frames_s16(music->dec, 4096/*music_spec.samples*/, music->buffer); if (amount > 0) { if (music->loop && (music->play_count != 1) && ((Sint64)music->dec->currentPCMFrame >= music->loop_end)) { amount -= (music->dec->currentPCMFrame - music->loop_end); music->loop_flag = true; } if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount * sizeof(drflac_int16) * music->channels)) { return -1; } } else { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (DRFLAC_Play(music, play_count) < 0) { return -1; } } } return 0; } static int DRFLAC_GetAudio(void *context, void *data, int bytes) { DRFLAC_Music *music = (DRFLAC_Music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, DRFLAC_GetSome); } static int DRFLAC_Seek(void *context, double position) { DRFLAC_Music *music = (DRFLAC_Music *)context; drflac_uint64 destpos = (drflac_uint64)(position * music->sample_rate); drflac_seek_to_pcm_frame(music->dec, destpos); return 0; } static double DRFLAC_Tell(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; return (double)music->dec->currentPCMFrame / music->sample_rate; } static double DRFLAC_Duration(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; drflac_uint64 samples = music->dec->totalPCMFrameCount; return (double)samples / music->sample_rate; } static double DRFLAC_LoopStart(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; if (music->loop > 0) { return (double)music->loop_start / music->sample_rate; } return -1.0; } static double DRFLAC_LoopEnd(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; if (music->loop > 0) { return (double)music->loop_end / music->sample_rate; } return -1.0; } static double DRFLAC_LoopLength(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; if (music->loop > 0) { return (double)music->loop_len / music->sample_rate; } return -1.0; } static const char* DRFLAC_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { DRFLAC_Music *music = (DRFLAC_Music *)context; return meta_tags_get(&music->tags, tag_type); } static void DRFLAC_Delete(void *context) { DRFLAC_Music *music = (DRFLAC_Music *)context; drflac_close(music->dec); meta_tags_clear(&music->tags); if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->file.src); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_DRFLAC = { "DRFLAC", MIX_MUSIC_DRFLAC, MUS_FLAC, false, false, NULL, /* Load */ NULL, /* Open */ DRFLAC_CreateFromIO, NULL, /* CreateFromFile */ DRFLAC_SetVolume, DRFLAC_GetVolume, DRFLAC_Play, NULL, /* IsPlaying */ DRFLAC_GetAudio, NULL, /* Jump */ DRFLAC_Seek, DRFLAC_Tell, DRFLAC_Duration, DRFLAC_LoopStart, DRFLAC_LoopEnd, DRFLAC_LoopLength, DRFLAC_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ DRFLAC_Stop, DRFLAC_Delete, NULL, /* Close */ NULL /* Unload */ }; #endif /* MUSIC_FLAC_DRFLAC */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_drflac.h000066400000000000000000000021661501405355700240510ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing FLAC files with dr_flac */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_DRFLAC; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_drmp3.c000066400000000000000000000205271501405355700236370ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_MP3_DRMP3 #include "music_drmp3.h" #include "mp3utils.h" #include #define DR_MP3_IMPLEMENTATION #if defined(__GNUC__) && (__GNUC__ >= 4) && \ !(defined(_WIN32) || defined(__EMX__)) #define DRMP3_API __attribute__((visibility("hidden"))) #elif defined(__APPLE__) #define DRMP3_API __private_extern__ #else #define DRMP3_API /* just in case.. */ #endif #define DR_MP3_NO_STDIO #define DRMP3_ASSERT(expression) #define DRMP3_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz)) #define DRMP3_MOVE_MEMORY(dst, src, sz) SDL_memmove((dst), (src), (sz)) #define DRMP3_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz)) #define DRMP3_MALLOC(sz) SDL_malloc((sz)) #define DRMP3_REALLOC(p, sz) SDL_realloc((p), (sz)) #define DRMP3_FREE(p) SDL_free((p)) #include "dr_libs/dr_mp3.h" typedef struct { struct mp3file_t file; drmp3 dec; int play_count; bool closeio; int volume; int status; SDL_AudioStream *stream; drmp3_int16 *buffer; int buffer_size; int channels; Mix_MusicMetaTags tags; } DRMP3_Music; static size_t DRMP3_ReadCB(void *context, void *buf, size_t size) { DRMP3_Music *music = (DRMP3_Music *)context; return MP3_IOread(&music->file, buf, 1, size); } static drmp3_bool32 DRMP3_SeekCB(void *context, int offset, drmp3_seek_origin origin) { DRMP3_Music *music = (DRMP3_Music *)context; int whence; switch (origin) { case drmp3_seek_origin_start: whence = SDL_IO_SEEK_SET; break; case drmp3_seek_origin_current: whence = SDL_IO_SEEK_CUR; break; case drmp3_seek_origin_end: whence = SDL_IO_SEEK_END; break; default: return DRMP3_FALSE; } if (MP3_IOseek(&music->file, offset, whence) < 0) { return DRMP3_FALSE; } return DRMP3_TRUE; } static drmp3_bool32 DRMP3_TellCB(void *context, drmp3_int64 *pos) { DRMP3_Music *music = (DRMP3_Music *)context; *pos = MP3_IOtell(&music->file); return (*pos < 0) ? DRMP3_FALSE : DRMP3_TRUE; } static int DRMP3_Seek(void *context, double position); static void *DRMP3_CreateFromIO(SDL_IOStream *src, bool closeio) { DRMP3_Music *music; SDL_AudioSpec file_spec; music = (DRMP3_Music *)SDL_calloc(1, sizeof(DRMP3_Music)); if (!music) { return NULL; } music->volume = MIX_MAX_VOLUME; if (MP3_IOinit(&music->file, src) < 0) { SDL_free(music); return NULL; } meta_tags_init(&music->tags); if (mp3_read_tags(&music->tags, &music->file, false) < 0) { SDL_free(music); SDL_SetError("music_drmp3: corrupt mp3 file (bad tags)."); return NULL; } MP3_IOseek(&music->file, 0, SDL_IO_SEEK_SET); if (!drmp3_init(&music->dec, DRMP3_ReadCB, DRMP3_SeekCB, DRMP3_TellCB, NULL, music, NULL)) { SDL_free(music); SDL_SetError("music_drmp3: corrupt mp3 file (bad stream)."); return NULL; } SDL_zero(file_spec); file_spec.format = SDL_AUDIO_S16; file_spec.channels = (Uint8)music->dec.channels; file_spec.freq = (int)music->dec.sampleRate; music->stream = SDL_CreateAudioStream(&file_spec, &music_spec); if (!music->stream) { drmp3_uninit(&music->dec); SDL_free(music); return NULL; } music->channels = music->dec.channels; music->buffer_size = 4096/*music_spec.samples*/ * sizeof(drmp3_int16) * music->channels; music->buffer = (drmp3_int16*)SDL_calloc(1, music->buffer_size); if (!music->buffer) { drmp3_uninit(&music->dec); SDL_free(music); return NULL; } music->closeio = closeio; return music; } static void DRMP3_SetVolume(void *context, int volume) { DRMP3_Music *music = (DRMP3_Music *)context; music->volume = volume; } static int DRMP3_GetVolume(void *context) { DRMP3_Music *music = (DRMP3_Music *)context; return music->volume; } /* Starts the playback. */ static int DRMP3_Play(void *context, int play_count) { DRMP3_Music *music = (DRMP3_Music *)context; music->play_count = play_count; return DRMP3_Seek(music, 0.0); } static void DRMP3_Stop(void *context) { DRMP3_Music *music = (DRMP3_Music *)context; SDL_ClearAudioStream(music->stream); } static int DRMP3_GetSome(void *context, void *data, int bytes, bool *done) { DRMP3_Music *music = (DRMP3_Music *)context; int filled; drmp3_uint64 amount; if (music->stream) { filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } } if (!music->play_count) { /* All done */ *done = true; return 0; } amount = drmp3_read_pcm_frames_s16(&music->dec, 4096/*music_spec.samples*/, music->buffer); if (amount > 0) { if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount * sizeof(drmp3_int16) * music->channels)) { return -1; } } else { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (DRMP3_Play(music, play_count) < 0) { return -1; } } } return 0; } static int DRMP3_GetAudio(void *context, void *data, int bytes) { DRMP3_Music *music = (DRMP3_Music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, DRMP3_GetSome); } static int DRMP3_Seek(void *context, double position) { DRMP3_Music *music = (DRMP3_Music *)context; drmp3_uint64 destpos = (drmp3_uint64)(position * music->dec.sampleRate); drmp3_seek_to_pcm_frame(&music->dec, destpos); return 0; } static double DRMP3_Tell(void *context) { DRMP3_Music *music = (DRMP3_Music *)context; return (double)music->dec.currentPCMFrame / music->dec.sampleRate; } static double DRMP3_Duration(void *context) { DRMP3_Music *music = (DRMP3_Music *)context; drmp3_uint64 samples = drmp3_get_pcm_frame_count(&music->dec); return (double)samples / music->dec.sampleRate; } static const char* DRMP3_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { DRMP3_Music *music = (DRMP3_Music *)context; return meta_tags_get(&music->tags, tag_type); } static void DRMP3_Delete(void *context) { DRMP3_Music *music = (DRMP3_Music *)context; drmp3_uninit(&music->dec); meta_tags_clear(&music->tags); if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->file.src); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_DRMP3 = { "DRMP3", MIX_MUSIC_DRMP3, MUS_MP3, false, false, NULL, /* Load */ NULL, /* Open */ DRMP3_CreateFromIO, NULL, /* CreateFromFile */ DRMP3_SetVolume, DRMP3_GetVolume, DRMP3_Play, NULL, /* IsPlaying */ DRMP3_GetAudio, NULL, /* Jump */ DRMP3_Seek, DRMP3_Tell, DRMP3_Duration, NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ DRMP3_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ DRMP3_Stop, DRMP3_Delete, NULL, /* Close */ NULL /* Unload */ }; #endif /* MUSIC_MP3_DRMP3 */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_drmp3.h000066400000000000000000000021631501405355700236400ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MP3 files with dr_mp3 */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_DRMP3; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_flac.c000066400000000000000000000674111501405355700235220ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file is used to support SDL_LoadMUS playback of FLAC files. ~ Austen Dicken (admin@cvpcs.org) */ #ifdef MUSIC_FLAC_LIBFLAC #include #include #include "music_flac.h" #include "utils.h" #include typedef struct { int loaded; void *handle; FLAC__StreamDecoder *(*FLAC__stream_decoder_new)(void); void (*FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder); FLAC__StreamDecoderInitStatus (*FLAC__stream_decoder_init_stream)( FLAC__StreamDecoder *decoder, FLAC__StreamDecoderReadCallback read_callback, FLAC__StreamDecoderSeekCallback seek_callback, FLAC__StreamDecoderTellCallback tell_callback, FLAC__StreamDecoderLengthCallback length_callback, FLAC__StreamDecoderEofCallback eof_callback, FLAC__StreamDecoderWriteCallback write_callback, FLAC__StreamDecoderMetadataCallback metadata_callback, FLAC__StreamDecoderErrorCallback error_callback, void *client_data); FLAC__StreamDecoderInitStatus (*FLAC__stream_decoder_init_ogg_stream)( FLAC__StreamDecoder *decoder, FLAC__StreamDecoderReadCallback read_callback, FLAC__StreamDecoderSeekCallback seek_callback, FLAC__StreamDecoderTellCallback tell_callback, FLAC__StreamDecoderLengthCallback length_callback, FLAC__StreamDecoderEofCallback eof_callback, FLAC__StreamDecoderWriteCallback write_callback, FLAC__StreamDecoderMetadataCallback metadata_callback, FLAC__StreamDecoderErrorCallback error_callback, void *client_data); FLAC__bool (*FLAC__stream_decoder_finish)(FLAC__StreamDecoder *decoder); FLAC__bool (*FLAC__stream_decoder_flush)(FLAC__StreamDecoder *decoder); FLAC__bool (*FLAC__stream_decoder_process_single)( FLAC__StreamDecoder *decoder); FLAC__bool (*FLAC__stream_decoder_process_until_end_of_metadata)( FLAC__StreamDecoder *decoder); FLAC__bool (*FLAC__stream_decoder_process_until_end_of_stream)( FLAC__StreamDecoder *decoder); FLAC__bool (*FLAC__stream_decoder_seek_absolute)( FLAC__StreamDecoder *decoder, FLAC__uint64 sample); FLAC__StreamDecoderState (*FLAC__stream_decoder_get_state)( const FLAC__StreamDecoder *decoder); FLAC__uint64 (*FLAC__stream_decoder_get_total_samples)( const FLAC__StreamDecoder *decoder); FLAC__bool (*FLAC__stream_decoder_set_metadata_respond)( FLAC__StreamDecoder *decoder, FLAC__MetadataType type); } flac_loader; static flac_loader flac; #ifdef FLAC_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ flac.FUNC = (SIG) SDL_LoadFunction(flac.handle, #FUNC); \ if (flac.FUNC == NULL) { SDL_UnloadObject(flac.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ flac.FUNC = FUNC; \ if (flac.FUNC == NULL) { SDL_SetError("Missing FLAC.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int FLAC_Load(void) { if (flac.loaded == 0) { #ifdef FLAC_DYNAMIC flac.handle = SDL_LoadObject(FLAC_DYNAMIC); if (flac.handle == NULL) { return -1; } #endif FUNCTION_LOADER(FLAC__stream_decoder_new, FLAC__StreamDecoder *(*)(void)) FUNCTION_LOADER(FLAC__stream_decoder_delete, void (*)(FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_init_stream, FLAC__StreamDecoderInitStatus (*)( FLAC__StreamDecoder *, FLAC__StreamDecoderReadCallback, FLAC__StreamDecoderSeekCallback, FLAC__StreamDecoderTellCallback, FLAC__StreamDecoderLengthCallback, FLAC__StreamDecoderEofCallback, FLAC__StreamDecoderWriteCallback, FLAC__StreamDecoderMetadataCallback, FLAC__StreamDecoderErrorCallback, void *)) FUNCTION_LOADER(FLAC__stream_decoder_init_ogg_stream, FLAC__StreamDecoderInitStatus (*)( FLAC__StreamDecoder *, FLAC__StreamDecoderReadCallback, FLAC__StreamDecoderSeekCallback, FLAC__StreamDecoderTellCallback, FLAC__StreamDecoderLengthCallback, FLAC__StreamDecoderEofCallback, FLAC__StreamDecoderWriteCallback, FLAC__StreamDecoderMetadataCallback, FLAC__StreamDecoderErrorCallback, void *)) FUNCTION_LOADER(FLAC__stream_decoder_finish, FLAC__bool (*)(FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_flush, FLAC__bool (*)(FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_process_single, FLAC__bool (*)(FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_process_until_end_of_metadata, FLAC__bool (*)(FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_process_until_end_of_stream, FLAC__bool (*)(FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_seek_absolute, FLAC__bool (*)(FLAC__StreamDecoder *, FLAC__uint64)) FUNCTION_LOADER(FLAC__stream_decoder_get_state, FLAC__StreamDecoderState (*)(const FLAC__StreamDecoder *decoder)) FUNCTION_LOADER(FLAC__stream_decoder_get_total_samples, FLAC__uint64 (*)(const FLAC__StreamDecoder *)) FUNCTION_LOADER(FLAC__stream_decoder_set_metadata_respond, FLAC__bool (*)(FLAC__StreamDecoder *, FLAC__MetadataType)) } ++flac.loaded; return 0; } static void FLAC_Unload(void) { if (flac.loaded == 0) { return; } if (flac.loaded == 1) { #ifdef FLAC_DYNAMIC SDL_UnloadObject(flac.handle); #endif } --flac.loaded; } typedef struct { int volume; int play_count; FLAC__StreamDecoder *flac_decoder; unsigned sample_rate; unsigned channels; unsigned bits_per_sample; SDL_IOStream *src; bool closeio; SDL_AudioStream *stream; int loop; FLAC__int64 pcm_pos; FLAC__int64 full_length; bool loop_flag; FLAC__int64 loop_start; FLAC__int64 loop_end; FLAC__int64 loop_len; Mix_MusicMetaTags tags; } FLAC_Music; static int FLAC_Seek(void *context, double position); static FLAC__StreamDecoderReadStatus flac_read_music_cb( const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) { FLAC_Music *data = (FLAC_Music*)client_data; (void)decoder; /* make sure there is something to be reading */ if (*bytes > 0) { SDL_IOStatus status; *bytes = SDL_ReadIO(data->src, buffer, *bytes); status = SDL_GetIOStatus(data->src); if (status == SDL_IO_STATUS_READY || status == SDL_IO_STATUS_NOT_READY) { return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } else if (status == SDL_IO_STATUS_EOF) { return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; } else { return FLAC__STREAM_DECODER_READ_STATUS_ABORT; } } else { return FLAC__STREAM_DECODER_READ_STATUS_ABORT; } } static FLAC__StreamDecoderSeekStatus flac_seek_music_cb( const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) { FLAC_Music *data = (FLAC_Music*)client_data; (void)decoder; if (SDL_SeekIO(data->src, (Sint64)absolute_byte_offset, SDL_IO_SEEK_SET) < 0) { return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; } else { return FLAC__STREAM_DECODER_SEEK_STATUS_OK; } } static FLAC__StreamDecoderTellStatus flac_tell_music_cb( const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) { FLAC_Music *data = (FLAC_Music*)client_data; Sint64 pos = SDL_TellIO(data->src); (void)decoder; if (pos < 0) { return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; } else { *absolute_byte_offset = (FLAC__uint64)pos; return FLAC__STREAM_DECODER_TELL_STATUS_OK; } } static FLAC__StreamDecoderLengthStatus flac_length_music_cb( const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) { FLAC_Music *data = (FLAC_Music*)client_data; Sint64 pos = SDL_TellIO(data->src); Sint64 length = SDL_SeekIO(data->src, 0, SDL_IO_SEEK_END); (void)decoder; if (SDL_SeekIO(data->src, pos, SDL_IO_SEEK_SET) != pos || length < 0) { /* there was an error attempting to return the stream to the original * position, or the length was invalid. */ return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; } else { *stream_length = (FLAC__uint64)length; return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; } } static FLAC__bool flac_eof_music_cb( const FLAC__StreamDecoder *decoder, void *client_data) { FLAC_Music *data = (FLAC_Music*)client_data; Sint64 pos = SDL_TellIO(data->src); Sint64 end = SDL_SeekIO(data->src, 0, SDL_IO_SEEK_END); (void)decoder; /* was the original position equal to the end (a.k.a. the seek didn't move)? */ if (pos == end) { /* must be EOF */ return true; } else { /* not EOF, return to the original position */ SDL_SeekIO(data->src, pos, SDL_IO_SEEK_SET); return false; } } static FLAC__StreamDecoderWriteStatus flac_write_music_cb( const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) { FLAC_Music *music = (FLAC_Music *)client_data; Sint16 *data; unsigned int i, j, channels; int shift_amount = 0, amount; (void)decoder; if (!music->stream) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } switch (music->bits_per_sample) { case 16: shift_amount = 0; break; case 20: shift_amount = 4; break; case 24: shift_amount = 8; break; default: SDL_SetError("FLAC decoder doesn't support %d bits_per_sample", music->bits_per_sample); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } if (music->channels == 3) { /* We'll just drop the center channel for now */ channels = 2; } else { channels = music->channels; } data = SDL_stack_alloc(Sint16, (frame->header.blocksize * channels)); if (!data) { SDL_SetError("Couldn't allocate %d bytes stack memory", (int)(frame->header.blocksize * channels * sizeof(*data))); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } if (music->channels == 3) { Sint16 *dst = data; for (i = 0; i < frame->header.blocksize; ++i) { Sint16 FL = (Sint16)(buffer[0][i] >> shift_amount); Sint16 FR = (Sint16)(buffer[1][i] >> shift_amount); Sint16 FCmix = (Sint16)((buffer[2][i] >> shift_amount) * 0.5f); int sample; sample = (FL + FCmix); if (sample > SDL_MAX_SINT16) { *dst = SDL_MAX_SINT16; } else if (sample < SDL_MIN_SINT16) { *dst = SDL_MIN_SINT16; } else { *dst = (Sint16)sample; } ++dst; sample = (FR + FCmix); if (sample > SDL_MAX_SINT16) { *dst = SDL_MAX_SINT16; } else if (sample < SDL_MIN_SINT16) { *dst = SDL_MIN_SINT16; } else { *dst = (Sint16)sample; } ++dst; } } else { for (i = 0; i < channels; ++i) { Sint16 *dst = data + i; for (j = 0; j < frame->header.blocksize; ++j) { *dst = (Sint16)(buffer[i][j] >> shift_amount); dst += channels; } } } amount = (int)(frame->header.blocksize * channels * sizeof(*data)); music->pcm_pos += (FLAC__int64) frame->header.blocksize; if (music->loop && (music->play_count != 1) && (music->pcm_pos >= music->loop_end)) { amount -= (music->pcm_pos - music->loop_end) * channels * (int)sizeof(*data); music->loop_flag = true; } SDL_PutAudioStreamData(music->stream, data, amount); SDL_stack_free(data); return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } static void flac_metadata_music_cb( const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) { FLAC_Music *music = (FLAC_Music *)client_data; const FLAC__StreamMetadata_VorbisComment *vc; int channels; unsigned rate; char *param, *argument, *value; bool is_loop_length = false; (void)decoder; if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { SDL_AudioSpec srcspec; music->sample_rate = metadata->data.stream_info.sample_rate; music->channels = metadata->data.stream_info.channels; music->bits_per_sample = metadata->data.stream_info.bits_per_sample; /*printf("FLAC: Sample rate = %d, channels = %d, bits_per_sample = %d\n", music->sample_rate, music->channels, music->bits_per_sample);*/ /* SDL's channel mapping and FLAC channel mapping are the same, except for 3 channels: SDL is FL FR LFE and FLAC is FL FR FC */ if (music->channels == 3) { channels = 2; } else { channels = (int)music->channels; } /* We check for NULL stream later when we get data */ SDL_assert(!music->stream); SDL_zero(srcspec); srcspec.format = SDL_AUDIO_S16; srcspec.channels = channels; srcspec.freq = (int)music->sample_rate; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); } else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { FLAC__uint32 i; vc = &metadata->data.vorbis_comment; rate = music->sample_rate; for (i = 0; i < vc->num_comments; ++i) { param = SDL_strdup((const char *) vc->comments[i].entry); argument = param; value = SDL_strchr(param, '='); if (value == NULL) { value = param + SDL_strlen(param); } else { *(value++) = '\0'; } /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from * string if it is present at position 4. */ if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) { SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4); } if (SDL_strcasecmp(argument, "LOOPSTART") == 0) music->loop_start = _Mix_ParseTime(value, rate); else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) { music->loop_len = SDL_strtoll(value, NULL, 10); is_loop_length = true; } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) { music->loop_end = _Mix_ParseTime(value, rate); is_loop_length = false; } else if (SDL_strcasecmp(argument, "TITLE") == 0) { meta_tags_set(&music->tags, MIX_META_TITLE, value); } else if (SDL_strcasecmp(argument, "ARTIST") == 0) { meta_tags_set(&music->tags, MIX_META_ARTIST, value); } else if (SDL_strcasecmp(argument, "ALBUM") == 0) { meta_tags_set(&music->tags, MIX_META_ALBUM, value); } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) { meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value); } SDL_free(param); } if (is_loop_length) { music->loop_end = music->loop_start + music->loop_len; } else { music->loop_len = music->loop_end - music->loop_start; } /* Ignore invalid loop tag */ if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) { music->loop_start = 0; music->loop_len = 0; music->loop_end = 0; } } } static void flac_error_music_cb( const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) { (void)decoder; (void)client_data; /* print an SDL error based on the error status */ switch (status) { case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: SDL_SetError("Error processing the FLAC file [LOST_SYNC]."); break; case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: SDL_SetError("Error processing the FLAC file [BAD_HEADER]."); break; case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: SDL_SetError("Error processing the FLAC file [CRC_MISMATCH]."); break; case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM: SDL_SetError("Error processing the FLAC file [UNPARSEABLE]."); break; default: SDL_SetError("Error processing the FLAC file [UNKNOWN]."); break; } } /* Load an FLAC stream from an SDL_IOStream object */ static void *FLAC_CreateFromIO(SDL_IOStream *src, bool closeio) { FLAC_Music *music; int init_stage = 0; int was_error = 1; FLAC__int64 full_length; int is_ogg_flac; Uint8 magic[4]; if (SDL_ReadIO(src, magic, 4) != 4) { SDL_SetError("Couldn't read first 4 bytes of audio data"); return NULL; } SDL_SeekIO(src, -4, SDL_IO_SEEK_CUR); is_ogg_flac = (SDL_memcmp(magic, "OggS", 4) == 0); music = (FLAC_Music *)SDL_calloc(1, sizeof(*music)); if (!music) { return NULL; } music->src = src; music->volume = MIX_MAX_VOLUME; music->flac_decoder = flac.FLAC__stream_decoder_new(); if (music->flac_decoder) { FLAC__StreamDecoderInitStatus ret; init_stage++; /* stage 1! */ flac.FLAC__stream_decoder_set_metadata_respond(music->flac_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); if (is_ogg_flac) { ret = flac.FLAC__stream_decoder_init_ogg_stream( music->flac_decoder, flac_read_music_cb, flac_seek_music_cb, flac_tell_music_cb, flac_length_music_cb, flac_eof_music_cb, flac_write_music_cb, flac_metadata_music_cb, flac_error_music_cb, music); } else { ret = flac.FLAC__stream_decoder_init_stream( music->flac_decoder, flac_read_music_cb, flac_seek_music_cb, flac_tell_music_cb, flac_length_music_cb, flac_eof_music_cb, flac_write_music_cb, flac_metadata_music_cb, flac_error_music_cb, music); } if (ret == FLAC__STREAM_DECODER_INIT_STATUS_OK) { init_stage++; /* stage 2! */ if (flac.FLAC__stream_decoder_process_until_end_of_metadata(music->flac_decoder)) { was_error = 0; } else { SDL_SetError("FLAC__stream_decoder_process_until_end_of_metadata() failed"); } } else { SDL_SetError("FLAC__stream_decoder_init_stream() failed"); } } else { SDL_SetError("FLAC__stream_decoder_new() failed"); } if (was_error) { switch (init_stage) { case 2: flac.FLAC__stream_decoder_finish(music->flac_decoder); /* fallthrough */ case 1: flac.FLAC__stream_decoder_delete(music->flac_decoder); /* fallthrough */ case 0: SDL_free(music); break; } return NULL; } /* loop_start, loop_end and loop_len get set by metadata callback if tags * are present in metadata. */ full_length = (FLAC__int64) flac.FLAC__stream_decoder_get_total_samples(music->flac_decoder); if ((music->loop_end > 0) && (music->loop_end <= full_length) && (music->loop_start < music->loop_end)) { music->loop = 1; } music->full_length = full_length; music->closeio = closeio; return music; } static const char* FLAC_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { FLAC_Music *music = (FLAC_Music *)context; return meta_tags_get(&music->tags, tag_type); } /* Set the volume for an FLAC stream */ static void FLAC_SetVolume(void *context, int volume) { FLAC_Music *music = (FLAC_Music *)context; music->volume = volume; } /* Get the volume for an FLAC stream */ static int FLAC_GetVolume(void *context) { FLAC_Music *music = (FLAC_Music *)context; return music->volume; } /* Start playback of a given FLAC stream */ static int FLAC_Play(void *context, int play_count) { FLAC_Music *music = (FLAC_Music *)context; music->play_count = play_count; return FLAC_Seek(music, 0.0); } static void FLAC_Stop(void *context) { FLAC_Music *music = (FLAC_Music *)context; SDL_ClearAudioStream(music->stream); } /* Read some FLAC stream data and convert it for output */ static int FLAC_GetSome(void *context, void *data, int bytes, bool *done) { FLAC_Music *music = (FLAC_Music *)context; int filled; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (!music->play_count) { /* All done */ *done = true; return 0; } if (!flac.FLAC__stream_decoder_process_single(music->flac_decoder)) { SDL_SetError("FLAC__stream_decoder_process_single() failed"); return -1; } if (music->loop_flag) { music->pcm_pos = music->loop_start; if (flac.FLAC__stream_decoder_seek_absolute(music->flac_decoder, (FLAC__uint64)music->loop_start) == FLAC__STREAM_DECODER_SEEK_ERROR) { flac.FLAC__stream_decoder_flush(music->flac_decoder); SDL_SetError("FLAC__stream_decoder_seek_absolute() failed"); return -1; } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } music->play_count = play_count; music->loop_flag = false; } } if (flac.FLAC__stream_decoder_get_state(music->flac_decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (FLAC_Play(music, play_count) < 0) { return -1; } } } return 0; } /* Play some of a stream previously started with FLAC_play() */ static int FLAC_GetAudio(void *context, void *data, int bytes) { FLAC_Music *music = (FLAC_Music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, FLAC_GetSome); } /* Jump (seek) to a given position (position is in seconds) */ static int FLAC_Seek(void *context, double position) { FLAC_Music *music = (FLAC_Music *)context; FLAC__uint64 seek_sample = (FLAC__uint64) (music->sample_rate * position); SDL_ClearAudioStream(music->stream); music->pcm_pos = (FLAC__int64) seek_sample; if (!flac.FLAC__stream_decoder_seek_absolute(music->flac_decoder, seek_sample)) { if (flac.FLAC__stream_decoder_get_state(music->flac_decoder) == FLAC__STREAM_DECODER_SEEK_ERROR) { flac.FLAC__stream_decoder_flush(music->flac_decoder); } SDL_SetError("Seeking of FLAC stream failed: libFLAC seek failed."); return -1; } return 0; } static double FLAC_Tell(void *context) { FLAC_Music *music = (FLAC_Music *)context; return (double)music->pcm_pos / music->sample_rate; } /* Return music duration in seconds */ static double FLAC_Duration(void *context) { FLAC_Music *music = (FLAC_Music *)context; return (double)music->full_length / music->sample_rate; } static double FLAC_LoopStart(void *music_p) { FLAC_Music *music = (FLAC_Music *)music_p; if (music->loop > 0) { return (double)music->loop_start / music->sample_rate; } return -1.0; } static double FLAC_LoopEnd(void *music_p) { FLAC_Music *music = (FLAC_Music *)music_p; if (music->loop > 0) { return (double)music->loop_end / music->sample_rate; } return -1.0; } static double FLAC_LoopLength(void *music_p) { FLAC_Music *music = (FLAC_Music *)music_p; if (music->loop > 0) { return (double)music->loop_len / music->sample_rate; } return -1.0; } /* Close the given FLAC_Music object */ static void FLAC_Delete(void *context) { FLAC_Music *music = (FLAC_Music *)context; if (music) { meta_tags_clear(&music->tags); if (music->flac_decoder) { flac.FLAC__stream_decoder_finish(music->flac_decoder); flac.FLAC__stream_decoder_delete(music->flac_decoder); } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->closeio) { SDL_CloseIO(music->src); } SDL_free(music); } } Mix_MusicInterface Mix_MusicInterface_FLAC = { "FLAC", MIX_MUSIC_FLAC, MUS_FLAC, false, false, FLAC_Load, NULL, /* Open */ FLAC_CreateFromIO, NULL, /* CreateFromFile */ FLAC_SetVolume, FLAC_GetVolume, FLAC_Play, NULL, /* IsPlaying */ FLAC_GetAudio, NULL, /* Jump */ FLAC_Seek, FLAC_Tell, FLAC_Duration, FLAC_LoopStart, FLAC_LoopEnd, FLAC_LoopLength, FLAC_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ FLAC_Stop, /* Stop */ FLAC_Delete, NULL, /* Close */ FLAC_Unload }; #endif /* MUSIC_FLAC_LIBFLAC */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_flac.h000066400000000000000000000020721501405355700235170ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_FLAC; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_fluidsynth.c000066400000000000000000000307321501405355700250020ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. James Le Cuirot chewi@aura-online.co.uk */ #ifdef MUSIC_MID_FLUIDSYNTH #include #include "music_fluidsynth.h" #include typedef struct { int loaded; void *handle; #if (FLUIDSYNTH_VERSION_MAJOR >= 2) void (*delete_fluid_player)(fluid_player_t*); void (*delete_fluid_synth)(fluid_synth_t*); int (*fluid_player_seek)(fluid_player_t *, int); #else int (*delete_fluid_player)(fluid_player_t*); int (*delete_fluid_synth)(fluid_synth_t*); #endif void (*delete_fluid_settings)(fluid_settings_t*); int (*fluid_player_add)(fluid_player_t*, const char*); int (*fluid_player_add_mem)(fluid_player_t*, const void*, size_t); int (*fluid_player_get_status)(fluid_player_t*); int (*fluid_player_play)(fluid_player_t*); int (*fluid_player_set_loop)(fluid_player_t*, int); int (*fluid_player_stop)(fluid_player_t*); int (*fluid_settings_setnum)(fluid_settings_t*, const char*, double); int (*fluid_settings_getnum)(fluid_settings_t*, const char*, double*); fluid_settings_t* (*fluid_synth_get_settings)(fluid_synth_t*); void (*fluid_synth_set_gain)(fluid_synth_t*, float); int (*fluid_synth_sfload)(fluid_synth_t*, const char*, int); int (*fluid_synth_write_s16)(fluid_synth_t*, int, void*, int, int, void*, int, int); int (*fluid_synth_write_float)(fluid_synth_t*, int, void*, int, int, void*, int, int); fluid_player_t* (*new_fluid_player)(fluid_synth_t*); fluid_settings_t* (*new_fluid_settings)(void); fluid_synth_t* (*new_fluid_synth)(fluid_settings_t*); } fluidsynth_loader; static fluidsynth_loader fluidsynth; #ifdef FLUIDSYNTH_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ fluidsynth.FUNC = (SIG) SDL_LoadFunction(fluidsynth.handle, #FUNC); \ if (fluidsynth.FUNC == NULL) { SDL_UnloadObject(fluidsynth.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ fluidsynth.FUNC = FUNC; #endif static int FLUIDSYNTH_Load() { if (fluidsynth.loaded == 0) { #ifdef FLUIDSYNTH_DYNAMIC fluidsynth.handle = SDL_LoadObject(FLUIDSYNTH_DYNAMIC); if (fluidsynth.handle == NULL) { return -1; } #endif #if (FLUIDSYNTH_VERSION_MAJOR >= 2) FUNCTION_LOADER(delete_fluid_player, void (*)(fluid_player_t*)) FUNCTION_LOADER(delete_fluid_synth, void (*)(fluid_synth_t*)) FUNCTION_LOADER(fluid_player_seek, int (*)(fluid_player_t *, int)) #else FUNCTION_LOADER(delete_fluid_player, int (*)(fluid_player_t*)) FUNCTION_LOADER(delete_fluid_synth, int (*)(fluid_synth_t*)) #endif FUNCTION_LOADER(delete_fluid_settings, void (*)(fluid_settings_t*)) FUNCTION_LOADER(fluid_player_add, int (*)(fluid_player_t*, const char*)) FUNCTION_LOADER(fluid_player_add_mem, int (*)(fluid_player_t*, const void*, size_t)) FUNCTION_LOADER(fluid_player_get_status, int (*)(fluid_player_t*)) FUNCTION_LOADER(fluid_player_play, int (*)(fluid_player_t*)) FUNCTION_LOADER(fluid_player_set_loop, int (*)(fluid_player_t*, int)) FUNCTION_LOADER(fluid_player_stop, int (*)(fluid_player_t*)) FUNCTION_LOADER(fluid_settings_setnum, int (*)(fluid_settings_t*, const char*, double)) FUNCTION_LOADER(fluid_settings_getnum, int (*)(fluid_settings_t*, const char*, double*)) FUNCTION_LOADER(fluid_synth_get_settings, fluid_settings_t* (*)(fluid_synth_t*)) FUNCTION_LOADER(fluid_synth_set_gain, void (*)(fluid_synth_t*, float)) FUNCTION_LOADER(fluid_synth_sfload, int(*)(fluid_synth_t*, const char*, int)) FUNCTION_LOADER(fluid_synth_write_s16, int(*)(fluid_synth_t*, int, void*, int, int, void*, int, int)) FUNCTION_LOADER(fluid_synth_write_float, int(*)(fluid_synth_t*, int, void*, int, int, void*, int, int)) FUNCTION_LOADER(new_fluid_player, fluid_player_t* (*)(fluid_synth_t*)) FUNCTION_LOADER(new_fluid_settings, fluid_settings_t* (*)(void)) FUNCTION_LOADER(new_fluid_synth, fluid_synth_t* (*)(fluid_settings_t*)) } ++fluidsynth.loaded; return 0; } static void FLUIDSYNTH_Unload() { if (fluidsynth.loaded == 0) { return; } if (fluidsynth.loaded == 1) { #ifdef FLUIDSYNTH_DYNAMIC SDL_UnloadObject(fluidsynth.handle); #endif } --fluidsynth.loaded; } typedef struct { fluid_synth_t *synth; fluid_settings_t *settings; fluid_player_t *player; int (*synth_write)(fluid_synth_t*, int, void*, int, int, void*, int, int); SDL_AudioStream *stream; void *buffer; int buffer_size; int volume; bool is_paused; } FLUIDSYNTH_Music; static void FLUIDSYNTH_Delete(void *context); static bool SDLCALL fluidsynth_check_soundfont(const char *path, void *data) { SDL_IOStream *io = SDL_IOFromFile(path, "rb"); (void)data; if (io) { SDL_CloseIO(io); return true; } else { SDL_SetError("Failed to access the SoundFont %s", path); return false; } } static bool SDLCALL fluidsynth_load_soundfont(const char *path, void *data) { /* If this fails, it's too late to try Timidity so pray that at least one works. */ fluidsynth.fluid_synth_sfload((fluid_synth_t*) data, path, 1); return true; } static int FLUIDSYNTH_Open(const SDL_AudioSpec *spec) { (void)spec; if (!Mix_EachSoundFont(fluidsynth_check_soundfont, NULL)) { return -1; } return 0; } static FLUIDSYNTH_Music *FLUIDSYNTH_LoadMusic(void *data) { SDL_AudioSpec srcspec; SDL_IOStream *src = (SDL_IOStream *)data; FLUIDSYNTH_Music *music; double samplerate; /* as set by the lib. */ const Uint8 channels = 2; int src_format = SDL_AUDIO_S16; void *io_mem; size_t io_size; int ret; if (!(music = SDL_calloc(1, sizeof(FLUIDSYNTH_Music)))) { return NULL; } music->volume = MIX_MAX_VOLUME; music->buffer_size = 4096/*music_spec.samples*/ * sizeof(Sint16) * channels; music->synth_write = fluidsynth.fluid_synth_write_s16; if (music_spec.format & 0x0020) { /* 32 bit. */ src_format = SDL_AUDIO_F32; music->buffer_size <<= 1; music->synth_write = fluidsynth.fluid_synth_write_float; } if (!(music->buffer = SDL_malloc((size_t)music->buffer_size))) { goto fail; } if (!(music->settings = fluidsynth.new_fluid_settings())) { SDL_SetError("Failed to create FluidSynth settings"); goto fail; } fluidsynth.fluid_settings_setnum(music->settings, "synth.sample-rate", (double) music_spec.freq); fluidsynth.fluid_settings_getnum(music->settings, "synth.sample-rate", &samplerate); if (!(music->synth = fluidsynth.new_fluid_synth(music->settings))) { SDL_SetError("Failed to create FluidSynth synthesizer"); goto fail; } if (!Mix_EachSoundFont(fluidsynth_load_soundfont, music->synth)) { goto fail; } if (!(music->player = fluidsynth.new_fluid_player(music->synth))) { SDL_SetError("Failed to create FluidSynth player"); goto fail; } io_mem = SDL_LoadFile_IO(src, &io_size, false); if (!io_mem) { goto fail; } ret = fluidsynth.fluid_player_add_mem(music->player, io_mem, io_size); SDL_free(io_mem); if (ret != FLUID_OK) { SDL_SetError("FluidSynth failed to load in-memory song"); goto fail; } SDL_zero(srcspec); srcspec.format = src_format; srcspec.channels = channels; srcspec.freq = (int) samplerate; if (!(music->stream = SDL_CreateAudioStream(&srcspec, &music_spec))) { goto fail; } return music; fail: FLUIDSYNTH_Delete(music); return NULL; } static void *FLUIDSYNTH_CreateFromIO(SDL_IOStream *src, bool closeio) { FLUIDSYNTH_Music *music; music = FLUIDSYNTH_LoadMusic(src); if (music && closeio) { SDL_CloseIO(src); } return music; } static void FLUIDSYNTH_SetVolume(void *context, int volume) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; /* FluidSynth's default gain is 0.2. Make 1.0 the maximum gain value to avoid sound overload. */ music->volume = volume; fluidsynth.fluid_synth_set_gain(music->synth, volume * 1.0f / MIX_MAX_VOLUME); } static int FLUIDSYNTH_GetVolume(void *context) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; return music->volume; } static int FLUIDSYNTH_Play(void *context, int play_count) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; fluidsynth.fluid_player_set_loop(music->player, play_count); #if (FLUIDSYNTH_VERSION_MAJOR >= 2) fluidsynth.fluid_player_seek(music->player, 0); #endif fluidsynth.fluid_player_play(music->player); music->is_paused = false; return 0; } static void FLUIDSYNTH_Resume(void *context) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; fluidsynth.fluid_player_play(music->player); music->is_paused = false; } static bool FLUIDSYNTH_IsPlaying(void *context) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; return music->is_paused || fluidsynth.fluid_player_get_status(music->player) == FLUID_PLAYER_PLAYING ? true : false; } static int FLUIDSYNTH_GetSome(void *context, void *data, int bytes, bool *done) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; int filled; (void)done; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (music->synth_write(music->synth, 4096/*music_spec.samples*/, music->buffer, 0, 2, music->buffer, 1, 2) != FLUID_OK) { SDL_SetError("Error generating FluidSynth audio"); return -1; } if (!SDL_PutAudioStreamData(music->stream, music->buffer, music->buffer_size)) { return -1; } return 0; } static int FLUIDSYNTH_GetAudio(void *context, void *data, int bytes) { return music_pcm_getaudio(context, data, bytes, MIX_MAX_VOLUME, FLUIDSYNTH_GetSome); } static void FLUIDSYNTH_Stop(void *context) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; fluidsynth.fluid_player_stop(music->player); #if (FLUIDSYNTH_VERSION_MAJOR >= 2) fluidsynth.fluid_player_seek(music->player, 0); #endif music->is_paused = false; } static void FLUIDSYNTH_Pause(void *context) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; fluidsynth.fluid_player_stop(music->player); music->is_paused = true; } static void FLUIDSYNTH_Delete(void *context) { FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context; if (music->player) { fluidsynth.delete_fluid_player(music->player); } if (music->synth) { fluidsynth.delete_fluid_synth(music->synth); } if (music->settings) { fluidsynth.delete_fluid_settings(music->settings); } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_FLUIDSYNTH = { "FLUIDSYNTH", MIX_MUSIC_FLUIDSYNTH, MUS_MID, false, false, FLUIDSYNTH_Load, FLUIDSYNTH_Open, FLUIDSYNTH_CreateFromIO, NULL, /* CreateFromFile */ FLUIDSYNTH_SetVolume, FLUIDSYNTH_GetVolume, FLUIDSYNTH_Play, FLUIDSYNTH_IsPlaying, FLUIDSYNTH_GetAudio, NULL, /* Jump */ NULL, /* Seek */ NULL, /* Tell */ NULL, /* Duration */ NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ NULL, /* GetMetaTag */ NULL, /* GetNumTracks */ NULL, /* StartTrack */ FLUIDSYNTH_Pause, FLUIDSYNTH_Resume, FLUIDSYNTH_Stop, FLUIDSYNTH_Delete, NULL, /* Close */ FLUIDSYNTH_Unload }; #endif /* MUSIC_MID_FLUIDSYNTH */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_fluidsynth.h000066400000000000000000000022521501405355700250030ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. James Le Cuirot chewi@aura-online.co.uk */ /* This file supports playing MIDI files with FluidSynth */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_FLUIDSYNTH; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_gme.c000066400000000000000000000311731501405355700233610ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_GME #include #include "music_gme.h" #include typedef struct { int loaded; void *handle; gme_err_t (*gme_open_data)(void const* data, long size, Music_Emu** out, int sample_rate); int (*gme_track_count)(Music_Emu const*); gme_err_t (*gme_start_track)(Music_Emu*, int index); int (*gme_track_ended)(Music_Emu const*); void (*gme_set_tempo)(Music_Emu*, double tempo); int (*gme_voice_count)(Music_Emu const*); void (*gme_mute_voice)(Music_Emu*, int index, int mute); void (*gme_set_fade)(Music_Emu*, int start_msec); void (*gme_set_autoload_playback_limit)(Music_Emu*, int do_autoload_limit); gme_err_t (*gme_track_info)(Music_Emu const*, gme_info_t** out, int track); void (*gme_free_info)(gme_info_t*); gme_err_t (*gme_seek)(Music_Emu*, int msec); int (*gme_tell)(Music_Emu const*); gme_err_t (*gme_play)(Music_Emu*, int count, short out[]); void (*gme_delete)(Music_Emu*); } gme_loader; static gme_loader gme; #ifdef GME_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ gme.FUNC = (SIG) SDL_LoadFunction(gme.handle, #FUNC); \ if (gme.FUNC == NULL) { SDL_UnloadObject(gme.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ gme.FUNC = FUNC; \ if (gme.FUNC == NULL) { SDL_SetError("Missing gme.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int GME_Load(void) { if (gme.loaded == 0) { #ifdef GME_DYNAMIC gme.handle = SDL_LoadObject(GME_DYNAMIC); if (gme.handle == NULL) { return -1; } #endif FUNCTION_LOADER(gme_open_data, gme_err_t (*)(void const*,long,Music_Emu**,int)) FUNCTION_LOADER(gme_track_count, int (*)(Music_Emu const*)) FUNCTION_LOADER(gme_start_track, gme_err_t (*)( Music_Emu*,int)) FUNCTION_LOADER(gme_track_ended, int (*)( Music_Emu const*)) FUNCTION_LOADER(gme_set_tempo, void (*)(Music_Emu*,double)) FUNCTION_LOADER(gme_voice_count, int (*)(Music_Emu const*)) FUNCTION_LOADER(gme_mute_voice, void (*)(Music_Emu*,int,int)) FUNCTION_LOADER(gme_set_fade, void (*)(Music_Emu*,int)) FUNCTION_LOADER(gme_track_info, gme_err_t (*)(Music_Emu const*, gme_info_t**, int)) FUNCTION_LOADER(gme_free_info, void (*)(gme_info_t*)) FUNCTION_LOADER(gme_seek, gme_err_t (*)(Music_Emu*,int)) FUNCTION_LOADER(gme_tell, int (*)(Music_Emu const*)) FUNCTION_LOADER(gme_play, gme_err_t (*)(Music_Emu*, int, short[])) FUNCTION_LOADER(gme_delete, void (*)(Music_Emu*)) #if defined(GME_DYNAMIC) gme.gme_set_autoload_playback_limit = (void (*)(Music_Emu*,int)) SDL_LoadFunction(gme.handle, "gme_set_autoload_playback_limit"); if (!gme.gme_set_autoload_playback_limit) { SDL_ClearError(); /* gme_set_autoload_playback_limit is optional. */ } #elif (GME_VERSION >= 0x000603) gme.gme_set_autoload_playback_limit = gme_set_autoload_playback_limit; #else gme.gme_set_autoload_playback_limit = NULL; #endif } ++gme.loaded; return 0; } static void GME_Unload(void) { if (gme.loaded == 0) { return; } if (gme.loaded == 1) { #ifdef GME_DYNAMIC SDL_UnloadObject(gme.handle); #endif } --gme.loaded; } /* This file supports Game Music Emulator music streams */ typedef struct { int play_count; Music_Emu* game_emu; bool closeio; bool has_track_length; int track_length; int intro_length; int loop_length; int volume; double tempo; double gain; SDL_AudioStream *stream; void *buffer; size_t buffer_size; Mix_MusicMetaTags tags; } GME_Music; static void GME_Delete(void *context); /* Set the volume for a GME stream */ static void GME_SetVolume(void *music_p, int volume) { GME_Music *music = (GME_Music*)music_p; double v = SDL_floor(((double)volume * music->gain) + 0.5); music->volume = (int)v; } /* Get the volume for a GME stream */ static int GME_GetVolume(void *music_p) { GME_Music *music = (GME_Music*)music_p; double v = SDL_floor(((double)(music->volume) / music->gain) + 0.5); return (int)v; } static int initialize_from_track_info(GME_Music *music, int track) { gme_info_t *musInfo; bool has_loop_length = true; const char *err; err = gme.gme_track_info(music->game_emu, &musInfo, track); if (err != 0) { SDL_SetError("GME: %s", err); return -1; } music->track_length = musInfo->length; music->intro_length = musInfo->intro_length; music->loop_length = musInfo->loop_length; music->has_track_length = true; if (music->track_length <= 0 ) { music->track_length = (int)(2.5 * 60 * 1000); music->has_track_length = false; } if (music->intro_length < 0 ) { music->intro_length = 0; } if (music->loop_length <= 0 ) { if (music->track_length > 0) { music->loop_length = music->track_length; } else { music->loop_length = (int)(2.5 * 60 * 1000); } has_loop_length = false; } if (!music->has_track_length && has_loop_length) { music->track_length = music->intro_length + music->loop_length; music->has_track_length = true; } meta_tags_set(&music->tags, MIX_META_TITLE, musInfo->song); meta_tags_set(&music->tags, MIX_META_ARTIST, musInfo->author); meta_tags_set(&music->tags, MIX_META_ALBUM, musInfo->game); meta_tags_set(&music->tags, MIX_META_COPYRIGHT, musInfo->copyright); gme.gme_free_info(musInfo); return 0; } static void *GME_CreateFromIO(struct SDL_IOStream *src, bool closeio) { SDL_AudioSpec srcspec; void *mem = 0; size_t size; GME_Music *music; const char *err; if (src == NULL) { SDL_SetError("GME: Empty source given"); return NULL; } music = (GME_Music *)SDL_calloc(1, sizeof(GME_Music)); music->tempo = 1.0; music->gain = 1.0; SDL_zero(srcspec); srcspec.format = SDL_AUDIO_S16; srcspec.channels = 2; srcspec.freq = music_spec.freq; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { GME_Delete(music); return NULL; } music->buffer_size = 4096/*music_spec.samples*/ * sizeof(Sint16) * 2/*channels*/ * music_spec.channels; music->buffer = SDL_malloc(music->buffer_size); if (!music->buffer) { GME_Delete(music); return NULL; } SDL_SeekIO(src, 0, SDL_IO_SEEK_SET); mem = SDL_LoadFile_IO(src, &size, false); if (mem) { err = gme.gme_open_data(mem, (long)size, &music->game_emu, music_spec.freq); SDL_free(mem); if (err != 0) { GME_Delete(music); SDL_SetError("GME: %s", err); return NULL; } } else { GME_Delete(music); return NULL; } /* Set this flag BEFORE calling the gme_start_track() to fix an inability to loop forever */ if (gme.gme_set_autoload_playback_limit) { gme.gme_set_autoload_playback_limit(music->game_emu, 0); } err = gme.gme_start_track(music->game_emu, 0); if (err != 0) { GME_Delete(music); SDL_SetError("GME: %s", err); return NULL; } gme.gme_set_tempo(music->game_emu, music->tempo); music->volume = MIX_MAX_VOLUME; meta_tags_init(&music->tags); if (initialize_from_track_info(music, 0) < 0) { GME_Delete(music); return NULL; } music->closeio = closeio; return music; } /* Start playback of a given Game Music Emulators stream */ static int GME_Play(void *music_p, int play_count) { GME_Music *music = (GME_Music*)music_p; int fade_start; if (music) { SDL_ClearAudioStream(music->stream); music->play_count = play_count; fade_start = play_count > 0 ? music->intro_length + (music->loop_length * play_count) : -1; /* libgme >= 0.6.4 has gme_set_fade_msecs(), * but gme_set_fade() sets msecs to 8000 by * default and we are OK with that. */ gme.gme_set_fade(music->game_emu, fade_start); gme.gme_seek(music->game_emu, 0); } return 0; } static int GME_GetSome(void *context, void *data, int bytes, bool *done) { GME_Music *music = (GME_Music*)context; int filled; const char *err = NULL; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (gme.gme_track_ended(music->game_emu)) { /* All done */ *done = true; return 0; } err = gme.gme_play(music->game_emu, (int)(music->buffer_size / 2), (short*)music->buffer); if (err != NULL) { SDL_SetError("GME: %s", err); return 0; } if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)music->buffer_size)) { return -1; } return 0; } /* Play some of a stream previously started with GME_Play() */ static int GME_PlayAudio(void *music_p, void *data, int bytes) { GME_Music *music = (GME_Music*)music_p; return music_pcm_getaudio(music_p, data, bytes, music->volume, GME_GetSome); } /* Close the given Game Music Emulators stream */ static void GME_Delete(void *context) { GME_Music *music = (GME_Music*)context; if (music) { meta_tags_clear(&music->tags); if (music->game_emu && music->closeio) { gme.gme_delete(music->game_emu); music->game_emu = NULL; } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } SDL_free(music); } } // TODO: this should accept a track number, not assume the current track! static const char* GME_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { GME_Music *music = (GME_Music *)context; return meta_tags_get(&music->tags, tag_type); } /* Jump (seek) to a given position (time is in seconds) */ static int GME_Seek(void *music_p, double time) { GME_Music *music = (GME_Music*)music_p; gme.gme_seek(music->game_emu, (int)(SDL_floor((time * 1000.0) + 0.5))); return 0; } static double GME_Tell(void *music_p) { GME_Music *music = (GME_Music*)music_p; return (double)(gme.gme_tell(music->game_emu)) / 1000.0; } static double GME_Duration(void *music_p) { GME_Music *music = (GME_Music*)music_p; if (music->has_track_length) { return (double)(music->track_length) / 1000.0; } return -1.0; } static int GME_GetNumTracks(void *music_p) { GME_Music *music = (GME_Music *)music_p; return gme.gme_track_count(music->game_emu); } static int GME_StartTrack(void *music_p, int track) { GME_Music *music = (GME_Music *)music_p; const char *err; if ((track < 0) || (track >= gme.gme_track_count(music->game_emu))) { track = gme.gme_track_count(music->game_emu) - 1; } err = gme.gme_start_track(music->game_emu, track); if (err != 0) { SDL_SetError("GME: %s", err); return -1; } GME_Play(music, music->play_count); return initialize_from_track_info(music, track); } Mix_MusicInterface Mix_MusicInterface_GME = { "GME", MIX_MUSIC_GME, MUS_GME, false, false, GME_Load, NULL, /* Open */ GME_CreateFromIO, NULL, /* CreateFromFile */ GME_SetVolume, GME_GetVolume, GME_Play, NULL, /* IsPlaying */ GME_PlayAudio, NULL, /* Jump */ GME_Seek, GME_Tell, GME_Duration, NULL, NULL, NULL, GME_GetMetaTag, GME_GetNumTracks, GME_StartTrack, NULL, /* Pause */ NULL, /* Resume */ NULL, /* Stop */ GME_Delete, NULL, /* Close */ GME_Unload }; #endif /* MUSIC_GME */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_gme.h000066400000000000000000000021221501405355700233560ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing chiptune files with libGME */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_GME; libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_mpg123.c000066400000000000000000000407171501405355700236260ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MP3 files with mpg123 */ #ifdef MUSIC_MP3_MPG123 #include #include #include "music_mpg123.h" #include "mp3utils.h" #include /* For SEEK_SET */ #ifdef MPG123_HEADER #include MPG123_HEADER #else #include #endif #ifdef _MSC_VER typedef ptrdiff_t MIX_SSIZE_T; #else typedef ssize_t MIX_SSIZE_T; #endif typedef struct { int loaded; void *handle; int (*mpg123_close)(mpg123_handle *mh); void (*mpg123_delete)(mpg123_handle *mh); void (*mpg123_exit)(void); int (*mpg123_format)( mpg123_handle *mh, long rate, int channels, int encodings ); int (*mpg123_format_none)(mpg123_handle *mh); int (*mpg123_getformat)( mpg123_handle *mh, long *rate, int *channels, int *encoding ); int (*mpg123_init)(void); mpg123_handle *(*mpg123_new)(const char* decoder, int *error); int (*mpg123_open_handle)(mpg123_handle *mh, void *iohandle); const char* (*mpg123_plain_strerror)(int errcode); void (*mpg123_rates)(const long **list, size_t *number); #if (MPG123_API_VERSION >= 45) /* api (but not abi) change as of mpg123-1.26.0 */ int (*mpg123_read)(mpg123_handle *mh, void *outmemory, size_t outmemsize, size_t *done ); #else int (*mpg123_read)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done ); #endif int (*mpg123_replace_reader_handle)( mpg123_handle *mh, MIX_SSIZE_T (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*) ); off_t (*mpg123_seek)( mpg123_handle *mh, off_t sampleoff, int whence ); off_t (*mpg123_tell)( mpg123_handle *mh); off_t (*mpg123_length)(mpg123_handle *mh); const char* (*mpg123_strerror)(mpg123_handle *mh); } mpg123_loader; static mpg123_loader mpg123; #ifdef MPG123_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ mpg123.FUNC = (SIG) SDL_LoadFunction(mpg123.handle, #FUNC); \ if (mpg123.FUNC == NULL) { SDL_UnloadObject(mpg123.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ mpg123.FUNC = FUNC; \ if (mpg123.FUNC == NULL) { SDL_SetError("Missing mpg123.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int MPG123_Load(void) { if (mpg123.loaded == 0) { #ifdef MPG123_DYNAMIC mpg123.handle = SDL_LoadObject(MPG123_DYNAMIC); if (mpg123.handle == NULL) { return -1; } #endif FUNCTION_LOADER(mpg123_close, int (*)(mpg123_handle *mh)) FUNCTION_LOADER(mpg123_delete, void (*)(mpg123_handle *mh)) FUNCTION_LOADER(mpg123_exit, void (*)(void)) FUNCTION_LOADER(mpg123_format, int (*)( mpg123_handle *mh, long rate, int channels, int encodings )) FUNCTION_LOADER(mpg123_format_none, int (*)(mpg123_handle *mh)) FUNCTION_LOADER(mpg123_getformat, int (*)( mpg123_handle *mh, long *rate, int *channels, int *encoding )) FUNCTION_LOADER(mpg123_init, int (*)(void)) FUNCTION_LOADER(mpg123_new, mpg123_handle *(*)(const char* decoder, int *error)) FUNCTION_LOADER(mpg123_open_handle, int (*)(mpg123_handle *mh, void *iohandle)) FUNCTION_LOADER(mpg123_plain_strerror, const char* (*)(int errcode)) FUNCTION_LOADER(mpg123_rates, void (*)(const long **list, size_t *number)) #if (MPG123_API_VERSION >= 45) /* api (but not abi) change as of mpg123-1.26.0 */ FUNCTION_LOADER(mpg123_read, int (*)(mpg123_handle *mh, void *outmemory, size_t outmemsize, size_t *done )) #else FUNCTION_LOADER(mpg123_read, int (*)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done )) #endif FUNCTION_LOADER(mpg123_replace_reader_handle, int (*)( mpg123_handle *mh, MIX_SSIZE_T (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*) )) FUNCTION_LOADER(mpg123_seek, off_t (*)( mpg123_handle *mh, off_t sampleoff, int whence )) FUNCTION_LOADER(mpg123_tell, off_t (*)( mpg123_handle *mh)) FUNCTION_LOADER(mpg123_length, off_t (*)(mpg123_handle *mh)) FUNCTION_LOADER(mpg123_strerror, const char* (*)(mpg123_handle *mh)) } ++mpg123.loaded; return 0; } static void MPG123_Unload(void) { if (mpg123.loaded == 0) { return; } if (mpg123.loaded == 1) { #ifdef MPG123_DYNAMIC SDL_UnloadObject(mpg123.handle); #endif } --mpg123.loaded; } typedef struct { struct mp3file_t mp3file; int play_count; bool closeio; int volume; mpg123_handle* handle; SDL_AudioStream *stream; unsigned char *buffer; size_t buffer_size; long sample_rate; off_t total_length; Mix_MusicMetaTags tags; } MPG123_Music; static int MPG123_Seek(void *context, double secs); static void MPG123_Delete(void *context); static int mpg123_format_to_sdl(int fmt) { switch (fmt) { case MPG123_ENC_SIGNED_8: return SDL_AUDIO_S8; case MPG123_ENC_UNSIGNED_8: return SDL_AUDIO_U8; case MPG123_ENC_SIGNED_16: return SDL_AUDIO_S16; case MPG123_ENC_SIGNED_32: return SDL_AUDIO_S32; case MPG123_ENC_FLOAT_32: return SDL_AUDIO_F32; default: return -1; } } /*#define DEBUG_MPG123*/ #ifdef DEBUG_MPG123 static const char *mpg123_format_str(int fmt) { switch (fmt) { #define f(x) case x: return #x; f(MPG123_ENC_UNSIGNED_8) f(MPG123_ENC_SIGNED_8) f(MPG123_ENC_SIGNED_16) f(MPG123_ENC_SIGNED_32) f(MPG123_ENC_FLOAT_32) #undef f } return "unknown"; } #endif static char const* mpg_err(mpg123_handle* mpg, int result) { char const* err = "unknown error"; if (mpg && result == MPG123_ERR) { err = mpg123.mpg123_strerror(mpg); } else { err = mpg123.mpg123_plain_strerror(result); } return err; } /* we're gonna override mpg123's I/O with these wrappers for SDL_IOStream */ static MIX_SSIZE_T IO_read(void* p, void* dst, size_t n) { struct mp3file_t *mp3file = (struct mp3file_t *)p; MIX_SSIZE_T r = (MIX_SSIZE_T)MP3_IOread(mp3file, dst, 1, n); if (!r && SDL_GetIOStatus(mp3file->src) != SDL_IO_STATUS_EOF) { return -1; } return r < 0 ? -1 : r; } static off_t IO_seek(void* p, off_t offset, int whence) { return (off_t)MP3_IOseek((struct mp3file_t *)p, (Sint64)offset, whence); } static void IO_cleanup(void* p) { (void)p; /* do nothing, we will free the file later */ } static int MPG123_Open(const SDL_AudioSpec *spec) { (void)spec; if (mpg123.mpg123_init() != MPG123_OK) { SDL_SetError("mpg123_init() failed"); return -1; } return 0; } static void *MPG123_CreateFromIO(SDL_IOStream *src, bool closeio) { SDL_AudioSpec srcspec; MPG123_Music *music; int result, format, channels, encoding; long rate; const long *rates; size_t i, num_rates; music = (MPG123_Music*)SDL_calloc(1, sizeof(*music)); if (!music) { return NULL; } music->volume = MIX_MAX_VOLUME; if (MP3_IOinit(&music->mp3file, src) < 0) { SDL_free(music); return NULL; } meta_tags_init(&music->tags); if (mp3_read_tags(&music->tags, &music->mp3file, true) < 0) { SDL_free(music); SDL_SetError("music_mpg123: corrupt mp3 file (bad tags.)"); return NULL; } /* Just assume 16-bit 2 channel audio for now */ music->buffer_size = 4096/*music_spec.samples*/ * sizeof(Sint16) * 2; music->buffer = (unsigned char *)SDL_malloc(music->buffer_size); if (!music->buffer) { MPG123_Delete(music); return NULL; } music->handle = mpg123.mpg123_new(0, &result); if (result != MPG123_OK) { MPG123_Delete(music); SDL_SetError("mpg123_new failed"); return NULL; } result = mpg123.mpg123_replace_reader_handle( music->handle, IO_read, IO_seek, IO_cleanup ); if (result != MPG123_OK) { SDL_SetError("mpg123_replace_reader_handle: %s", mpg_err(music->handle, result)); MPG123_Delete(music); return NULL; } result = mpg123.mpg123_format_none(music->handle); if (result != MPG123_OK) { SDL_SetError("mpg123_format_none: %s", mpg_err(music->handle, result)); MPG123_Delete(music); return NULL; } mpg123.mpg123_rates(&rates, &num_rates); for (i = 0; i < num_rates; ++i) { const int channels = (MPG123_MONO|MPG123_STEREO); const int formats = (MPG123_ENC_SIGNED_8 | MPG123_ENC_UNSIGNED_8 | MPG123_ENC_SIGNED_16 | MPG123_ENC_SIGNED_32 | MPG123_ENC_FLOAT_32); mpg123.mpg123_format(music->handle, rates[i], channels, formats); } result = mpg123.mpg123_open_handle(music->handle, &music->mp3file); if (result != MPG123_OK) { SDL_SetError("mpg123_open_handle: %s", mpg_err(music->handle, result)); MPG123_Delete(music); return NULL; } result = mpg123.mpg123_getformat(music->handle, &rate, &channels, &encoding); if (result != MPG123_OK) { SDL_SetError("mpg123_getformat: %s", mpg_err(music->handle, result)); MPG123_Delete(music); return NULL; } #ifdef DEBUG_MPG123 printf("MPG123 format: %s, channels: %d, rate: %ld\n", mpg123_format_str(encoding), channels, rate); #endif format = mpg123_format_to_sdl(encoding); SDL_assert(format != -1); music->sample_rate = rate; SDL_zero(srcspec); srcspec.format = (SDL_AudioFormat)format; srcspec.channels = channels; srcspec.freq = (int)rate; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { MPG123_Delete(music); return NULL; } music->total_length = mpg123.mpg123_length(music->handle); music->closeio = closeio; return music; } static void MPG123_SetVolume(void *context, int volume) { MPG123_Music *music = (MPG123_Music *)context; music->volume = volume; } static int MPG123_GetVolume(void *context) { MPG123_Music *music = (MPG123_Music *)context; return music->volume; } static int MPG123_Play(void *context, int play_count) { MPG123_Music *music = (MPG123_Music *)context; music->play_count = play_count; return MPG123_Seek(music, 0.0); } static void MPG123_Stop(void *context) { MPG123_Music *music = (MPG123_Music *)context; SDL_ClearAudioStream(music->stream); } /* read some mp3 stream data and convert it for output */ static int MPG123_GetSome(void *context, void *data, int bytes, bool *done) { SDL_AudioSpec srcspec; MPG123_Music *music = (MPG123_Music *)context; int filled, result; size_t amount = 0; long rate; int channels, encoding, format; if (music->stream) { filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } } if (!music->play_count) { /* All done */ *done = true; return 0; } result = mpg123.mpg123_read(music->handle, music->buffer, music->buffer_size, &amount); switch (result) { case MPG123_OK: if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount)) { return -1; } break; case MPG123_NEW_FORMAT: result = mpg123.mpg123_getformat(music->handle, &rate, &channels, &encoding); if (result != MPG123_OK) { SDL_SetError("mpg123_getformat: %s", mpg_err(music->handle, result)); return -1; } #ifdef DEBUG_MPG123 printf("MPG123 format: %s, channels: %d, rate: %ld\n", mpg123_format_str(encoding), channels, rate); #endif format = mpg123_format_to_sdl(encoding); SDL_assert(format != -1); if (music->stream) { SDL_DestroyAudioStream(music->stream); } SDL_zero(srcspec); srcspec.format = (SDL_AudioFormat)format; srcspec.channels = channels; srcspec.freq = (int)rate; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { return -1; } music->sample_rate = rate; break; case MPG123_DONE: if (amount > 0) { if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount)) { return -1; } break; } if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (MPG123_Play(music, play_count) < 0) { return -1; } } break; default: SDL_SetError("mpg123_read: %s", mpg_err(music->handle, result)); return -1; } return 0; } static int MPG123_GetAudio(void *context, void *data, int bytes) { MPG123_Music *music = (MPG123_Music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, MPG123_GetSome); } static int MPG123_Seek(void *context, double secs) { MPG123_Music *music = (MPG123_Music *)context; off_t offset = (off_t)(music->sample_rate * secs); if ((offset = mpg123.mpg123_seek(music->handle, offset, SEEK_SET)) < 0) { SDL_SetError("mpg123_seek: %s", mpg_err(music->handle, (int)-offset)); return -1; } return 0; } static double MPG123_Tell(void *context) { MPG123_Music *music = (MPG123_Music *)context; off_t offset = 0; if (!music->sample_rate) { return 0.0; } if ((offset = mpg123.mpg123_tell(music->handle)) < 0) { SDL_SetError("mpg123_tell: %s", mpg_err(music->handle, (int)-offset)); return -1.0; } return (double)offset / music->sample_rate; } /* Return music duration in seconds */ static double MPG123_Duration(void *context) { MPG123_Music *music = (MPG123_Music *)context; if (music->total_length < 0) { return -1.0; } return (double)music->total_length / music->sample_rate; } static const char* MPG123_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { MPG123_Music *music = (MPG123_Music *)context; return meta_tags_get(&music->tags, tag_type); } static void MPG123_Delete(void *context) { MPG123_Music *music = (MPG123_Music *)context; meta_tags_clear(&music->tags); if (music->handle) { mpg123.mpg123_close(music->handle); mpg123.mpg123_delete(music->handle); } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->mp3file.src); } SDL_free(music); } static void MPG123_Close(void) { mpg123.mpg123_exit(); } Mix_MusicInterface Mix_MusicInterface_MPG123 = { "MPG123", MIX_MUSIC_MPG123, MUS_MP3, false, false, MPG123_Load, MPG123_Open, MPG123_CreateFromIO, NULL, /* CreateFromFile */ MPG123_SetVolume, MPG123_GetVolume, MPG123_Play, NULL, /* IsPlaying */ MPG123_GetAudio, NULL, /* Jump */ MPG123_Seek, MPG123_Tell, MPG123_Duration, NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ MPG123_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ MPG123_Stop, MPG123_Delete, MPG123_Close, MPG123_Unload }; #endif /* MUSIC_MP3_MPG123 */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_mpg123.h000066400000000000000000000021641501405355700236250ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MP3 files with mpg123 */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_MPG123; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_nativemidi.c000066400000000000000000000060171501405355700247410ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_MID_NATIVE /* This file supports playing MIDI files with OS APIs */ #include "music_nativemidi.h" #include "native_midi/native_midi.h" static void *NATIVEMIDI_CreateFromIO(SDL_IOStream *src, bool closeio) { NativeMidiSong *music = native_midi_loadsong_IO(src, closeio); if (!music) { SDL_SetError("%s", native_midi_error()); } return music; } static int NATIVEMIDI_Play(void *context, int play_count) { NativeMidiSong *music = (NativeMidiSong *)context; int loops = play_count; if (loops > 0) { --loops; } native_midi_start(music, loops); return 0; } static void NATIVEMIDI_SetVolume(void *context, int volume) { (void)context; native_midi_setvolume(volume); } static bool NATIVEMIDI_IsPlaying(void *context) { (void)context; return native_midi_active(); } static void NATIVEMIDI_Pause(void *context) { (void)context; native_midi_pause(); } static void NATIVEMIDI_Resume(void *context) { (void)context; native_midi_resume(); } static void NATIVEMIDI_Stop(void *context) { (void)context; native_midi_stop(); } static void NATIVEMIDI_Delete(void *context) { NativeMidiSong *music = (NativeMidiSong *)context; native_midi_freesong(music); } Mix_MusicInterface Mix_MusicInterface_NATIVEMIDI = { "NATIVEMIDI", MIX_MUSIC_NATIVEMIDI, MUS_MID, false, false, NULL, /* Load */ NULL, /* Open */ NATIVEMIDI_CreateFromIO, NULL, /* CreateFromFile */ NATIVEMIDI_SetVolume, NULL, /* GetVolume */ NATIVEMIDI_Play, NATIVEMIDI_IsPlaying, NULL, /* GetAudio */ NULL, /* Jump */ NULL, /* Seek */ NULL, /* Tell */ NULL, /* Duration */ NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ NULL, /* GetMetaTag */ NULL, /* GetNumTracks */ NULL, /* StartTrack */ NATIVEMIDI_Pause, NATIVEMIDI_Resume, NATIVEMIDI_Stop, NATIVEMIDI_Delete, NULL, /* Close */ NULL /* Unload */ }; #endif /* MUSIC_MID_NATIVE */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_nativemidi.h000066400000000000000000000021721501405355700247440ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MIDI files with OS APIs */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_NATIVEMIDI; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_ogg.c000066400000000000000000000405271501405355700233700ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #if defined(MUSIC_OGG) && !defined(OGG_USE_STB) /* This file supports Ogg Vorbis music streams */ #include #include "music_ogg.h" #include "utils.h" #define OV_EXCLUDE_STATIC_CALLBACKS #if defined(OGG_HEADER) #include OGG_HEADER #elif defined(OGG_USE_TREMOR) #include #else #include #endif typedef struct { int loaded; void *handle; int (*ov_clear)(OggVorbis_File *vf); vorbis_info *(*ov_info)(OggVorbis_File *vf,int link); vorbis_comment *(*ov_comment)(OggVorbis_File *vf,int link); int (*ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks); ogg_int64_t (*ov_pcm_total)(OggVorbis_File *vf,int i); #ifdef OGG_USE_TREMOR long (*ov_read)(OggVorbis_File *vf,char *buffer,int length, int *bitstream); int (*ov_time_seek)(OggVorbis_File *vf,ogg_int64_t pos); ogg_int64_t (*ov_time_tell)(OggVorbis_File *vf); ogg_int64_t (*ov_time_total)(OggVorbis_File *vf, int i); #else long (*ov_read)(OggVorbis_File *vf,char *buffer,int length, int bigendianp,int word,int sgned,int *bitstream); int (*ov_time_seek)(OggVorbis_File *vf,double pos); double (*ov_time_tell)(OggVorbis_File *vf); double (*ov_time_total)(OggVorbis_File *vf, int i); #endif int (*ov_pcm_seek)(OggVorbis_File *vf, ogg_int64_t pos); ogg_int64_t (*ov_pcm_tell)(OggVorbis_File *vf); } vorbis_loader; static vorbis_loader vorbis; #ifdef OGG_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ vorbis.FUNC = (SIG) SDL_LoadFunction(vorbis.handle, #FUNC); \ if (vorbis.FUNC == NULL) { SDL_UnloadObject(vorbis.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ vorbis.FUNC = FUNC; \ if (vorbis.FUNC == NULL) { SDL_SetError("Missing vorbis.framework or tremor.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int OGG_Load(void) { if (vorbis.loaded == 0) { #ifdef OGG_DYNAMIC vorbis.handle = SDL_LoadObject(OGG_DYNAMIC); if (vorbis.handle == NULL) { return -1; } #endif FUNCTION_LOADER(ov_clear, int (*)(OggVorbis_File *)) FUNCTION_LOADER(ov_info, vorbis_info *(*)(OggVorbis_File *,int)) FUNCTION_LOADER(ov_comment, vorbis_comment *(*)(OggVorbis_File *,int)) FUNCTION_LOADER(ov_open_callbacks, int (*)(void *,OggVorbis_File *,const char *,long,ov_callbacks)) FUNCTION_LOADER(ov_pcm_total, ogg_int64_t (*)(OggVorbis_File *,int)) #ifdef OGG_USE_TREMOR FUNCTION_LOADER(ov_read, long (*)(OggVorbis_File *,char *,int,int *)) FUNCTION_LOADER(ov_time_seek, int (*)(OggVorbis_File *,ogg_int64_t)) FUNCTION_LOADER(ov_time_tell, ogg_int64_t (*)(OggVorbis_File *)) FUNCTION_LOADER(ov_time_total, ogg_int64_t (*)(OggVorbis_File *, int)) #else FUNCTION_LOADER(ov_read, long (*)(OggVorbis_File *,char *,int,int,int,int,int *)) FUNCTION_LOADER(ov_time_seek, int (*)(OggVorbis_File *,double)) FUNCTION_LOADER(ov_time_tell, double (*)(OggVorbis_File *)) FUNCTION_LOADER(ov_time_total, double (*)(OggVorbis_File *, int)) #endif FUNCTION_LOADER(ov_pcm_seek, int (*)(OggVorbis_File *,ogg_int64_t)) FUNCTION_LOADER(ov_pcm_tell, ogg_int64_t (*)(OggVorbis_File *)) } ++vorbis.loaded; return 0; } static void OGG_Unload(void) { if (vorbis.loaded == 0) { return; } if (vorbis.loaded == 1) { #ifdef OGG_DYNAMIC SDL_UnloadObject(vorbis.handle); #endif } --vorbis.loaded; } typedef struct { SDL_IOStream *src; bool closeio; int play_count; int volume; OggVorbis_File vf; vorbis_info vi; int section; SDL_AudioStream *stream; char *buffer; int buffer_size; int loop; ogg_int64_t loop_start; ogg_int64_t loop_end; ogg_int64_t loop_len; Mix_MusicMetaTags tags; } OGG_music; static int set_ov_error(const char *function, int error) { #define HANDLE_ERROR_CASE(X) case X: SDL_SetError("%s: %s", function, #X); break; switch (error) { HANDLE_ERROR_CASE(OV_FALSE) HANDLE_ERROR_CASE(OV_EOF) HANDLE_ERROR_CASE(OV_HOLE) HANDLE_ERROR_CASE(OV_EREAD) HANDLE_ERROR_CASE(OV_EFAULT) HANDLE_ERROR_CASE(OV_EIMPL) HANDLE_ERROR_CASE(OV_EINVAL) HANDLE_ERROR_CASE(OV_ENOTVORBIS) HANDLE_ERROR_CASE(OV_EBADHEADER) HANDLE_ERROR_CASE(OV_EVERSION) HANDLE_ERROR_CASE(OV_ENOTAUDIO) HANDLE_ERROR_CASE(OV_EBADPACKET) HANDLE_ERROR_CASE(OV_EBADLINK) HANDLE_ERROR_CASE(OV_ENOSEEK) default: SDL_SetError("%s: unknown error %d\n", function, error); break; } return -1; } static size_t sdl_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { if (size > 0 && nmemb > 0) { return SDL_ReadIO((SDL_IOStream*)datasource, ptr, size * nmemb) / size; } return 0; } static int sdl_seek_func(void *datasource, ogg_int64_t offset, int whence) { return (SDL_SeekIO((SDL_IOStream*)datasource, offset, whence) < 0)? -1 : 0; } static long sdl_tell_func(void *datasource) { return (long) SDL_TellIO((SDL_IOStream*)datasource); } static int sdl_close_func(void *datasource) { (void)datasource; return 0; } static int OGG_Seek(void *context, double time); static void OGG_Delete(void *context); static int OGG_UpdateSection(OGG_music *music) { SDL_AudioSpec srcspec; vorbis_info *vi; vi = vorbis.ov_info(&music->vf, -1); if (!vi) { SDL_SetError("ov_info returned NULL"); return -1; } if (vi->channels == music->vi.channels && vi->rate == music->vi.rate) { return 0; } SDL_memcpy(&music->vi, vi, sizeof(*vi)); if (music->buffer) { SDL_free(music->buffer); music->buffer = NULL; } if (music->stream) { SDL_DestroyAudioStream(music->stream); music->stream = NULL; } SDL_zero(srcspec); srcspec.format = SDL_AUDIO_S16; srcspec.channels = vi->channels; srcspec.freq = (int)vi->rate; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { return -1; } music->buffer_size = 4096/*music_spec.samples*/ * (int)sizeof(Sint16) * vi->channels; music->buffer = (char *)SDL_malloc((size_t)music->buffer_size); if (!music->buffer) { return -1; } return 0; } /* Load an OGG stream from an SDL_IOStream object */ static void *OGG_CreateFromIO(SDL_IOStream *src, bool closeio) { OGG_music *music; ov_callbacks callbacks; vorbis_comment *vc; long rate; ogg_int64_t full_length; bool is_loop_length = false; int i; music = (OGG_music *)SDL_calloc(1, sizeof *music); if (!music) { return NULL; } music->src = src; music->volume = MIX_MAX_VOLUME; music->section = -1; callbacks.read_func = sdl_read_func; callbacks.seek_func = sdl_seek_func; callbacks.close_func = sdl_close_func; callbacks.tell_func = sdl_tell_func; if (vorbis.ov_open_callbacks(src, &music->vf, NULL, 0, callbacks) < 0) { SDL_SetError("Not an Ogg Vorbis audio stream"); SDL_free(music); return NULL; } if (OGG_UpdateSection(music) < 0) { OGG_Delete(music); return NULL; } rate = music->vi.rate; vc = vorbis.ov_comment(&music->vf, -1); if (vc != NULL) { for (i = 0; i < vc->comments; i++) { char *param = SDL_strdup(vc->user_comments[i]); char *argument = param; char *value = SDL_strchr(param, '='); if (value == NULL) { value = param + SDL_strlen(param); } else { *(value++) = '\0'; } /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from * string if it is present at position 4. */ if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) { SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4); } if (SDL_strcasecmp(argument, "LOOPSTART") == 0) music->loop_start = _Mix_ParseTime(value, rate); else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) { music->loop_len = SDL_strtoll(value, NULL, 10); is_loop_length = true; } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) { music->loop_end = _Mix_ParseTime(value, rate); is_loop_length = false; } else if (SDL_strcasecmp(argument, "TITLE") == 0) { meta_tags_set(&music->tags, MIX_META_TITLE, value); } else if (SDL_strcasecmp(argument, "ARTIST") == 0) { meta_tags_set(&music->tags, MIX_META_ARTIST, value); } else if (SDL_strcasecmp(argument, "ALBUM") == 0) { meta_tags_set(&music->tags, MIX_META_ALBUM, value); } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) { meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value); } SDL_free(param); } if (is_loop_length) { music->loop_end = music->loop_start + music->loop_len; } else { music->loop_len = music->loop_end - music->loop_start; } /* Ignore invalid loop tag */ if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) { music->loop_start = 0; music->loop_len = 0; music->loop_end = 0; } } full_length = vorbis.ov_pcm_total(&music->vf, -1); if ((music->loop_end > 0) && (music->loop_end <= full_length) && (music->loop_start < music->loop_end)) { music->loop = 1; } music->closeio = closeio; return music; } static const char* OGG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { OGG_music *music = (OGG_music *)context; return meta_tags_get(&music->tags, tag_type); } /* Set the volume for an OGG stream */ static void OGG_SetVolume(void *context, int volume) { OGG_music *music = (OGG_music *)context; music->volume = volume; } /* Get the volume for an OGG stream */ static int OGG_GetVolume(void *context) { OGG_music *music = (OGG_music *)context; return music->volume; } /* Start playback of a given OGG stream */ static int OGG_Play(void *context, int play_count) { OGG_music *music = (OGG_music *)context; music->play_count = play_count; return OGG_Seek(music, 0.0); } static void OGG_Stop(void *context) { OGG_music *music = (OGG_music *)context; SDL_ClearAudioStream(music->stream); } /* Play some of a stream previously started with OGG_play() */ static int OGG_GetSome(void *context, void *data, int bytes, bool *done) { OGG_music *music = (OGG_music *)context; bool looped = false; int filled, amount, result; int section; ogg_int64_t pcmPos; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (!music->play_count) { /* All done */ *done = true; return 0; } section = music->section; #ifdef OGG_USE_TREMOR amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, §ion); #else amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, 2, 1, §ion); #endif if (amount < 0) { return set_ov_error("ov_read", amount); } if (section != music->section) { music->section = section; if (OGG_UpdateSection(music) < 0) { return -1; } } pcmPos = vorbis.ov_pcm_tell(&music->vf); if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) { amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(Sint16); result = vorbis.ov_pcm_seek(&music->vf, music->loop_start); if (result < 0) { return set_ov_error("ov_pcm_seek", result); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } music->play_count = play_count; } looped = true; } if (amount > 0) { if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) { return -1; } } else if (!looped) { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (OGG_Play(music, play_count) < 0) { return -1; } } } return 0; } static int OGG_GetAudio(void *context, void *data, int bytes) { OGG_music *music = (OGG_music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, OGG_GetSome); } /* Jump (seek) to a given position (time is in seconds) */ static int OGG_Seek(void *context, double time) { OGG_music *music = (OGG_music *)context; int result; #ifdef OGG_USE_TREMOR result = vorbis.ov_time_seek(&music->vf, (ogg_int64_t)(time * 1000.0)); #else result = vorbis.ov_time_seek(&music->vf, time); #endif if (result < 0) { return set_ov_error("ov_time_seek", result); } return 0; } static double OGG_Tell(void *context) { OGG_music *music = (OGG_music *)context; #ifdef OGG_USE_TREMOR return vorbis.ov_time_tell(&music->vf) / 1000.0; #else return vorbis.ov_time_tell(&music->vf); #endif } /* Return music duration in seconds */ static double OGG_Duration(void *context) { OGG_music *music = (OGG_music *)context; #ifdef OGG_USE_TREMOR return vorbis.ov_time_total(&music->vf, -1) / 1000.0; #else return vorbis.ov_time_total(&music->vf, -1); #endif } static double OGG_LoopStart(void *music_p) { OGG_music *music = (OGG_music *)music_p; if (music->loop > 0) { return (double)music->loop_start / music->vi.rate; } return -1.0; } static double OGG_LoopEnd(void *music_p) { OGG_music *music = (OGG_music *)music_p; if (music->loop > 0) { return (double)music->loop_end / music->vi.rate; } return -1.0; } static double OGG_LoopLength(void *music_p) { OGG_music *music = (OGG_music *)music_p; if (music->loop > 0) { return (double)music->loop_len / music->vi.rate; } return -1.0; } /* Close the given OGG stream */ static void OGG_Delete(void *context) { OGG_music *music = (OGG_music *)context; meta_tags_clear(&music->tags); vorbis.ov_clear(&music->vf); if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->src); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_OGG = { "OGG", MIX_MUSIC_OGG, MUS_OGG, false, false, OGG_Load, NULL, /* Open */ OGG_CreateFromIO, NULL, /* CreateFromFile */ OGG_SetVolume, OGG_GetVolume, OGG_Play, NULL, /* IsPlaying */ OGG_GetAudio, NULL, /* Jump */ OGG_Seek, OGG_Tell, OGG_Duration, OGG_LoopStart, OGG_LoopEnd, OGG_LoopLength, OGG_GetMetaTag, /* GetMetaTag */ NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ OGG_Stop, OGG_Delete, NULL, /* Close */ OGG_Unload }; #endif /* MUSIC_OGG */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_ogg.h000066400000000000000000000021541501405355700233670ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports Ogg Vorbis music streams */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_OGG; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_ogg_stb.c000066400000000000000000000340311501405355700242310ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #if defined(MUSIC_OGG) && defined(OGG_USE_STB) /* This file supports Ogg Vorbis music streams using a modified stb_vorbis module */ #include "music_ogg.h" #include "utils.h" #include #define STB_VORBIS_SDL 1 /* for SDL_mixer-specific stuff. */ #define STB_VORBIS_NO_STDIO 1 #define STB_VORBIS_NO_CRT 1 #define STB_VORBIS_NO_PUSHDATA_API 1 #define STB_VORBIS_MAX_CHANNELS 8 /* For 7.1 surround sound */ #define STB_FORCEINLINE SDL_FORCE_INLINE #if SDL_BYTEORDER == SDL_BIG_ENDIAN #define STB_VORBIS_BIG_ENDIAN 1 #endif #define STBV_CDECL SDLCALL /* for SDL_qsort() */ #ifdef assert #undef assert #endif #ifdef memset #undef memset #endif #ifdef memcpy #undef memcpy #endif #define assert SDL_assert #define memset SDL_memset #define memcmp SDL_memcmp #define memcpy SDL_memcpy #define qsort SDL_qsort #define malloc SDL_malloc #define realloc SDL_realloc #define free SDL_free #define pow SDL_pow #define floor SDL_floor #define ldexp(v, e) SDL_scalbn((v), (e)) #define abs(x) SDL_abs(x) #define cos(x) SDL_cos(x) #define sin(x) SDL_sin(x) #define log(x) SDL_log(x) #define exp(x) SDL_exp(x) #include "stb_vorbis/stb_vorbis.h" typedef struct { SDL_IOStream *src; bool closeio; int play_count; int volume; stb_vorbis *vf; stb_vorbis_info vi; int section; SDL_AudioStream *stream; char *buffer; int buffer_size; int loop; Sint64 loop_start; Sint64 loop_end; Sint64 loop_len; Sint64 full_length; Mix_MusicMetaTags tags; } OGG_music; static int set_ov_error(const char *function, int error) { #define HANDLE_ERROR_CASE(X) case X: SDL_SetError("%s: %s", function, #X); break; switch (error) { HANDLE_ERROR_CASE(VORBIS_need_more_data) HANDLE_ERROR_CASE(VORBIS_invalid_api_mixing) HANDLE_ERROR_CASE(VORBIS_outofmem) HANDLE_ERROR_CASE(VORBIS_feature_not_supported) HANDLE_ERROR_CASE(VORBIS_too_many_channels) HANDLE_ERROR_CASE(VORBIS_file_open_failure) HANDLE_ERROR_CASE(VORBIS_seek_without_length) HANDLE_ERROR_CASE(VORBIS_unexpected_eof) HANDLE_ERROR_CASE(VORBIS_seek_invalid) HANDLE_ERROR_CASE(VORBIS_invalid_setup) HANDLE_ERROR_CASE(VORBIS_invalid_stream) HANDLE_ERROR_CASE(VORBIS_missing_capture_pattern) HANDLE_ERROR_CASE(VORBIS_invalid_stream_structure_version) HANDLE_ERROR_CASE(VORBIS_continued_packet_flag_invalid) HANDLE_ERROR_CASE(VORBIS_incorrect_stream_serial_number) HANDLE_ERROR_CASE(VORBIS_invalid_first_page) HANDLE_ERROR_CASE(VORBIS_bad_packet_type) HANDLE_ERROR_CASE(VORBIS_cant_find_last_page) HANDLE_ERROR_CASE(VORBIS_seek_failed) HANDLE_ERROR_CASE(VORBIS_ogg_skeleton_not_supported) default: SDL_SetError("%s: unknown error %d\n", function, error); break; } return -1; } static int OGG_Seek(void *context, double time); static void OGG_Delete(void *context); static int OGG_UpdateSection(OGG_music *music) { stb_vorbis_info vi; SDL_AudioSpec srcspec; vi = stb_vorbis_get_info(music->vf); if (vi.channels == music->vi.channels && vi.sample_rate == music->vi.sample_rate) { return 0; } SDL_memcpy(&music->vi, &vi, sizeof(vi)); if (music->buffer) { SDL_free(music->buffer); music->buffer = NULL; } if (music->stream) { SDL_DestroyAudioStream(music->stream); music->stream = NULL; } SDL_zero(srcspec); srcspec.format = SDL_AUDIO_F32; srcspec.channels = vi.channels; srcspec.freq = (int)vi.sample_rate; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { return -1; } music->buffer_size = 4096/*music_spec.samples*/ * (int)sizeof(float) * vi.channels; if (music->buffer_size <= 0) { return -1; } music->buffer = (char *)SDL_malloc((size_t)music->buffer_size); if (!music->buffer) { return -1; } return 0; } /* Load an OGG stream from an SDL_IOStream object */ static void *OGG_CreateFromIO(SDL_IOStream *src, bool closeio) { OGG_music *music; stb_vorbis_comment vc; long rate; bool is_loop_length = false; int i, error; music = (OGG_music *)SDL_calloc(1, sizeof *music); if (!music) { return NULL; } music->src = src; music->volume = MIX_MAX_VOLUME; music->section = -1; music->vf = stb_vorbis_open_io(src, 0, &error, NULL); if (music->vf == NULL) { set_ov_error("stb_vorbis_open_io", error); SDL_free(music); return NULL; } if (OGG_UpdateSection(music) < 0) { OGG_Delete(music); return NULL; } music->vi = stb_vorbis_get_info(music->vf); if ((int)music->vi.sample_rate <= 0) { SDL_SetError("Invalid sample rate value"); OGG_Delete(music); return NULL; } rate = music->vi.sample_rate; music->full_length = stb_vorbis_stream_length_in_samples(music->vf); if (music->full_length <= 0) { SDL_SetError("No samples in ogg/vorbis stream."); OGG_Delete(music); return NULL; } vc = stb_vorbis_get_comment(music->vf); if (vc.comment_list != NULL) { for (i = 0; i < vc.comment_list_length; i++) { char *param = SDL_strdup(vc.comment_list[i]); char *argument = param; char *value = SDL_strchr(param, '='); if (value == NULL) { value = param + SDL_strlen(param); } else { *(value++) = '\0'; } /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from * string if it is present at position 4. */ if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) { SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4); } if (SDL_strcasecmp(argument, "LOOPSTART") == 0) music->loop_start = _Mix_ParseTime(value, rate); else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) { music->loop_len = SDL_strtoll(value, NULL, 10); is_loop_length = true; } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) { music->loop_end = _Mix_ParseTime(value, rate); is_loop_length = false; } else if (SDL_strcasecmp(argument, "TITLE") == 0) { meta_tags_set(&music->tags, MIX_META_TITLE, value); } else if (SDL_strcasecmp(argument, "ARTIST") == 0) { meta_tags_set(&music->tags, MIX_META_ARTIST, value); } else if (SDL_strcasecmp(argument, "ALBUM") == 0) { meta_tags_set(&music->tags, MIX_META_ALBUM, value); } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) { meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value); } SDL_free(param); } if (is_loop_length) { music->loop_end = music->loop_start + music->loop_len; } else { music->loop_len = music->loop_end - music->loop_start; } /* Ignore invalid loop tag */ if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) { music->loop_start = 0; music->loop_len = 0; music->loop_end = 0; } } if ((music->loop_end > 0) && (music->loop_end <= music->full_length) && (music->loop_start < music->loop_end)) { music->loop = 1; } OGG_Seek(music, 0.0); music->closeio = closeio; return music; } static const char* OGG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { OGG_music *music = (OGG_music *)context; return meta_tags_get(&music->tags, tag_type); } /* Set the volume for an OGG stream */ static void OGG_SetVolume(void *context, int volume) { OGG_music *music = (OGG_music *)context; music->volume = volume; } /* Get the volume for an OGG stream */ static int OGG_GetVolume(void *context) { OGG_music *music = (OGG_music *)context; return music->volume; } /* Start playback of a given OGG stream */ static int OGG_Play(void *context, int play_count) { OGG_music *music = (OGG_music *)context; music->play_count = play_count; return OGG_Seek(music, 0.0); } static void OGG_Stop(void *context) { OGG_music *music = (OGG_music *)context; SDL_ClearAudioStream(music->stream); } /* Play some of a stream previously started with OGG_play() */ static int OGG_GetSome(void *context, void *data, int bytes, bool *done) { OGG_music *music = (OGG_music *)context; bool looped = false; int filled, amount, result; int section; Sint64 pcmPos; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (!music->play_count) { /* All done */ *done = true; return 0; } section = music->section; amount = stb_vorbis_get_samples_float_interleaved(music->vf, music->vi.channels, (float *)music->buffer, 4096/*music_spec.samples*/ * music->vi.channels); amount *= music->vi.channels * sizeof(float); if (section != music->section) { music->section = section; if (OGG_UpdateSection(music) < 0) { return -1; } } pcmPos = stb_vorbis_get_playback_sample_offset(music->vf); if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) { amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(float); result = stb_vorbis_seek(music->vf, (Uint32)music->loop_start); if (!result) { return set_ov_error("stb_vorbis_seek", stb_vorbis_get_error(music->vf)); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } music->play_count = play_count; } looped = true; } if (amount > 0) { if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) { return -1; } } else if (!looped) { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (OGG_Play(music, play_count) < 0) { return -1; } } } return 0; } static int OGG_GetAudio(void *context, void *data, int bytes) { OGG_music *music = (OGG_music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, OGG_GetSome); } /* Jump (seek) to a given position (time is in seconds) */ static int OGG_Seek(void *context, double time) { OGG_music *music = (OGG_music *)context; int result; result = stb_vorbis_seek(music->vf, (unsigned int)(time * music->vi.sample_rate)); if (!result) { return set_ov_error("stb_vorbis_seek", stb_vorbis_get_error(music->vf)); } return 0; } static double OGG_Tell(void *context) { OGG_music *music = (OGG_music *)context; return (double)stb_vorbis_get_playback_sample_offset(music->vf) / music->vi.sample_rate; } /* Return music duration in seconds */ static double OGG_Duration(void *context) { OGG_music *music = (OGG_music *)context; return (double)music->full_length / music->vi.sample_rate; } static double OGG_LoopStart(void *music_p) { OGG_music *music = (OGG_music *)music_p; if (music->loop > 0) { return (double)music->loop_start / music->vi.sample_rate; } return -1.0; } static double OGG_LoopEnd(void *music_p) { OGG_music *music = (OGG_music *)music_p; if (music->loop > 0) { return (double)music->loop_end / music->vi.sample_rate; } return -1.0; } static double OGG_LoopLength(void *music_p) { OGG_music *music = (OGG_music *)music_p; if (music->loop > 0) { return (double)music->loop_len / music->vi.sample_rate; } return -1.0; } /* Close the given OGG stream */ static void OGG_Delete(void *context) { OGG_music *music = (OGG_music *)context; meta_tags_clear(&music->tags); stb_vorbis_close(music->vf); if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->src); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_OGG = { "OGG", MIX_MUSIC_OGG, MUS_OGG, false, false, NULL, /* Load */ NULL, /* Open */ OGG_CreateFromIO, NULL, /* CreateFromFile */ OGG_SetVolume, OGG_GetVolume, OGG_Play, NULL, /* IsPlaying */ OGG_GetAudio, NULL, /* Jump */ OGG_Seek, OGG_Tell, OGG_Duration, OGG_LoopStart, OGG_LoopEnd, OGG_LoopLength, OGG_GetMetaTag, /* GetMetaTag */ NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ OGG_Stop, OGG_Delete, NULL, /* Close */ NULL /* Unload */ }; #endif /* MUSIC_OGG */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_opus.c000066400000000000000000000361611501405355700236010ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_OPUS /* This file supports Ogg Opus music streams */ #include #include "music_opus.h" #include "utils.h" #ifdef OPUSFILE_HEADER #include OPUSFILE_HEADER #else #include #endif typedef struct { int loaded; void *handle; const OpusTags *(*op_tags)(const OggOpusFile *,int); OggOpusFile *(*op_open_callbacks)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *); void (*op_free)(OggOpusFile *); const OpusHead *(*op_head)(const OggOpusFile *,int); int (*op_seekable)(const OggOpusFile *); int (*op_read)(OggOpusFile *, opus_int16 *,int,int *); int (*op_pcm_seek)(OggOpusFile *,ogg_int64_t); ogg_int64_t (*op_pcm_tell)(const OggOpusFile *); ogg_int64_t (*op_pcm_total)(const OggOpusFile *, int); } opus_loader; static opus_loader opus; #ifdef OPUS_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ opus.FUNC = (SIG) SDL_LoadFunction(opus.handle, #FUNC); \ if (opus.FUNC == NULL) { SDL_UnloadObject(opus.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ opus.FUNC = FUNC; \ if (opus.FUNC == NULL) { SDL_SetError("Missing opus.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int OPUS_Load(void) { if (opus.loaded == 0) { #ifdef OPUS_DYNAMIC opus.handle = SDL_LoadObject(OPUS_DYNAMIC); if (opus.handle == NULL) { return -1; } #endif FUNCTION_LOADER(op_open_callbacks, OggOpusFile *(*)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *)) FUNCTION_LOADER(op_tags, const OpusTags *(*)(const OggOpusFile *,int)) FUNCTION_LOADER(op_free, void (*)(OggOpusFile *)) FUNCTION_LOADER(op_head, const OpusHead *(*)(const OggOpusFile *,int)) FUNCTION_LOADER(op_seekable, int (*)(const OggOpusFile *)) FUNCTION_LOADER(op_read, int (*)(OggOpusFile *, opus_int16 *,int,int *)) FUNCTION_LOADER(op_pcm_seek, int (*)(OggOpusFile *,ogg_int64_t)) FUNCTION_LOADER(op_pcm_tell, ogg_int64_t (*)(const OggOpusFile *)) FUNCTION_LOADER(op_pcm_total, ogg_int64_t (*)(const OggOpusFile *, int)) } ++opus.loaded; return 0; } static void OPUS_Unload(void) { if (opus.loaded == 0) { return; } if (opus.loaded == 1) { #ifdef OPUS_DYNAMIC SDL_UnloadObject(opus.handle); #endif } --opus.loaded; } typedef struct { SDL_IOStream *src; bool closeio; int play_count; int volume; OggOpusFile *of; const OpusHead *op_info; int section; SDL_AudioStream *stream; char *buffer; int buffer_size; int loop; ogg_int64_t loop_start; ogg_int64_t loop_end; ogg_int64_t loop_len; ogg_int64_t full_length; Mix_MusicMetaTags tags; } OPUS_music; static int set_op_error(const char *function, int error) { #define HANDLE_ERROR_CASE(X) case X: SDL_SetError("%s: %s", function, #X); break; switch (error) { HANDLE_ERROR_CASE(OP_FALSE) HANDLE_ERROR_CASE(OP_EOF) HANDLE_ERROR_CASE(OP_HOLE) HANDLE_ERROR_CASE(OP_EREAD) HANDLE_ERROR_CASE(OP_EFAULT) HANDLE_ERROR_CASE(OP_EIMPL) HANDLE_ERROR_CASE(OP_EINVAL) HANDLE_ERROR_CASE(OP_ENOTFORMAT) HANDLE_ERROR_CASE(OP_EBADHEADER) HANDLE_ERROR_CASE(OP_EVERSION) HANDLE_ERROR_CASE(OP_ENOTAUDIO) HANDLE_ERROR_CASE(OP_EBADPACKET) HANDLE_ERROR_CASE(OP_EBADLINK) HANDLE_ERROR_CASE(OP_ENOSEEK) HANDLE_ERROR_CASE(OP_EBADTIMESTAMP) default: SDL_SetError("%s: unknown error %d\n", function, error); break; } return -1; } static int sdl_read_func(void *datasource, unsigned char *ptr, int size) { return (int) SDL_ReadIO((SDL_IOStream*)datasource, ptr, (size_t)size); } static int sdl_seek_func(void *datasource, opus_int64 offset, int whence) { return (SDL_SeekIO((SDL_IOStream*)datasource, offset, whence) < 0)? -1 : 0; } static opus_int64 sdl_tell_func(void *datasource) { return SDL_TellIO((SDL_IOStream*)datasource); } static int OPUS_Seek(void*, double); static void OPUS_Delete(void*); static int OPUS_UpdateSection(OPUS_music *music) { const OpusHead *op_info; SDL_AudioSpec srcspec; op_info = opus.op_head(music->of, -1); if (!op_info) { SDL_SetError("op_head returned NULL"); return -1; } if (music->op_info && op_info->channel_count == music->op_info->channel_count) { return 0; } music->op_info = op_info; if (music->buffer) { SDL_free(music->buffer); music->buffer = NULL; } if (music->stream) { SDL_DestroyAudioStream(music->stream); music->stream = NULL; } SDL_zero(srcspec); srcspec.format = SDL_AUDIO_S16; srcspec.channels = op_info->channel_count; srcspec.freq = 48000; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { return -1; } music->buffer_size = (int)4096/*music_spec.samples*/ * (int)sizeof(opus_int16) * op_info->channel_count; music->buffer = (char *)SDL_malloc((size_t)music->buffer_size); if (!music->buffer) { return -1; } return 0; } /* Load an Opus stream from an SDL_IOStream object */ static void *OPUS_CreateFromIO(SDL_IOStream *src, bool closeio) { OPUS_music *music; OpusFileCallbacks callbacks; const OpusTags* tags; int err = 0, ci; bool is_loop_length = false; ogg_int64_t full_length; music = (OPUS_music *)SDL_calloc(1, sizeof *music); if (!music) { return NULL; } music->src = src; music->volume = MIX_MAX_VOLUME; music->section = -1; SDL_zero(callbacks); callbacks.read = sdl_read_func; callbacks.seek = sdl_seek_func; callbacks.tell = sdl_tell_func; music->of = opus.op_open_callbacks(src, &callbacks, NULL, 0, &err); if (music->of == NULL) { /* set_op_error("op_open_callbacks", err);*/ SDL_SetError("Not an Opus audio stream"); SDL_free(music); return NULL; } if (!opus.op_seekable(music->of)) { OPUS_Delete(music); SDL_SetError("Opus stream not seekable"); return NULL; } if (OPUS_UpdateSection(music) < 0) { OPUS_Delete(music); return NULL; } tags = opus.op_tags(music->of, -1); if (tags != NULL) { for (ci = 0; ci < tags->comments; ci++) { char *param = SDL_strdup(tags->user_comments[ci]); char *argument = param; char *value = SDL_strchr(param, '='); if (value == NULL) { value = param + SDL_strlen(param); } else { *(value++) = '\0'; } /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from * string if it is present at position 4. */ if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) { SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4); } if (SDL_strcasecmp(argument, "LOOPSTART") == 0) music->loop_start = _Mix_ParseTime(value, 48000); else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) { music->loop_len = SDL_strtoll(value, NULL, 10); is_loop_length = true; } else if (SDL_strcasecmp(argument, "LOOPEND") == 0) { music->loop_end = _Mix_ParseTime(value, 48000); is_loop_length = false; } else if (SDL_strcasecmp(argument, "TITLE") == 0) { meta_tags_set(&music->tags, MIX_META_TITLE, value); } else if (SDL_strcasecmp(argument, "ARTIST") == 0) { meta_tags_set(&music->tags, MIX_META_ARTIST, value); } else if (SDL_strcasecmp(argument, "ALBUM") == 0) { meta_tags_set(&music->tags, MIX_META_ALBUM, value); } else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) { meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value); } SDL_free(param); } if (is_loop_length) { music->loop_end = music->loop_start + music->loop_len; } else { music->loop_len = music->loop_end - music->loop_start; } /* Ignore invalid loop tag */ if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) { music->loop_start = 0; music->loop_len = 0; music->loop_end = 0; } } full_length = opus.op_pcm_total(music->of, -1); if ((music->loop_end > 0) && (music->loop_end <= full_length) && (music->loop_start < music->loop_end)) { music->loop = 1; } music->full_length = full_length; music->closeio = closeio; return music; } static const char* OPUS_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { OPUS_music *music = (OPUS_music *)context; return meta_tags_get(&music->tags, tag_type); } /* Set the volume for an Opus stream */ static void OPUS_SetVolume(void *context, int volume) { OPUS_music *music = (OPUS_music *)context; music->volume = volume; } /* Get the volume for an Opus stream */ static int OPUS_GetVolume(void *context) { OPUS_music *music = (OPUS_music *)context; return music->volume; } /* Start playback of a given Opus stream */ static int OPUS_Play(void *context, int play_count) { OPUS_music *music = (OPUS_music *)context; music->play_count = play_count; return OPUS_Seek(music, 0.0); } /* Clean-up the output buffer */ static void OPUS_Stop(void *context) { OPUS_music *music = (OPUS_music *)context; SDL_ClearAudioStream(music->stream); } /* Play some of a stream previously started with OPUS_Play() */ static int OPUS_GetSome(void *context, void *data, int bytes, bool *done) { OPUS_music *music = (OPUS_music *)context; int filled, samples, section; int result; bool looped = false; ogg_int64_t pcmPos; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (!music->play_count) { /* All done */ *done = true; return 0; } section = music->section; samples = opus.op_read(music->of, (opus_int16 *)music->buffer, music->buffer_size / (int)sizeof(opus_int16), §ion); if (samples < 0) { return set_op_error("op_read", samples); } if (section != music->section) { music->section = section; if (OPUS_UpdateSection(music) < 0) { return -1; } } pcmPos = opus.op_pcm_tell(music->of); if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) { samples -= (int)((pcmPos - music->loop_end) * music->op_info->channel_count) * (int)sizeof(Sint16); result = opus.op_pcm_seek(music->of, music->loop_start); if (result < 0) { return set_op_error("ov_pcm_seek", result); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } music->play_count = play_count; } looped = true; } if (samples > 0) { filled = samples * music->op_info->channel_count * 2; if (!SDL_PutAudioStreamData(music->stream, music->buffer, filled)) { return -1; } } else if (!looped) { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (OPUS_Play(music, play_count) < 0) { return -1; } } } return 0; } static int OPUS_GetAudio(void *context, void *data, int bytes) { OPUS_music *music = (OPUS_music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, OPUS_GetSome); } /* Jump (seek) to a given position (time is in seconds) */ static int OPUS_Seek(void *context, double time) { OPUS_music *music = (OPUS_music *)context; int result = opus.op_pcm_seek(music->of, (ogg_int64_t)(time * 48000)); if (result < 0) { return set_op_error("op_pcm_seek", result); } return 0; } static double OPUS_Tell(void *context) { OPUS_music *music = (OPUS_music *)context; return (double)(opus.op_pcm_tell(music->of)) / 48000.0; } /* Return music duration in seconds */ static double OPUS_Duration(void *context) { OPUS_music *music = (OPUS_music *)context; return music->full_length / 48000.0; } static double OPUS_LoopStart(void *music_p) { OPUS_music *music = (OPUS_music *)music_p; if (music->loop > 0) { return (double)music->loop_start / 48000.0; } return -1.0; } static double OPUS_LoopEnd(void *music_p) { OPUS_music *music = (OPUS_music *)music_p; if (music->loop > 0) { return (double)music->loop_end / 48000.0; } return -1.0; } static double OPUS_LoopLength(void *music_p) { OPUS_music *music = (OPUS_music *)music_p; if (music->loop > 0) { return (double)music->loop_len / 48000.0; } return -1.0; } /* Close the given Opus stream */ static void OPUS_Delete(void *context) { OPUS_music *music = (OPUS_music *)context; meta_tags_clear(&music->tags); opus.op_free(music->of); if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->src); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_Opus = { "OPUS", MIX_MUSIC_OPUS, MUS_OPUS, false, false, OPUS_Load, NULL, /* Open */ OPUS_CreateFromIO, NULL, /* CreateFromFile */ OPUS_SetVolume, OPUS_GetVolume, OPUS_Play, NULL, /* IsPlaying */ OPUS_GetAudio, NULL, /* Jump */ OPUS_Seek, OPUS_Tell, OPUS_Duration, OPUS_LoopStart, OPUS_LoopEnd, OPUS_LoopLength, OPUS_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ OPUS_Stop, OPUS_Delete, NULL, /* Close */ OPUS_Unload }; #endif /* MUSIC_OPUS */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_opus.h000066400000000000000000000021531501405355700236000ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports Ogg Opus music streams */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_Opus; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_timidity.c000066400000000000000000000167711501405355700244540ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MIDI files with timidity */ #ifdef MUSIC_MID_TIMIDITY #include "music_timidity.h" #include "timidity/timidity.h" typedef struct { int play_count; MidiSong *song; SDL_AudioStream *stream; void *buffer; Sint32 buffer_size; int volume; } TIMIDITY_Music; static int TIMIDITY_Seek(void *context, double position); static void TIMIDITY_Delete(void *context); /* Config file should contain any other directory that needs * to be added to the search path. The library adds the path * of the config file to its search path, too. */ #if defined(SDL_PLATFORM_WIN32) # define TIMIDITY_CFG "C:\\TIMIDITY\\TIMIDITY.CFG" #else /* unix: */ # define TIMIDITY_CFG_ETC "/etc/timidity.cfg" # define TIMIDITY_CFG_FREEPATS "/etc/timidity/freepats.cfg" #endif static int TIMIDITY_Open(const SDL_AudioSpec *spec) { const char *cfg; int rc = -1; (void) spec; cfg = SDL_getenv("TIMIDITY_CFG"); if(!cfg) cfg = Mix_GetTimidityCfg(); if (cfg) { return Timidity_Init(cfg); /* env or user override: no other tries */ } #if defined(TIMIDITY_CFG) if (rc < 0) rc = Timidity_Init(TIMIDITY_CFG); #endif #if defined(TIMIDITY_CFG_ETC) if (rc < 0) rc = Timidity_Init(TIMIDITY_CFG_ETC); #endif #if defined(TIMIDITY_CFG_FREEPATS) if (rc < 0) rc = Timidity_Init(TIMIDITY_CFG_FREEPATS); #endif if (rc < 0) rc = Timidity_Init(NULL); /* library's default cfg. */ return rc; } static void TIMIDITY_Close(void) { Timidity_Exit(); } void *TIMIDITY_CreateFromIO(SDL_IOStream *src, bool closeio) { TIMIDITY_Music *music; SDL_AudioSpec spec; bool need_stream = false; music = (TIMIDITY_Music *)SDL_calloc(1, sizeof(*music)); if (!music) { return NULL; } music->volume = MIX_MAX_VOLUME; SDL_memcpy(&spec, &music_spec, sizeof(spec)); if (spec.channels > 2) { need_stream = true; spec.channels = 2; } music->song = Timidity_LoadSong(src, &spec); if (!music->song) { TIMIDITY_Delete(music); return NULL; } if (need_stream) { music->stream = SDL_CreateAudioStream(&spec, &music_spec); if (!music->stream) { TIMIDITY_Delete(music); return NULL; } music->buffer_size = 4096/*spec.samples*/ * (SDL_AUDIO_BITSIZE(spec.format) / 8) * spec.channels; music->buffer = SDL_malloc((size_t)music->buffer_size); if (!music->buffer) { TIMIDITY_Delete(music); return NULL; } } if (closeio) { SDL_CloseIO(src); } return music; } static void TIMIDITY_SetVolume(void *context, int volume) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; music->volume = volume; Timidity_SetVolume(music->song, volume); } static int TIMIDITY_GetVolume(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; return music->volume; } static int TIMIDITY_Play(void *context, int play_count) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; music->play_count = play_count; Timidity_Start(music->song); return TIMIDITY_Seek(music, 0.0); } static bool TIMIDITY_IsPlaying(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; return Timidity_IsActive(music->song); } static int TIMIDITY_GetSome(void *context, void *data, int bytes, bool *done) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; int filled, amount, expected; if (music->stream) { filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } } if (!music->play_count) { /* All done */ *done = true; return 0; } if (music->stream) { expected = music->buffer_size; amount = Timidity_PlaySome(music->song, music->buffer, music->buffer_size); if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) { return -1; } } else { expected = bytes; amount = Timidity_PlaySome(music->song, data, bytes); } if (amount < expected) { if (music->play_count == 1) { /* We didn't consume anything and we're done */ music->play_count = 0; } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (TIMIDITY_Play(music, play_count) < 0) { return -1; } } } if (music->stream) { /* We'll pick it up from the stream next time around */ return 0; } else { /* We wrote output data */ return amount; } } static int TIMIDITY_GetAudio(void *context, void *data, int bytes) { return music_pcm_getaudio(context, data, bytes, MIX_MAX_VOLUME, TIMIDITY_GetSome); } static int TIMIDITY_Seek(void *context, double position) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; Timidity_Seek(music->song, (Uint32)(position * 1000)); return 0; } static double TIMIDITY_Tell(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; return Timidity_GetSongTime(music->song) / 1000.0; } static double TIMIDITY_Duration(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; return Timidity_GetSongLength(music->song) / 1000.0; } static void TIMIDITY_Delete(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; if (music->song) { Timidity_FreeSong(music->song); } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } SDL_free(music); } static void TIMIDITY_Stop(void *context) { TIMIDITY_Music *music = (TIMIDITY_Music *)context; Timidity_Stop(music->song); } Mix_MusicInterface Mix_MusicInterface_TIMIDITY = { "TIMIDITY", MIX_MUSIC_TIMIDITY, MUS_MID, false, false, NULL, /* Load */ TIMIDITY_Open, TIMIDITY_CreateFromIO, NULL, /* CreateFromFile */ TIMIDITY_SetVolume, TIMIDITY_GetVolume, TIMIDITY_Play, TIMIDITY_IsPlaying, TIMIDITY_GetAudio, NULL, /* Jump */ TIMIDITY_Seek, TIMIDITY_Tell, TIMIDITY_Duration, NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ NULL, /* GetMetaTag */ NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ TIMIDITY_Stop, TIMIDITY_Delete, TIMIDITY_Close, NULL /* Unload */ }; #endif /* MUSIC_MID_TIMIDITY */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_timidity.h000066400000000000000000000021711501405355700244460ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MIDI files with timidity */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_TIMIDITY; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_wav.c000066400000000000000000001765271501405355700234230ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_WAV /* This file supports streaming WAV files */ #include "music_wav.h" #include "mp3utils.h" typedef struct ADPCM_DecoderState { Uint32 channels; /* Number of channels. */ size_t blocksize; /* Size of an ADPCM block in bytes. */ size_t blockheadersize; /* Size of an ADPCM block header in bytes. */ size_t samplesperblock; /* Number of samples per channel in an ADPCM block. */ void *ddata; /* Decoder data from initialization. */ void *cstate; /* Decoding state for each channel. */ /* Current ADPCM block */ struct { Uint8 *data; size_t size; size_t pos; } block; /* Decoded 16-bit PCM data. */ struct { Sint16 *data; size_t size; size_t pos; size_t read; } output; } ADPCM_DecoderState; typedef struct MS_ADPCM_CoeffData { Uint16 coeffcount; Sint16 *coeff; Sint16 aligndummy; /* Has to be last member. */ } MS_ADPCM_CoeffData; typedef struct MS_ADPCM_ChannelState { Uint16 delta; Sint16 coeff1; Sint16 coeff2; } MS_ADPCM_ChannelState; typedef struct { bool active; Uint32 start; Uint32 stop; Uint32 initial_play_count; Uint32 current_play_count; } WAVLoopPoint; typedef struct { SDL_IOStream *src; bool closeio; SDL_AudioSpec spec; int volume; int play_count; Sint64 start; Sint64 stop; Sint64 samplesize; Uint8 *buffer; size_t buflen; size_t buffered; SDL_AudioStream *stream; ADPCM_DecoderState adpcm_state; unsigned int numloops; WAVLoopPoint *loops; Mix_MusicMetaTags tags; Uint16 encoding; int (*decode)(void *music, int length); } WAV_Music; /* Taken with permission from SDL_wave.h, part of the SDL library, available at: http://www.libsdl.org/ and placed under the same license as this mixer library. */ /* WAVE files are little-endian */ /*******************************************/ /* Define values for Microsoft WAVE format */ /*******************************************/ #define RIFF 0x46464952 /* "RIFF" */ #define WAVE 0x45564157 /* "WAVE" */ #define FMT 0x20746D66 /* "fmt " */ #define DATA 0x61746164 /* "data" */ #define SMPL 0x6c706d73 /* "smpl" */ #define LIST 0x5453494c /* "LIST" */ #define ID3_ 0x20336469 /* "id3 " */ #define UNKNOWN_CODE 0x0000 #define PCM_CODE 0x0001 /* WAVE_FORMAT_PCM */ #define MS_ADPCM_CODE 0x0002 /* WAVE_FORMAT_ADPCM */ #define IEEE_FLOAT_CODE 0x0003 /* WAVE_FORMAT_IEEE_FLOAT */ #define ALAW_CODE 0x0006 /* WAVE_FORMAT_ALAW */ #define MULAW_CODE 0x0007 /* WAVE_FORMAT_MULAW */ #define IMA_ADPCM_CODE 0x0011 #define MPEG_CODE 0x0050 #define MPEGLAYER3_CODE 0x0055 #define EXTENSIBLE_CODE 0xFFFE #define WAVE_MONO 1 #define WAVE_STEREO 2 #pragma pack(push, 1) typedef struct { /* Not saved in the chunk we read: Uint32 chunkID; Uint32 chunkLen; */ Uint16 encoding; Uint16 channels; /* 1 = mono, 2 = stereo */ Uint32 frequency; /* One of 11025, 22050, or 44100 Hz */ Uint32 byterate; /* Average bytes per second */ Uint16 blockalign; /* Bytes per sample block */ Uint16 bitspersample; /* One of 8, 12, 16, or 4 for ADPCM */ } WaveFMT; typedef struct { WaveFMT format; Uint16 cbSize; union { Uint16 validbitspersample; /* bits of precision */ Uint16 samplesperblock; /* valid if wBitsPerSample==0 */ Uint16 reserved; /* If neither applies, set to zero. */ } Samples; Uint32 channelsmask; /* GUID subFormat 16 bytes */ Uint32 subencoding; Uint16 sub_data2; Uint16 sub_data3; Uint8 sub_data[8]; } WaveFMTEx; typedef struct { Uint32 identifier; Uint32 type; Uint32 start; Uint32 end; Uint32 fraction; Uint32 play_count; } SampleLoop; typedef struct { /* Not saved in the chunk we read: Uint32 chunkID; Uint32 chunkLen; */ Uint32 manufacturer; Uint32 product; Uint32 sample_period; Uint32 MIDI_unity_note; Uint32 MIDI_pitch_fraction; Uint32 SMTPE_format; Uint32 SMTPE_offset; Uint32 sample_loops; Uint32 sampler_data; SampleLoop loops[1]; } SamplerChunk; #pragma pack(pop) /*********************************************/ /* Define values for AIFF (IFF audio) format */ /*********************************************/ #define FORM 0x4d524f46 /* "FORM" */ #define AIFF 0x46464941 /* "AIFF" */ #define AIFC 0x43464941 /* "AIFС" */ #define FVER 0x52455646 /* "FVER" */ #define SSND 0x444e5353 /* "SSND" */ #define COMM 0x4d4d4f43 /* "COMM" */ #define AIFF_ID3_ 0x20334449 /* "ID3 " */ #define MARK 0x4B52414D /* "MARK" */ #define INST 0x54534E49 /* "INST" */ #define AUTH 0x48545541 /* "AUTH" */ #define NAME 0x454D414E /* "NAME" */ #define _c__ 0x20296328 /* "(c) " */ /* Supported compression types */ #define NONE 0x454E4F4E /* "NONE" */ #define sowt 0x74776F73 /* "sowt" */ #define raw_ 0x20776172 /* "raw " */ #define ulaw 0x77616C75 /* "ulaw" */ #define alaw 0x77616C61 /* "alaw" */ #define ULAW 0x57414C55 /* "ULAW" */ #define ALAW 0x57414C41 /* "ALAW" */ #define fl32 0x32336C66 /* "fl32" */ #define fl64 0x34366C66 /* "fl64" */ #define FL32 0x32334C46 /* "FL32" */ /* Function to load the WAV/AIFF stream */ static bool LoadWAVMusic(WAV_Music *wave); static bool LoadAIFFMusic(WAV_Music *wave); static void WAV_Delete(void *context); static int fetch_pcm(void *context, int length); /* Load a WAV stream from the given SDL_IOStream object */ static void *WAV_CreateFromIO(SDL_IOStream *src, bool closeio) { WAV_Music *music; Uint32 magic; bool loaded = false; music = (WAV_Music *)SDL_calloc(1, sizeof(*music)); if (!music) { return NULL; } music->src = src; music->volume = MIX_MAX_VOLUME; /* Default decoder is PCM */ music->decode = fetch_pcm; music->encoding = PCM_CODE; if (SDL_ReadU32LE(src, &magic)) { if (magic == RIFF || magic == WAVE) { loaded = LoadWAVMusic(music); } else if (magic == FORM) { loaded = LoadAIFFMusic(music); } else { SDL_SetError("Unknown WAVE format"); } } if (!loaded) { WAV_Delete(music); return NULL; } music->buflen = SDL_AUDIO_BITSIZE(music->spec.format) / 8; music->buflen *= music->spec.channels; music->buflen *= 4096; /* Good default sample frame count */ music->buffer = (Uint8*)SDL_malloc(music->buflen); if (!music->buffer) { WAV_Delete(music); return NULL; } music->stream = SDL_CreateAudioStream(&music->spec, &music_spec); if (!music->stream) { WAV_Delete(music); return NULL; } music->closeio = closeio; return music; } static void WAV_SetVolume(void *context, int volume) { WAV_Music *music = (WAV_Music *)context; music->volume = volume; } static int WAV_GetVolume(void *context) { WAV_Music *music = (WAV_Music *)context; return music->volume; } /* Start playback of a given WAV stream */ static int WAV_Play(void *context, int play_count) { WAV_Music *music = (WAV_Music *)context; unsigned int i; for (i = 0; i < music->numloops; ++i) { WAVLoopPoint *loop = &music->loops[i]; loop->active = true; loop->current_play_count = loop->initial_play_count; } music->play_count = play_count; if (SDL_SeekIO(music->src, music->start, SDL_IO_SEEK_SET) < 0) { return -1; } return 0; } static void WAV_Stop(void *context) { WAV_Music *music = (WAV_Music *)context; SDL_ClearAudioStream(music->stream); } static int fetch_pcm(void *context, int length) { WAV_Music *music = (WAV_Music *)context; return (int) SDL_ReadIO(music->src, music->buffer, (size_t)length); } static Uint32 PCM_S24_to_S32_BE(Uint8 *x) { const Uint32 bits = 24; Uint32 in = (((Uint32)x[0] << 0) & 0x0000FF) | (((Uint32)x[1] << 8) & 0x00FF00) | (((Uint32)x[2] << 16) & 0xFF0000); Uint32 m = 1u << (bits - 1); return (in ^ m) - m; } static Uint32 PCM_S24_to_S32_LE(Uint8 *x) { const Uint32 bits = 24; Uint32 in = (((Uint32)x[2] << 0) & 0x0000FF) | (((Uint32)x[1] << 8) & 0x00FF00) | (((Uint32)x[0] << 16) & 0xFF0000); Uint32 m = 1u << (bits - 1); return (in ^ m) - m; } static int fetch_pcm24be(void *context, int length) { WAV_Music *music = (WAV_Music *)context; int i = 0, o = 0; length = (int) SDL_ReadIO(music->src, music->buffer, (size_t)((length / 4) * 3)); if (length % music->samplesize != 0) { length -= length % music->samplesize; } for (i = length - 3, o = ((length - 3) / 3) * 4; i >= 0; i -= 3, o -= 4) { Uint32 decoded = PCM_S24_to_S32_BE(music->buffer + i); music->buffer[o + 0] = (decoded >> 0) & 0xFF; music->buffer[o + 1] = (decoded >> 8) & 0xFF; music->buffer[o + 2] = (decoded >> 16) & 0xFF; music->buffer[o + 3] = (decoded >> 24) & 0xFF; } return (length / 3) * 4; } static int fetch_pcm24le(void *context, int length) { WAV_Music *music = (WAV_Music *)context; int i = 0, o = 0; length = (int) SDL_ReadIO(music->src, music->buffer, (size_t)((length / 4) * 3)); if (length % music->samplesize != 0) { length -= length % music->samplesize; } for (i = length - 3, o = ((length - 3) / 3) * 4; i >= 0; i -= 3, o -= 4) { Uint32 decoded = PCM_S24_to_S32_LE(music->buffer + i); music->buffer[o + 3] = (decoded >> 0) & 0xFF; music->buffer[o + 2] = (decoded >> 8) & 0xFF; music->buffer[o + 1] = (decoded >> 16) & 0xFF; music->buffer[o + 0] = (decoded >> 24) & 0xFF; } return (length / 3) * 4; } SDL_FORCE_INLINE double Mix_SwapDouble(double x) { union { double f; Uint64 ui64; } swapper; swapper.f = x; swapper.ui64 = SDL_Swap64(swapper.ui64); return swapper.f; } #if SDL_BYTEORDER == SDL_LIL_ENDIAN #define Mix_SwapDoubleLE(X) (X) #define Mix_SwapDoubleBE(X) Mix_SwapDouble(X) #else #define Mix_SwapDoubleLE(X) Mix_SwapDouble(X) #define Mix_SwapDoubleBE(X) (X) #endif static int fetch_float64be(void *context, int length) { WAV_Music *music = (WAV_Music *)context; int i = 0, o = 0; length = (int) SDL_ReadIO(music->src, music->buffer, (size_t)length); if (length % music->samplesize != 0) { length -= length % music->samplesize; } for (i = 0, o = 0; i < length; i += 8, o += 4) { union { float f; Uint32 ui32; } sample; sample.f = (float)Mix_SwapDoubleBE(*(double*)(music->buffer + i)); music->buffer[o + 0] = (sample.ui32 >> 0) & 0xFF; music->buffer[o + 1] = (sample.ui32 >> 8) & 0xFF; music->buffer[o + 2] = (sample.ui32 >> 16) & 0xFF; music->buffer[o + 3] = (sample.ui32 >> 24) & 0xFF; } return length / 2; } static int fetch_float64le(void *context, int length) { WAV_Music *music = (WAV_Music *)context; int i = 0, o = 0; length = (int) SDL_ReadIO(music->src, music->buffer, (size_t)length); if (length % music->samplesize != 0) { length -= length % music->samplesize; } for (i = 0, o = 0; i < length; i += 8, o += 4) { union { float f; Uint32 ui32; } sample; sample.f = (float)Mix_SwapDoubleLE(*(double*)(music->buffer + i)); music->buffer[o + 0] = (sample.ui32 >> 0) & 0xFF; music->buffer[o + 1] = (sample.ui32 >> 8) & 0xFF; music->buffer[o + 2] = (sample.ui32 >> 16) & 0xFF; music->buffer[o + 3] = (sample.ui32 >> 24) & 0xFF; } return length / 2; } static int MS_ADPCM_Init(ADPCM_DecoderState *state, const Uint8 *chunk_data, Uint32 chunk_length) { const WaveFMTEx *fmt = (WaveFMTEx *)chunk_data; const Uint16 channels = SDL_Swap16LE(fmt->format.channels); const Uint16 blockalign = SDL_Swap16LE(fmt->format.blockalign); const Uint16 bitspersample = SDL_Swap16LE(fmt->format.bitspersample); const size_t blockheadersize = (size_t)channels * 7; const size_t blockdatasize = (size_t)blockalign - blockheadersize; const size_t blockframebitsize = (size_t)bitspersample * channels; const size_t blockdatasamples = (blockdatasize * 8) / blockframebitsize; const Sint16 presetcoeffs[14] = { 256, 0, 512, -256, 0, 0, 192, 64, 240, 0, 460, -208, 392, -232 }; Uint16 cbExtSize, samplesperblock; size_t i, coeffcount; MS_ADPCM_CoeffData *coeffdata; /* Sanity checks. */ /* While it's clear how IMA ADPCM handles more than two channels, the nibble * order of MS ADPCM makes it awkward. The Standards Update does not talk * about supporting more than stereo anyway. */ if (channels > 2) { SDL_SetError("Invalid number of channels"); return -1; } if (bitspersample != 4) { SDL_SetError("Invalid MS ADPCM bits per sample of %u", (unsigned int)bitspersample); return -1; } /* The block size must be big enough to contain the block header. */ if (blockalign < blockheadersize) { SDL_SetError("Invalid MS ADPCM block size (nBlockAlign)"); return -1; } /* There are wSamplesPerBlock, wNumCoef, and at least 7 coefficient pairs in * the extended part of the header. */ if (chunk_length < 22) { SDL_SetError("Could not read MS ADPCM format header"); return -1; } cbExtSize = SDL_Swap16LE(fmt->cbSize); samplesperblock = SDL_Swap16LE(fmt->Samples.samplesperblock); /* Number of coefficient pairs. A pair has two 16-bit integers. */ coeffcount = chunk_data[20] | ((size_t)chunk_data[21] << 8); /* bPredictor, the integer offset into the coefficients array, is only * 8 bits. It can only address the first 256 coefficients. Let's limit * the count number here. */ if (coeffcount > 256) { coeffcount = 256; } if (chunk_length < 22 + coeffcount * 4) { SDL_SetError("Could not read custom coefficients in MS ADPCM format header"); return -1; } else if (cbExtSize < 4 + coeffcount * 4) { SDL_SetError("Invalid MS ADPCM format header (too small)"); return -1; } else if (coeffcount < 7) { SDL_SetError("Missing required coefficients in MS ADPCM format header"); return -1; } coeffdata = (MS_ADPCM_CoeffData *)SDL_malloc(sizeof(MS_ADPCM_CoeffData) + coeffcount * 4); if (coeffdata == NULL) { return -1; } coeffdata->coeff = &coeffdata->aligndummy; coeffdata->coeffcount = (Uint16)coeffcount; state->ddata = coeffdata; /* Freed in cleanup. */ /* Copy the 16-bit pairs. */ for (i = 0; i < coeffcount * 2; i++) { Sint32 c = chunk_data[22 + i * 2] | ((Sint32)chunk_data[23 + i * 2] << 8); if (c >= 0x8000) { c -= 0x10000; } if (i < 14 && c != presetcoeffs[i]) { SDL_SetError("Wrong preset coefficients in MS ADPCM format header"); return -1; } coeffdata->coeff[i] = (Sint16)c; } /* Technically, wSamplesPerBlock is required, but we have all the * information in the other fields to calculate it, if it's zero. */ if (samplesperblock == 0) { /* Let's be nice to the encoders that didn't know how to fill this. * The Standards Update calculates it this way: * * x = Block size (in bits) minus header size (in bits) * y = Bit depth multiplied by channel count * z = Number of samples per channel in block header * wSamplesPerBlock = x / y + z */ samplesperblock = (Uint16)(blockdatasamples + 2); } /* nBlockAlign can be in conflict with wSamplesPerBlock. For example, if * the number of samples doesn't fit into the block. The Standards Update * also describes wSamplesPerBlock with a formula that makes it necessary to * always fill the block with the maximum amount of samples, but this is not * enforced here as there are no compatibility issues. * A truncated block header with just one sample is not supported. */ if (samplesperblock == 1 || blockdatasamples < (size_t)(samplesperblock - 2)) { SDL_SetError("Invalid number of samples per MS ADPCM block (wSamplesPerBlock)"); return -1; } state->blocksize = blockalign; state->channels = channels; state->blockheadersize = blockheadersize; state->samplesperblock = samplesperblock; state->cstate = SDL_calloc(channels, sizeof(MS_ADPCM_ChannelState)); if (!state->cstate) { return -1; } state->block.pos = 0; state->block.size = blockalign; state->block.data = (Uint8 *)SDL_malloc(state->block.size); if (!state->block.data) { return -1; } state->output.read = 0; state->output.pos = 0; state->output.size = state->samplesperblock * state->channels; state->output.data = (Sint16 *)SDL_malloc(state->output.size * sizeof(Sint16)); if (!state->output.data) { return -1; } return 0; } static Sint16 MS_ADPCM_ProcessNibble(MS_ADPCM_ChannelState *cstate, Sint32 sample1, Sint32 sample2, Uint8 nybble) { const Sint32 max_audioval = 32767; const Sint32 min_audioval = -32768; const Uint16 max_deltaval = 65535; const Uint16 adaptive[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; Sint32 new_sample; Sint32 errordelta; Uint32 delta = cstate->delta; new_sample = (sample1 * cstate->coeff1 + sample2 * cstate->coeff2) / 256; /* The nibble is a signed 4-bit error delta. */ errordelta = (Sint32)nybble - (nybble >= 0x08 ? 0x10 : 0); new_sample += (Sint32)delta * errordelta; if (new_sample < min_audioval) { new_sample = min_audioval; } else if (new_sample > max_audioval) { new_sample = max_audioval; } delta = (delta * adaptive[nybble]) / 256; if (delta < 16) { delta = 16; } else if (delta > max_deltaval) { /* This issue is not described in the Standards Update and therefore * undefined. It seems sensible to prevent overflows with a limit. */ delta = max_deltaval; } cstate->delta = (Uint16)delta; return (Sint16)new_sample; } static int MS_ADPCM_DecodeBlockHeader(ADPCM_DecoderState *state) { Uint8 coeffindex; const Uint32 channels = state->channels; Sint32 sample; Uint32 c; MS_ADPCM_ChannelState *cstate = (MS_ADPCM_ChannelState *)state->cstate; MS_ADPCM_CoeffData *ddata = (MS_ADPCM_CoeffData *)state->ddata; if (state->block.size < state->blockheadersize) { SDL_SetError("Invalid ADPCM header"); return -1; } for (c = 0; c < channels; c++) { size_t o = c; /* Load the coefficient pair into the channel state. */ coeffindex = state->block.data[o]; if (coeffindex > ddata->coeffcount) { SDL_SetError("Invalid MS ADPCM coefficient index in block header"); return -1; } cstate[c].coeff1 = ddata->coeff[coeffindex * 2]; cstate[c].coeff2 = ddata->coeff[coeffindex * 2 + 1]; /* Initial delta value. */ o = (size_t)channels + c * 2; cstate[c].delta = state->block.data[o] | ((Uint16)state->block.data[o + 1] << 8); /* Load the samples from the header. Interestingly, the sample later in * the output stream comes first. */ o = (size_t)channels * 3 + c * 2; sample = state->block.data[o] | ((Sint32)state->block.data[o + 1] << 8); if (sample >= 0x8000) { sample -= 0x10000; } state->output.data[state->output.pos + channels] = (Sint16)sample; o = (size_t)channels * 5 + c * 2; sample = state->block.data[o] | ((Sint32)state->block.data[o + 1] << 8); if (sample >= 0x8000) { sample -= 0x10000; } state->output.data[state->output.pos] = (Sint16)sample; state->output.pos++; } state->block.pos += state->blockheadersize; /* Skip second sample frame that came from the header. */ state->output.pos += state->channels; return 0; } /* Decodes the data of the MS ADPCM block. Decoding will stop if a block is too * short, returning with none or partially decoded data. The partial data * will always contain full sample frames (same sample count for each channel). * Incomplete sample frames are discarded. */ static int MS_ADPCM_DecodeBlockData(ADPCM_DecoderState *state) { Uint16 nybble = 0; Sint16 sample1, sample2; const Uint32 channels = state->channels; Uint32 c; MS_ADPCM_ChannelState *cstate = (MS_ADPCM_ChannelState *)state->cstate; size_t blockpos = state->block.pos; size_t blocksize = state->block.size; size_t outpos = state->output.pos; size_t blockframesleft = state->samplesperblock - 2; while (blockframesleft > 0) { for (c = 0; c < channels; c++) { if (nybble & 0x4000) { nybble <<= 4; } else if (blockpos < blocksize) { nybble = state->block.data[blockpos++] | 0x4000; } else { /* Out of input data. Drop the incomplete frame and return. */ state->output.pos = outpos - c; return -1; } /* Load previous samples which may come from the block header. */ sample1 = state->output.data[outpos - channels]; sample2 = state->output.data[outpos - channels * 2]; sample1 = MS_ADPCM_ProcessNibble(cstate + c, sample1, sample2, (nybble >> 4) & 0x0f); state->output.data[outpos++] = sample1; } blockframesleft--; } state->output.pos = outpos; return 0; } static int IMA_ADPCM_Init(ADPCM_DecoderState *state, const Uint8 *chunk_data, Uint32 chunk_length) { const WaveFMTEx *fmt = (WaveFMTEx *)chunk_data; const Uint16 formattag = SDL_Swap16LE(fmt->format.encoding); const Uint16 channels = SDL_Swap16LE(fmt->format.channels); const Uint16 blockalign = SDL_Swap16LE(fmt->format.blockalign); const Uint16 bitspersample = SDL_Swap16LE(fmt->format.bitspersample); const size_t blockheadersize = (size_t)channels * 4; const size_t blockdatasize = (size_t)blockalign - blockheadersize; const size_t blockframebitsize = (size_t)bitspersample * channels; const size_t blockdatasamples = (blockdatasize * 8) / blockframebitsize; Uint16 samplesperblock = 0; /* Sanity checks. */ /* IMA ADPCM can also have 3-bit samples, but it's not supported by SDL at this time. */ if (bitspersample == 3) { SDL_SetError("3-bit IMA ADPCM currently not supported"); return -1; } else if (bitspersample != 4) { SDL_SetError("Invalid IMA ADPCM bits per sample of %u", (unsigned int)bitspersample); return -1; } /* The block size is required to be a multiple of 4 and it must be able to * hold a block header. */ if (blockalign < blockheadersize || blockalign % 4) { SDL_SetError("Invalid IMA ADPCM block size (nBlockAlign)"); return -1; } if (formattag == EXTENSIBLE_CODE) { /* There's no specification for this, but it's basically the same * format because the extensible header has wSampePerBlocks too. */ } else if (chunk_length >= 20) { Uint16 cbExtSize = SDL_Swap16LE(fmt->cbSize); if (cbExtSize >= 2) { samplesperblock = SDL_Swap16LE(fmt->Samples.samplesperblock); } } if (samplesperblock == 0) { /* Field zero? No problem. We just assume the encoder packed the block. * The specification calculates it this way: * * x = Block size (in bits) minus header size (in bits) * y = Bit depth multiplied by channel count * z = Number of samples per channel in header * wSamplesPerBlock = x / y + z */ samplesperblock = (Uint16)(blockdatasamples + 1); } /* nBlockAlign can be in conflict with wSamplesPerBlock. For example, if * the number of samples doesn't fit into the block. The Standards Update * also describes wSamplesPerBlock with a formula that makes it necessary * to always fill the block with the maximum amount of samples, but this is * not enforced here as there are no compatibility issues. */ if (blockdatasamples < (size_t)(samplesperblock - 1)) { SDL_SetError("Invalid number of samples per IMA ADPCM block (wSamplesPerBlock)"); return -1; } state->blocksize = blockalign; state->channels = channels; state->blockheadersize = blockheadersize; state->samplesperblock = samplesperblock; state->cstate = SDL_calloc(channels, sizeof(Sint8)); if (!state->cstate) { return -1; } state->block.pos = 0; state->block.size = blockalign; state->block.data = (Uint8 *)SDL_malloc(state->block.size); if (!state->block.data) { return -1; } state->output.read = 0; state->output.pos = 0; state->output.size = state->samplesperblock * state->channels; state->output.data = (Sint16 *)SDL_malloc(state->output.size * sizeof(Sint16)); if (!state->output.data) { return -1; } return 0; } static Sint16 IMA_ADPCM_ProcessNibble(Sint8 *cindex, Sint16 lastsample, Uint8 nybble) { const Sint32 max_audioval = 32767; const Sint32 min_audioval = -32768; const Sint8 index_table_4b[16] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 }; const Uint16 step_table[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; Uint32 step; Sint32 sample, delta; Sint8 index = *cindex; /* Clamp index into valid range. */ if (index > 88) { index = 88; } else if (index < 0) { index = 0; } /* explicit cast to avoid gcc warning about using 'char' as array index */ step = step_table[(size_t)index]; /* Update index value */ *cindex = index + index_table_4b[nybble]; /* This calculation uses shifts and additions because multiplications were * much slower back then. Sadly, this can't just be replaced with an actual * multiplication now as the old algorithm drops some bits. The closest * approximation I could find is something like this: * (nybble & 0x8 ? -1 : 1) * ((nybble & 0x7) * step / 4 + step / 8) */ delta = step >> 3; if (nybble & 0x04) { delta += step; } if (nybble & 0x02) { delta += step >> 1; } if (nybble & 0x01) { delta += step >> 2; } if (nybble & 0x08) { delta = -delta; } sample = lastsample + delta; /* Clamp output sample */ if (sample > max_audioval) { sample = max_audioval; } else if (sample < min_audioval) { sample = min_audioval; } return (Sint16)sample; } static int IMA_ADPCM_DecodeBlockHeader(ADPCM_DecoderState *state) { Sint16 step; Uint32 c; Uint8 *cstate = (Uint8 *)state->cstate; if (state->block.size < state->blockheadersize) { SDL_SetError("Invalid ADPCM header"); return -1; } for (c = 0; c < state->channels; c++) { size_t o = state->block.pos + c * 4; /* Extract the sample from the header. */ Sint32 sample = state->block.data[o] | ((Sint32)state->block.data[o + 1] << 8); if (sample >= 0x8000) { sample -= 0x10000; } state->output.data[state->output.pos++] = (Sint16)sample; /* Channel step index. */ step = (Sint16)state->block.data[o + 2]; cstate[c] = (Sint8)(step > 0x80 ? step - 0x100 : step); /* Reserved byte in block header, should be 0. */ if (state->block.data[o + 3] != 0) { /* Uh oh, corrupt data? Buggy code? */; } } state->block.pos += state->blockheadersize; return 0; } /* Decodes the data of the IMA ADPCM block. Decoding will stop if a block is too * short, returning with none or partially decoded data. The partial data always * contains full sample frames (same sample count for each channel). * Incomplete sample frames are discarded. */ static int IMA_ADPCM_DecodeBlockData(ADPCM_DecoderState *state) { size_t i; int retval = 0; const Uint32 channels = state->channels; const size_t subblockframesize = (size_t)channels * 4; Uint64 bytesrequired; Uint32 c; size_t blockpos = state->block.pos; size_t blocksize = state->block.size; size_t blockleft = blocksize - blockpos; size_t outpos = state->output.pos; Sint64 blockframesleft = state->samplesperblock - 1; bytesrequired = (blockframesleft + 7) / 8 * subblockframesize; if (blockleft < bytesrequired) { /* Data truncated. Calculate how many samples we can get out if it. */ const size_t guaranteedframes = blockleft / subblockframesize; const size_t remainingbytes = blockleft % subblockframesize; blockframesleft = guaranteedframes; if (remainingbytes > subblockframesize - 4) { blockframesleft += (remainingbytes % 4) * 2; } /* Signal the truncation. */ retval = -1; } /* Each channel has their nibbles packed into 32-bit blocks. These blocks * are interleaved and make up the data part of the ADPCM block. This loop * decodes the samples as they come from the input data and puts them at * the appropriate places in the output data. */ while (blockframesleft > 0) { const size_t subblocksamples = blockframesleft < 8 ? (size_t)blockframesleft : 8; for (c = 0; c < channels; c++) { Uint8 nybble = 0; /* Load previous sample which may come from the block header. */ Sint16 sample = state->output.data[outpos + c - channels]; for (i = 0; i < subblocksamples; i++) { if (i & 1) { nybble >>= 4; } else { nybble = state->block.data[blockpos++]; } sample = IMA_ADPCM_ProcessNibble((Sint8 *)state->cstate + c, sample, nybble & 0x0f); state->output.data[outpos + c + i * channels] = sample; } } outpos += channels * subblocksamples; blockframesleft -= subblocksamples; } state->block.pos = blockpos; state->output.pos = outpos; return retval; } static void ADPCM_Cleanup(ADPCM_DecoderState *state) { if (state->ddata) { SDL_free(state->ddata); state->ddata = NULL; } if (state->cstate) { SDL_free(state->cstate); state->cstate = NULL; } if (state->block.data) { SDL_free(state->block.data); SDL_zero(state->block); } if (state->output.data) { SDL_free(state->output.data); SDL_zero(state->output); } } static int fetch_adpcm(void *context, int length, int (*DecodeBlockHeader)(ADPCM_DecoderState *state), int (*DecodeBlockData)(ADPCM_DecoderState *state)) { WAV_Music *music = (WAV_Music *)context; ADPCM_DecoderState *state = &music->adpcm_state; size_t len, left = (size_t)length; Uint8 *dst = music->buffer; while (left > 0) { if (state->output.read == state->output.pos) { size_t bytesread = SDL_ReadIO(music->src, state->block.data, state->blocksize); if (bytesread == 0) { break; } state->block.size = bytesread < state->blocksize ? bytesread : state->blocksize; state->block.pos = 0; state->output.pos = 0; state->output.read = 0; if (DecodeBlockHeader(state) < 0) { return -1; } if (DecodeBlockData(state) < 0) { return -1; } } len = SDL_min(left, (state->output.pos - state->output.read) * sizeof(Sint16)); SDL_memcpy(dst, &state->output.data[state->output.read], len); state->output.read += (len / sizeof(Sint16)); dst += len; left -= len; } music->buffered = (state->output.pos - state->output.read) * sizeof(Sint16); return length; } static int fetch_ms_adpcm(void *context, int length) { return fetch_adpcm(context, length, MS_ADPCM_DecodeBlockHeader, MS_ADPCM_DecodeBlockData); } static int fetch_ima_adpcm(void *context, int length) { return fetch_adpcm(context, length, IMA_ADPCM_DecodeBlockHeader, IMA_ADPCM_DecodeBlockData); } /* G711 decode tables taken from SDL (src/audio/SDL_wave.c) */ #ifdef SDL_WAVE_LAW_LUT static const Sint16 alaw_lut[256] = { -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 }; static const Sint16 mulaw_lut[256] = { -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876, -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372, -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120, -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, 0, 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876, 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372, 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 0 }; #endif static Sint16 uLAW_To_PCM16(Uint8 u_val) { #ifdef SDL_WAVE_LAW_LUT return mulaw_lut[u_val]; #else Uint8 nibble = ~u_val; Sint16 mantissa = nibble & 0xf; Uint8 exponent = (nibble >> 4) & 0x7; Sint16 step = (Sint16)(4 << (exponent + 1)); mantissa = (Sint16)(0x80 << exponent) + step * mantissa + step / 2 - 132; return nibble & 0x80 ? -mantissa : mantissa; #endif } static Sint16 ALAW_To_PCM16(Uint8 a_val) { #ifdef SDL_WAVE_LAW_LUT return alaw_lut[a_val]; #else Uint8 nibble = a_val; Uint8 exponent = (nibble & 0x7f) ^ 0x55; Sint16 mantissa = exponent & 0xf; exponent >>= 4; if (exponent > 0) { mantissa |= 0x10; } mantissa = (Sint16)(mantissa << 4) | 0x8; if (exponent > 1) { mantissa <<= exponent - 1; } return nibble & 0x80 ? mantissa : -mantissa; #endif } static int fetch_xlaw(Sint16 (*decode_sample)(Uint8), void *context, int length) { WAV_Music *music = (WAV_Music *)context; int i = 0, o = 0; length = (int) SDL_ReadIO(music->src, music->buffer, (size_t)(length / 2)); if (length % music->samplesize != 0) { length -= length % music->samplesize; } for (i = length - 1, o = (length - 1) * 2; i >= 0; i--, o -= 2) { Uint16 decoded = (Uint16)decode_sample(music->buffer[i]); music->buffer[o] = decoded & 0xFF; music->buffer[o + 1] = (decoded >> 8) & 0xFF; } return length * 2; } static int fetch_ulaw(void *context, int length) { return fetch_xlaw(uLAW_To_PCM16, context, length); } static int fetch_alaw(void *context, int length) { return fetch_xlaw(ALAW_To_PCM16, context, length); } static Sint64 WAV_Position(WAV_Music *music) { return SDL_TellIO(music->src) - music->buffered; } /* Play some of a stream previously started with WAV_Play() */ static int WAV_GetSome(void *context, void *data, int bytes, bool *done) { WAV_Music *music = (WAV_Music *)context; Sint64 pos, stop; WAVLoopPoint *loop; Sint64 loop_start = music->start; Sint64 loop_stop = music->stop; bool looped = false; bool at_end = false; unsigned int i; int filled, amount, result; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (!music->play_count) { /* All done */ *done = true; return 0; } pos = WAV_Position(music); stop = music->stop; loop = NULL; for (i = 0; i < music->numloops; ++i) { loop = &music->loops[i]; if (loop->active) { const int bytes_per_sample = (SDL_AUDIO_BITSIZE(music->spec.format) / 8) * music->spec.channels; loop_start = music->start + loop->start * (Uint32)bytes_per_sample; loop_stop = music->start + (loop->stop + 1) * (Uint32)bytes_per_sample; if (pos >= loop_start && pos < loop_stop) { stop = loop_stop; break; } } loop = NULL; } amount = (int)music->buflen; if ((stop - pos) < amount) { amount = (int)(stop - pos); } amount = music->decode(music, amount); if (amount > 0) { result = SDL_PutAudioStreamData(music->stream, music->buffer, amount); if (result < 0) { return -1; } } else { /* We might be looping, continue */ at_end = true; } if (loop && WAV_Position(music) >= stop) { if (loop->current_play_count == 1) { loop->active = false; } else { if (loop->current_play_count > 0) { --loop->current_play_count; } if (SDL_SeekIO(music->src, loop_start, SDL_IO_SEEK_SET) < 0) return -1; looped = true; } } if (!looped && (at_end || WAV_Position(music) >= music->stop)) { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (WAV_Play(music, play_count) < 0) { return -1; } } } /* We'll get called again in the case where we looped or have more data */ return 0; } static int WAV_GetAudio(void *context, void *data, int bytes) { WAV_Music *music = (WAV_Music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, WAV_GetSome); } static int WAV_Seek(void *context, double position) { WAV_Music *music = (WAV_Music *)context; Sint64 destpos; if (music->encoding == MS_ADPCM_CODE || music->encoding == IMA_ADPCM_CODE) { Sint64 dest_offset = (Sint64)(position * music->spec.freq * ((double)music->adpcm_state.blocksize / music->adpcm_state.samplesperblock)); int remainder = (int)(dest_offset % music->adpcm_state.blocksize); dest_offset -= remainder; destpos = music->start + dest_offset; if (destpos > music->stop) { return -1; } if (SDL_SeekIO(music->src, destpos, SDL_IO_SEEK_SET) < 0) { return -1; } music->buffered = 0; music->adpcm_state.output.read = music->adpcm_state.output.pos; if (remainder > 0) { music->decode(music, remainder); } } else { Sint64 sample_size = music->spec.freq * music->samplesize; Sint64 dest_offset = (Sint64)(position * music->spec.freq * music->samplesize); destpos = music->start + dest_offset; destpos -= dest_offset % sample_size; if (destpos > music->stop) { return -1; } if (SDL_SeekIO(music->src, destpos, SDL_IO_SEEK_SET) < 0) { return -1; } } return 0; } static double WAV_Tell(void *context) { WAV_Music *music = (WAV_Music *)context; Sint64 byte_pos = WAV_Position(music) - music->start; Sint64 sample_pos; if (music->encoding == MS_ADPCM_CODE || music->encoding == IMA_ADPCM_CODE) { sample_pos = ((byte_pos * music->adpcm_state.samplesperblock) / music->adpcm_state.blocksize); } else { sample_pos = byte_pos / music->samplesize; } return (double)sample_pos / music->spec.freq; } /* Return music duration in seconds */ static double WAV_Duration(void *context) { WAV_Music *music = (WAV_Music *)context; Sint64 samples; if (music->encoding == MS_ADPCM_CODE || music->encoding == IMA_ADPCM_CODE) { samples = (((music->stop - music->start) * music->adpcm_state.samplesperblock) / music->adpcm_state.blocksize); } else { samples = (music->stop - music->start) / music->samplesize; } return (double)samples / music->spec.freq; } static const char* WAV_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { WAV_Music *music = (WAV_Music *)context; return meta_tags_get(&music->tags, tag_type); } /* Close the given WAV stream */ static void WAV_Delete(void *context) { WAV_Music *music = (WAV_Music *)context; /* Clean up associated data */ meta_tags_clear(&music->tags); if (music->loops) { SDL_free(music->loops); } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } if (music->closeio) { SDL_CloseIO(music->src); } ADPCM_Cleanup(&music->adpcm_state); SDL_free(music); } static bool ParseFMT(WAV_Music *wave, Uint32 chunk_length) { SDL_AudioSpec *spec = &wave->spec; WaveFMTEx fmt; size_t size; int bits; Uint8 *chunk; if (chunk_length < sizeof(fmt.format)) { SDL_SetError("Wave format chunk too small"); return false; } chunk = (Uint8 *)SDL_malloc(chunk_length); if (!chunk) { return false; } if (SDL_ReadIO(wave->src, chunk, chunk_length) != chunk_length) { SDL_SetError("Couldn't read %" SDL_PRIu32 " bytes from WAV file", chunk_length); SDL_free(chunk); return false; } size = (chunk_length >= sizeof(fmt)) ? sizeof(fmt) : sizeof(fmt.format); SDL_zero(fmt); SDL_memcpy(&fmt, chunk, size); wave->encoding = SDL_Swap16LE(fmt.format.encoding); if (wave->encoding == EXTENSIBLE_CODE) { if (size < sizeof(fmt)) { SDL_SetError("Wave format chunk too small"); SDL_free(chunk); return false; } wave->encoding = (Uint16)SDL_Swap32LE(fmt.subencoding); } /* Decode the audio data format */ switch (wave->encoding) { case PCM_CODE: case IEEE_FLOAT_CODE: wave->decode = fetch_pcm; break; case MULAW_CODE: wave->decode = fetch_ulaw; break; case ALAW_CODE: wave->decode = fetch_alaw; break; case MS_ADPCM_CODE: wave->decode = fetch_ms_adpcm; if (MS_ADPCM_Init(&wave->adpcm_state, chunk, chunk_length) < 0) { SDL_free(chunk); return false; } break; case IMA_ADPCM_CODE: wave->decode = fetch_ima_adpcm; if (IMA_ADPCM_Init(&wave->adpcm_state, chunk, chunk_length) < 0) { SDL_free(chunk); return false; } break; default: /* but NOT this */ SDL_SetError("Unknown WAVE data format"); SDL_free(chunk); return false; } SDL_free(chunk); spec->freq = (int)SDL_Swap32LE(fmt.format.frequency); bits = (int)SDL_Swap16LE(fmt.format.bitspersample); switch (bits) { case 4: switch(wave->encoding) { case MS_ADPCM_CODE: spec->format = SDL_AUDIO_S16; break; case IMA_ADPCM_CODE: spec->format = SDL_AUDIO_S16; break; default: goto unknown_bits; } break; case 8: switch(wave->encoding) { case PCM_CODE: spec->format = SDL_AUDIO_U8; break; case ALAW_CODE: spec->format = SDL_AUDIO_S16; break; case MULAW_CODE: spec->format = SDL_AUDIO_S16; break; default: goto unknown_bits; } break; case 16: switch(wave->encoding) { case PCM_CODE: spec->format = SDL_AUDIO_S16; break; default: goto unknown_bits; } break; case 24: switch(wave->encoding) { case PCM_CODE: wave->decode = fetch_pcm24le; spec->format = SDL_AUDIO_S32; break; default: goto unknown_bits; } break; case 32: switch(wave->encoding) { case PCM_CODE: spec->format = SDL_AUDIO_S32; break; case IEEE_FLOAT_CODE: spec->format = SDL_AUDIO_F32; break; default: goto unknown_bits; } break; case 64: switch(wave->encoding) { case IEEE_FLOAT_CODE: wave->decode = fetch_float64le; spec->format = SDL_AUDIO_F32; break; default: goto unknown_bits; } break; default: unknown_bits: SDL_SetError("Unknown PCM format with %d bits", bits); return false; } spec->channels = (Uint8) SDL_Swap16LE(fmt.format.channels); wave->samplesize = spec->channels * (bits / 8); /* SDL_CalculateAudioSpec */ wave->buflen = SDL_AUDIO_BITSIZE(spec->format) / 8; wave->buflen *= spec->channels; wave->buflen *= 4096; /* reasonable sample frame count */ return true; } static bool ParseDATA(WAV_Music *wave, Uint32 chunk_length) { wave->start = SDL_TellIO(wave->src); wave->stop = wave->start + chunk_length; if (SDL_SeekIO(wave->src, chunk_length, SDL_IO_SEEK_CUR) < 0) return false; return true; } static bool AddLoopPoint(WAV_Music *wave, Uint32 play_count, Uint32 start, Uint32 stop) { WAVLoopPoint *loop; WAVLoopPoint *loops = SDL_realloc(wave->loops, (wave->numloops + 1) * sizeof(*wave->loops)); if (!loops) { return false; } loop = &loops[ wave->numloops ]; loop->start = start; loop->stop = stop; loop->initial_play_count = play_count; loop->current_play_count = play_count; wave->loops = loops; ++wave->numloops; return true; } static bool ParseSMPL(WAV_Music *wave, Uint32 chunk_length) { SamplerChunk *chunk; Uint8 *data; Uint32 i; bool loaded = false; data = (Uint8 *)SDL_malloc(chunk_length); if (!data) { return false; } if (SDL_ReadIO(wave->src, data, chunk_length) != chunk_length) { SDL_SetError("Couldn't read %" SDL_PRIu32 " bytes from WAV file", chunk_length); SDL_free(data); return false; } chunk = (SamplerChunk *)data; for (i = 0; i < SDL_Swap32LE(chunk->sample_loops); ++i) { const Uint32 LOOP_TYPE_FORWARD = 0; Uint32 loop_type = SDL_Swap32LE(chunk->loops[i].type); if (loop_type == LOOP_TYPE_FORWARD) { AddLoopPoint(wave, SDL_Swap32LE(chunk->loops[i].play_count), SDL_Swap32LE(chunk->loops[i].start), SDL_Swap32LE(chunk->loops[i].end)); } } loaded = true; SDL_free(data); return loaded; } static void read_meta_field(Mix_MusicMetaTags *tags, Mix_MusicMetaTag tag_type, size_t *i, Uint32 chunk_length, Uint8 *data, size_t fieldOffset) { Uint32 len = 0; int isID3 = fieldOffset == 7; char *field = NULL; *i += 4; len = isID3 ? SDL_Swap32BE(*((Uint32 *)(data + *i))) : /* ID3 */ SDL_Swap32LE(*((Uint32 *)(data + *i))); /* LIST */ if (len > chunk_length) { return; /* Do nothing due to broken lenght */ } *i += fieldOffset; field = (char *)SDL_malloc(len + 1); SDL_memset(field, 0, (len + 1)); SDL_strlcpy(field, (char *)(data + *i), isID3 ? len - 1 : len); *i += len; meta_tags_set(tags, tag_type, field); SDL_free(field); } static bool ParseLIST(WAV_Music *wave, Uint32 chunk_length) { Uint8 *data; data = (Uint8 *)SDL_malloc(chunk_length); if (!data) { return false; } if (SDL_ReadIO(wave->src, data, chunk_length) != chunk_length) { SDL_SetError("Couldn't read %" SDL_PRIu32 " bytes from WAV file", chunk_length); SDL_free(data); return false; } if (SDL_strncmp((char *)data, "INFO", 4) == 0) { size_t i = 4; for (i = 4; i < chunk_length - 4;) { if(SDL_strncmp((char *)(data + i), "INAM", 4) == 0) { read_meta_field(&wave->tags, MIX_META_TITLE, &i, chunk_length, data, 4); continue; } else if(SDL_strncmp((char *)(data + i), "IART", 4) == 0) { read_meta_field(&wave->tags, MIX_META_ARTIST, &i, chunk_length, data, 4); continue; } else if(SDL_strncmp((char *)(data + i), "IALB", 4) == 0) { read_meta_field(&wave->tags, MIX_META_ALBUM, &i, chunk_length, data, 4); continue; } else if (SDL_strncmp((char *)(data + i), "BCPR", 4) == 0) { read_meta_field(&wave->tags, MIX_META_COPYRIGHT, &i, chunk_length, data, 4); continue; } i++; } } /* done: */ SDL_free(data); return true; } static bool ParseID3(WAV_Music *wave, Uint32 chunk_length) { bool loaded = true; Uint8 *data; data = (Uint8 *)SDL_malloc(chunk_length); if (!data) { return false; } if (SDL_ReadIO(wave->src, data, chunk_length) != chunk_length) { SDL_SetError("Couldn't read %" SDL_PRIu32 " bytes from WAV file", chunk_length); loaded = false; } if (loaded) { read_id3v2_from_mem(&wave->tags, data, chunk_length); } /* done: */ SDL_free(data); return loaded; } static bool LoadWAVMusic(WAV_Music *wave) { SDL_IOStream *src = wave->src; Uint32 chunk_type; Uint32 chunk_length; bool found_FMT = false; bool found_DATA = false; /* WAV magic header */ Uint32 wavelen; Uint32 WAVEmagic; meta_tags_init(&wave->tags); /* Check the magic header */ if (!SDL_ReadU32LE(src, &wavelen) || !SDL_ReadU32LE(src, &WAVEmagic)) { return false; } (void)wavelen; /* unused */ (void)WAVEmagic; /* unused */ /* Read the chunks */ for (; ;) { if (!SDL_ReadU32LE(src, &chunk_type) || !SDL_ReadU32LE(src, &chunk_length)) { if (SDL_GetIOStatus(src) == SDL_IO_STATUS_EOF) { break; } return false; } if (chunk_length == 0) break; switch (chunk_type) { case FMT: found_FMT = true; if (!ParseFMT(wave, chunk_length)) return false; break; case DATA: found_DATA = true; if (!ParseDATA(wave, chunk_length)) return false; break; case SMPL: if (!ParseSMPL(wave, chunk_length)) return false; break; case LIST: if (!ParseLIST(wave, chunk_length)) return false; break; case ID3_: if (!ParseID3(wave, chunk_length)) return false; break; default: if (SDL_SeekIO(src, chunk_length, SDL_IO_SEEK_CUR) < 0) return false; break; } /* RIFF chunks have a 2-byte alignment. Skip padding byte. */ if (chunk_length & 1) { if (SDL_SeekIO(src, 1, SDL_IO_SEEK_CUR) < 0) return false; } } if (!found_FMT) { SDL_SetError("Bad WAV file (no FMT chunk)"); return false; } if (!found_DATA) { SDL_SetError("Bad WAV file (no DATA chunk)"); return false; } return true; } /* I couldn't get SANE_to_double() to work, so I stole this from libsndfile. * I don't pretend to fully understand it. */ static Uint32 SANE_to_Uint32 (Uint8 *sanebuf) { /* Negative number? */ if (sanebuf[0] & 0x80) return 0; /* Less than 1? */ if (sanebuf[0] <= 0x3F) return 1; /* Way too big? */ if (sanebuf[0] > 0x40) return 0x4000000; /* Still too big? */ if (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C) return 800000000; return (Uint32)(((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7) | (sanebuf[5] >> 1)) >> (29 - sanebuf[1])); } static bool LoadAIFFMusic(WAV_Music *wave) { SDL_IOStream *src = wave->src; SDL_AudioSpec *spec = &wave->spec; bool found_SSND = false; bool found_COMM = false; bool found_FVER = false; bool is_AIFC = false; Uint32 chunk_type; Uint32 chunk_length; Sint64 next_chunk = 0; Sint64 file_length; /* AIFF magic header */ Uint32 AIFFmagic; /* SSND chunk */ Uint32 offset; Uint32 blocksize; /* COMM format chunk */ Uint16 channels = 0; Uint32 numsamples = 0; Uint16 samplesize = 0; Uint8 sane_freq[10]; Uint32 frequency = 0; Uint32 AIFCVersion1 = 0; Uint32 compressionType = 0; char *chunk_buffer; file_length = SDL_GetIOSize(src); /* Check the magic header */ if (!SDL_ReadU32BE(src, &chunk_length) || !SDL_ReadU32LE(src, &AIFFmagic)) { return false; } if (AIFFmagic != AIFF && AIFFmagic != AIFC) { SDL_SetError("Unrecognized file type (not AIFF or AIFC)"); return false; } if (AIFFmagic == AIFC) { is_AIFC = true; } /* From what I understand of the specification, chunks may appear in * any order, and we should just ignore unknown ones. * * TODO: Better sanity-checking. E.g. what happens if the AIFF file * contains compressed sound data? */ do { if (!SDL_ReadU32LE(src, &chunk_type) || !SDL_ReadU32BE(src, &chunk_length)) { return false; } next_chunk = SDL_TellIO(src) + chunk_length; if (chunk_length % 2) { next_chunk++; } switch (chunk_type) { case SSND: found_SSND = true; if (!SDL_ReadU32BE(src, &offset) || !SDL_ReadU32BE(src, &blocksize)) { return false; } wave->start = SDL_TellIO(src) + offset; (void)blocksize; /* unused */ break; case FVER: found_FVER = true; if (!SDL_ReadU32BE(src, &AIFCVersion1)) { return false; } (void)AIFCVersion1; /* unused */ break; case AIFF_ID3_: if (!ParseID3(wave, chunk_length)) return false; break; case MARK: case INST: /* Just skip those chunks */ break; case NAME: case AUTH: case _c__: chunk_buffer = (char*)SDL_calloc(1, chunk_length + 1); if (SDL_ReadIO(src, chunk_buffer, chunk_length) != chunk_length) { SDL_free(chunk_buffer); return false; } meta_tags_set(&wave->tags, chunk_type == NAME ? MIX_META_TITLE : chunk_type == AUTH ? MIX_META_ARTIST : chunk_type == _c__ ? MIX_META_COPYRIGHT : 0, chunk_buffer); SDL_free(chunk_buffer); break; case COMM: found_COMM = true; /* Read the audio data format chunk */ if (!SDL_ReadU16BE(src, &channels) || !SDL_ReadU32BE(src, &numsamples) || !SDL_ReadU16BE(src, &samplesize) || SDL_ReadIO(src, sane_freq, sizeof(sane_freq)) != sizeof(sane_freq)) { return false; } frequency = SANE_to_Uint32(sane_freq); if (is_AIFC) { if (!SDL_ReadU32LE(src, &compressionType)) { return false; } /* here must be a "compressionName" which is a padded string */ } break; default: /* Unknown/unsupported chunk: we just skip over */ break; } } while (next_chunk < file_length && SDL_SeekIO(src, next_chunk, SDL_IO_SEEK_SET) >= 0); if (!found_SSND) { SDL_SetError("Bad AIFF/AIFF-C file (no SSND chunk)"); return false; } if (!found_COMM) { SDL_SetError("Bad AIFF/AIFF-C file (no COMM chunk)"); return false; } if (is_AIFC && !found_FVER) { SDL_SetError("Bad AIFF-C file (no FVER chunk)"); return false; } wave->samplesize = channels * (samplesize / 8); wave->stop = wave->start + channels * numsamples * (samplesize / 8); /* Decode the audio data format */ SDL_memset(spec, 0, (sizeof *spec)); spec->freq = (int)frequency; switch (samplesize) { case 8: if (!is_AIFC) spec->format = SDL_AUDIO_S8; else switch (compressionType) { case raw_: spec->format = SDL_AUDIO_U8; break; case sowt: spec->format = SDL_AUDIO_S8; break; case ulaw: spec->format = SDL_AUDIO_S16LE; wave->encoding = MULAW_CODE; wave->decode = fetch_ulaw; break; case alaw: spec->format = SDL_AUDIO_S16LE; wave->encoding = ALAW_CODE; wave->decode = fetch_alaw; break; default: goto unsupported_format; } break; case 16: if (!is_AIFC) spec->format = SDL_AUDIO_S16BE; else switch (compressionType) { case sowt: spec->format = SDL_AUDIO_S16LE; break; case NONE: spec->format = SDL_AUDIO_S16BE; break; case ULAW: spec->format = SDL_AUDIO_S16LE; wave->encoding = MULAW_CODE; wave->decode = fetch_ulaw; break; case ALAW: spec->format = SDL_AUDIO_S16LE; wave->encoding = ALAW_CODE; wave->decode = fetch_alaw; break; default: goto unsupported_format; } break; case 24: wave->encoding = PCM_CODE; wave->decode = fetch_pcm24be; if (!is_AIFC) spec->format = SDL_AUDIO_S32BE; else switch (compressionType) { case sowt: spec->format = SDL_AUDIO_S32LE; break; case NONE: spec->format = SDL_AUDIO_S32BE; break; default: goto unsupported_format; } break; case 32: if (!is_AIFC) spec->format = SDL_AUDIO_S32BE; else switch (compressionType) { case sowt: spec->format = SDL_AUDIO_S32LE; break; case NONE: spec->format = SDL_AUDIO_S32BE; break; case fl32: case FL32: spec->format = SDL_AUDIO_F32BE; break; default: goto unsupported_format; } break; case 64: wave->encoding = IEEE_FLOAT_CODE; wave->decode = fetch_float64be; if (!is_AIFC) spec->format = SDL_AUDIO_F32; else switch (compressionType) { case fl64: spec->format = SDL_AUDIO_F32; break; default: goto unsupported_format; } break; default: unsupported_format: SDL_SetError("Unknown samplesize in data format"); return false; } spec->channels = (Uint8) channels; return true; } Mix_MusicInterface Mix_MusicInterface_WAV = { "WAVE", MIX_MUSIC_WAVE, MUS_WAV, false, false, NULL, /* Load */ NULL, /* Open */ WAV_CreateFromIO, NULL, /* CreateFromFile */ WAV_SetVolume, WAV_GetVolume, WAV_Play, NULL, /* IsPlaying */ WAV_GetAudio, NULL, /* Jump */ WAV_Seek, /* Seek */ WAV_Tell, /* Tell */ WAV_Duration, NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ WAV_GetMetaTag, /* GetMetaTag */ NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ WAV_Stop, /* Stop */ WAV_Delete, NULL, /* Close */ NULL /* Unload */ }; #endif /* MUSIC_WAV */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_wav.h000066400000000000000000000021471501405355700234120ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports streaming WAV files */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_WAV; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_wavpack.c000066400000000000000000000600041501405355700242400ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #if defined(MUSIC_WAVPACK) #define WAVPACK_DBG 0 /* This file supports WavPack music streams */ #include #include #include "music_wavpack.h" #if defined(WAVPACK_HEADER) #include WAVPACK_HEADER #elif defined(HAVE_WAVPACK_H) #include #else #include #endif #include /* SEEK_SET, ... */ #ifndef OPEN_DSD_NATIVE #define OPEN_DSD_NATIVE 0x100 #define OPEN_DSD_AS_PCM 0x200 #define WAVPACK4_OR_OLDER #endif #ifdef WAVPACK4_OR_OLDER typedef struct { int32_t (*read_bytes)(void *id, void *data, int32_t bcount); int32_t (*write_bytes)(void *id, void *data, int32_t bcount); int64_t (*get_pos)(void *id); int (*set_pos_abs)(void *id, int64_t pos); int (*set_pos_rel)(void *id, int64_t delta, int mode); int (*push_back_byte)(void *id, int c); int64_t (*get_length)(void *id); int (*can_seek)(void *id); int (*truncate_here)(void *id); int (*close)(void *id); } WavpackStreamReader64; #endif typedef struct { int loaded; void *handle; uint32_t libversion; uint32_t (*WavpackGetLibraryVersion)(void); char *(*WavpackGetErrorMessage)(WavpackContext*); WavpackContext *(*WavpackOpenFileInputEx)(WavpackStreamReader *reader, void *wv_id, void *wvc_id, char *error, int flags, int norm_offset); WavpackContext *(*WavpackCloseFile)(WavpackContext*); int (*WavpackGetMode)(WavpackContext*); int (*WavpackGetBytesPerSample)(WavpackContext*); int (*WavpackGetNumChannels)(WavpackContext*); uint32_t (*WavpackGetNumSamples)(WavpackContext*); uint32_t (*WavpackGetSampleRate)(WavpackContext*); uint32_t (*WavpackUnpackSamples)(WavpackContext*, int32_t *buffer, uint32_t samples); int (*WavpackSeekSample)(WavpackContext*, uint32_t sample); uint32_t (*WavpackGetSampleIndex)(WavpackContext*); int (*WavpackGetTagItem)(WavpackContext*, const char *item, char *value, int size); /* WavPack 5.x functions with 64 bit support: */ WavpackContext *(*WavpackOpenFileInputEx64)(WavpackStreamReader64 *reader, void *wv_id, void *wvc_id, char *error, int flags, int norm_offset); int64_t (*WavpackGetNumSamples64)(WavpackContext*); int64_t (*WavpackGetSampleIndex64)(WavpackContext*); int (*WavpackSeekSample64)(WavpackContext*, int64_t sample); } wavpack_loader; static wavpack_loader wvpk; #ifdef WAVPACK_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ wvpk.FUNC = (SIG) SDL_LoadFunction(wvpk.handle, #FUNC); \ if (wvpk.FUNC == NULL) { SDL_UnloadObject(wvpk.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ wvpk.FUNC = FUNC; \ if (wvpk.FUNC == NULL) { SDL_SetError("Missing wavpack.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int WAVPACK_Load(void) { if (wvpk.loaded == 0) { #ifdef WAVPACK_DYNAMIC wvpk.handle = SDL_LoadObject(WAVPACK_DYNAMIC); if (wvpk.handle == NULL) { return -1; } #endif FUNCTION_LOADER(WavpackGetLibraryVersion, uint32_t (*)(void)); FUNCTION_LOADER(WavpackGetErrorMessage, char *(*)(WavpackContext*)); FUNCTION_LOADER(WavpackOpenFileInputEx, WavpackContext *(*)(WavpackStreamReader*, void*, void*, char*, int, int)); FUNCTION_LOADER(WavpackCloseFile, WavpackContext *(*)(WavpackContext*)); FUNCTION_LOADER(WavpackGetMode, int (*)(WavpackContext*)); FUNCTION_LOADER(WavpackGetBytesPerSample, int (*)(WavpackContext*)); FUNCTION_LOADER(WavpackGetNumChannels, int (*)(WavpackContext*)); FUNCTION_LOADER(WavpackGetNumSamples, uint32_t (*)(WavpackContext*)); FUNCTION_LOADER(WavpackGetSampleRate, uint32_t (*)(WavpackContext*)); FUNCTION_LOADER(WavpackUnpackSamples, uint32_t (*)(WavpackContext*, int32_t*, uint32_t)); FUNCTION_LOADER(WavpackSeekSample, int (*)(WavpackContext*, uint32_t)); FUNCTION_LOADER(WavpackGetSampleIndex, uint32_t (*)(WavpackContext*)); FUNCTION_LOADER(WavpackGetTagItem, int (*)(WavpackContext*, const char*, char*, int)); /* WavPack 5.x functions with 64 bit support: */ #ifdef WAVPACK_DYNAMIC wvpk.WavpackOpenFileInputEx64 = (WavpackContext *(*)(WavpackStreamReader64*, void*, void*, char*, int, int)) SDL_LoadFunction(wvpk.handle, "WavpackOpenFileInputEx64"); wvpk.WavpackGetNumSamples64 = (int64_t (*)(WavpackContext*)) SDL_LoadFunction(wvpk.handle, "WavpackGetNumSamples64"); wvpk.WavpackGetSampleIndex64 = (int64_t (*)(WavpackContext*)) SDL_LoadFunction(wvpk.handle, "WavpackGetSampleIndex64"); wvpk.WavpackSeekSample64 = (int (*)(WavpackContext*, int64_t)) SDL_LoadFunction(wvpk.handle, "WavpackSeekSample64"); if (!wvpk.WavpackOpenFileInputEx64 || !wvpk.WavpackGetNumSamples64 || !wvpk.WavpackGetSampleIndex64 || !wvpk.WavpackSeekSample64) { wvpk.WavpackOpenFileInputEx64 = NULL; wvpk.WavpackGetNumSamples64 = NULL; wvpk.WavpackGetSampleIndex64 = NULL; wvpk.WavpackSeekSample64 = NULL; SDL_ClearError(); /* WavPack 5.x functions are optional. */ } #if WAVPACK_DBG else { SDL_Log("WavPack 5.x functions available"); } #endif #elif !defined(WAVPACK4_OR_OLDER) wvpk.WavpackOpenFileInputEx64 = WavpackOpenFileInputEx64; wvpk.WavpackGetNumSamples64 = WavpackGetNumSamples64; wvpk.WavpackGetSampleIndex64 = WavpackGetSampleIndex64; wvpk.WavpackSeekSample64 = WavpackSeekSample64; #else wvpk.WavpackOpenFileInputEx64 = NULL; wvpk.WavpackGetNumSamples64 = NULL; wvpk.WavpackGetSampleIndex64 = NULL; wvpk.WavpackSeekSample64 = NULL; #endif wvpk.libversion = wvpk.WavpackGetLibraryVersion(); #if WAVPACK_DBG SDL_Log("WavPack library version: 0x%x", wvpk.libversion); #endif } ++wvpk.loaded; return 0; } static void WAVPACK_Unload(void) { if (wvpk.loaded == 0) { return; } if (wvpk.loaded == 1) { #ifdef WAVPACK_DYNAMIC SDL_UnloadObject(wvpk.handle); #endif } --wvpk.loaded; } typedef struct { SDL_IOStream *src1; /* wavpack file */ SDL_IOStream *src2; /* correction file */ bool closeio; int play_count; int volume; WavpackContext *ctx; int64_t numsamples; uint32_t samplerate; int bps, channels, mode; #ifdef MUSIC_WAVPACK_DSD int decimation; void *decimation_ctx; #endif SDL_AudioStream *stream; void *buffer; int32_t frames; Mix_MusicMetaTags tags; } WAVPACK_music; static int32_t sdl_read_bytes(void *id, void *data, int32_t bcount) { return (int32_t) SDL_ReadIO((SDL_IOStream*)id, data, (size_t)bcount); } static uint32_t sdl_get_pos32(void *id) { return (uint32_t) SDL_TellIO((SDL_IOStream*)id); } static int64_t sdl_get_pos64(void *id) { return SDL_TellIO((SDL_IOStream*)id); } static int sdl_setpos_rel64(void *id, int64_t delta, int mode) { switch (mode) { /* just in case SDL_IO doesn't match stdio.. */ case SEEK_SET: mode = SDL_IO_SEEK_SET; break; case SEEK_CUR: mode = SDL_IO_SEEK_CUR; break; case SEEK_END: mode = SDL_IO_SEEK_END; break; default: return -1; } return (SDL_SeekIO((SDL_IOStream*)id, delta, mode) < 0)? -1 : 0; } static int sdl_setpos_rel32(void *id, int32_t delta, int mode) { return sdl_setpos_rel64(id, delta, mode); } static int sdl_setpos_abs64(void *id, int64_t pos) { return (SDL_SeekIO((SDL_IOStream*)id, pos, SDL_IO_SEEK_SET) < 0)? -1 : 0; } static int sdl_setpos_abs32(void *id, uint32_t pos) { return (SDL_SeekIO((SDL_IOStream*)id, pos, SDL_IO_SEEK_SET) < 0)? -1 : 0; } static int sdl_pushback_byte(void *id, int c) { (void)c; /* libwavpack calls ungetc(), but doesn't really modify buffer. */ return (SDL_SeekIO((SDL_IOStream*)id, -1, SDL_IO_SEEK_CUR) < 0)? -1 : 0; } static uint32_t sdl_get_length32(void *id) { return (uint32_t) SDL_GetIOSize((SDL_IOStream*)id); } static int64_t sdl_get_length64(void *id) { return SDL_GetIOSize((SDL_IOStream*)id); } static int sdl_can_seek(void *id) { return (SDL_SeekIO((SDL_IOStream*)id, 0, SDL_IO_SEEK_CUR) < 0)? 0 : 1; } static WavpackStreamReader sdl_reader32 = { sdl_read_bytes, sdl_get_pos32, sdl_setpos_abs32, sdl_setpos_rel32, sdl_pushback_byte, sdl_get_length32, sdl_can_seek, NULL /* write_bytes */ }; static WavpackStreamReader64 sdl_reader64 = { sdl_read_bytes, NULL, /* write_bytes */ sdl_get_pos64, sdl_setpos_abs64, sdl_setpos_rel64, sdl_pushback_byte, sdl_get_length64, sdl_can_seek, NULL, /* truncate_here */ NULL /* close */ }; static int WAVPACK_Seek(void *context, double time); static void WAVPACK_Delete(void *context); static void *WAVPACK_CreateFromIO_internal(SDL_IOStream *src1, SDL_IOStream *src2, bool closeio, bool *closeio2); #ifdef MUSIC_WAVPACK_DSD static void *decimation_init(int num_channels, int ratio); static int decimation_run(void *context, int32_t *samples, int num_samples); static void decimation_reset(void *context); #define FLAGS_DSD OPEN_DSD_AS_PCM #define DECIMATION(x) (x)->decimation #else #define FLAGS_DSD 0 #define DECIMATION(x) 1 #endif static void *WAVPACK_CreateFromIO(SDL_IOStream *src, bool closeio) { return WAVPACK_CreateFromIO_internal(src, NULL, closeio, NULL); } static void *WAVPACK_CreateFromFile(const char *file) { SDL_IOStream *src1, *src2; WAVPACK_music *music; bool closeio2 = true; size_t len; char *file2; src1 = SDL_IOFromFile(file, "rb"); if (!src1) { SDL_SetError("Couldn't open '%s'", file); return NULL; } len = SDL_strlen(file); file2 = SDL_stack_alloc(char, len + 2); if (!file2) src2 = NULL; else { /* this assumes 'file' is a good boy and has 'wv' as an extension. * official wavpack command line tools do the same thing so I'm not * doing anything extra. besides, the wavpack library validates the * correction file, therefore, no harm done. */ SDL_memcpy(file2, file, len); file2[len] = 'c'; file2[len + 1] = '\0'; src2 = SDL_IOFromFile(file2, "rb"); #if WAVPACK_DBG if (src2) { SDL_Log("Loaded WavPack correction file %s", file2); } #endif SDL_stack_free(file2); } music = WAVPACK_CreateFromIO_internal(src1, src2, true, &closeio2); if (!music) { SDL_CloseIO(src1); if (closeio2 && src2) { SDL_CloseIO(src2); } } return music; } /* Load a WavPack stream from an SDL_IOStream object */ static void *WAVPACK_CreateFromIO_internal(SDL_IOStream *src1, SDL_IOStream *src2, bool closeio, bool *closeio2) { SDL_AudioSpec srcspec; WAVPACK_music *music; SDL_AudioFormat format; char *tag; char err[80]; int n; music = (WAVPACK_music *)SDL_calloc(1, sizeof *music); if (!music) { return NULL; } music->src1 = src1; music->src2 = src2; music->volume = MIX_MAX_VOLUME; music->ctx = (wvpk.WavpackOpenFileInputEx64 != NULL) ? wvpk.WavpackOpenFileInputEx64(&sdl_reader64, src1, src2, err, OPEN_NORMALIZE|OPEN_TAGS|FLAGS_DSD, 0) : wvpk.WavpackOpenFileInputEx(&sdl_reader32, src1, src2, err, OPEN_NORMALIZE|OPEN_TAGS, 0); if (!music->ctx) { SDL_SetError("%s", err); SDL_free(music); if (src2) { SDL_CloseIO(src2); } return NULL; } music->numsamples = (wvpk.WavpackGetNumSamples64 != NULL) ? wvpk.WavpackGetNumSamples64(music->ctx) : wvpk.WavpackGetNumSamples(music->ctx); music->samplerate = wvpk.WavpackGetSampleRate(music->ctx); music->bps = wvpk.WavpackGetBytesPerSample(music->ctx) << 3; music->channels = wvpk.WavpackGetNumChannels(music->ctx); music->mode = wvpk.WavpackGetMode(music->ctx); if (closeio2) { *closeio2 = false; /* WAVPACK_Delete() will free it */ } #ifdef MUSIC_WAVPACK_DSD music->decimation = 1; /* for very high sample rates (including DSD, which will normally be 352,800 Hz) * decimate 4x here before sending on */ if (music->samplerate >= 256000) { music->decimation = 4; music->decimation_ctx = decimation_init(music->channels, music->decimation); if (!music->decimation_ctx) { WAVPACK_Delete(music); return NULL; } } #endif #if WAVPACK_DBG SDL_Log("WavPack loader:\n numsamples: %" SDL_PRIs64 "\n samplerate: %d\n bitspersample: %d\n channels: %d\n mode: 0x%x\n lossy: %d\n duration: %f\n", (Sint64)music->numsamples, music->samplerate, music->bps, music->channels, music->mode, !(music->mode & MODE_LOSSLESS), music->numsamples / (double)music->samplerate); #endif /* library returns the samples in 8, 16, 24, or 32 bit depth, but * always in an int32_t[] buffer, in signed host-endian format. */ switch (music->bps) { case 8: format = SDL_AUDIO_U8; break; case 16: format = SDL_AUDIO_S16; break; default: format = (music->mode & MODE_FLOAT) ? SDL_AUDIO_F32 : SDL_AUDIO_S32; break; } SDL_zero(srcspec); srcspec.format = format; srcspec.channels = music->channels; srcspec.freq = (int)music->samplerate / DECIMATION(music); music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { WAVPACK_Delete(music); return NULL; } music->frames = 4096/*music_spec.samples*/; music->buffer = SDL_malloc(music->frames * music->channels * sizeof(int32_t) * DECIMATION(music)); if (!music->buffer) { WAVPACK_Delete(music); return NULL; } tag = NULL; n = wvpk.WavpackGetTagItem(music->ctx, "TITLE", NULL, 0); if (n > 0) { tag = SDL_realloc(tag, (size_t)(++n)); wvpk.WavpackGetTagItem(music->ctx, "TITLE", tag, n); meta_tags_set(&music->tags, MIX_META_TITLE, tag); } n = wvpk.WavpackGetTagItem(music->ctx, "ARTIST", NULL, 0); if (n > 0) { tag = SDL_realloc(tag, (size_t)(++n)); wvpk.WavpackGetTagItem(music->ctx, "ARTIST", tag, n); meta_tags_set(&music->tags, MIX_META_ARTIST, tag); } n = wvpk.WavpackGetTagItem(music->ctx, "ALBUM", NULL, 0); if (n > 0) { tag = SDL_realloc(tag, (size_t)(++n)); wvpk.WavpackGetTagItem(music->ctx, "ALBUM", tag, n); meta_tags_set(&music->tags, MIX_META_ALBUM, tag); } n = wvpk.WavpackGetTagItem(music->ctx, "COPYRIGHT", NULL, 0); if (n > 0) { tag = SDL_realloc(tag, (size_t)(++n)); wvpk.WavpackGetTagItem(music->ctx, "COPYRIGHT", tag, n); meta_tags_set(&music->tags, MIX_META_COPYRIGHT, tag); } SDL_free(tag); music->closeio = closeio; return music; } static const char* WAVPACK_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { WAVPACK_music *music = (WAVPACK_music *)context; return meta_tags_get(&music->tags, tag_type); } static void WAVPACK_SetVolume(void *context, int volume) { WAVPACK_music *music = (WAVPACK_music *)context; music->volume = volume; } static int WAVPACK_GetVolume(void *context) { WAVPACK_music *music = (WAVPACK_music *)context; return music->volume; } /* Start playback of a given WavPack stream */ static int WAVPACK_Play(void *context, int play_count) { WAVPACK_music *music = (WAVPACK_music *)context; music->play_count = play_count; return WAVPACK_Seek(music, 0.0); } static void WAVPACK_Stop(void *context) { WAVPACK_music *music = (WAVPACK_music *)context; SDL_ClearAudioStream(music->stream); } /* Play some of a stream previously started with WAVPACK_play() */ static int WAVPACK_GetSome(void *context, void *data, int bytes, bool *done) { WAVPACK_music *music = (WAVPACK_music *)context; int amount; amount = SDL_GetAudioStreamData(music->stream, data, bytes); if (amount != 0) { return amount; } if (!music->play_count) { /* All done */ *done = true; return 0; } amount = (int) wvpk.WavpackUnpackSamples(music->ctx, music->buffer, music->frames * DECIMATION(music)); #ifdef MUSIC_WAVPACK_DSD if (amount && music->decimation_ctx) { amount = decimation_run(music->decimation_ctx, music->buffer, amount); } #endif if (amount) { int32_t *src = (int32_t *)music->buffer; int c = 0; amount *= music->channels; switch (music->bps) { case 8: { Uint8 *dst = (Uint8 *)music->buffer; for (; c < amount; ++c) { *dst++ = 0x80 ^ (Uint8)*src++; } } break; case 16: { Sint16 *dst = (Sint16 *)music->buffer; for (; c < amount; ++c) { *dst++ = *src++; } } amount *= sizeof(Sint16); break; case 24: for (; c < amount; ++c) { src[c] <<= 8; } /* FALLTHRU */ default: amount *= sizeof(Sint32); break; } if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) { return -1; } } else { if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (WAVPACK_Play(music, play_count) < 0) { return -1; } } } return 0; } static int WAVPACK_GetAudio(void *context, void *data, int bytes) { WAVPACK_music *music = (WAVPACK_music *)context; return music_pcm_getaudio(music, data, bytes, music->volume, WAVPACK_GetSome); } /* Jump (seek) to a given position (time is in seconds) */ static int WAVPACK_Seek(void *context, double time) { WAVPACK_music *music = (WAVPACK_music *)context; int64_t sample = (int64_t)(time * music->samplerate); int success = (wvpk.WavpackSeekSample64 != NULL) ? wvpk.WavpackSeekSample64(music->ctx, sample) : wvpk.WavpackSeekSample(music->ctx, (uint32_t)sample); if (!success) { SDL_SetError("%s", wvpk.WavpackGetErrorMessage(music->ctx)); return -1; } #ifdef MUSIC_WAVPACK_DSD if (music->decimation_ctx) { decimation_reset(music->decimation_ctx); } #endif return 0; } static double WAVPACK_Tell(void *context) { WAVPACK_music *music = (WAVPACK_music *)context; if (wvpk.WavpackGetSampleIndex64 != NULL) { return wvpk.WavpackGetSampleIndex64(music->ctx) / (double)music->samplerate; } return wvpk.WavpackGetSampleIndex(music->ctx) / (double)music->samplerate; } /* Return music duration in seconds */ static double WAVPACK_Duration(void *context) { WAVPACK_music *music = (WAVPACK_music *)context; return music->numsamples / (double)music->samplerate; } /* Close the given WavPack stream */ static void WAVPACK_Delete(void *context) { WAVPACK_music *music = (WAVPACK_music *)context; meta_tags_clear(&music->tags); wvpk.WavpackCloseFile(music->ctx); if (music->stream) { SDL_DestroyAudioStream(music->stream); } SDL_free(music->buffer); #ifdef MUSIC_WAVPACK_DSD SDL_free(music->decimation_ctx); #endif if (music->src2) { SDL_CloseIO(music->src2); } if (music->closeio) { SDL_CloseIO(music->src1); } SDL_free(music); } #ifdef MUSIC_WAVPACK_DSD /* Decimation code for playing DSD (which comes from the library already decimated 8x) */ /* Code provided by David Bryant. */ /* sinc low-pass filter, cutoff = fs/12, 80 terms */ #define NUM_TERMS 80 static const int32_t filter[NUM_TERMS] = { 50, 464, 968, 711, -1203, -5028, -9818, -13376, -12870, -6021, 7526, 25238, 41688, 49778, 43050, 18447, -21428, -67553, -105876, -120890, -100640, -41752, 47201, 145510, 224022, 252377, 208224, 86014, -97312, -301919, -470919, -541796, -461126, -199113, 239795, 813326, 1446343, 2043793, 2509064, 2763659, 2763659, 2509064, 2043793, 1446343, 813326, 239795, -199113, -461126, -541796, -470919, -301919, -97312, 86014, 208224, 252377, 224022, 145510, 47201, -41752, -100640, -120890, -105876, -67553, -21428, 18447, 43050, 49778, 41688, 25238, 7526, -6021, -12870, -13376, -9818, -5028, -1203, 711, 968, 464, 50 }; typedef struct chan_state { int32_t delay[NUM_TERMS]; int index, num_channels, ratio; } ChanState; static void *decimation_init(int num_channels, int ratio) { ChanState *sp = (ChanState *)SDL_calloc(num_channels, sizeof(ChanState)); if (sp) { int i = 0; for (; i < num_channels; ++i) { sp[i].num_channels = num_channels; sp[i].index = NUM_TERMS - ratio; sp[i].ratio = ratio; } } return sp; } /** FIXME: This isn't particularly easy on the CPU ! **/ static int decimation_run(void *context, int32_t *samples, int num_samples) { ChanState *sp = (ChanState *)context; int32_t *in_samples = samples; int32_t *out_samples = samples; const int num_channels = sp->num_channels; const int ratio = sp->ratio; int chan = 0; while (num_samples) { sp = (ChanState *)context + chan; sp->delay[sp->index++] = *in_samples++; if (sp->index == NUM_TERMS) { int64_t sum = 0; int i = 0; for (; i < NUM_TERMS; ++i) { sum += (int64_t)filter[i] * sp->delay[i]; } *out_samples++ = (int32_t)(sum >> 24); SDL_memmove(sp->delay, sp->delay + ratio, sizeof(sp->delay[0]) * (NUM_TERMS - ratio)); sp->index = NUM_TERMS - ratio; } if (++chan == num_channels) { num_samples--; chan = 0; } } return (int)(out_samples - samples) / num_channels; } static void decimation_reset(void *context) { ChanState *sp = (ChanState *)context; const int num_channels = sp->num_channels; const int ratio = sp->ratio; int i = 0; SDL_memset(sp, 0, sizeof(ChanState) * num_channels); for (; i < num_channels; ++i) { sp[i].num_channels = num_channels; sp[i].index = NUM_TERMS - ratio; sp[i].ratio = ratio; } } #endif /* MUSIC_WAVPACK_DSD */ Mix_MusicInterface Mix_MusicInterface_WAVPACK = { "WAVPACK", MIX_MUSIC_WAVPACK, MUS_WAVPACK, false, false, WAVPACK_Load, NULL, /* Open */ WAVPACK_CreateFromIO, WAVPACK_CreateFromFile, WAVPACK_SetVolume, WAVPACK_GetVolume, WAVPACK_Play, NULL, /* IsPlaying */ WAVPACK_GetAudio, NULL, /* Jump */ WAVPACK_Seek, WAVPACK_Tell, WAVPACK_Duration, NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ WAVPACK_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ WAVPACK_Stop, WAVPACK_Delete, NULL, /* Close */ WAVPACK_Unload }; #endif /* MUSIC_WAVPACK */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_wavpack.h000066400000000000000000000021571501405355700242520ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports streaming WavPack files */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_WAVPACK; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_xmp.c000066400000000000000000000320471501405355700234160ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef MUSIC_MOD_XMP #include #include "music_xmp.h" #ifdef LIBXMP_HEADER #include LIBXMP_HEADER #else #include #endif /* libxmp >= 4.5.0 constified several funcs */ /* and also added load using file callbacks */ #if (XMP_VERCODE < 0x040500) struct xmp_callbacks { unsigned long (*read_func)(void *, unsigned long, unsigned long, void *); int (*seek_func)(void *, long, int); long (*tell_func)(void *); int (*close_func)(void*); }; #define LIBXMP_CONST #else #define LIBXMP_CONST const #endif typedef struct { int loaded; void *handle; xmp_context (*xmp_create_context)(void); int (*xmp_load_module_from_memory)(xmp_context, LIBXMP_CONST void *, long); int (*xmp_load_module_from_callbacks)(xmp_context, void *, struct xmp_callbacks); int (*xmp_start_player)(xmp_context, int, int); void (*xmp_end_player)(xmp_context); void (*xmp_get_module_info)(xmp_context, struct xmp_module_info *); int (*xmp_play_buffer)(xmp_context, void *, int, int); int (*xmp_set_position)(xmp_context, int); int (*xmp_seek_time)(xmp_context, int); void (*xmp_get_frame_info)(xmp_context, struct xmp_frame_info *); void (*xmp_stop_module)(xmp_context); void (*xmp_release_module)(xmp_context); void (*xmp_free_context)(xmp_context); } xmp_loader; static xmp_loader libxmp; #ifdef XMP_DYNAMIC #define FUNCTION_LOADER(FUNC, SIG) \ libxmp.FUNC = (SIG) SDL_LoadFunction(libxmp.handle, #FUNC); \ if (libxmp.FUNC == NULL) { SDL_UnloadObject(libxmp.handle); return -1; } #else #define FUNCTION_LOADER(FUNC, SIG) \ libxmp.FUNC = FUNC; \ if (libxmp.FUNC == NULL) { SDL_SetError("Missing xmp.framework"); return -1; } #endif #ifdef __APPLE__ /* Need to turn off optimizations so weak framework load check works */ __attribute__ ((optnone)) #endif static int XMP_Load(void) { if (libxmp.loaded == 0) { #ifdef XMP_DYNAMIC libxmp.handle = SDL_LoadObject(XMP_DYNAMIC); if (libxmp.handle == NULL) { return -1; } #endif FUNCTION_LOADER(xmp_create_context, xmp_context(*)(void)) FUNCTION_LOADER(xmp_load_module_from_memory, int(*)(xmp_context,LIBXMP_CONST void *,long)) FUNCTION_LOADER(xmp_start_player, int(*)(xmp_context,int,int)) FUNCTION_LOADER(xmp_end_player, void(*)(xmp_context)) FUNCTION_LOADER(xmp_get_module_info, void(*)(xmp_context,struct xmp_module_info*)) FUNCTION_LOADER(xmp_play_buffer, int(*)(xmp_context,void*,int,int)) FUNCTION_LOADER(xmp_set_position, int(*)(xmp_context,int)) FUNCTION_LOADER(xmp_seek_time, int(*)(xmp_context,int)) FUNCTION_LOADER(xmp_get_frame_info, void(*)(xmp_context,struct xmp_frame_info*)) FUNCTION_LOADER(xmp_stop_module, void(*)(xmp_context)) FUNCTION_LOADER(xmp_release_module, void(*)(xmp_context)) FUNCTION_LOADER(xmp_free_context, void(*)(xmp_context)) #if defined(XMP_DYNAMIC) libxmp.xmp_load_module_from_callbacks = (int (*)(xmp_context,void*,struct xmp_callbacks)) SDL_LoadFunction(libxmp.handle, "xmp_load_module_from_callbacks"); if (libxmp.xmp_load_module_from_callbacks == NULL) { SDL_ClearError(); /* xmp_load_module_from_callbacks is optional. */ } #elif (XMP_VERCODE >= 0x040500) libxmp.xmp_load_module_from_callbacks = xmp_load_module_from_callbacks; #else libxmp.xmp_load_module_from_callbacks = NULL; #endif } ++libxmp.loaded; return 0; } static void XMP_Unload(void) { if (libxmp.loaded == 0) { return; } if (libxmp.loaded == 1) { #ifdef XMP_DYNAMIC SDL_UnloadObject(libxmp.handle); #endif } --libxmp.loaded; } typedef struct { SDL_IOStream *src; Sint64 src_offset; int volume; int play_count; struct xmp_module_info mi; struct xmp_frame_info fi; xmp_context ctx; SDL_AudioStream *stream; void *buffer; int buffer_size; Mix_MusicMetaTags tags; } XMP_Music; static int XMP_Seek(void *ctx, double pos); static void XMP_Delete(void *ctx); static void libxmp_set_error(int e) { const char *msg; switch (e) { case -XMP_ERROR_INTERNAL: msg = "Internal error in libxmp"; break; case -XMP_ERROR_FORMAT: msg = "Unrecognized file format"; break; case -XMP_ERROR_LOAD: msg = "Error loading file"; break; case -XMP_ERROR_DEPACK: msg = "Error depacking file"; break; case -XMP_ERROR_SYSTEM: msg = "System error in libxmp"; break; case -XMP_ERROR_INVALID: msg = "Invalid parameter"; break; case -XMP_ERROR_STATE: msg = "Invalid player state"; break; default: msg = "Unknown error"; break; } SDL_SetError("XMP: %s", msg); } static unsigned long xmp_fread(void *dst, unsigned long len, unsigned long nmemb, void *src) { XMP_Music *music = (XMP_Music *)src; if (len > 0 && nmemb > 0) { return SDL_ReadIO(music->src, dst, len * nmemb) / len; } return 0; } static int xmp_fseek(void *src, long offset, int whence) { XMP_Music *music = (XMP_Music *)src; Sint64 offset64 = (Sint64)offset; if (whence == SDL_IO_SEEK_SET) { offset64 += music->src_offset; } return (SDL_SeekIO(music->src, offset64, whence) < 0) ? -1 : 0; } static long xmp_ftell(void *src) { XMP_Music *music = (XMP_Music *)src; return (long)(SDL_TellIO(music->src) - music->src_offset); } /* Load a libxmp stream from an SDL_IOStream object */ void *XMP_CreateFromIO(SDL_IOStream *src, bool closeio) { SDL_AudioSpec srcspec; XMP_Music *music; struct xmp_callbacks file_callbacks = { xmp_fread, xmp_fseek, xmp_ftell, NULL }; int err; music = (XMP_Music *)SDL_calloc(1, sizeof(*music)); if (!music) { return NULL; } music->ctx = libxmp.xmp_create_context(); if (!music->ctx) { SDL_OutOfMemory(); goto e0; } music->buffer_size = 4096/*music_spec.samples*/ * 2 * 2; music->buffer = SDL_malloc((size_t)music->buffer_size); if (!music->buffer) { goto e1; } if (libxmp.xmp_load_module_from_callbacks) { music->src = src; music->src_offset = SDL_TellIO(src); err = libxmp.xmp_load_module_from_callbacks(music->ctx, music, file_callbacks); } else { size_t size; void *mem = SDL_LoadFile_IO(src, &size, false); if (!mem) { goto e1; } err = libxmp.xmp_load_module_from_memory(music->ctx, mem, (long)size); SDL_free(mem); } if (err < 0) { libxmp_set_error(err); goto e1; } err = libxmp.xmp_start_player(music->ctx, music_spec.freq, 0); if (err < 0) { libxmp_set_error(err); goto e2; } music->volume = MIX_MAX_VOLUME; SDL_zero(srcspec); srcspec.format = SDL_AUDIO_S16; srcspec.channels = 2; srcspec.freq = music_spec.freq; music->stream = SDL_CreateAudioStream(&srcspec, &music_spec); if (!music->stream) { goto e3; } meta_tags_init(&music->tags); libxmp.xmp_get_module_info(music->ctx, &music->mi); if (music->mi.mod->name[0]) { meta_tags_set(&music->tags, MIX_META_TITLE, music->mi.mod->name); } if (music->mi.comment) { meta_tags_set(&music->tags, MIX_META_COPYRIGHT, music->mi.comment); } if (closeio) { SDL_CloseIO(src); } return music; e3: libxmp.xmp_end_player(music->ctx); e2: libxmp.xmp_release_module(music->ctx); e1: libxmp.xmp_free_context(music->ctx); e0: SDL_free(music->buffer); SDL_free(music); return NULL; } /* Set the volume for a libxmp stream */ static void XMP_SetVolume(void *context, int volume) { XMP_Music *music = (XMP_Music *)context; music->volume = volume; } /* Get the volume for a libxmp stream */ static int XMP_GetVolume(void *context) { XMP_Music *music = (XMP_Music *)context; return music->volume; } /* Start playback of a given libxmp stream */ static int XMP_Play(void *context, int play_count) { XMP_Music *music = (XMP_Music *)context; music->play_count = play_count; return XMP_Seek(music, 0); } /* Clean-up the output buffer */ static void XMP_Stop(void *context) { XMP_Music *music = (XMP_Music *)context; SDL_ClearAudioStream(music->stream); } /* Play some of a stream previously started with xmp_play() */ static int XMP_GetSome(void *context, void *data, int bytes, bool *done) { XMP_Music *music = (XMP_Music *)context; int filled, amount, ret; filled = SDL_GetAudioStreamData(music->stream, data, bytes); if (filled != 0) { return filled; } if (!music->play_count) { /* All done */ *done = true; return 0; } /* if the data write is partial, rest of the buffer will be zero-filled. * the loop param is the max number that the current sequence of song * will be looped, or 0 to disable loop checking: 0 for play_count < 0 * for an endless loop, or 1 for our own loop checks to do their job. */ ret = libxmp.xmp_play_buffer(music->ctx, music->buffer, music->buffer_size, (music->play_count > 0)); amount = music->buffer_size; if (ret == 0) { if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) { return -1; } } else { if (ret != -XMP_END) { return -1; } if (music->play_count == 1) { music->play_count = 0; SDL_FlushAudioStream(music->stream); } else { int play_count = -1; if (music->play_count > 0) { play_count = (music->play_count - 1); } if (XMP_Play(music, play_count) < 0) { return -1; } } } return 0; } static int XMP_GetAudio(void *context, void *data, int bytes) { XMP_Music *music = (XMP_Music *)context; return music_pcm_getaudio(context, data, bytes, music->volume, XMP_GetSome); } /* Jump to a given order */ static int XMP_Jump(void *context, int order) { XMP_Music *music = (XMP_Music *)context; return libxmp.xmp_set_position(music->ctx, order); } /* Jump (seek) to a given position */ static int XMP_Seek(void *context, double pos) { XMP_Music *music = (XMP_Music *)context; libxmp.xmp_seek_time(music->ctx, (int)(pos * 1000)); libxmp.xmp_play_buffer(music->ctx, NULL, 0, 0); /* reset internal state. */ return 0; } static double XMP_Tell(void *context) { XMP_Music *music = (XMP_Music *)context; libxmp.xmp_get_frame_info(music->ctx, &music->fi); return music->fi.time / 1000.0; } static double XMP_Duration(void *context) { XMP_Music *music = (XMP_Music *)context; libxmp.xmp_get_frame_info(music->ctx, &music->fi); return music->fi.total_time / 1000.0; } static const char* XMP_GetMetaTag(void *context, Mix_MusicMetaTag tag_type) { XMP_Music *music = (XMP_Music *)context; return meta_tags_get(&music->tags, tag_type); } /* Close the given libxmp stream */ static void XMP_Delete(void *context) { XMP_Music *music = (XMP_Music *)context; meta_tags_clear(&music->tags); if (music->ctx) { libxmp.xmp_stop_module(music->ctx); libxmp.xmp_end_player(music->ctx); libxmp.xmp_release_module(music->ctx); libxmp.xmp_free_context(music->ctx); } if (music->stream) { SDL_DestroyAudioStream(music->stream); } if (music->buffer) { SDL_free(music->buffer); } SDL_free(music); } Mix_MusicInterface Mix_MusicInterface_XMP = { "XMP", MIX_MUSIC_LIBXMP, MUS_MOD, false, false, XMP_Load, NULL, /* Open */ XMP_CreateFromIO, NULL, /* CreateFromFile */ XMP_SetVolume, XMP_GetVolume, XMP_Play, NULL, /* IsPlaying */ XMP_GetAudio, XMP_Jump, XMP_Seek, XMP_Tell, XMP_Duration, NULL, /* LoopStart */ NULL, /* LoopEnd */ NULL, /* LoopLength */ XMP_GetMetaTag, NULL, /* GetNumTracks */ NULL, /* StartTrack */ NULL, /* Pause */ NULL, /* Resume */ XMP_Stop, XMP_Delete, NULL, /* Close */ XMP_Unload }; #endif /* MUSIC_MOD_XMP */ /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/music_xmp.h000066400000000000000000000021611501405355700234150ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* This file supports playing MOD files with libxmp */ #include "music.h" extern Mix_MusicInterface Mix_MusicInterface_XMP; /* vi: set ts=4 sw=4 expandtab: */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/000077500000000000000000000000001501405355700235305ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi.h000066400000000000000000000030301501405355700261650ustar00rootroot00000000000000/* native_midi: Hardware Midi support for the SDL_mixer library Copyright (C) 2000 Florian 'Proff' Schulze This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef NATIVE_MIDI_H_ #define NATIVE_MIDI_H_ #include typedef struct _NativeMidiSong NativeMidiSong; bool native_midi_detect(void); NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio); void native_midi_freesong(NativeMidiSong *song); void native_midi_start(NativeMidiSong *song, int loops); void native_midi_pause(void); void native_midi_resume(void); void native_midi_stop(void); bool native_midi_active(void); void native_midi_setvolume(int volume); const char *native_midi_error(void); #endif /* NATIVE_MIDI_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi_common.c000066400000000000000000000270431501405355700275420ustar00rootroot00000000000000/* native_midi: Hardware Midi support for the SDL_mixer library Copyright (C) 2000,2001 Florian 'Proff' Schulze This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include "native_midi_common.h" #include /* The constant 'MThd' */ #define MIDI_MAGIC 0x4d546864 /* The constant 'RIFF' */ #define RIFF_MAGIC 0x52494646 /* A single midi track as read from the midi file */ typedef struct { Uint8 *data; /* MIDI message stream */ int len; /* length of the track data */ } MIDITrack; /* A midi file, stripped down to the absolute minimum - divison & track data */ typedef struct { int division; /* number of pulses per quarter note (ppqn) */ int nTracks; /* number of tracks */ MIDITrack *track; /* tracks */ } MIDIFile; /* Some macros that help us stay endianess-independant */ #if SDL_BYTEORDER == SDL_BIG_ENDIAN #define BE_SHORT(x) (x) #define BE_LONG(x) (x) #else #define BE_SHORT(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF)) #define BE_LONG(x) ((((x)&0x0000FF)<<24) | \ (((x)&0x00FF00)<<8) | \ (((x)&0xFF0000)>>8) | \ (((x)>>24)&0xFF)) #endif /* Get Variable Length Quantity */ static int GetVLQ(MIDITrack *track, int *currentPos) { int l = 0; Uint8 c; while (1) { c = track->data[*currentPos]; (*currentPos)++; l += (c & 0x7f); if (!(c & 0x80)) { return l; } l <<= 7; } } /* Create a single MIDIEvent */ static MIDIEvent *CreateEvent(Uint32 time, Uint8 event, Uint8 a, Uint8 b) { MIDIEvent *newEvent; newEvent = SDL_calloc(1, sizeof(MIDIEvent)); if (newEvent) { newEvent->time = time; newEvent->status = event; newEvent->data[0] = a; newEvent->data[1] = b; } return newEvent; } /* Convert a single midi track to a list of MIDIEvents */ static MIDIEvent *MIDITracktoStream(MIDITrack *track) { Uint32 atime = 0; Uint32 len = 0; Uint8 event,type,a,b; Uint8 laststatus = 0; Uint8 lastchan = 0; int currentPos = 0; int end = 0; MIDIEvent *head = CreateEvent(0,0,0,0); /* dummy event to make handling the list easier */ MIDIEvent *currentEvent = head; while (!end) { if (currentPos >= track->len) { break; /* End of data stream reached */ } atime += GetVLQ(track, ¤tPos); event = track->data[currentPos++]; /* Handle SysEx seperatly */ if (((event>>4) & 0x0F) == MIDI_STATUS_SYSEX) { if (event == 0xFF) { type = track->data[currentPos]; currentPos++; switch(type) { case 0x2f: /* End of data marker */ end = 1; case 0x51: /* Tempo change */ /* a=track->data[currentPos]; b=track->data[currentPos+1]; c=track->data[currentPos+2]; AddEvent(song, atime, MEVT_TEMPO, c, b, a); */ break; } } else { type = 0; } len = GetVLQ(track, ¤tPos); /* Create an event and attach the extra data, if any */ currentEvent->next = CreateEvent(atime, event, type, 0); currentEvent = currentEvent->next; if (NULL == currentEvent) { FreeMIDIEventList(head); return NULL; } if (len) { currentEvent->extraLen = len; currentEvent->extraData = SDL_malloc(len); SDL_memcpy(currentEvent->extraData, &(track->data[currentPos]), len); currentPos += len; } } else { a = event; if (a & 0x80) { /* It's a status byte */ /* Extract channel and status information */ lastchan = a & 0x0F; laststatus = (a>>4) & 0x0F; /* Read the next byte which should always be a data byte */ a = track->data[currentPos++] & 0x7F; } switch(laststatus) { case MIDI_STATUS_NOTE_OFF: case MIDI_STATUS_NOTE_ON: /* Note on */ case MIDI_STATUS_AFTERTOUCH: /* Key Pressure */ case MIDI_STATUS_CONTROLLER: /* Control change */ case MIDI_STATUS_PITCH_WHEEL: /* Pitch wheel */ b = track->data[currentPos++] & 0x7F; currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, b); currentEvent = currentEvent->next; if (NULL == currentEvent) { FreeMIDIEventList(head); return NULL; } break; case MIDI_STATUS_PROG_CHANGE: /* Program change */ case MIDI_STATUS_PRESSURE: /* Channel pressure */ a &= 0x7f; currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, 0); currentEvent = currentEvent->next; if (NULL == currentEvent) { FreeMIDIEventList(head); return NULL; } break; default: /* Sysex already handled above */ break; } } } currentEvent = head->next; SDL_free(head); /* release the dummy head event */ return currentEvent; } /* * Convert a midi song, consisting of up to 32 tracks, to a list of MIDIEvents. * To do so, first convert the tracks seperatly, then interweave the resulting * MIDIEvent-Lists to one big list. */ static MIDIEvent *MIDItoStream(MIDIFile *mididata) { MIDIEvent **track; MIDIEvent *head = CreateEvent(0,0,0,0); /* dummy event to make handling the list easier */ MIDIEvent *currentEvent = head; int trackID; if (NULL == head) { return NULL; } track = (MIDIEvent**) SDL_calloc(1, sizeof(MIDIEvent*) * mididata->nTracks); if (NULL == track) { SDL_free(head); return NULL; } /* First, convert all tracks to MIDIEvent lists */ for (trackID = 0; trackID < mididata->nTracks; trackID++) { track[trackID] = MIDITracktoStream(&mididata->track[trackID]); } /* Now, merge the lists. */ /* TODO */ while (1) { Uint32 lowestTime = 0x7FFFFFFF; /* INT_MAX */ int currentTrackID = -1; /* Find the next event */ for (trackID = 0; trackID < mididata->nTracks; trackID++) { if (track[trackID] && (track[trackID]->time < lowestTime)) { currentTrackID = trackID; lowestTime = track[currentTrackID]->time; } } /* Check if we processes all events */ if (currentTrackID == -1) { break; } currentEvent->next = track[currentTrackID]; track[currentTrackID] = track[currentTrackID]->next; currentEvent = currentEvent->next; lowestTime = 0; } /* Make sure the list is properly terminated */ currentEvent->next = 0; currentEvent = head->next; SDL_free(track); SDL_free(head); /* release the dummy head event */ return currentEvent; } static int ReadMIDIFile(MIDIFile *mididata, SDL_IOStream *src) { int i = -1; Uint32 ID = 0; Uint32 size = 0; Uint16 format = 0; Uint16 tracks = 0; Uint16 division = 0; if (!mididata) { return 0; } if (!src) { return 0; } /* Make sure this is really a MIDI file */ if (SDL_ReadIO(src, &ID, 4) != 4) { return 0; } if (BE_LONG(ID) == RIFF_MAGIC) { SDL_SeekIO(src, 16, SDL_IO_SEEK_CUR); if (SDL_ReadIO(src, &ID, 4) != 4) { return 0; } } if (BE_LONG(ID) != MIDI_MAGIC) { return 0; } /* Header size must be 6 */ if (SDL_ReadIO(src, &size, 4) != 4) { return 0; } size = BE_LONG(size); if (size != 6) { return 0; } /* We only support format 0 and 1, but not 2 */ if (SDL_ReadIO(src, &format, 2) != 2) { return 0; } format = BE_SHORT(format); if (format != 0 && format != 1) { return 0; } if (SDL_ReadIO(src, &tracks, 2) != 2) { return 0; } tracks = BE_SHORT(tracks); mididata->nTracks = tracks; /* Allocate tracks */ mididata->track = (MIDITrack*) SDL_calloc(1, sizeof(MIDITrack) * mididata->nTracks); if (NULL == mididata->track) { goto bail; } /* Retrieve the PPQN value, needed for playback */ if (SDL_ReadIO(src, &division, 2) != 2) { goto bail; } mididata->division = BE_SHORT(division); for (i = 0; i < tracks; i++) { if (SDL_ReadIO(src, &ID, 4) != 4) { /* We might want to verify this is MTrk... */ goto bail; } if (SDL_ReadIO(src, &size, 4) != 4) { goto bail; } size = BE_LONG(size); mididata->track[i].len = size; mididata->track[i].data = SDL_malloc(size); if (NULL == mididata->track[i].data) { goto bail; } if (SDL_ReadIO(src, mididata->track[i].data, size) != size) { goto bail; } } return 1; bail: for (; i >= 0; i--) { if (mididata->track[i].data) { SDL_free(mididata->track[i].data); } } SDL_free(mididata->track); return 0; } MIDIEvent *CreateMIDIEventList(SDL_IOStream *src, Uint16 *division) { MIDIFile *mididata = NULL; MIDIEvent *eventList; int trackID; mididata = SDL_calloc(1, sizeof(MIDIFile)); if (!mididata) { return NULL; } /* Open the file */ if (src != NULL) { /* Read in the data */ if (!ReadMIDIFile(mididata, src)) { SDL_free(mididata); return NULL; } } else { SDL_free(mididata); return NULL; } if (division) { *division = mididata->division; } eventList = MIDItoStream(mididata); if (eventList == NULL) { SDL_free(mididata); return NULL; } for (trackID = 0; trackID < mididata->nTracks; trackID++) { if (mididata->track[trackID].data) { SDL_free(mididata->track[trackID].data); } } SDL_free(mididata->track); SDL_free(mididata); return eventList; } void FreeMIDIEventList(MIDIEvent *head) { MIDIEvent *cur, *next; cur = head; while (cur) { next = cur->next; if (cur->extraData) { SDL_free(cur->extraData); } SDL_free(cur); cur = next; } } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi_common.h000066400000000000000000000043351501405355700275460ustar00rootroot00000000000000/* native_midi: Hardware Midi support for the SDL_mixer library Copyright (C) 2000,2001 Florian 'Proff' Schulze This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef _NATIVE_MIDI_COMMON_H_ #define _NATIVE_MIDI_COMMON_H_ #include /* Midi Status Bytes */ #define MIDI_STATUS_NOTE_OFF 0x8 #define MIDI_STATUS_NOTE_ON 0x9 #define MIDI_STATUS_AFTERTOUCH 0xA #define MIDI_STATUS_CONTROLLER 0xB #define MIDI_STATUS_PROG_CHANGE 0xC #define MIDI_STATUS_PRESSURE 0xD #define MIDI_STATUS_PITCH_WHEEL 0xE #define MIDI_STATUS_SYSEX 0xF /* We store the midi events in a linked list; this way it is easy to shuffle the tracks together later on; and we are flexible in the size of each elemnt. */ typedef struct MIDIEvent { Uint32 time; /* Time at which this midi events occurs */ Uint8 status; /* Status byte */ Uint8 data[2]; /* 1 or 2 bytes additional data for most events */ Uint32 extraLen; /* For some SysEx events, we need additional storage */ Uint8 *extraData; struct MIDIEvent *next; } MIDIEvent; /* Load a midifile to memory, converting it to a list of MIDIEvents. This function returns a linked lists of MIDIEvents, 0 if an error occured. */ MIDIEvent *CreateMIDIEventList(SDL_IOStream *src, Uint16 *division); /* Release a MIDIEvent list after usage. */ void FreeMIDIEventList(MIDIEvent *head); #endif /* _NATIVE_MIDI_COMMON_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi_haiku.cpp000066400000000000000000000150501501405355700277060ustar00rootroot00000000000000/* native_midi_haiku: Native Midi support on Haiku for the SDL_mixer library Copyright (C) 2010 Egor Suvorov This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #ifdef SDL_PLATFORM_HAIKU #include #include #include #include #include #include #include #include extern "C" { #include "native_midi.h" #include "native_midi_common.h" } bool compareMIDIEvent(const MIDIEvent &a, const MIDIEvent &b) { return a.time < b.time; } class MidiEventsStore : public BMidi { public: MidiEventsStore() { fPlaying = false; fLoops = 0; } virtual status_t Import(SDL_IOStream *src) { fEvs = CreateMIDIEventList(src, &fDivision); if (!fEvs) { return B_BAD_MIDI_DATA; } fTotal = 0; for (MIDIEvent *x = fEvs; x; x = x->next) fTotal++; fPos = fTotal; sort_events(); return B_OK; } virtual void Run() { fPlaying = true; fPos = 0; MIDIEvent *ev = fEvs; uint32 startTime = B_NOW; while (KeepRunning()) { if (!ev) { if (fLoops && fEvs) { if (fLoops > 0) --fLoops; fPos = 0; ev = fEvs; } else break; } SprayEvent(ev, ev->time + startTime); ev = ev->next; fPos++; } fPos = fTotal; fPlaying = false; } virtual ~MidiEventsStore() { if (!fEvs) return; FreeMIDIEventList(fEvs); fEvs = 0; } bool IsPlaying() { return fPlaying; } void SetLoops(int loops) { fLoops = loops; } protected: MIDIEvent *fEvs; Uint16 fDivision; int fPos, fTotal; int fLoops; bool fPlaying; void SprayEvent(MIDIEvent *ev, uint32 time) { switch (ev->status & 0xF0) { case B_NOTE_OFF: SprayNoteOff((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time); break; case B_NOTE_ON: SprayNoteOn((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time); break; case B_KEY_PRESSURE: SprayKeyPressure((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time); break; case B_CONTROL_CHANGE: SprayControlChange((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time); break; case B_PROGRAM_CHANGE: SprayProgramChange((ev->status & 0x0F) + 1, ev->data[0], time); break; case B_CHANNEL_PRESSURE: SprayChannelPressure((ev->status & 0x0F) + 1, ev->data[0], time); break; case B_PITCH_BEND: SprayPitchBend((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time); break; case 0xF: switch (ev->status) { case B_SYS_EX_START: SpraySystemExclusive(ev->extraData, ev->extraLen, time); break; case B_MIDI_TIME_CODE: case B_SONG_POSITION: case B_SONG_SELECT: case B_CABLE_MESSAGE: case B_TUNE_REQUEST: case B_SYS_EX_END: SpraySystemCommon(ev->status, ev->data[0], ev->data[1], time); break; case B_TIMING_CLOCK: case B_START: case B_STOP: case B_CONTINUE: case B_ACTIVE_SENSING: SpraySystemRealTime(ev->status, time); break; case B_SYSTEM_RESET: if (ev->data[0] == 0x51 && ev->data[1] == 0x03) { assert(ev->extraLen == 3); int val = (ev->extraData[0] << 16) | (ev->extraData[1] << 8) | ev->extraData[2]; int tempo = 60000000 / val; SprayTempoChange(tempo, time); } else { SpraySystemRealTime(ev->status, time); } } break; } } void sort_events() { MIDIEvent *items = new MIDIEvent[fTotal]; MIDIEvent *x = fEvs; for (int i = 0; i < fTotal; i++) { memcpy(items + i, x, sizeof(MIDIEvent)); x = x->next; } std::sort(items, items + fTotal, compareMIDIEvent); x = fEvs; for (int i = 0; i < fTotal; i++) { MIDIEvent *ne = x->next; memcpy(x, items + i, sizeof(MIDIEvent)); x->next = ne; x = ne; } for (x = fEvs; x && x->next; x = x->next) assert(x->time <= x->next->time); delete[] items; } }; BMidiSynth synth; struct _NativeMidiSong { MidiEventsStore *store; } *currentSong = NULL; char lasterr[1024]; bool native_midi_detect(void) { status_t res = synth.EnableInput(true, false); return res == B_OK; } void native_midi_setvolume(int volume) { if (volume < 0) volume = 0; if (volume > 128) volume = 128; synth.SetVolume(volume / 128.0); } NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio) { NativeMidiSong *song = new NativeMidiSong; song->store = new MidiEventsStore; status_t res = song->store->Import(src); if (res != B_OK) { snprintf(lasterr, sizeof lasterr, "Cannot Import() midi file: status_t=%d", res); delete song->store; delete song; return NULL; } else { if (closeio) { SDL_CloseIO(src); } } return song; } void native_midi_freesong(NativeMidiSong *song) { if (song == NULL) return; song->store->Stop(); song->store->Disconnect(&synth); if (currentSong == song) { currentSong = NULL; } delete song->store; delete song; song = 0; } void native_midi_start(NativeMidiSong *song, int loops) { native_midi_stop(); song->store->Connect(&synth); song->store->SetLoops(loops); song->store->Start(); currentSong = song; } void native_midi_pause(void) { } void native_midi_resume(void) { } void native_midi_stop(void) { if (currentSong == NULL) return; currentSong->store->Stop(); currentSong->store->Disconnect(&synth); while (currentSong->store->IsPlaying()) usleep(1000); currentSong = NULL; } bool native_midi_active(void) { if (currentSong == NULL) return false; return currentSong->store->IsPlaying(); } const char* native_midi_error(void) { return lasterr; } #endif /* SDL_PLATFORM_HAIKU */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi_linux_alsa.c000066400000000000000000000742711501405355700304160ustar00rootroot00000000000000/* native_midi_linux_alsa: Native Midi support on Linux for the SDL_mixer library Copyright (C) 2024 Tasos Sahanidis This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #ifdef SDL_PLATFORM_LINUX #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include "native_midi.h" #include "native_midi_common.h" #include #include #include #include #include #include #include #include #include #include #include //#define SDL_NATIVE_MIDI_ALSA_DYNAMIC "libasound.so.2" static int load_alsa_syms(void); #ifdef SDL_NATIVE_MIDI_ALSA_DYNAMIC #define snd_seq_client_info_sizeof ALSA_snd_seq_client_info_sizeof #define snd_seq_port_info_sizeof ALSA_snd_seq_port_info_sizeof #define snd_seq_queue_tempo_sizeof ALSA_snd_seq_queue_tempo_sizeof #define snd_seq_control_queue ALSA_snd_seq_control_queue static void *alsa_handle = NULL; static int load_alsa_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(alsa_handle, fn); if (!*addr) { // Don't call SDL_SetError(): SDL_LoadFunction already did. return 0; } return 1; } // cast funcs to char* first, to please GCC's strict aliasing rules. #define SDL_ALSA_SYM(x) \ if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \ return -1 static void unload_alsa_library(void) { if (alsa_handle) { SDL_UnloadObject(alsa_handle); alsa_handle = NULL; } } static int load_alsa_library(void) { int retval = 0; if (!alsa_handle) { alsa_handle = SDL_LoadObject(SDL_NATIVE_MIDI_ALSA_DYNAMIC); if (!alsa_handle) { retval = -1; // Don't call SDL_SetError(): SDL_LoadObject already did. } else { retval = load_alsa_syms(); if (retval < 0) { unload_alsa_library(); } } } return retval; } #else #define SDL_ALSA_SYM(x) ALSA_##x = x static void unload_alsa_library(void) { } static int load_alsa_library(void) { return load_alsa_syms(); } #endif // SDL_NATIVE_MIDI_ALSA_DYNAMIC static int (*ALSA_snd_seq_alloc_named_queue)(snd_seq_t *seq, const char *name); static int (*ALSA_snd_seq_client_id)(snd_seq_t *handle); static int (*ALSA_snd_seq_client_info_get_client)(const snd_seq_client_info_t *info); static size_t (*ALSA_snd_seq_client_info_sizeof)(void); static int (*ALSA_snd_seq_close)(snd_seq_t *handle); static int (*ALSA_snd_seq_connect_to)(snd_seq_t *seq, int my_port, int dest_client, int dest_port); static int (*ALSA_snd_seq_control_queue)(snd_seq_t *seq, int q, int type, int value, snd_seq_event_t *ev); static int (*ALSA_snd_seq_create_simple_port)(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type); static int (*ALSA_snd_seq_delete_simple_port)(snd_seq_t *seq, int port); static int (*ALSA_snd_seq_drain_output)(snd_seq_t *handle); static int (*ALSA_snd_seq_drop_output)(snd_seq_t *handle); static int (*ALSA_snd_seq_event_input)(snd_seq_t *handle, snd_seq_event_t **ev); static int (*ALSA_snd_seq_event_output)(snd_seq_t *handle, snd_seq_event_t *ev); static int (*ALSA_snd_seq_event_output_direct)(snd_seq_t *handle, snd_seq_event_t *ev); static int (*ALSA_snd_seq_free_queue)(snd_seq_t *handle, int q); static int (*ALSA_snd_seq_get_any_client_info)(snd_seq_t *handle, int client, snd_seq_client_info_t *info); static int (*ALSA_snd_seq_get_any_port_info)(snd_seq_t *handle, int client, int port, snd_seq_port_info_t *info); static int (*ALSA_snd_seq_nonblock)(snd_seq_t *handle, int nonblock); static int (*ALSA_snd_seq_open)(snd_seq_t **handle, const char *name, int streams, int mode); static int (*ALSA_snd_seq_parse_address)(snd_seq_t *seq, snd_seq_addr_t *addr, const char *str); static int (*ALSA_snd_seq_poll_descriptors)(snd_seq_t *handle, struct pollfd *pfds, unsigned int space, short events); static unsigned int (*ALSA_snd_seq_port_info_get_capability)(const snd_seq_port_info_t *info); static int (*ALSA_snd_seq_port_info_get_port)(const snd_seq_port_info_t *info); static unsigned int (*ALSA_snd_seq_port_info_get_type)(const snd_seq_port_info_t *info); static size_t (*ALSA_snd_seq_port_info_sizeof)(void); static int (*ALSA_snd_seq_query_next_client)(snd_seq_t *handle, snd_seq_client_info_t *info); static int (*ALSA_snd_seq_query_next_port)(snd_seq_t *handle, snd_seq_port_info_t *info); static void (*ALSA_snd_seq_queue_tempo_set_ppq)(snd_seq_queue_tempo_t *info, int ppq); static void (*ALSA_snd_seq_queue_tempo_set_tempo)(snd_seq_queue_tempo_t *info, unsigned int tempo); static size_t (*ALSA_snd_seq_queue_tempo_sizeof)(void); static int (*ALSA_snd_seq_set_client_event_filter)(snd_seq_t *seq, int event_type); static int (*ALSA_snd_seq_set_client_name)(snd_seq_t *seq, const char *name); static int (*ALSA_snd_seq_set_queue_tempo)(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo); static int load_alsa_syms(void) { SDL_ALSA_SYM(snd_seq_alloc_named_queue); SDL_ALSA_SYM(snd_seq_client_id); SDL_ALSA_SYM(snd_seq_client_info_get_client); SDL_ALSA_SYM(snd_seq_client_info_sizeof); SDL_ALSA_SYM(snd_seq_close); SDL_ALSA_SYM(snd_seq_connect_to); SDL_ALSA_SYM(snd_seq_control_queue); SDL_ALSA_SYM(snd_seq_create_simple_port); SDL_ALSA_SYM(snd_seq_delete_simple_port); SDL_ALSA_SYM(snd_seq_drain_output); SDL_ALSA_SYM(snd_seq_drop_output); SDL_ALSA_SYM(snd_seq_event_input); SDL_ALSA_SYM(snd_seq_event_output); SDL_ALSA_SYM(snd_seq_event_output_direct); SDL_ALSA_SYM(snd_seq_free_queue); SDL_ALSA_SYM(snd_seq_get_any_client_info); SDL_ALSA_SYM(snd_seq_get_any_port_info); SDL_ALSA_SYM(snd_seq_nonblock); SDL_ALSA_SYM(snd_seq_open); SDL_ALSA_SYM(snd_seq_parse_address); SDL_ALSA_SYM(snd_seq_poll_descriptors); SDL_ALSA_SYM(snd_seq_port_info_get_capability); SDL_ALSA_SYM(snd_seq_port_info_get_port); SDL_ALSA_SYM(snd_seq_port_info_get_type); SDL_ALSA_SYM(snd_seq_port_info_sizeof); SDL_ALSA_SYM(snd_seq_query_next_client); SDL_ALSA_SYM(snd_seq_query_next_port); SDL_ALSA_SYM(snd_seq_queue_tempo_set_ppq); SDL_ALSA_SYM(snd_seq_queue_tempo_set_tempo); SDL_ALSA_SYM(snd_seq_queue_tempo_sizeof); SDL_ALSA_SYM(snd_seq_set_client_event_filter); SDL_ALSA_SYM(snd_seq_set_client_name); SDL_ALSA_SYM(snd_seq_set_queue_tempo); return 0; } #ifndef NDEBUG #define MIDIDbgLog(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, __VA_ARGS__) #else #define MIDIDbgLog(...) \ do { \ } while (0) #endif #define MIDI_Mix_OutOfMemory() errmsg_final = oom static const char *const oom = "Out of memory"; static char errmsg[256] = ""; static const char *errmsg_final = errmsg; #define MIDI_SET_ERROR(...) SDL_snprintf(errmsg, sizeof(errmsg), __VA_ARGS__) #define MIDI_SMF_META_EVENT 0xFF #define MIDI_SMF_META_TEMPO 0x51 typedef enum { NATIVE_MIDI_STOPPED, NATIVE_MIDI_STARTING, NATIVE_MIDI_PLAYING, NATIVE_MIDI_PAUSED } native_midi_state; typedef enum { THREAD_CMD_QUIT = 1, THREAD_CMD_PAUSE, THREAD_CMD_RESUME, THREAD_CMD_SETVOL, } native_midi_thread_cmd; struct _NativeMidiSong { SDL_Thread *playerthread; int mainsock, threadsock; Uint16 ppqn; MIDIEvent *evtlist; snd_seq_t *seq; int srcport; snd_seq_addr_t dstaddr; int loopcount; Uint32 endtime; SDL_AtomicInt playerstate; /* Stores a native_midi_state */ bool allow_pause; }; /* Fixed length command packets */ #define CMD_PKT_LEN 2 static const unsigned char pkt_thread_cmd_quit[CMD_PKT_LEN] = { THREAD_CMD_QUIT }; static const unsigned char pkt_thread_cmd_pause[CMD_PKT_LEN] = { THREAD_CMD_PAUSE }; static const unsigned char pkt_thread_cmd_resume[CMD_PKT_LEN] = { THREAD_CMD_RESUME }; static SDL_INLINE const char *get_app_name_hint(void) { const char *ret = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME); if (ret && *ret) return ret; ret = SDL_GetHint(SDL_HINT_APP_NAME); if (ret && *ret) return ret; return NULL; } /* Make sure to SDL_free this */ static char *get_app_name(void) { /* Try the SDL hints first */ const char *hint = get_app_name_hint(); if (hint) { char *ret = SDL_strdup(hint); if (!ret) MIDI_Mix_OutOfMemory(); return ret; } /* Build the path to access the application's cmdline */ char procfs_path[64]; SDL_snprintf(procfs_path, sizeof(procfs_path), "/proc/%ld/cmdline", (long)getpid()); long pathmax = pathconf("/", _PC_PATH_MAX); if (pathmax == -1) pathmax = 4096; char *cmdline = SDL_calloc(1, pathmax + 1); if (!cmdline) { MIDI_Mix_OutOfMemory(); return NULL; } size_t len = 0; int fd = open(procfs_path, O_RDONLY); if (fd >= 0) { if (read(fd, cmdline, pathmax) > 0) { char *base = SDL_strrchr(cmdline, '/') + 1; /* Absolute worst case we get to a '\0' */ if (base) { len = strlen(base); if (len) SDL_memmove(cmdline, base, len + 1); } } close(fd); } /* len is used both for checking if we read any data and if a path separator was found */ if (!len) SDL_snprintf(cmdline, pathmax + 1, "SDL_Mixer Application"); return cmdline; } static snd_seq_t *open_seq(int *srcport_out) { snd_seq_t *seq; int ret; if (load_alsa_library()) { MIDI_SET_ERROR("Failed to load libasound"); return NULL; } if ((ret = ALSA_snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0)) < 0) { MIDI_SET_ERROR("snd_seq_open returned %d", ret); return NULL; } char *seq_name = get_app_name(); if (!seq_name) { ALSA_snd_seq_close(seq); return NULL; } ALSA_snd_seq_set_client_name(seq, seq_name); if ((ret = ALSA_snd_seq_create_simple_port(seq, seq_name, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SYNC_READ, SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC)) < 0) { MIDI_SET_ERROR("snd_seq_create_simple_port failed with %d", ret); ALSA_snd_seq_close(seq); SDL_free(seq_name); return NULL; } SDL_free(seq_name); *srcport_out = ret; return seq; } static void close_seq(snd_seq_t *seq, const int port) { ALSA_snd_seq_delete_simple_port(seq, port); ALSA_snd_seq_close(seq); unload_alsa_library(); } bool native_midi_detect(void) { int port; snd_seq_t *seq_temp = open_seq(&port); if (!seq_temp) return 0; close_seq(seq_temp, port); return 1; } static void close_sockpair(NativeMidiSong *song) { shutdown(song->mainsock, SHUT_RDWR); shutdown(song->threadsock, SHUT_RDWR); close(song->mainsock); close(song->threadsock); } static SDL_INLINE int subscribe_to_first_available_port(snd_seq_t *seq, const int srcport, const unsigned int required_type) { snd_seq_client_info_t *clientinfo; snd_seq_client_info_alloca(&clientinfo); /* Query System to fill the struct initially */ if (ALSA_snd_seq_get_any_client_info(seq, 0, clientinfo)) return -1; while (ALSA_snd_seq_query_next_client(seq, clientinfo) == 0) { int client = ALSA_snd_seq_client_info_get_client(clientinfo); /* Not necessary, as we don't allow subscription to our ports, but let's ignore ourselves anyway */ if (client == ALSA_snd_seq_client_id(seq)) continue; snd_seq_port_info_t *portinfo; snd_seq_port_info_alloca(&portinfo); /* Start with port 0 */ if (ALSA_snd_seq_get_any_port_info(seq, client, 0, portinfo)) continue; do { int port = ALSA_snd_seq_port_info_get_port(portinfo); unsigned int cap = ALSA_snd_seq_port_info_get_capability(portinfo); unsigned int type = ALSA_snd_seq_port_info_get_type(portinfo); if ((type & required_type) == required_type && cap & SND_SEQ_PORT_CAP_WRITE && cap & SND_SEQ_PORT_CAP_SUBS_WRITE && !(cap & SND_SEQ_PORT_CAP_NO_EXPORT)) { MIDIDbgLog("Client %d Cap %x Type %x", client, cap, type); /* Could we connect to it? */ if (ALSA_snd_seq_connect_to(seq, srcport, client, port) == 0) return 0; } } while (ALSA_snd_seq_query_next_port(seq, portinfo) == 0); } return 1; } static SDL_INLINE void pick_seq_dest_addr(NativeMidiSong *song) { /* Send events to all subscribers */ song->dstaddr.client = SND_SEQ_ADDRESS_SUBSCRIBERS; song->dstaddr.port = SND_SEQ_ADDRESS_UNKNOWN; /* Connect us somewhere, unless it's not desired */ if (SDL_GetHintBoolean("SDL_NATIVE_MUSIC_NO_CONNECT_PORTS", false)) return; /* If ALSA_OUTPUT_PORTS is specified, try to parse it and connect to it */ snd_seq_addr_t conn_addr; const char *ports_env = SDL_getenv("ALSA_OUTPUT_PORTS"); if (ports_env && ALSA_snd_seq_parse_address(song->seq, &conn_addr, ports_env) == 0) if (ALSA_snd_seq_connect_to(song->seq, song->srcport, conn_addr.client, conn_addr.port) == 0) return; /* If we're not connecting to a specific client, pick the first one available after System (0) */ /* Prefer connecting to synthesizers, as that is the primary use case */ if (!subscribe_to_first_available_port(song->seq, song->srcport, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_SYNTHESIZER)) return; /* If we can't find a synth, then pick the first available port */ if (!subscribe_to_first_available_port(song->seq, song->srcport, SND_SEQ_PORT_TYPE_MIDI_GENERIC)) return; } static NativeMidiSong *currentsong = NULL; NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio) { NativeMidiSong *song; MIDIEvent *event; int sv[2]; if (!(song = SDL_calloc(1, sizeof(NativeMidiSong)))) { MIDI_Mix_OutOfMemory(); return NULL; } if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) == -1) { MIDI_SET_ERROR("Failed to create socketpair with errno %d", errno); SDL_free(song); return NULL; } song->mainsock = sv[0]; song->threadsock = sv[1]; event = song->evtlist = CreateMIDIEventList(src, &song->ppqn); if (!song->evtlist) { close_sockpair(song); SDL_free(song); MIDI_SET_ERROR("Failed to create MIDIEventList"); return NULL; } /* Since ALSA requires the starting F0 for SysEx, but MIDIEvent.extraData doesn't contain it, we must preprocess the list */ /* In addition, since we're going through the list, store the last event's time for looping purposes */ do { /* Is this a SysEx? */ if (event->status == MIDI_CMD_COMMON_SYSEX && event->extraLen) { /* Sanity check in case something changes in the future */ /* This is safe to do since we can't have an F0 manufacturer ID */ assert(event->extraData[0] != MIDI_CMD_COMMON_SYSEX); /* Resize by + 1 */ Uint8 *newData = SDL_realloc(event->extraData, event->extraLen + 1); if (newData == NULL) { close_sockpair(song); /* Original allocation is still valid on failure */ FreeMIDIEventList(song->evtlist); SDL_free(song); MIDI_SET_ERROR("Failed to preprocess MIDIEventList SysEx"); return NULL; } /* Prepend the F0 */ event->extraData = newData; SDL_memmove(event->extraData + 1, event->extraData, event->extraLen); event->extraData[0] = MIDI_CMD_COMMON_SYSEX; event->extraLen++; } /* Store the end time */ song->endtime = event->time; } while ((event = event->next)); if (!(song->seq = open_seq(&song->srcport))) { FreeMIDIEventList(song->evtlist); close_sockpair(song); SDL_free(song); return NULL; } /* Only allow echo events to be sent */ ALSA_snd_seq_set_client_event_filter(song->seq, SND_SEQ_EVENT_ECHO); pick_seq_dest_addr(song); SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STOPPED); /* Since there's no reliable volume control solution it's better to leave the music playing instead of having hanging notes */ song->allow_pause = SDL_GetHintBoolean("SDL_NATIVE_MUSIC_ALLOW_PAUSE", false); if (closeio) SDL_CloseIO(src); currentsong = song; return song; } void native_midi_freesong(NativeMidiSong *song) { if (!song) return; close_seq(song->seq, song->srcport); FreeMIDIEventList(song->evtlist); close_sockpair(song); SDL_free(song); } /* Schedule an echo event right after the last event to know when playback is finished */ static SDL_INLINE void enqueue_echo_event(const NativeMidiSong *song, const int queue) { snd_seq_event_t evt; snd_seq_ev_clear(&evt); evt.type = SND_SEQ_EVENT_ECHO; snd_seq_ev_set_source(&evt, song->srcport); snd_seq_ev_set_dest(&evt, ALSA_snd_seq_client_id(song->seq), song->srcport); snd_seq_ev_schedule_tick(&evt, queue, 0, song->endtime + 1); while (ALSA_snd_seq_event_output(song->seq, &evt) == -EAGAIN) ; } /* Reset the queue position to 0 */ static SDL_INLINE void enqueue_queue_reset_event(const NativeMidiSong *song, const int queue) { snd_seq_event_t evt; snd_seq_ev_clear(&evt); snd_seq_ev_set_source(&evt, song->srcport); snd_seq_ev_set_queue_pos_tick(&evt, queue, 0); /* Schedule it to some point in the past, so that it is guaranteed */ /* to run immediately and before the echo */ snd_seq_ev_schedule_tick(&evt, queue, 0, 0); while (ALSA_snd_seq_event_output(song->seq, &evt) == -EAGAIN) ; } /* Sysex to set the volume */ static SDL_INLINE void send_volume_sysex(const NativeMidiSong *song, const unsigned char vol) { unsigned char vol_sysex[] = { MIDI_CMD_COMMON_SYSEX, 0x7F, 0x7F, 0x04, 0x01, 0x00, vol, MIDI_CMD_COMMON_SYSEX_END }; /* Event used to set the volume */ snd_seq_event_t evt; snd_seq_ev_clear(&evt); snd_seq_ev_set_source(&evt, song->srcport); snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port); snd_seq_ev_set_direct(&evt); snd_seq_ev_set_sysex(&evt, sizeof(vol_sysex), vol_sysex); ALSA_snd_seq_event_output_direct(song->seq, &evt); } /* Sequencer queue control */ static SDL_INLINE void stop_queue(const NativeMidiSong *song, const int queue) { snd_seq_event_t evt; snd_seq_ev_clear(&evt); snd_seq_ev_set_queue_control(&evt, SND_SEQ_EVENT_STOP, queue, 0); snd_seq_ev_set_direct(&evt); ALSA_snd_seq_event_output_direct(song->seq, &evt); } static SDL_INLINE void continue_queue(const NativeMidiSong *song, const int queue) { snd_seq_event_t evt; snd_seq_ev_clear(&evt); snd_seq_ev_set_queue_control(&evt, SND_SEQ_EVENT_CONTINUE, queue, 0); snd_seq_ev_set_direct(&evt); ALSA_snd_seq_event_output_direct(song->seq, &evt); } /* Playback thread */ static int native_midi_player_thread(void *d) { unsigned char current_volume = 0x7F; bool playback_finished = false; NativeMidiSong *song = d; MIDIEvent *event = song->evtlist; int i; int queue = ALSA_snd_seq_alloc_named_queue(song->seq, "SDL_Mixer Playback"); snd_seq_start_queue(song->seq, queue, NULL); /* Prepare main sequencer event */ snd_seq_event_t evt; snd_seq_ev_clear(&evt); snd_seq_ev_set_source(&evt, song->srcport); snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port); /* Set up nonblock functionality */ struct pollfd pfds[2] = { { .fd = song->threadsock, .events = POLLIN, } }; ALSA_snd_seq_poll_descriptors(song->seq, pfds + 1, 1, POLLIN | POLLOUT); ALSA_snd_seq_nonblock(song->seq, 1); /* Set initial queue tempo and ppqn */ snd_seq_queue_tempo_t *tempo; snd_seq_queue_tempo_alloca(&tempo); ALSA_snd_seq_queue_tempo_set_tempo(tempo, 500000); ALSA_snd_seq_queue_tempo_set_ppq(tempo, song->ppqn); ALSA_snd_seq_set_queue_tempo(song->seq, queue, tempo); /* We use this to know when the track has finished playing */ enqueue_echo_event(song, queue); SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_PLAYING); while (1) { unsigned char readbuf[CMD_PKT_LEN]; MIDIDbgLog("Poll..."); if (poll(pfds, 2, -1) <= 0) break; MIDIDbgLog("revents: cmdsock %hd, ALSA %hd", pfds[0].revents, pfds[1].revents); /* Do we have a command from the main thread? */ if (pfds[0].revents & POLLIN) { /* This will process exactly one command by design because all packets are fixed size (CMD_PKT_LEN) */ if (read(song->threadsock, readbuf, sizeof(readbuf)) == sizeof(readbuf)) { MIDIDbgLog("Got control %hhx", readbuf[0]); switch ((native_midi_thread_cmd)readbuf[0]) { case THREAD_CMD_QUIT: event = NULL; song->loopcount = 0; playback_finished = true; break; case THREAD_CMD_SETVOL: current_volume = readbuf[1]; send_volume_sysex(song, current_volume); break; case THREAD_CMD_PAUSE: send_volume_sysex(song, 0); stop_queue(song, queue); SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_PAUSED); break; case THREAD_CMD_RESUME: continue_queue(song, queue); send_volume_sysex(song, current_volume); SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_PLAYING); break; } } } /* Can we read from the sequencer? */ if (pfds[1].revents & POLLIN) { snd_seq_event_t *revt; /* Make sure we read an echo event, and that it came from us */ if (ALSA_snd_seq_event_input(song->seq, &revt) >= 0 && revt->type == SND_SEQ_EVENT_ECHO && revt->source.client == ALSA_snd_seq_client_id(song->seq) && revt->source.port == song->srcport) playback_finished = true; } /* Have we reached the end of the event list? */ if (!event) { /* If we have, are we done playing? */ if (playback_finished) { if (song->loopcount == 0) break; MIDIDbgLog("Playback is looping"); /* If we need to loop, roll back the list head and keep going */ event = song->evtlist; /* We need to reset the queue, otherwise the ticks will be wrong */ enqueue_queue_reset_event(song, queue); enqueue_echo_event(song, queue); if (song->loopcount > 0) song->loopcount--; playback_finished = false; /* Allow ready to write events again */ pfds[1].events |= POLLOUT; } else { /* If not, keep draining, otherwise we'll never reach the echo event */ /* When we finish though, prevent any "ready to write to alsa" polls */ MIDIDbgLog("Draining output!"); if (ALSA_snd_seq_drain_output(song->seq) == 0) pfds[1].events &= ~POLLOUT; continue; } } /* Don't proceed if we can't write to the sequencer */ if (!(pfds[1].revents & POLLOUT)) continue; /* Finally, if we get here, we process MIDI events and send them to the sequencer */ const unsigned char cmd = event->status & 0xF0; const unsigned char channel = event->status & 0x0F; snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port); snd_seq_ev_set_fixed(&evt); snd_seq_ev_schedule_tick(&evt, queue, 0, event->time); bool unhandled = false; switch (cmd) { case MIDI_CMD_NOTE_ON: snd_seq_ev_set_noteon(&evt, channel, event->data[0], event->data[1]); break; case MIDI_CMD_NOTE_OFF: snd_seq_ev_set_noteoff(&evt, channel, event->data[0], event->data[1]); break; case MIDI_CMD_CONTROL: snd_seq_ev_set_controller(&evt, channel, event->data[0], event->data[1]); break; case MIDI_CMD_NOTE_PRESSURE: snd_seq_ev_set_keypress(&evt, channel, event->data[0], event->data[1]); break; case MIDI_CMD_PGM_CHANGE: snd_seq_ev_set_pgmchange(&evt, channel, event->data[0]); break; case MIDI_CMD_BENDER: snd_seq_ev_set_pitchbend(&evt, channel, ((((int)event->data[1]) << 7) | (event->data[0] & 0x7F)) - 8192); break; default: if (event->status == MIDI_SMF_META_EVENT) { if (event->data[0] == MIDI_SMF_META_TEMPO && event->extraLen == 3) { unsigned int t = ((unsigned)event->extraData[0] << 16) | ((unsigned)event->extraData[1] << 8) | event->extraData[2]; /* This changes the event destination, so we have to restore it in the next iteration */ snd_seq_ev_set_queue_tempo(&evt, queue, t); break; } } else if (event->status == MIDI_CMD_COMMON_SYSEX) { snd_seq_ev_set_sysex(&evt, event->extraLen, event->extraData); break; } unhandled = true; } if (unhandled || ALSA_snd_seq_event_output(song->seq, &evt) != -EAGAIN) { MIDIDbgLog("%s %" SDL_PRIu32 ": %hhx %hhx %hhx (extraLen %" SDL_PRIu32 ")", (unhandled ? "Unhandled" : "Event"), event->time, event->status, event->data[0], event->data[1], event->extraLen); event = event->next; } } SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STOPPED); /* Switch back to blocking mode and drop everything */ ALSA_snd_seq_nonblock(song->seq, 0); ALSA_snd_seq_drop_output(song->seq); snd_seq_stop_queue(song->seq, queue, NULL); ALSA_snd_seq_drain_output(song->seq); ALSA_snd_seq_free_queue(song->seq, queue); /* Stop all audio */ /* Some of these are bound to work */ snd_seq_ev_set_direct(&evt); for (i = 0; i < MIDI_CHANNELS; i++) { snd_seq_ev_set_controller(&evt, i, MIDI_CTL_SUSTAIN, 0); ALSA_snd_seq_event_output_direct(song->seq, &evt); snd_seq_ev_set_controller(&evt, i, MIDI_CTL_ALL_NOTES_OFF, 0); ALSA_snd_seq_event_output_direct(song->seq, &evt); snd_seq_ev_set_controller(&evt, i, MIDI_CTL_RESET_CONTROLLERS, 0); ALSA_snd_seq_event_output_direct(song->seq, &evt); snd_seq_ev_set_controller(&evt, i, MIDI_CTL_ALL_SOUNDS_OFF, 0); ALSA_snd_seq_event_output_direct(song->seq, &evt); } MIDIDbgLog("Playback thread returns"); return 0; } void native_midi_start(NativeMidiSong *song, int loops) { if (!song) return; if (song->playerthread) { if (SDL_GetAtomicInt(¤tsong->playerstate) > NATIVE_MIDI_STOPPED) if (write(song->mainsock, pkt_thread_cmd_quit, CMD_PKT_LEN) != sizeof(pkt_thread_cmd_quit)) return; SDL_WaitThread(song->playerthread, NULL); } song->loopcount = loops; /* If this isn't set here, then the application might think we finished before playback even started */ SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STARTING); song->playerthread = SDL_CreateThread(native_midi_player_thread, "SDL_Mixer Midi", song); } /* The following functions require song to be global (thus currentsong is used) */ void native_midi_pause(void) { NativeMidiSong *song = currentsong; if (!song || SDL_GetAtomicInt(&song->playerstate) == NATIVE_MIDI_STOPPED || !song->allow_pause) return; (void)!write(song->mainsock, pkt_thread_cmd_pause, CMD_PKT_LEN); } void native_midi_resume(void) { NativeMidiSong *song = currentsong; if (!song || SDL_GetAtomicInt(&song->playerstate) != NATIVE_MIDI_PAUSED || !song->allow_pause) return; (void)!write(song->mainsock, pkt_thread_cmd_resume, CMD_PKT_LEN); } void native_midi_stop(void) { NativeMidiSong *song = currentsong; if (!song || !song->playerthread) return; /* Don't send any messages to the main thread if it's out of the main loop */ if (SDL_GetAtomicInt(&song->playerstate) > NATIVE_MIDI_STOPPED) if (write(song->mainsock, pkt_thread_cmd_quit, CMD_PKT_LEN) != sizeof(pkt_thread_cmd_quit)) return; SDL_WaitThread(song->playerthread, NULL); song->playerthread = NULL; } bool native_midi_active(void) { NativeMidiSong *song = currentsong; if (!song) return 0; return SDL_GetAtomicInt(&song->playerstate) > NATIVE_MIDI_STOPPED; } void native_midi_setvolume(int volume) { NativeMidiSong *song = currentsong; if (!song || SDL_GetAtomicInt(&song->playerstate) != NATIVE_MIDI_PLAYING) return; if (volume < 0) volume = 0; else if (volume > 0x7F) volume = 0x7F; unsigned char pkt_thread_cmd_setvol[CMD_PKT_LEN] = { THREAD_CMD_SETVOL, volume }; (void)!write(song->mainsock, pkt_thread_cmd_setvol, CMD_PKT_LEN); } const char *native_midi_error(void) { return errmsg_final; } #endif libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi_macosx.c000066400000000000000000000236401501405355700275430ustar00rootroot00000000000000/* native_midi_macosx: Native Midi support on Mac OS X for the SDL_mixer library Copyright (C) 2009 Ryan C. Gordon This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #ifdef SDL_PLATFORM_MACOS /* Mac OS X 10.6+, using Core MIDI. */ #include #include #include #include #include #include #include "../../mixer.h" #include "native_midi.h" /* Native Midi song */ struct _NativeMidiSong { MusicPlayer player; MusicSequence sequence; MusicTimeStamp endTime; AudioUnit audiounit; int loops; }; static NativeMidiSong *currentsong = NULL; static int latched_volume = MIX_MAX_VOLUME; static OSStatus GetSequenceLength(MusicSequence sequence, MusicTimeStamp *_sequenceLength) { /* http://lists.apple.com/archives/Coreaudio-api/2003/Jul/msg00370.html * figure out sequence length */ UInt32 ntracks, i; MusicTimeStamp sequenceLength = 0; OSStatus err; err = MusicSequenceGetTrackCount(sequence, &ntracks); if (err != noErr) return err; for (i = 0; i < ntracks; ++i) { MusicTrack track; MusicTimeStamp tracklen = 0; UInt32 tracklenlen = sizeof (tracklen); err = MusicSequenceGetIndTrack(sequence, i, &track); if (err != noErr) return err; err = MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &tracklen, &tracklenlen); if (err != noErr) return err; if (sequenceLength < tracklen) sequenceLength = tracklen; } *_sequenceLength = sequenceLength; return noErr; } static OSStatus GetSequenceAudioUnitMatching(MusicSequence sequence, AudioUnit *aunit, OSType type, OSType subtype) { AUGraph graph; UInt32 nodecount, i; OSStatus err; err = MusicSequenceGetAUGraph(sequence, &graph); if (err != noErr) return err; err = AUGraphGetNodeCount(graph, &nodecount); if (err != noErr) return err; for (i = 0; i < nodecount; i++) { AUNode node; AudioComponentDescription desc; if (AUGraphGetIndNode(graph, i, &node) != noErr) continue; /* better luck next time. */ if (AUGraphNodeInfo(graph, node, &desc, aunit) != noErr) continue; else if (desc.componentType != type) continue; else if (desc.componentSubType != subtype) continue; return noErr; /* found it! */ } *aunit = NULL; return kAUGraphErr_NodeNotFound; } typedef struct { AudioUnit aunit; bool soundfont_set; CFURLRef default_url; } macosx_load_soundfont_ctx; static bool SDLCALL macosx_load_soundfont(const char *path, void *data) { CFURLRef url; OSStatus err; macosx_load_soundfont_ctx *ctx = data; if (ctx->soundfont_set) return false; url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)path, strlen(path), false); if (!url) return false; err = AudioUnitSetProperty(ctx->aunit, kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global, 0, &url, sizeof(url)); CFRelease(url); if (err != noErr) { if (ctx->default_url) err = AudioUnitSetProperty(ctx->aunit, kMusicDeviceProperty_SoundBankURL, kAudioUnitScope_Global, 0, &ctx->default_url, sizeof(CFURLRef)); if (err != noErr) { /* uh-oh, this might leave the audio unit in an unusable state (e.g. if the soundfont was an incompatible file type) */ } return false; } ctx->soundfont_set = true; return true; } static void SetSequenceSoundFont(MusicSequence sequence) { OSStatus err; macosx_load_soundfont_ctx ctx; ctx.soundfont_set = false; ctx.default_url = NULL; CFBundleRef bundle = CFBundleGetBundleWithIdentifier( CFSTR("com.apple.audio.units.Components")); if (bundle) ctx.default_url = CFBundleCopyResourceURL(bundle, CFSTR("gs_instruments"), CFSTR("dls"), NULL); err = GetSequenceAudioUnitMatching(sequence, &ctx.aunit, kAudioUnitType_MusicDevice, kAudioUnitSubType_DLSSynth); if (err != noErr) return; Mix_EachSoundFont(macosx_load_soundfont, &ctx); if (ctx.default_url) CFRelease(ctx.default_url); return; } bool native_midi_detect(void) { return true; /* always available. */ } NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio) { NativeMidiSong *retval = NULL; void *buf = NULL; size_t len = 0; CFDataRef data = NULL; buf = SDL_LoadFile_IO(src, &len, false); if (buf == NULL) goto fail; retval = SDL_malloc(sizeof(NativeMidiSong)); if (retval == NULL) goto fail; SDL_memset(retval, '\0', sizeof (*retval)); if (NewMusicPlayer(&retval->player) != noErr) goto fail; if (NewMusicSequence(&retval->sequence) != noErr) goto fail; data = CFDataCreate(NULL, (const UInt8 *) buf, len); if (data == NULL) goto fail; SDL_free(buf); buf = NULL; if (MusicSequenceFileLoadData(retval->sequence, data, 0, 0) != noErr) goto fail; CFRelease(data); data = NULL; if (GetSequenceLength(retval->sequence, &retval->endTime) != noErr) goto fail; if (MusicPlayerSetSequence(retval->player, retval->sequence) != noErr) goto fail; if (closeio) SDL_CloseIO(src); return retval; fail: if (retval) { if (retval->sequence) DisposeMusicSequence(retval->sequence); if (retval->player) DisposeMusicPlayer(retval->player); SDL_free(retval); } if (data) CFRelease(data); if (buf) SDL_free(buf); return NULL; } void native_midi_freesong(NativeMidiSong *song) { if (song != NULL) { if (currentsong == song) currentsong = NULL; MusicPlayerStop(song->player); /* needed to prevent error and memory leak when disposing sequence */ MusicPlayerSetSequence(song->player, NULL); DisposeMusicSequence(song->sequence); DisposeMusicPlayer(song->player); SDL_free(song); } } void native_midi_start(NativeMidiSong *song, int loops) { int vol; if (song == NULL) return; if (currentsong) MusicPlayerStop(currentsong->player); currentsong = song; currentsong->loops = loops; MusicPlayerPreroll(song->player); GetSequenceAudioUnitMatching(song->sequence, &song->audiounit, kAudioUnitType_Output, kAudioUnitSubType_DefaultOutput); SetSequenceSoundFont(song->sequence); vol = latched_volume; latched_volume++; /* just make this not match. */ native_midi_setvolume(vol); MusicPlayerSetTime(song->player, 0); MusicPlayerStart(song->player); } void native_midi_pause(void) { } void native_midi_resume(void) { } void native_midi_stop(void) { if (currentsong) { MusicPlayerStop(currentsong->player); currentsong = NULL; } } bool native_midi_active(void) { MusicTimeStamp currentTime = 0; if (currentsong == NULL) return false; MusicPlayerGetTime(currentsong->player, ¤tTime); if ((currentTime < currentsong->endTime) || (currentTime >= kMusicTimeStamp_EndOfTrack)) { return true; } else if (currentsong->loops) { --currentsong->loops; MusicPlayerSetTime(currentsong->player, 0); return true; } return false; } void native_midi_setvolume(int volume) { if (latched_volume == volume) return; latched_volume = volume; if ((currentsong) && (currentsong->audiounit)) { const float floatvol = ((float) volume) / ((float) MIX_MAX_VOLUME); AudioUnitSetParameter(currentsong->audiounit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, floatvol, 0); } } const char *native_midi_error(void) { return ""; /* !!! FIXME */ } #else #include "native_midi.h" bool native_midi_detect(void) { return false; } NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio) { if (closeio) { SDL_CloseIO(src); } SDL_Unsupported(); return NULL; } void native_midi_freesong(NativeMidiSong *song) { } void native_midi_start(NativeMidiSong *song, int loops) { } void native_midi_pause(void) { } void native_midi_resume(void) { } void native_midi_stop(void) { } bool native_midi_active(void) { } void native_midi_setvolume(int volume) { } const char *native_midi_error(void) { return ""; } #endif /* Mac OS X native MIDI support */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/native_midi/native_midi_win32.c000066400000000000000000000205401501405355700272070ustar00rootroot00000000000000/* native_midi: Hardware Midi support for the SDL_mixer library Copyright (C) 2000,2001 Florian 'Proff' Schulze This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include /* everything below is currently one very big bad hack ;) Proff */ #ifdef SDL_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include #include #include "native_midi.h" #include "native_midi_common.h" struct _NativeMidiSong { bool MusicLoaded; bool MusicPlaying; int Loops; int CurrentHdr; MIDIHDR MidiStreamHdr[2]; MIDIEVENT *NewEvents; Uint16 ppqn; int Size; int NewPos; SDL_Mutex *mutex; }; static UINT MidiDevice=MIDI_MAPPER; static HMIDISTRM hMidiStream; static NativeMidiSong *currentsong; static int BlockOut(NativeMidiSong *song) { MMRESULT err; int BlockSize; MIDIHDR *hdr; if ((song->MusicLoaded) && (song->NewEvents)) { /* proff 12/8/98: Added for safety*/ song->CurrentHdr = !song->CurrentHdr; hdr = &song->MidiStreamHdr[song->CurrentHdr]; midiOutUnprepareHeader((HMIDIOUT)hMidiStream,hdr,sizeof(MIDIHDR)); if (song->NewPos>=song->Size) return 0; BlockSize=(song->Size-song->NewPos); if (BlockSize<=0) return 0; if (BlockSize>36000) BlockSize=36000; hdr->lpData=(void *)((unsigned char *)song->NewEvents+song->NewPos); song->NewPos+=BlockSize; hdr->dwBufferLength=BlockSize; hdr->dwBytesRecorded=BlockSize; hdr->dwFlags=0; hdr->dwOffset=0; err=midiOutPrepareHeader((HMIDIOUT)hMidiStream,hdr,sizeof(MIDIHDR)); if (err!=MMSYSERR_NOERROR) return 0; err=midiStreamOut(hMidiStream,hdr,sizeof(MIDIHDR)); return 0; } return 1; } static void MIDItoStream(NativeMidiSong *song, MIDIEvent *evntlist) { int eventcount; MIDIEvent *event; MIDIEVENT *newevent; eventcount=0; event=evntlist; while (event) { eventcount++; event=event->next; } song->NewEvents = SDL_malloc(eventcount*3*sizeof(DWORD)); if (!song->NewEvents) return; SDL_memset(song->NewEvents,0,(eventcount*3*sizeof(DWORD))); eventcount=0; event=evntlist; newevent=song->NewEvents; while (event) { int status = (event->status&0xF0)>>4; switch (status) { case MIDI_STATUS_NOTE_OFF: case MIDI_STATUS_NOTE_ON: case MIDI_STATUS_AFTERTOUCH: case MIDI_STATUS_CONTROLLER: case MIDI_STATUS_PROG_CHANGE: case MIDI_STATUS_PRESSURE: case MIDI_STATUS_PITCH_WHEEL: newevent->dwDeltaTime=event->time; newevent->dwEvent=(event->status|0x80)|(event->data[0]<<8)|(event->data[1]<<16)|(MEVT_SHORTMSG<<24); newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD))); eventcount++; break; case MIDI_STATUS_SYSEX: if (event->status == 0xFF && event->data[0] == 0x51) /* Tempo change */ { int tempo = (event->extraData[0] << 16) | (event->extraData[1] << 8) | event->extraData[2]; newevent->dwDeltaTime=event->time; newevent->dwEvent=(MEVT_TEMPO<<24) | tempo; newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD))); eventcount++; } break; } event=event->next; } song->Size=eventcount*3*sizeof(DWORD); { int time; int temptime; song->NewPos=0; time=0; newevent=song->NewEvents; while (song->NewPosSize) { temptime=newevent->dwDeltaTime; newevent->dwDeltaTime-=time; time=temptime; if ((song->NewPos+12)>=song->Size) newevent->dwEvent |= MEVT_F_CALLBACK; newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD))); song->NewPos+=12; } } song->NewPos=0; song->MusicLoaded = true; } void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) { NativeMidiSong *song = (NativeMidiSong *)dwInstance; (void)hMidi; (void)dwParam2; if (!song) { return; } SDL_LockMutex(song->mutex); switch( uMsg ) { case MOM_DONE: if (song->MusicPlaying && song->MusicLoaded && (dwParam1 == (DWORD_PTR)&song->MidiStreamHdr[song->CurrentHdr])) BlockOut(song); break; case MOM_POSITIONCB: if (song->MusicPlaying && song->MusicLoaded && (dwParam1 == (DWORD_PTR)&song->MidiStreamHdr[song->CurrentHdr])) { if (song->Loops) { if (song->Loops > 0) --song->Loops; song->NewPos=0; BlockOut(song); } else { song->MusicPlaying = false; } } break; case MOM_CLOSE: song->MusicPlaying = false; break; default: break; } SDL_UnlockMutex(song->mutex); } bool native_midi_detect(void) { MMRESULT merr; HMIDISTRM MidiStream; merr=midiStreamOpen(&MidiStream,&MidiDevice,(DWORD)1,(DWORD_PTR)MidiProc,(DWORD_PTR)0,CALLBACK_FUNCTION); if (merr != MMSYSERR_NOERROR) return false; midiStreamClose(MidiStream); return true; } NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio) { NativeMidiSong *newsong; MIDIEvent *evntlist = NULL; newsong = SDL_malloc(sizeof(NativeMidiSong)); if (!newsong) { return NULL; } SDL_memset(newsong,0,sizeof(NativeMidiSong)); /* Attempt to load the midi file */ evntlist = CreateMIDIEventList(src, &newsong->ppqn); if (!evntlist) { SDL_free(newsong); return NULL; } MIDItoStream(newsong, evntlist); FreeMIDIEventList(evntlist); newsong->mutex = SDL_CreateMutex(); if (closeio) { SDL_CloseIO(src); } return newsong; } void native_midi_freesong(NativeMidiSong *song) { if (song) { if (song->NewEvents) SDL_free(song->NewEvents); SDL_DestroyMutex(song->mutex); SDL_free(song); } } void native_midi_start(NativeMidiSong *song, int loops) { MMRESULT merr; MIDIPROPTIMEDIV mptd; native_midi_stop(); if (!hMidiStream) { merr=midiStreamOpen(&hMidiStream,&MidiDevice,(DWORD)1,(DWORD_PTR)MidiProc,(DWORD_PTR)song,CALLBACK_FUNCTION); if (merr!=MMSYSERR_NOERROR) { hMidiStream = NULL; /* should I do midiStreamClose(hMidiStream) before? */ return; } /* midiStreamStop(hMidiStream); */ currentsong=song; currentsong->NewPos=0; currentsong->MusicPlaying = true; currentsong->Loops=loops; mptd.cbStruct=sizeof(MIDIPROPTIMEDIV); mptd.dwTimeDiv=currentsong->ppqn; merr=midiStreamProperty(hMidiStream,(LPBYTE)&mptd,MIDIPROP_SET | MIDIPROP_TIMEDIV); BlockOut(song); merr=midiStreamRestart(hMidiStream); } } void native_midi_pause(void) { if (!hMidiStream) return; midiStreamPause(hMidiStream); } void native_midi_resume(void) { if (!hMidiStream) return; midiStreamRestart(hMidiStream); } void native_midi_stop(void) { NativeMidiSong *song = currentsong; if (!hMidiStream) return; SDL_LockMutex(song->mutex); midiStreamStop(hMidiStream); midiStreamClose(hMidiStream); currentsong=NULL; hMidiStream = NULL; SDL_UnlockMutex(song->mutex); } bool native_midi_active(void) { if (!hMidiStream) return false; if (!currentsong) return false; return currentsong->MusicPlaying; } void native_midi_setvolume(int volume) { int calcVolume; if (volume > 128) volume = 128; if (volume < 0) volume = 0; calcVolume = (65535 * volume / 128); midiOutSetVolume((HMIDIOUT)hMidiStream, MAKELONG(calcVolume , calcVolume)); } const char *native_midi_error(void) { return ""; } #endif /* Windows native MIDI support */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/stb_vorbis/000077500000000000000000000000001501405355700234145ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/stb_vorbis/README.txt000066400000000000000000000004341501405355700251130ustar00rootroot00000000000000stb ------------------- single-file public domain (or MIT licensed) libraries for C/C++ ------------------- https://github.com/nothings/stb ------------------- stv_vorbis.c (renamed into h) version 1.22 decode ogg vorbis files from file/memory to float/16-bit signed output libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/stb_vorbis/stb_vorbis.h000066400000000000000000006107631501405355700257560ustar00rootroot00000000000000// Ogg Vorbis audio decoder - v1.22 - public domain // http://nothings.org/stb_vorbis/ // // Original version written by Sean Barrett in 2007. // // Originally sponsored by RAD Game Tools. Seeking implementation // sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, // Elias Software, Aras Pranckevicius, and Sean Barrett. // // LICENSE // // See end of file for license information. // // Limitations: // // - floor 0 not supported (used in old ogg vorbis files pre-2004) // - lossless sample-truncation at beginning ignored // - cannot concatenate multiple vorbis streams // - sample positions are 32-bit, limiting seekable 192Khz // files to around 6 hours (Ogg supports 64-bit) // // Feature contributors: // Dougall Johnson (sample-exact seeking) // Vitaly Novichkov (sample-accurate tell) // // Bugfix/warning contributors: // Terje Mathisen Niklas Frykholm Andy Hill // Casey Muratori John Bolton Gargaj // Laurent Gomila Marc LeBlanc Ronny Chevalier // Bernhard Wodo Evan Balster github:alxprd // Tom Beaumont Ingo Leitgeb Nicolas Guillemot // Phillip Bennefall Rohit Thiago Goulart // github:manxorist Saga Musix github:infatum // Timur Gagiev Maxwell Koo Peter Waller // github:audinowho Dougall Johnson David Reid // github:Clownacy Pedro J. Estebanez Remi Verschelde // AnthoFoxo github:morlat Gabriel Ravier // Alice Rowan // // Partial history: // 1.22 - 2021-07-11 - various small fixes // 1.21 - 2021-07-02 - fix bug for files with no comments // 1.20 - 2020-07-11 - several small fixes // 1.19 - 2020-02-05 - warnings // 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. // 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) // 1.16 - 2019-03-04 - fix warnings // 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found // 1.14 - 2018-02-11 - delete bogus dealloca usage // 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) // 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files // 1.11 - 2017-07-23 - fix MinGW compilation // 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory // 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version // 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame // 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const // 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) // some crash fixes when out of memory or with corrupt files // fix some inappropriately signed shifts // 1.05 - 2015-04-19 - don't define __forceinline if it's redundant // 1.04 - 2014-08-27 - fix missing const-correct case in API // 1.03 - 2014-08-07 - warning fixes // 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows // 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) // 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; // (API change) report sample rate for decode-full-file funcs // // See end of file for full version history. ////////////////////////////////////////////////////////////////////////////// // // HEADER BEGINS HERE // #ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H #define STB_VORBIS_INCLUDE_STB_VORBIS_H #if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) #define STB_VORBIS_NO_STDIO 1 #endif #ifndef STB_VORBIS_NO_STDIO #include #endif #ifdef __cplusplus extern "C" { #endif /////////// THREAD SAFETY // Individual stb_vorbis* handles are not thread-safe; you cannot decode from // them from multiple threads at the same time. However, you can have multiple // stb_vorbis* handles and decode from them independently in multiple thrads. /////////// MEMORY ALLOCATION // normally stb_vorbis uses malloc() to allocate memory at startup, // and alloca() to allocate temporary memory during a frame on the // stack. (Memory consumption will depend on the amount of setup // data in the file and how you set the compile flags for speed // vs. size. In my test files the maximal-size usage is ~150KB.) // // You can modify the wrapper functions in the source (setup_malloc, // setup_temp_malloc, temp_malloc) to change this behavior, or you // can use a simpler allocation model: you pass in a buffer from // which stb_vorbis will allocate _all_ its memory (including the // temp memory). "open" may fail with a VORBIS_outofmem if you // do not pass in enough data; there is no way to determine how // much you do need except to succeed (at which point you can // query get_info to find the exact amount required. yes I know // this is lame). // // If you pass in a non-NULL buffer of the type below, allocation // will occur from it as described above. Otherwise just pass NULL // to use malloc()/alloca() typedef struct { char *alloc_buffer; int alloc_buffer_length_in_bytes; } stb_vorbis_alloc; /////////// FUNCTIONS USEABLE WITH ALL INPUT MODES typedef struct stb_vorbis stb_vorbis; typedef struct { unsigned int sample_rate; int channels; unsigned int setup_memory_required; unsigned int setup_temp_memory_required; unsigned int temp_memory_required; int max_frame_size; } stb_vorbis_info; typedef struct { char *vendor; int comment_list_length; char **comment_list; } stb_vorbis_comment; // get general information about the file extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); #ifndef STB_VORBIS_NO_COMMENTS // get ogg comments extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); #endif // get the last error detected (clears it, too) extern int stb_vorbis_get_error(stb_vorbis *f); // close an ogg vorbis file and free all memory in use extern void stb_vorbis_close(stb_vorbis *f); // this function returns the offset (in samples) from the beginning of the // file that will be returned by the next decode, if it is known, or -1 // otherwise. after a flush_pushdata() call, this may take a while before // it becomes valid again. // NOT WORKING YET after a seek with PULLDATA API extern int stb_vorbis_get_sample_offset(stb_vorbis *f); // this function returns the count of returned samples from the beginning of the // file. Functions "stb_vorbis_get_samples_*", "stb_vorbis_seek_*()" will // affect the returned value. Use this call to get the accurate sample position // during playback. extern int stb_vorbis_get_playback_sample_offset(stb_vorbis *f); // returns the current seek point within the file, or offset from the beginning // of the memory buffer. In pushdata mode it returns 0. extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); /////////// PUSHDATA API #ifndef STB_VORBIS_NO_PUSHDATA_API // this API allows you to get blocks of data from any source and hand // them to stb_vorbis. you have to buffer them; stb_vorbis will tell // you how much it used, and you have to give it the rest next time; // and stb_vorbis may not have enough data to work with and you will // need to give it the same data again PLUS more. Note that the Vorbis // specification does not bound the size of an individual frame. extern stb_vorbis *stb_vorbis_open_pushdata( const unsigned char * datablock, int datablock_length_in_bytes, int *datablock_memory_consumed_in_bytes, int *error, const stb_vorbis_alloc *alloc_buffer); // create a vorbis decoder by passing in the initial data block containing // the ogg&vorbis headers (you don't need to do parse them, just provide // the first N bytes of the file--you're told if it's not enough, see below) // on success, returns an stb_vorbis *, does not set error, returns the amount of // data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; // on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed // if returns NULL and *error is VORBIS_need_more_data, then the input block was // incomplete and you need to pass in a larger block from the start of the file extern int stb_vorbis_decode_frame_pushdata( stb_vorbis *f, const unsigned char *datablock, int datablock_length_in_bytes, int *channels, // place to write number of float * buffers float ***output, // place to write float ** array of float * buffers int *samples // place to write number of output samples ); // decode a frame of audio sample data if possible from the passed-in data block // // return value: number of bytes we used from datablock // // possible cases: // 0 bytes used, 0 samples output (need more data) // N bytes used, 0 samples output (resynching the stream, keep going) // N bytes used, M samples output (one frame of data) // note that after opening a file, you will ALWAYS get one N-bytes,0-sample // frame, because Vorbis always "discards" the first frame. // // Note that on resynch, stb_vorbis will rarely consume all of the buffer, // instead only datablock_length_in_bytes-3 or less. This is because it wants // to avoid missing parts of a page header if they cross a datablock boundary, // without writing state-machiney code to record a partial detection. // // The number of channels returned are stored in *channels (which can be // NULL--it is always the same as the number of channels reported by // get_info). *output will contain an array of float* buffers, one per // channel. In other words, (*output)[0][0] contains the first sample from // the first channel, and (*output)[1][0] contains the first sample from // the second channel. // // *output points into stb_vorbis's internal output buffer storage; these // buffers are owned by stb_vorbis and application code should not free // them or modify their contents. They are transient and will be overwritten // once you ask for more data to get decoded, so be sure to grab any data // you need before then. extern void stb_vorbis_flush_pushdata(stb_vorbis *f); // inform stb_vorbis that your next datablock will not be contiguous with // previous ones (e.g. you've seeked in the data); future attempts to decode // frames will cause stb_vorbis to resynchronize (as noted above), and // once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it // will begin decoding the _next_ frame. // // if you want to seek using pushdata, you need to seek in your file, then // call stb_vorbis_flush_pushdata(), then start calling decoding, then once // decoding is returning you data, call stb_vorbis_get_sample_offset, and // if you don't like the result, seek your file again and repeat. #endif ////////// PULLING INPUT API #ifndef STB_VORBIS_NO_PULLDATA_API // This API assumes stb_vorbis is allowed to pull data from a source-- // either a block of memory containing the _entire_ vorbis stream, or a // FILE * that you or it create, or possibly some other reading mechanism // if you go modify the source to replace the FILE * case with some kind // of callback to your code. (But if you don't support seeking, you may // just want to go ahead and use pushdata.) #if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); #endif #if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); #endif // decode an entire file and output the data interleaved into a malloc()ed // buffer stored in *output. The return value is the number of samples // decoded, or -1 if the file could not be opened or was not an ogg vorbis file. // When you're done with it, just free() the pointer returned in *output. extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc_buffer); // create an ogg vorbis decoder from an ogg vorbis stream in memory (note // this must be the entire stream!). on failure, returns NULL and sets *error #ifndef STB_VORBIS_NO_STDIO extern stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc_buffer); // create an ogg vorbis decoder from a filename via fopen(). on failure, // returns NULL and sets *error (possibly to VORBIS_file_open_failure). extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, int *error, const stb_vorbis_alloc *alloc_buffer); // create an ogg vorbis decoder from an open FILE *, looking for a stream at // the _current_ seek point (ftell). on failure, returns NULL and sets *error. // note that stb_vorbis must "own" this stream; if you seek it in between // calls to stb_vorbis, it will become confused. Moreover, if you attempt to // perform stb_vorbis_seek_*() operations on this file, it will assume it // owns the _entire_ rest of the file after the start point. Use the next // function, stb_vorbis_open_file_section(), to limit it. extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); // create an ogg vorbis decoder from an open FILE *, looking for a stream at // the _current_ seek point (ftell); the stream will be of length 'len' bytes. // on failure, returns NULL and sets *error. note that stb_vorbis must "own" // this stream; if you seek it in between calls to stb_vorbis, it will become // confused. #endif #ifdef STB_VORBIS_SDL extern stb_vorbis * stb_vorbis_open_io_section(SDL_IOStream *io, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length); extern stb_vorbis * stb_vorbis_open_io(SDL_IOStream *io, int close_on_free, int *error, const stb_vorbis_alloc *alloc); #define IO_BUFFER_SIZE 2048 #endif extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); // these functions seek in the Vorbis file to (approximately) 'sample_number'. // after calling seek_frame(), the next call to get_frame_*() will include // the specified sample. after calling stb_vorbis_seek(), the next call to // stb_vorbis_get_samples_* will start with the specified sample. If you // do not need to seek to EXACTLY the target sample when using get_samples_*, // you can also use seek_frame(). extern int stb_vorbis_seek_start(stb_vorbis *f); // this function is equivalent to stb_vorbis_seek(f,0) extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); // these functions return the total length of the vorbis stream extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); // decode the next frame and return the number of samples. the number of // channels returned are stored in *channels (which can be NULL--it is always // the same as the number of channels reported by get_info). *output will // contain an array of float* buffers, one per channel. These outputs will // be overwritten on the next call to stb_vorbis_get_frame_*. // // You generally should not intermix calls to stb_vorbis_get_frame_*() // and stb_vorbis_get_samples_*(), since the latter calls the former. #ifndef STB_VORBIS_NO_INTEGER_CONVERSION extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); #endif // decode the next frame and return the number of *samples* per channel. // Note that for interleaved data, you pass in the number of shorts (the // size of your array), but the return value is the number of samples per // channel, not the total number of samples. // // The data is coerced to the number of channels you request according to the // channel coercion rules (see below). You must pass in the size of your // buffer(s) so that stb_vorbis will not overwrite the end of the buffer. // The maximum buffer size needed can be gotten from get_info(); however, // the Vorbis I specification implies an absolute maximum of 4096 samples // per channel. // Channel coercion rules: // Let M be the number of channels requested, and N the number of channels present, // and Cn be the nth channel; let stereo L be the sum of all L and center channels, // and stereo R be the sum of all R and center channels (channel assignment from the // vorbis spec). // M N output // 1 k sum(Ck) for all k // 2 * stereo L, stereo R // k l k > l, the first l channels, then 0s // k l k <= l, the first k channels // Note that this is not _good_ surround etc. mixing at all! It's just so // you get something useful. extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); // gets num_samples samples, not necessarily on a frame boundary--this requires // buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. // Returns the number of samples stored per channel; it may be less than requested // at the end of the file. If there are no more samples in the file, returns 0. #ifndef STB_VORBIS_NO_INTEGER_CONVERSION extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); #endif // gets num_samples samples, not necessarily on a frame boundary--this requires // buffering so you have to supply the buffers. Applies the coercion rules above // to produce 'channels' channels. Returns the number of samples stored per channel; // it may be less than requested at the end of the file. If there are no more // samples in the file, returns 0. #endif //////// ERROR CODES enum STBVorbisError { VORBIS__no_error, VORBIS_need_more_data=1, // not a real error VORBIS_invalid_api_mixing, // can't mix API modes VORBIS_outofmem, // not enough memory VORBIS_feature_not_supported, // uses floor 0 VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small VORBIS_file_open_failure, // fopen() failed VORBIS_seek_without_length, // can't seek in unknown-length file VORBIS_unexpected_eof=10, // file is truncated? VORBIS_seek_invalid, // seek past EOF // decoding errors (corrupt/invalid stream) -- you probably // don't care about the exact details of these // vorbis errors: VORBIS_invalid_setup=20, VORBIS_invalid_stream, // ogg errors: VORBIS_missing_capture_pattern=30, VORBIS_invalid_stream_structure_version, VORBIS_continued_packet_flag_invalid, VORBIS_incorrect_stream_serial_number, VORBIS_invalid_first_page, VORBIS_bad_packet_type, VORBIS_cant_find_last_page, VORBIS_seek_failed, VORBIS_ogg_skeleton_not_supported }; #ifdef __cplusplus } #endif #endif // STB_VORBIS_INCLUDE_STB_VORBIS_H // // HEADER ENDS HERE // ////////////////////////////////////////////////////////////////////////////// #ifndef STB_VORBIS_HEADER_ONLY // global configuration settings (e.g. set these in the project/makefile), // or just set them in this file at the top (although ideally the first few // should be visible when the header file is compiled too, although it's not // crucial) // STB_VORBIS_NO_PUSHDATA_API // does not compile the code for the various stb_vorbis_*_pushdata() // functions // #define STB_VORBIS_NO_PUSHDATA_API // STB_VORBIS_NO_PULLDATA_API // does not compile the code for the non-pushdata APIs // #define STB_VORBIS_NO_PULLDATA_API // STB_VORBIS_NO_STDIO // does not compile the code for the APIs that use FILE *s internally // or externally (implied by STB_VORBIS_NO_PULLDATA_API) // #define STB_VORBIS_NO_STDIO // STB_VORBIS_NO_INTEGER_CONVERSION // does not compile the code for converting audio sample data from // float to integer (implied by STB_VORBIS_NO_PULLDATA_API) // #define STB_VORBIS_NO_INTEGER_CONVERSION // STB_VORBIS_NO_FAST_SCALED_FLOAT // does not use a fast float-to-int trick to accelerate float-to-int on // most platforms which requires endianness be defined correctly. //#define STB_VORBIS_NO_FAST_SCALED_FLOAT // STB_VORBIS_MAX_CHANNELS [number] // globally define this to the maximum number of channels you need. // The spec does not put a restriction on channels except that // the count is stored in a byte, so 255 is the hard limit. // Reducing this saves about 16 bytes per value, so using 16 saves // (255-16)*16 or around 4KB. Plus anything other memory usage // I forgot to account for. Can probably go as low as 8 (7.1 audio), // 6 (5.1 audio), or 2 (stereo only). #ifndef STB_VORBIS_MAX_CHANNELS #define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? #endif // STB_VORBIS_PUSHDATA_CRC_COUNT [number] // after a flush_pushdata(), stb_vorbis begins scanning for the // next valid page, without backtracking. when it finds something // that looks like a page, it streams through it and verifies its // CRC32. Should that validation fail, it keeps scanning. But it's // possible that _while_ streaming through to check the CRC32 of // one candidate page, it sees another candidate page. This #define // determines how many "overlapping" candidate pages it can search // at once. Note that "real" pages are typically ~4KB to ~8KB, whereas // garbage pages could be as big as 64KB, but probably average ~16KB. // So don't hose ourselves by scanning an apparent 64KB page and // missing a ton of real ones in the interim; so minimum of 2 #ifndef STB_VORBIS_PUSHDATA_CRC_COUNT #define STB_VORBIS_PUSHDATA_CRC_COUNT 4 #endif // STB_VORBIS_FAST_HUFFMAN_LENGTH [number] // sets the log size of the huffman-acceleration table. Maximum // supported value is 24. with larger numbers, more decodings are O(1), // but the table size is larger so worse cache missing, so you'll have // to probe (and try multiple ogg vorbis files) to find the sweet spot. #ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH #define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 #endif // STB_VORBIS_FAST_BINARY_LENGTH [number] // sets the log size of the binary-search acceleration table. this // is used in similar fashion to the fast-huffman size to set initial // parameters for the binary search // STB_VORBIS_FAST_HUFFMAN_INT // The fast huffman tables are much more efficient if they can be // stored as 16-bit results instead of 32-bit results. This restricts // the codebooks to having only 65535 possible outcomes, though. // (At least, accelerated by the huffman table.) #ifndef STB_VORBIS_FAST_HUFFMAN_INT #define STB_VORBIS_FAST_HUFFMAN_SHORT #endif // STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH // If the 'fast huffman' search doesn't succeed, then stb_vorbis falls // back on binary searching for the correct one. This requires storing // extra tables with the huffman codes in sorted order. Defining this // symbol trades off space for speed by forcing a linear search in the // non-fast case, except for "sparse" codebooks. // #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH // STB_VORBIS_DIVIDES_IN_RESIDUE // stb_vorbis precomputes the result of the scalar residue decoding // that would otherwise require a divide per chunk. you can trade off // space for time by defining this symbol. // #define STB_VORBIS_DIVIDES_IN_RESIDUE // STB_VORBIS_DIVIDES_IN_CODEBOOK // vorbis VQ codebooks can be encoded two ways: with every case explicitly // stored, or with all elements being chosen from a small range of values, // and all values possible in all elements. By default, stb_vorbis expands // this latter kind out to look like the former kind for ease of decoding, // because otherwise an integer divide-per-vector-element is required to // unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can // trade off storage for speed. //#define STB_VORBIS_DIVIDES_IN_CODEBOOK #ifdef STB_VORBIS_CODEBOOK_SHORTS #error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" #endif // STB_VORBIS_DIVIDE_TABLE // this replaces small integer divides in the floor decode loop with // table lookups. made less than 1% difference, so disabled by default. // STB_VORBIS_NO_INLINE_DECODE // disables the inlining of the scalar codebook fast-huffman decode. // might save a little codespace; useful for debugging // #define STB_VORBIS_NO_INLINE_DECODE // STB_VORBIS_NO_DEFER_FLOOR // Normally we only decode the floor without synthesizing the actual // full curve. We can instead synthesize the curve immediately. This // requires more memory and is very likely slower, so I don't think // you'd ever want to do it except for debugging. // #define STB_VORBIS_NO_DEFER_FLOOR // STB_VORBIS_NO_COMMENTS // Disables reading and storing user comments. // #define STB_VORBIS_NO_COMMENTS ////////////////////////////////////////////////////////////////////////////// #ifdef STB_VORBIS_NO_PULLDATA_API #define STB_VORBIS_NO_INTEGER_CONVERSION #define STB_VORBIS_NO_STDIO #endif #if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) #define STB_VORBIS_NO_STDIO 1 #endif #ifndef STB_VORBIS_NO_INTEGER_CONVERSION #ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT // only need endianness for fast-float-to-int, which we don't // use for pushdata #ifndef STB_VORBIS_BIG_ENDIAN #define STB_VORBIS_ENDIAN 0 #else #define STB_VORBIS_ENDIAN 1 #endif #endif #endif #ifndef STB_VORBIS_NO_STDIO #include #endif #ifndef STB_VORBIS_NO_CRT #include #include #include #include #else // STB_VORBIS_NO_CRT #ifndef NULL #define NULL 0 #endif #ifndef malloc #define malloc(s) 0 #endif #ifndef free #define free(p) ((void) 0) #endif #ifndef realloc #define realloc(p, s) 0 #endif #endif // STB_VORBIS_NO_CRT #include #ifndef STB_FORCEINLINE #if defined(_MSC_VER) && (_MSC_VER >= 1200) #define STB_FORCEINLINE __forceinline #elif defined(_MSC_VER) #define STB_FORCEINLINE static __inline #elif (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2))) || defined(__clang__) #define STB_FORCEINLINE static __inline __attribute__((always_inline)) #else #define STB_FORCEINLINE static inline #endif #endif #if STB_VORBIS_MAX_CHANNELS > 256 #error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" #endif #if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 #error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" #endif #if 0 #include #define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) #else #define CHECK(f) do {} while(0) #endif #define MAX_BLOCKSIZE_LOG 13 // from specification #define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) #ifdef STB_VORBIS_SDL typedef Uint8 uint8; typedef Sint8 int8; typedef Uint16 uint16; typedef Sint16 int16; typedef Uint32 uint32; typedef Sint32 int32; #else typedef unsigned char uint8; typedef signed char int8; typedef unsigned short uint16; typedef signed short int16; typedef unsigned int uint32; typedef signed int int32; #endif #ifdef __has_feature #if __has_feature(undefined_behavior_sanitizer) #define HAS_UBSAN #endif #endif #ifdef HAS_UBSAN #define STB_NO_SANITIZE(s) __attribute__((no_sanitize(s))) #else #define STB_NO_SANITIZE(s) #endif #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif typedef float codetype; #ifdef _MSC_VER #define STBV_NOTUSED(v) (void)(v) #else #define STBV_NOTUSED(v) (void)sizeof(v) #endif // @NOTE // // Some arrays below are tagged "//varies", which means it's actually // a variable-sized piece of data, but rather than malloc I assume it's // small enough it's better to just allocate it all together with the // main thing // // Most of the variables are specified with the smallest size I could pack // them into. It might give better performance to make them all full-sized // integers. It should be safe to freely rearrange the structures or change // the sizes larger--nothing relies on silently truncating etc., nor the // order of variables. #define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) #define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) typedef struct { int dimensions, entries; uint8 *codeword_lengths; float minimum_value; float delta_value; uint8 value_bits; uint8 lookup_type; uint8 sequence_p; uint8 sparse; uint32 lookup_values; codetype *multiplicands; uint32 *codewords; #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; #else int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; #endif uint32 *sorted_codewords; int *sorted_values; int sorted_entries; } Codebook; typedef struct { uint8 order; uint16 rate; uint16 bark_map_size; uint8 amplitude_bits; uint8 amplitude_offset; uint8 number_of_books; uint8 book_list[16]; // varies } Floor0; typedef struct { uint8 partitions; uint8 partition_class_list[32]; // varies uint8 class_dimensions[16]; // varies uint8 class_subclasses[16]; // varies uint8 class_masterbooks[16]; // varies int16 subclass_books[16][8]; // varies uint16 Xlist[31*8+2]; // varies uint8 sorted_order[31*8+2]; uint8 neighbors[31*8+2][2]; uint8 floor1_multiplier; uint8 rangebits; int values; } Floor1; typedef union { Floor0 floor0; Floor1 floor1; } Floor; typedef struct { uint32 begin, end; uint32 part_size; uint8 classifications; uint8 classbook; uint8 **classdata; int16 (*residue_books)[8]; } Residue; typedef struct { uint8 magnitude; uint8 angle; uint8 mux; } MappingChannel; typedef struct { MappingChannel *chan; uint16 coupling_steps; uint8 submaps; uint8 submap_floor[16]; // varies uint8 submap_residue[16]; // varies } Mapping; typedef struct { uint8 blockflag; uint8 mapping; uint16 windowtype; uint16 transformtype; } Mode; typedef struct { uint32 goal_crc; // expected crc if match int bytes_left; // bytes left in packet uint32 crc_so_far; // running crc int bytes_done; // bytes processed in _current_ chunk uint32 sample_loc; // granule pos encoded in page } CRCscan; typedef struct { uint32 page_start, page_end; uint32 last_decoded_sample; } ProbedPage; struct stb_vorbis { // user-accessible info unsigned int sample_rate; int channels; unsigned int setup_memory_required; unsigned int temp_memory_required; unsigned int setup_temp_memory_required; #ifndef STB_VORBIS_NO_COMMENTS char *vendor; int comment_list_length; char **comment_list; #endif // input config #ifndef STB_VORBIS_NO_STDIO FILE *f; uint32 f_start; int close_on_free; #endif #ifdef STB_VORBIS_SDL SDL_IOStream *io; uint32 io_start; uint32 io_virtual_pos; uint32 io_buffer_pos; uint32 io_buffer_fill; uint8 io_buffer[IO_BUFFER_SIZE]; int close_on_free; #endif const uint8 *stream; const uint8 *stream_start; const uint8 *stream_end; uint32 stream_len; uint8 push_mode; // the page to seek to when seeking to start, may be zero uint32 first_audio_page_offset; // p_first is the page on which the first audio packet ends // (but not necessarily the page on which it starts) ProbedPage p_first, p_last; // memory management stb_vorbis_alloc alloc; int setup_offset; int temp_offset; // run-time results int eof; enum STBVorbisError error; // user-useful data // header info int blocksize[2]; int blocksize_0, blocksize_1; int codebook_count; Codebook *codebooks; int floor_count; uint16 floor_types[64]; // varies Floor *floor_config; int residue_count; uint16 residue_types[64]; // varies Residue *residue_config; int mapping_count; Mapping *mapping; int mode_count; Mode mode_config[64]; // varies uint32 total_samples; // decode buffer float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; float *outputs [STB_VORBIS_MAX_CHANNELS]; float *previous_window[STB_VORBIS_MAX_CHANNELS]; int previous_length; #ifndef STB_VORBIS_NO_DEFER_FLOOR int16 *finalY[STB_VORBIS_MAX_CHANNELS]; #else float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; #endif uint32 current_loc; // sample location of next frame to decode int current_loc_valid; int32 current_playback_loc; // sample location of played samples int current_playback_loc_valid; // per-blocksize precomputed data // twiddle factors float *A[2],*B[2],*C[2]; float *window[2]; uint16 *bit_reverse[2]; // current page/packet/segment streaming info uint32 serial; // stream serial number for verification int last_page; int segment_count; uint8 segments[255]; uint8 page_flag; uint8 bytes_in_seg; uint8 first_decode; int next_seg; int last_seg; // flag that we're on the last segment int last_seg_which; // what was the segment number of the last seg? uint32 acc; int valid_bits; int packet_bytes; int end_seg_with_known_loc; uint32 known_loc_for_packet; int discard_samples_deferred; uint32 samples_output; // push mode scanning int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching #ifndef STB_VORBIS_NO_PUSHDATA_API CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; #endif // sample-access int channel_buffer_start; int channel_buffer_end; // hack: decode work buffer (used in inverse_mdct and decode_residues) void *work_buffer; // temporary buffers void *temp_lengths; void *temp_codewords; void *temp_values; void *temp_mults; }; #if defined(STB_VORBIS_NO_PUSHDATA_API) #define IS_PUSH_MODE(f) FALSE #elif defined(STB_VORBIS_NO_PULLDATA_API) #define IS_PUSH_MODE(f) TRUE #else #define IS_PUSH_MODE(f) ((f)->push_mode) #endif typedef struct stb_vorbis vorb; static int error(vorb *f, enum STBVorbisError e) { f->error = e; if (!f->eof && e != VORBIS_need_more_data) { f->error=e; // breakpoint for debugging } return 0; } // these functions are used for allocating temporary memory // while decoding. if you can afford the stack space, use // alloca(); otherwise, provide a temp buffer and it will // allocate out of those. #define array_size_required(count,size) (count*(sizeof(void *)+(size))) #define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : f->work_buffer) #define temp_free(f,p) do {} while (0) #define temp_alloc_save(f) ((f)->temp_offset) #define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) #define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) // given a sufficiently large block of memory, make an array of pointers to subblocks of it static void *make_block_array(void *mem, int count, int size) { if (!mem) return NULL; else { int i; void ** p = (void **) mem; char *q = (char *) (p + count); for (i=0; i < count; ++i) { p[i] = q; q += size; } return p; } } static void *setup_malloc(vorb *f, int sz) { if (sz <= 0 || INT_MAX - 7 < sz) return NULL; sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. f->setup_memory_required += sz; if (f->alloc.alloc_buffer) { void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; if (f->setup_offset + sz > f->temp_offset) return NULL; f->setup_offset += sz; return p; } return sz ? malloc(sz) : NULL; } static void setup_free(vorb *f, void *p) { if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack free(p); } static void *setup_temp_malloc(vorb *f, int sz) { if (sz <= 0 || INT_MAX - 7 < sz) return NULL; sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. if (f->alloc.alloc_buffer) { if (f->temp_offset - sz < f->setup_offset) return NULL; f->temp_offset -= sz; return (char *) f->alloc.alloc_buffer + f->temp_offset; } return malloc(sz); } static void setup_temp_free(vorb *f, void **_p, int sz) { void *p = *_p; *_p = NULL; if (f->alloc.alloc_buffer) { f->temp_offset += (sz+7)&~7; return; } free(p); } #define CRC32_POLY 0x04c11db7 // from spec static uint32 crc_table[256]; static void crc32_init(void) { int i,j; uint32 s; for(i=0; i < 256; i++) { for (s=(uint32) i << 24, j=0; j < 8; ++j) s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); crc_table[i] = s; } } STB_FORCEINLINE uint32 crc32_update(uint32 crc, uint8 byte) { return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; } // used in setup, and for huffman that doesn't go fast path static unsigned int bit_reverse(unsigned int n) { n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); return (n >> 16) | (n << 16); } static float square(float x) { return x*x; } // this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 // as required by the specification. fast(?) implementation from stb.h // @OPTIMIZE: called multiple times per-packet with "constants"; move to setup static int ilog(int32 n) { static const signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; if (n < 0) return 0; // signed n returns 0 // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) if (n < (1 << 14)) if (n < (1 << 4)) return 0 + log2_4[n ]; else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; else return 10 + log2_4[n >> 10]; else if (n < (1 << 24)) if (n < (1 << 19)) return 15 + log2_4[n >> 15]; else return 20 + log2_4[n >> 20]; else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; else return 30 + log2_4[n >> 30]; } #ifndef M_PI #define M_PI 3.14159265358979323846264f // from CRC #endif // code length assigned to a value with no huffman encoding #define NO_CODE 255 /////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// // // these functions are only called at setup, and only a few times // per file static float float32_unpack(uint32 x) { // from the specification uint32 mantissa = x & 0x1fffff; uint32 sign = x & 0x80000000; uint32 exp = (x & 0x7fe00000) >> 21; double res = sign ? -(double)mantissa : (double)mantissa; return (float) ldexp((float)res, (int)exp-788); } // zlib & jpeg huffman tables assume that the output symbols // can either be arbitrarily arranged, or have monotonically // increasing frequencies--they rely on the lengths being sorted; // this makes for a very simple generation algorithm. // vorbis allows a huffman table with non-sorted lengths. This // requires a more sophisticated construction, since symbols in // order do not map to huffman codes "in order". static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) { if (!c->sparse) { c->codewords [symbol] = huff_code; } else { c->codewords [count] = huff_code; c->codeword_lengths[count] = len; values [count] = symbol; } } static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) { int i,k,m=0; uint32 available[32]; memset(available, 0, sizeof(available)); // find the first entry for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; if (k == n) { assert(c->sorted_entries == 0); return TRUE; } assert(len[k] < 32); // no error return required, code reading lens checks this // add to the list add_entry(c, 0, k, m++, len[k], values); // add all available leaves for (i=1; i <= len[k]; ++i) available[i] = 1U << (32-i); // note that the above code treats the first case specially, // but it's really the same as the following code, so they // could probably be combined (except the initial code is 0, // and I use 0 in available[] to mean 'empty') for (i=k+1; i < n; ++i) { uint32 res; int z = len[i], y; if (z == NO_CODE) continue; assert(z < 32); // no error return required, code reading lens checks this // find lowest available leaf (should always be earliest, // which is what the specification calls for) // note that this property, and the fact we can never have // more than one free leaf at a given level, isn't totally // trivial to prove, but it seems true and the assert never // fires, so! while (z > 0 && !available[z]) --z; if (z == 0) { return FALSE; } res = available[z]; available[z] = 0; add_entry(c, bit_reverse(res), i, m++, len[i], values); // propagate availability up the tree if (z != len[i]) { for (y=len[i]; y > z; --y) { assert(available[y] == 0); available[y] = res + (1 << (32-y)); } } } return TRUE; } // accelerated huffman table allows fast O(1) match of all symbols // of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH static void compute_accelerated_huffman(Codebook *c) { int i, len; for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) c->fast_huffman[i] = -1; len = c->sparse ? c->sorted_entries : c->entries; #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT if (len > 32767) len = 32767; // largest possible value we can encode! #endif for (i=0; i < len; ++i) { if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; // set table entries for all bit combinations in the higher bits while (z < FAST_HUFFMAN_TABLE_SIZE) { c->fast_huffman[z] = i; z += 1 << c->codeword_lengths[i]; } } } } #ifndef STBV_CDECL #ifdef _MSC_VER #define STBV_CDECL __cdecl #else #define STBV_CDECL #endif #endif static int STBV_CDECL uint32_compare(const void *p, const void *q) { uint32 x = * (const uint32 *) p; uint32 y = * (const uint32 *) q; return x < y ? -1 : x > y; } static int include_in_sort(Codebook *c, uint8 len) { if (c->sparse) { assert(len != NO_CODE); return TRUE; } if (len == NO_CODE) return FALSE; if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; return FALSE; } // if the fast table above doesn't work, we want to binary // search them... need to reverse the bits static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) { int i, len; // build a list of all the entries // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. // this is kind of a frivolous optimization--I don't see any performance improvement, // but it's like 4 extra lines of code, so. if (!c->sparse) { int k = 0; for (i=0; i < c->entries; ++i) if (include_in_sort(c, lengths[i])) c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); assert(k == c->sorted_entries); } else { for (i=0; i < c->sorted_entries; ++i) c->sorted_codewords[i] = bit_reverse(c->codewords[i]); } qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); c->sorted_codewords[c->sorted_entries] = 0xffffffff; len = c->sparse ? c->sorted_entries : c->entries; // now we need to indicate how they correspond; we could either // #1: sort a different data structure that says who they correspond to // #2: for each sorted entry, search the original list to find who corresponds // #3: for each original entry, find the sorted entry // #1 requires extra storage, #2 is slow, #3 can use binary search! for (i=0; i < len; ++i) { int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; if (include_in_sort(c,huff_len)) { uint32 code = bit_reverse(c->codewords[i]); int x=0, n=c->sorted_entries; while (n > 1) { // invariant: sc[x] <= code < sc[x+n] int m = x + (n >> 1); if (c->sorted_codewords[m] <= code) { x = m; n -= (n>>1); } else { n >>= 1; } } assert(c->sorted_codewords[x] == code); if (c->sparse) { c->sorted_values[x] = values[i]; c->codeword_lengths[x] = huff_len; } else { c->sorted_values[x] = i; } } } } // only run while parsing the header (3 times) static int vorbis_validate(uint8 *data) { static const uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; return memcmp(data, vorbis, 6) == 0; } // called from setup only, once per code book // (formula implied by specification) // // suppress an UBSan error caused by invalid input data. // upstream: https://github.com/nothings/stb/issues/1168. STB_NO_SANITIZE("float-cast-overflow") static int lookup1_values(int entries, int dim) { int r = (int) floor(exp((float) log((float) entries) / dim)); if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; ++r; // floor() to avoid _ftol() when non-CRT if (pow((float) r+1, dim) <= entries) return -1; if ((int) floor(pow((float) r, dim)) > entries) return -1; return r; } // called twice per file static void compute_twiddle_factors(int n, float *A, float *B, float *C) { int n4 = n >> 2, n8 = n >> 3; int k,k2; for (k=k2=0; k < n4; ++k,k2+=2) { A[k2 ] = (float) cos(4*k*M_PI/n); A[k2+1] = (float) -sin(4*k*M_PI/n); B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; } for (k=k2=0; k < n8; ++k,k2+=2) { C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); } } static void compute_window(int n, float *window) { int n2 = n >> 1, i; for (i=0; i < n2; ++i) window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); } static void compute_bitreverse(int n, uint16 *rev) { int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions int i, n8 = n >> 3; for (i=0; i < n8; ++i) rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; } static int init_blocksize(vorb *f, int b, int n) { int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); if (!f->window[b]) return error(f, VORBIS_outofmem); compute_window(n, f->window[b]); f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); compute_bitreverse(n, f->bit_reverse[b]); return TRUE; } static void neighbors(uint16 *x, int n, int *plow, int *phigh) { int low = -1; int high = 65536; int i; for (i=0; i < n; ++i) { if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } } } // this has been repurposed so y is now the original index instead of y typedef struct { uint16 x,id; } stbv__floor_ordering; static int STBV_CDECL point_compare(const void *p, const void *q) { const stbv__floor_ordering *a = (const stbv__floor_ordering *) p; const stbv__floor_ordering *b = (const stbv__floor_ordering *) q; return a->x < b->x ? -1 : a->x > b->x; } // /////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// #ifdef STB_VORBIS_SDL #define USE_MEMORY(z) FALSE #elif defined(STB_VORBIS_NO_STDIO) #define USE_MEMORY(z) TRUE #else #define USE_MEMORY(z) ((z)->stream) #endif static uint8 get8(vorb *z) { #ifdef STB_VORBIS_SDL if (z->io_buffer_pos >= z->io_buffer_fill) { z->io_buffer_fill = SDL_ReadIO(z->io, z->io_buffer, IO_BUFFER_SIZE); z->io_buffer_pos = 0; if (z->io_buffer_fill == 0) { z->eof = TRUE; return 0; } } z->io_virtual_pos++; return z->io_buffer[z->io_buffer_pos++]; #else if (USE_MEMORY(z)) { if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } return *z->stream++; } #endif #ifndef STB_VORBIS_NO_STDIO { int c = fgetc(z->f); if (c == EOF) { z->eof = TRUE; return 0; } return c; } #endif } static uint32 get32(vorb *f) { uint32 x; x = get8(f); x += get8(f) << 8; x += get8(f) << 16; x += (uint32) get8(f) << 24; return x; } static int getn(vorb *z, uint8 *data, int n) { #ifdef STB_VORBIS_SDL while (n > 0) { int chunk; if (z->io_buffer_pos >= z->io_buffer_fill) { z->io_buffer_fill = SDL_ReadIO(z->io, z->io_buffer, IO_BUFFER_SIZE); z->io_buffer_pos = 0; if (z->io_buffer_fill == 0) { z->eof = 1; return 0; } } chunk = z->io_buffer_fill - z->io_buffer_pos; if (chunk > n) chunk = n; memcpy(data, z->io_buffer + z->io_buffer_pos, chunk); z->io_buffer_pos += chunk; z->io_virtual_pos += chunk; data += chunk; n -= chunk; } return 1; #else if (USE_MEMORY(z)) { if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } memcpy(data, z->stream, n); z->stream += n; return 1; } #endif #ifndef STB_VORBIS_NO_STDIO if (fread(data, n, 1, z->f) == 1) return 1; else { z->eof = 1; return 0; } #endif } static int set_file_offset(stb_vorbis *f, unsigned int loc); static void skip(vorb *z, int n) { #ifdef STB_VORBIS_SDL set_file_offset(z, z->io_virtual_pos + n); #else if (USE_MEMORY(z)) { z->stream += n; if (z->stream >= z->stream_end) z->eof = 1; return; } #endif #ifndef STB_VORBIS_NO_STDIO { long x = ftell(z->f); fseek(z->f, x+n, SEEK_SET); } #endif } static int set_file_offset(stb_vorbis *f, unsigned int loc) { #ifndef STB_VORBIS_NO_PUSHDATA_API if (f->push_mode) return 0; #endif f->eof = 0; #ifdef STB_VORBIS_SDL { unsigned int io_pos; uint32 buffer_start = f->io_virtual_pos - f->io_buffer_pos; uint32 buffer_end = buffer_start + f->io_buffer_fill; f->io_virtual_pos = loc; // Move within buffer if possible if (loc >= buffer_start && loc < buffer_end) { f->io_buffer_pos = loc - buffer_start; return 1; } io_pos = loc + f->io_start; if (io_pos < loc || loc >= 0x80000000) { io_pos = 0x7fffffff; f->eof = 1; } f->io_buffer_pos = f->io_buffer_fill = 0; // Invalidate buffer if (SDL_SeekIO(f->io, io_pos, SDL_IO_SEEK_SET) != -1) return 1; f->eof = 1; SDL_SeekIO(f->io, f->io_start, SDL_IO_SEEK_END); return 0; } #else if (USE_MEMORY(f)) { if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { f->stream = f->stream_end; f->eof = 1; return 0; } else { f->stream = f->stream_start + loc; return 1; } } #endif #ifndef STB_VORBIS_NO_STDIO if (loc + f->f_start < loc || loc >= 0x80000000) { loc = 0x7fffffff; f->eof = 1; } else { loc += f->f_start; } if (!fseek(f->f, loc, SEEK_SET)) return 1; f->eof = 1; fseek(f->f, f->f_start, SEEK_END); return 0; #endif } static const uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; static int capture_pattern(vorb *f) { if (0x4f != get8(f)) return FALSE; if (0x67 != get8(f)) return FALSE; if (0x67 != get8(f)) return FALSE; if (0x53 != get8(f)) return FALSE; return TRUE; } #define PAGEFLAG_continued_packet 1 #define PAGEFLAG_first_page 2 #define PAGEFLAG_last_page 4 static int start_page_no_capturepattern(vorb *f) { uint32 loc0,loc1,n; if (f->first_decode && !IS_PUSH_MODE(f)) { f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; } // stream structure version if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); // header flag f->page_flag = get8(f); // absolute granule position loc0 = get32(f); loc1 = get32(f); // @TODO: validate loc0,loc1 as valid positions? // stream serial number -- vorbis doesn't interleave, so discard get32(f); //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); // page sequence number n = get32(f); f->last_page = n; // CRC32 get32(f); // page_segments f->segment_count = get8(f); if (!getn(f, f->segments, f->segment_count)) return error(f, VORBIS_unexpected_eof); // assume we _don't_ know any the sample position of any segments f->end_seg_with_known_loc = -2; if (loc0 != ~0U || loc1 != ~0U) { int i; // determine which packet is the last one that will complete for (i=f->segment_count-1; i >= 0; --i) if (f->segments[i] < 255) break; // 'i' is now the index of the _last_ segment of a packet that ends if (i >= 0) { f->end_seg_with_known_loc = i; f->known_loc_for_packet = loc0; } } if (f->first_decode) { int i,len; len = 0; for (i=0; i < f->segment_count; ++i) len += f->segments[i]; len += 27 + f->segment_count; f->p_first.page_end = f->p_first.page_start + len; f->p_first.last_decoded_sample = loc0; } f->next_seg = 0; return TRUE; } static int start_page(vorb *f) { if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); return start_page_no_capturepattern(f); } static int start_packet(vorb *f) { while (f->next_seg == -1) { if (!start_page(f)) return FALSE; if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_continued_packet_flag_invalid); } f->last_seg = FALSE; f->valid_bits = 0; f->packet_bytes = 0; f->bytes_in_seg = 0; // f->next_seg is now valid return TRUE; } static int maybe_start_packet(vorb *f) { if (f->next_seg == -1) { int x = get8(f); if (f->eof) return FALSE; // EOF at page boundary is not an error! if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); if (!start_page_no_capturepattern(f)) return FALSE; if (f->page_flag & PAGEFLAG_continued_packet) { // set up enough state that we can read this packet if we want, // e.g. during recovery f->last_seg = FALSE; f->bytes_in_seg = 0; return error(f, VORBIS_continued_packet_flag_invalid); } } return start_packet(f); } static int next_segment(vorb *f) { int len; if (f->last_seg) return 0; if (f->next_seg == -1) { f->last_seg_which = f->segment_count-1; // in case start_page fails if (!start_page(f)) { f->last_seg = 1; return 0; } if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); } len = f->segments[f->next_seg++]; if (len < 255) { f->last_seg = TRUE; f->last_seg_which = f->next_seg-1; } if (f->next_seg >= f->segment_count) f->next_seg = -1; assert(f->bytes_in_seg == 0); f->bytes_in_seg = len; return len; } #define EOP (-1) #define INVALID_BITS (-1) static int get8_packet_raw(vorb *f) { if (!f->bytes_in_seg) { // CLANG! if (f->last_seg) return EOP; else if (!next_segment(f)) return EOP; } assert(f->bytes_in_seg > 0); --f->bytes_in_seg; ++f->packet_bytes; return get8(f); } static int get8_packet(vorb *f) { int x = get8_packet_raw(f); f->valid_bits = 0; return x; } #ifndef STB_VORBIS_NO_COMMENTS static int get32_packet(vorb *f) { uint32 x; x = get8_packet(f); x += get8_packet(f) << 8; x += get8_packet(f) << 16; x += (uint32) get8_packet(f) << 24; return x; } #endif static void flush_packet(vorb *f) { while (get8_packet_raw(f) != EOP); } // @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important // as the huffman decoder? static uint32 get_bits(vorb *f, int n) { uint32 z; if (f->valid_bits < 0) return 0; if (f->valid_bits < n) { if (n > 24) { // the accumulator technique below would not work correctly in this case z = get_bits(f, 24); z += get_bits(f, n-24) << 24; return z; } if (f->valid_bits == 0) f->acc = 0; while (f->valid_bits < n) { int z = get8_packet_raw(f); if (z == EOP) { f->valid_bits = INVALID_BITS; return 0; } f->acc += z << f->valid_bits; f->valid_bits += 8; } } assert(f->valid_bits >= n); z = f->acc & ((1 << n)-1); f->acc >>= n; f->valid_bits -= n; return z; } // @OPTIMIZE: primary accumulator for huffman // expand the buffer to as many bits as possible without reading off end of packet // it might be nice to allow f->valid_bits and f->acc to be stored in registers, // e.g. cache them locally and decode locally STB_FORCEINLINE void prep_huffman(vorb *f) { if (f->valid_bits <= 24) { if (f->valid_bits == 0) f->acc = 0; do { int z; if (f->last_seg && !f->bytes_in_seg) return; z = get8_packet_raw(f); if (z == EOP) return; f->acc += (unsigned) z << f->valid_bits; f->valid_bits += 8; } while (f->valid_bits <= 24); } } enum { VORBIS_packet_id = 1, VORBIS_packet_comment = 3, VORBIS_packet_setup = 5 }; static int codebook_decode_scalar_raw(vorb *f, Codebook *c) { int i; prep_huffman(f); if (c->codewords == NULL && c->sorted_codewords == NULL) return -1; // cases to use binary search: sorted_codewords && !c->codewords // sorted_codewords && c->entries > 8 if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { // binary search uint32 code = bit_reverse(f->acc); int x=0, n=c->sorted_entries, len; while (n > 1) { // invariant: sc[x] <= code < sc[x+n] int m = x + (n >> 1); if (c->sorted_codewords[m] <= code) { x = m; n -= (n>>1); } else { n >>= 1; } } // x is now the sorted index if (!c->sparse) x = c->sorted_values[x]; // x is now sorted index if sparse, or symbol otherwise len = c->codeword_lengths[x]; if (f->valid_bits >= len) { f->acc >>= len; f->valid_bits -= len; return x; } f->valid_bits = 0; return -1; } // if small, linear search assert(!c->sparse); for (i=0; i < c->entries; ++i) { if (c->codeword_lengths[i] == NO_CODE) continue; /* unsigned left shift for 32-bit codewords. * https://github.com/nothings/stb/issues/1168 */ if (c->codewords[i] == (f->acc & ((1U << c->codeword_lengths[i])-1))) { if (f->valid_bits >= c->codeword_lengths[i]) { f->acc >>= c->codeword_lengths[i]; f->valid_bits -= c->codeword_lengths[i]; return i; } f->valid_bits = 0; return -1; } } error(f, VORBIS_invalid_stream); f->valid_bits = 0; return -1; } #ifndef STB_VORBIS_NO_INLINE_DECODE #define DECODE_RAW(var, f,c) \ if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ prep_huffman(f); \ var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ var = c->fast_huffman[var]; \ if (var >= 0) { \ int n = c->codeword_lengths[var]; \ f->acc >>= n; \ f->valid_bits -= n; \ if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ } else { \ var = codebook_decode_scalar_raw(f,c); \ } #else static int codebook_decode_scalar(vorb *f, Codebook *c) { int i; if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) prep_huffman(f); // fast huffman table lookup i = f->acc & FAST_HUFFMAN_TABLE_MASK; i = c->fast_huffman[i]; if (i >= 0) { f->acc >>= c->codeword_lengths[i]; f->valid_bits -= c->codeword_lengths[i]; if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } return i; } return codebook_decode_scalar_raw(f,c); } #define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); #endif #define DECODE(var,f,c) \ DECODE_RAW(var,f,c) \ if (c->sparse && var >= 0) var = c->sorted_values[var]; #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) #else #define DECODE_VQ(var,f,c) DECODE(var,f,c) #endif // CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case // where we avoid one addition #define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) #define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) #define CODEBOOK_ELEMENT_BASE(c) (0) static int codebook_decode_start(vorb *f, Codebook *c) { int z = -1; // type 0 is only legal in a scalar context if (c->lookup_type == 0) error(f, VORBIS_invalid_stream); else { DECODE_VQ(z,f,c); if (c->sparse) assert(z < c->sorted_entries); if (z < 0) { // check for EOP if (!f->bytes_in_seg) if (f->last_seg) return z; error(f, VORBIS_invalid_stream); } } return z; } static int codebook_decode(vorb *f, Codebook *c, float *output, int len) { int i,z = codebook_decode_start(f,c); if (z < 0) return FALSE; if (len > c->dimensions) len = c->dimensions; #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK if (c->lookup_type == 1) { float last = CODEBOOK_ELEMENT_BASE(c); int div = 1; for (i=0; i < len; ++i) { int off = (z / div) % c->lookup_values; float val = CODEBOOK_ELEMENT_FAST(c,off) + last; output[i] += val; if (c->sequence_p) last = val + c->minimum_value; div *= c->lookup_values; } return TRUE; } #endif z *= c->dimensions; if (c->sequence_p) { float last = CODEBOOK_ELEMENT_BASE(c); for (i=0; i < len; ++i) { float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; output[i] += val; last = val + c->minimum_value; } } else { float last = CODEBOOK_ELEMENT_BASE(c); for (i=0; i < len; ++i) { output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; } } return TRUE; } static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) { int i,z = codebook_decode_start(f,c); float last = CODEBOOK_ELEMENT_BASE(c); if (z < 0) return FALSE; if (len > c->dimensions) len = c->dimensions; #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK if (c->lookup_type == 1) { int div = 1; for (i=0; i < len; ++i) { int off = (z / div) % c->lookup_values; float val = CODEBOOK_ELEMENT_FAST(c,off) + last; output[i*step] += val; if (c->sequence_p) last = val; div *= c->lookup_values; } return TRUE; } #endif z *= c->dimensions; for (i=0; i < len; ++i) { float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; output[i*step] += val; if (c->sequence_p) last = val; } return TRUE; } static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) { int c_inter = *c_inter_p; int p_inter = *p_inter_p; int i,z, effective = c->dimensions; // type 0 is only legal in a scalar context if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); while (total_decode > 0) { float last = CODEBOOK_ELEMENT_BASE(c); DECODE_VQ(z,f,c); #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK assert(!c->sparse || z < c->sorted_entries); #endif if (z < 0) { if (!f->bytes_in_seg) if (f->last_seg) return FALSE; return error(f, VORBIS_invalid_stream); } // if this will take us off the end of the buffers, stop short! // we check by computing the length of the virtual interleaved // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), // and the length we'll be using (effective) if (c_inter + p_inter*ch + effective > len * ch) { effective = len*ch - (p_inter*ch + c_inter); } #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK if (c->lookup_type == 1) { int div = 1; for (i=0; i < effective; ++i) { int off = (z / div) % c->lookup_values; float val = CODEBOOK_ELEMENT_FAST(c,off) + last; if (outputs[c_inter]) outputs[c_inter][p_inter] += val; if (++c_inter == ch) { c_inter = 0; ++p_inter; } if (c->sequence_p) last = val; div *= c->lookup_values; } } else #endif { z *= c->dimensions; if (c->sequence_p) { for (i=0; i < effective; ++i) { float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; if (outputs[c_inter]) outputs[c_inter][p_inter] += val; if (++c_inter == ch) { c_inter = 0; ++p_inter; } last = val; } } else { for (i=0; i < effective; ++i) { float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; if (outputs[c_inter]) outputs[c_inter][p_inter] += val; if (++c_inter == ch) { c_inter = 0; ++p_inter; } } } } total_decode -= effective; } *c_inter_p = c_inter; *p_inter_p = p_inter; return TRUE; } static int predict_point(int x, int x0, int x1, int y0, int y1) { int dy = y1 - y0; int adx = x1 - x0; // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? int err = abs(dy) * (x - x0); int off = err / adx; return dy < 0 ? y0 - off : y0 + off; } // the following table is block-copied from the specification static const float inverse_db_table[256] = { 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, 0.82788260f, 0.88168307f, 0.9389798f, 1.0f }; // @OPTIMIZE: if you want to replace this bresenham line-drawing routine, // note that you must produce bit-identical output to decode correctly; // this specific sequence of operations is specified in the spec (it's // drawing integer-quantized frequency-space lines that the encoder // expects to be exactly the same) // ... also, isn't the whole point of Bresenham's algorithm to NOT // have to divide in the setup? sigh. #ifndef STB_VORBIS_NO_DEFER_FLOOR #define LINE_OP(a,b) a *= b #else #define LINE_OP(a,b) a = b #endif #ifdef STB_VORBIS_DIVIDE_TABLE #define DIVTAB_NUMER 32 #define DIVTAB_DENOM 64 int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB #endif STB_FORCEINLINE void draw_line(float *output, int x0, int y0, int x1, int y1, int n) { int dy = y1 - y0; int adx = x1 - x0; int ady = abs(dy); int base; int x=x0,y=y0; int err = 0; int sy; #ifdef STB_VORBIS_DIVIDE_TABLE if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { if (dy < 0) { base = -integer_divide_table[ady][adx]; sy = base-1; } else { base = integer_divide_table[ady][adx]; sy = base+1; } } else { base = dy / adx; if (dy < 0) sy = base - 1; else sy = base+1; } #else base = dy / adx; if (dy < 0) sy = base - 1; else sy = base+1; #endif ady -= abs(base) * adx; if (x1 > n) x1 = n; if (x < x1) { LINE_OP(output[x], inverse_db_table[(uint32)y&255]); for (++x; x < x1; ++x) { err += ady; if (err >= adx) { err -= adx; y += sy; } else y += base; LINE_OP(output[x], inverse_db_table[(uint32)y&255]); } } } static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) { int k; if (rtype == 0) { int step = n / book->dimensions; for (k=0; k < step; ++k) if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) return FALSE; } else { for (k=0; k < n; ) { if (!codebook_decode(f, book, target+offset, n-k)) return FALSE; k += book->dimensions; offset += book->dimensions; } } return TRUE; } // n is 1/2 of the blocksize -- // specification: "Correct per-vector decode length is [n]/2" static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) { int i,j,pass; Residue *r = f->residue_config + rn; int rtype = f->residue_types[rn]; int c = r->classbook; int classwords = f->codebooks[c].dimensions; unsigned int actual_size = rtype == 2 ? n*2 : n; unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); int n_read = limit_r_end - limit_r_begin; int part_read = n_read / r->part_size; int temp_alloc_point = temp_alloc_save(f); #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); #else int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); #endif CHECK(f); for (i=0; i < ch; ++i) if (!do_not_decode[i]) memset(residue_buffers[i], 0, sizeof(float) * n); if (rtype == 2 && ch != 1) { for (j=0; j < ch; ++j) if (!do_not_decode[j]) break; if (j == ch) goto done; for (pass=0; pass < 8; ++pass) { int pcount = 0, class_set = 0; if (ch == 2) { while (pcount < part_read) { int z = r->begin + pcount*r->part_size; int c_inter = (z & 1), p_inter = z>>1; if (pass == 0) { Codebook *c = f->codebooks+r->classbook; int q; DECODE(q,f,c); if (q == EOP) goto done; #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE part_classdata[0][class_set] = r->classdata[q]; #else for (i=classwords-1; i >= 0; --i) { classifications[0][i+pcount] = q % r->classifications; q /= r->classifications; } #endif } for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { int z = r->begin + pcount*r->part_size; #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE int c = part_classdata[0][class_set][i]; #else int c = classifications[0][pcount]; #endif int b = r->residue_books[c][pass]; if (b >= 0) { Codebook *book = f->codebooks + b; #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) goto done; #else // saves 1% if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) goto done; #endif } else { z += r->part_size; c_inter = z & 1; p_inter = z >> 1; } } #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE ++class_set; #endif } } else if (ch > 2) { while (pcount < part_read) { int z = r->begin + pcount*r->part_size; int c_inter = z % ch, p_inter = z/ch; if (pass == 0) { Codebook *c = f->codebooks+r->classbook; int q; DECODE(q,f,c); if (q == EOP) goto done; #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE part_classdata[0][class_set] = r->classdata[q]; #else for (i=classwords-1; i >= 0; --i) { classifications[0][i+pcount] = q % r->classifications; q /= r->classifications; } #endif } for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { int z = r->begin + pcount*r->part_size; #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE int c = part_classdata[0][class_set][i]; #else int c = classifications[0][pcount]; #endif int b = r->residue_books[c][pass]; if (b >= 0) { Codebook *book = f->codebooks + b; if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) goto done; } else { z += r->part_size; c_inter = z % ch; p_inter = z / ch; } } #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE ++class_set; #endif } } } goto done; } CHECK(f); for (pass=0; pass < 8; ++pass) { int pcount = 0, class_set=0; while (pcount < part_read) { if (pass == 0) { for (j=0; j < ch; ++j) { if (!do_not_decode[j]) { Codebook *c = f->codebooks+r->classbook; int temp; DECODE(temp,f,c); if (temp == EOP) goto done; #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE part_classdata[j][class_set] = r->classdata[temp]; #else for (i=classwords-1; i >= 0; --i) { classifications[j][i+pcount] = temp % r->classifications; temp /= r->classifications; } #endif } } } for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { for (j=0; j < ch; ++j) { if (!do_not_decode[j]) { #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE int c = part_classdata[j][class_set][i]; #else int c = classifications[j][pcount]; #endif int b = r->residue_books[c][pass]; if (b >= 0) { float *target = residue_buffers[j]; int offset = r->begin + pcount * r->part_size; int n = r->part_size; Codebook *book = f->codebooks + b; if (!residue_decode(f, book, target, offset, n, rtype)) goto done; } } } } #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE ++class_set; #endif } } done: CHECK(f); #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE temp_free(f,part_classdata); #else temp_free(f,classifications); #endif temp_alloc_restore(f,temp_alloc_point); } #if 0 // slow way for debugging void inverse_mdct_slow(float *buffer, int n) { int i,j; int n2 = n >> 1; float *x = (float *) malloc(sizeof(*x) * n2); memcpy(x, buffer, sizeof(*x) * n2); for (i=0; i < n; ++i) { float acc = 0; for (j=0; j < n2; ++j) // formula from paper: //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); // formula from wikipedia //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); // these are equivalent, except the formula from the paper inverts the multiplier! // however, what actually works is NO MULTIPLIER!?! //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); buffer[i] = acc; } free(x); } #elif 0 // same as above, but just barely able to run in real time on modern machines void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) { float mcos[16384]; int i,j; int n2 = n >> 1, nmask = (n << 2) -1; float *x = (float *) malloc(sizeof(*x) * n2); memcpy(x, buffer, sizeof(*x) * n2); for (i=0; i < 4*n; ++i) mcos[i] = (float) cos(M_PI / 2 * i / n); for (i=0; i < n; ++i) { float acc = 0; for (j=0; j < n2; ++j) acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; buffer[i] = acc; } free(x); } #elif 0 // transform to use a slow dct-iv; this is STILL basically trivial, // but only requires half as many ops void dct_iv_slow(float *buffer, int n) { float mcos[16384]; float x[2048]; int i,j; int n2 = n >> 1, nmask = (n << 3) - 1; memcpy(x, buffer, sizeof(*x) * n); for (i=0; i < 8*n; ++i) mcos[i] = (float) cos(M_PI / 4 * i / n); for (i=0; i < n; ++i) { float acc = 0; for (j=0; j < n; ++j) acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; buffer[i] = acc; } } void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) { int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; float temp[4096]; memcpy(temp, buffer, n2 * sizeof(float)); dct_iv_slow(temp, n2); // returns -c'-d, a-b' for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d } #endif #ifndef LIBVORBIS_MDCT #define LIBVORBIS_MDCT 0 #endif #if LIBVORBIS_MDCT // directly call the vorbis MDCT using an interface documented // by Jeff Roberts... useful for performance comparison typedef struct { int n; int log2n; float *trig; int *bitrev; float scale; } mdct_lookup; extern void mdct_init(mdct_lookup *lookup, int n); extern void mdct_clear(mdct_lookup *l); extern void mdct_backward(mdct_lookup *init, float *in, float *out); mdct_lookup M1,M2; void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) { mdct_lookup *M; if (M1.n == n) M = &M1; else if (M2.n == n) M = &M2; else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } else { if (M2.n) __asm int 3; mdct_init(&M2, n); M = &M2; } mdct_backward(M, buffer, buffer); } #endif // the following were split out into separate functions while optimizing; // they could be pushed back up but eh. __forceinline showed no change; // they're probably already being inlined. static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) { float *ee0 = e + i_off; float *ee2 = ee0 + k_off; int i; assert((n & 3) == 0); for (i=(n>>2); i > 0; --i) { float k00_20, k01_21; k00_20 = ee0[ 0] - ee2[ 0]; k01_21 = ee0[-1] - ee2[-1]; ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; A += 8; k00_20 = ee0[-2] - ee2[-2]; k01_21 = ee0[-3] - ee2[-3]; ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; A += 8; k00_20 = ee0[-4] - ee2[-4]; k01_21 = ee0[-5] - ee2[-5]; ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; A += 8; k00_20 = ee0[-6] - ee2[-6]; k01_21 = ee0[-7] - ee2[-7]; ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; A += 8; ee0 -= 8; ee2 -= 8; } } static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) { int i; float k00_20, k01_21; float *e0 = e + d0; float *e2 = e0 + k_off; for (i=lim >> 2; i > 0; --i) { k00_20 = e0[-0] - e2[-0]; k01_21 = e0[-1] - e2[-1]; e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; A += k1; k00_20 = e0[-2] - e2[-2]; k01_21 = e0[-3] - e2[-3]; e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; A += k1; k00_20 = e0[-4] - e2[-4]; k01_21 = e0[-5] - e2[-5]; e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; A += k1; k00_20 = e0[-6] - e2[-6]; k01_21 = e0[-7] - e2[-7]; e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; e0 -= 8; e2 -= 8; A += k1; } } static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) { int i; float A0 = A[0]; float A1 = A[0+1]; float A2 = A[0+a_off]; float A3 = A[0+a_off+1]; float A4 = A[0+a_off*2+0]; float A5 = A[0+a_off*2+1]; float A6 = A[0+a_off*3+0]; float A7 = A[0+a_off*3+1]; float k00,k11; float *ee0 = e +i_off; float *ee2 = ee0+k_off; for (i=n; i > 0; --i) { k00 = ee0[ 0] - ee2[ 0]; k11 = ee0[-1] - ee2[-1]; ee0[ 0] = ee0[ 0] + ee2[ 0]; ee0[-1] = ee0[-1] + ee2[-1]; ee2[ 0] = (k00) * A0 - (k11) * A1; ee2[-1] = (k11) * A0 + (k00) * A1; k00 = ee0[-2] - ee2[-2]; k11 = ee0[-3] - ee2[-3]; ee0[-2] = ee0[-2] + ee2[-2]; ee0[-3] = ee0[-3] + ee2[-3]; ee2[-2] = (k00) * A2 - (k11) * A3; ee2[-3] = (k11) * A2 + (k00) * A3; k00 = ee0[-4] - ee2[-4]; k11 = ee0[-5] - ee2[-5]; ee0[-4] = ee0[-4] + ee2[-4]; ee0[-5] = ee0[-5] + ee2[-5]; ee2[-4] = (k00) * A4 - (k11) * A5; ee2[-5] = (k11) * A4 + (k00) * A5; k00 = ee0[-6] - ee2[-6]; k11 = ee0[-7] - ee2[-7]; ee0[-6] = ee0[-6] + ee2[-6]; ee0[-7] = ee0[-7] + ee2[-7]; ee2[-6] = (k00) * A6 - (k11) * A7; ee2[-7] = (k11) * A6 + (k00) * A7; ee0 -= k0; ee2 -= k0; } } STB_FORCEINLINE void iter_54(float *z) { float k00,k11,k22,k33; float y0,y1,y2,y3; k00 = z[ 0] - z[-4]; y0 = z[ 0] + z[-4]; y2 = z[-2] + z[-6]; k22 = z[-2] - z[-6]; z[-0] = y0 + y2; // z0 + z4 + z2 + z6 z[-2] = y0 - y2; // z0 + z4 - z2 - z6 // done with y0,y2 k33 = z[-3] - z[-7]; z[-4] = k00 + k33; // z0 - z4 + z3 - z7 z[-6] = k00 - k33; // z0 - z4 - z3 + z7 // done with k33 k11 = z[-1] - z[-5]; y1 = z[-1] + z[-5]; y3 = z[-3] + z[-7]; z[-1] = y1 + y3; // z1 + z5 + z3 + z7 z[-3] = y1 - y3; // z1 + z5 - z3 - z7 z[-5] = k11 - k22; // z1 - z5 + z2 - z6 z[-7] = k11 + k22; // z1 - z5 - z2 + z6 } static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) { int a_off = base_n >> 3; float A2 = A[0+a_off]; float *z = e + i_off; float *base = z - 16 * n; while (z > base) { float k00,k11; float l00,l11; k00 = z[-0] - z[ -8]; k11 = z[-1] - z[ -9]; l00 = z[-2] - z[-10]; l11 = z[-3] - z[-11]; z[ -0] = z[-0] + z[ -8]; z[ -1] = z[-1] + z[ -9]; z[ -2] = z[-2] + z[-10]; z[ -3] = z[-3] + z[-11]; z[ -8] = k00; z[ -9] = k11; z[-10] = (l00+l11) * A2; z[-11] = (l11-l00) * A2; k00 = z[ -4] - z[-12]; k11 = z[ -5] - z[-13]; l00 = z[ -6] - z[-14]; l11 = z[ -7] - z[-15]; z[ -4] = z[ -4] + z[-12]; z[ -5] = z[ -5] + z[-13]; z[ -6] = z[ -6] + z[-14]; z[ -7] = z[ -7] + z[-15]; z[-12] = k11; z[-13] = -k00; z[-14] = (l11-l00) * A2; z[-15] = (l00+l11) * -A2; iter_54(z); iter_54(z-8); z -= 16; } } static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) { int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; int ld; // @OPTIMIZE: reduce register pressure by using fewer variables? int save_point = temp_alloc_save(f); float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); float *u=NULL,*v=NULL; // twiddle factors float *A = f->A[blocktype]; // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. // kernel from paper // merged: // copy and reflect spectral data // step 0 // note that it turns out that the items added together during // this step are, in fact, being added to themselves (as reflected // by step 0). inexplicable inefficiency! this became obvious // once I combined the passes. // so there's a missing 'times 2' here (for adding X to itself). // this propagates through linearly to the end, where the numbers // are 1/2 too small, and need to be compensated for. { float *d,*e, *AA, *e_stop; d = &buf2[n2-2]; AA = A; e = &buffer[0]; e_stop = &buffer[n2]; while (e != e_stop) { d[1] = (e[0] * AA[0] - e[2]*AA[1]); d[0] = (e[0] * AA[1] + e[2]*AA[0]); d -= 2; AA += 2; e += 4; } e = &buffer[n2-3]; while (d >= buf2) { d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); d -= 2; AA += 2; e -= 4; } } // now we use symbolic names for these, so that we can // possibly swap their meaning as we change which operations // are in place u = buffer; v = buf2; // step 2 (paper output is w, now u) // this could be in place, but the data ends up in the wrong // place... _somebody_'s got to swap it, so this is nominated { float *AA = &A[n2-8]; float *d0,*d1, *e0, *e1; e0 = &v[n4]; e1 = &v[0]; d0 = &u[n4]; d1 = &u[0]; while (AA >= A) { float v40_20, v41_21; v41_21 = e0[1] - e1[1]; v40_20 = e0[0] - e1[0]; d0[1] = e0[1] + e1[1]; d0[0] = e0[0] + e1[0]; d1[1] = v41_21*AA[4] - v40_20*AA[5]; d1[0] = v40_20*AA[4] + v41_21*AA[5]; v41_21 = e0[3] - e1[3]; v40_20 = e0[2] - e1[2]; d0[3] = e0[3] + e1[3]; d0[2] = e0[2] + e1[2]; d1[3] = v41_21*AA[0] - v40_20*AA[1]; d1[2] = v40_20*AA[0] + v41_21*AA[1]; AA -= 8; d0 += 4; d1 += 4; e0 += 4; e1 += 4; } } // step 3 ld = ilog(n) - 1; // ilog is off-by-one from normal definitions // optimized step 3: // the original step3 loop can be nested r inside s or s inside r; // it's written originally as s inside r, but this is dumb when r // iterates many times, and s few. So I have two copies of it and // switch between them halfway. // this is iteration 0 of step 3 imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); // this is iteration 1 of step 3 imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); l=2; for (; l < (ld-3)>>1; ++l) { int k0 = n >> (l+2), k0_2 = k0>>1; int lim = 1 << (l+1); int i; for (i=0; i < lim; ++i) imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); } for (; l < ld-6; ++l) { int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; int rlim = n >> (l+6), r; int lim = 1 << (l+1); int i_off; float *A0 = A; i_off = n2-1; for (r=rlim; r > 0; --r) { imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); A0 += k1*4; i_off -= 8; } } // iterations with count: // ld-6,-5,-4 all interleaved together // the big win comes from getting rid of needless flops // due to the constants on pass 5 & 4 being all 1 and 0; // combining them to be simultaneous to improve cache made little difference imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); // output is u // step 4, 5, and 6 // cannot be in-place because of step 5 { uint16 *bitrev = f->bit_reverse[blocktype]; // weirdly, I'd have thought reading sequentially and writing // erratically would have been better than vice-versa, but in // fact that's not what my testing showed. (That is, with // j = bitreverse(i), do you read i and write j, or read j and write i.) float *d0 = &v[n4-4]; float *d1 = &v[n2-4]; while (d0 >= v) { int k4; k4 = bitrev[0]; d1[3] = u[k4+0]; d1[2] = u[k4+1]; d0[3] = u[k4+2]; d0[2] = u[k4+3]; k4 = bitrev[1]; d1[1] = u[k4+0]; d1[0] = u[k4+1]; d0[1] = u[k4+2]; d0[0] = u[k4+3]; d0 -= 4; d1 -= 4; bitrev += 2; } } // (paper output is u, now v) // data must be in buf2 assert(v == buf2); // step 7 (paper output is v, now v) // this is now in place { float *C = f->C[blocktype]; float *d, *e; d = v; e = v + n2 - 4; while (d < e) { float a02,a11,b0,b1,b2,b3; a02 = d[0] - e[2]; a11 = d[1] + e[3]; b0 = C[1]*a02 + C[0]*a11; b1 = C[1]*a11 - C[0]*a02; b2 = d[0] + e[ 2]; b3 = d[1] - e[ 3]; d[0] = b2 + b0; d[1] = b3 + b1; e[2] = b2 - b0; e[3] = b1 - b3; a02 = d[2] - e[0]; a11 = d[3] + e[1]; b0 = C[3]*a02 + C[2]*a11; b1 = C[3]*a11 - C[2]*a02; b2 = d[2] + e[ 0]; b3 = d[3] - e[ 1]; d[2] = b2 + b0; d[3] = b3 + b1; e[0] = b2 - b0; e[1] = b1 - b3; C += 4; d += 4; e -= 4; } } // data must be in buf2 // step 8+decode (paper output is X, now buffer) // this generates pairs of data a la 8 and pushes them directly through // the decode kernel (pushing rather than pulling) to avoid having // to make another pass later // this cannot POSSIBLY be in place, so we refer to the buffers directly { float *d0,*d1,*d2,*d3; float *B = f->B[blocktype] + n2 - 8; float *e = buf2 + n2 - 8; d0 = &buffer[0]; d1 = &buffer[n2-4]; d2 = &buffer[n2]; d3 = &buffer[n-4]; while (e >= v) { float p0,p1,p2,p3; p3 = e[6]*B[7] - e[7]*B[6]; p2 = -e[6]*B[6] - e[7]*B[7]; d0[0] = p3; d1[3] = - p3; d2[0] = p2; d3[3] = p2; p1 = e[4]*B[5] - e[5]*B[4]; p0 = -e[4]*B[4] - e[5]*B[5]; d0[1] = p1; d1[2] = - p1; d2[1] = p0; d3[2] = p0; p3 = e[2]*B[3] - e[3]*B[2]; p2 = -e[2]*B[2] - e[3]*B[3]; d0[2] = p3; d1[1] = - p3; d2[2] = p2; d3[1] = p2; p1 = e[0]*B[1] - e[1]*B[0]; p0 = -e[0]*B[0] - e[1]*B[1]; d0[3] = p1; d1[0] = - p1; d2[3] = p0; d3[0] = p0; B -= 8; e -= 8; d0 += 4; d2 += 4; d1 -= 4; d3 -= 4; } } temp_free(f,buf2); temp_alloc_restore(f,save_point); } #if 0 // this is the original version of the above code, if you want to optimize it from scratch void inverse_mdct_naive(float *buffer, int n) { float s; float A[1 << 12], B[1 << 12], C[1 << 11]; int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; int n3_4 = n - n4, ld; // how can they claim this only uses N words?! // oh, because they're only used sparsely, whoops float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; // set up twiddle factors for (k=k2=0; k < n4; ++k,k2+=2) { A[k2 ] = (float) cos(4*k*M_PI/n); A[k2+1] = (float) -sin(4*k*M_PI/n); B[k2 ] = (float) cos((k2+1)*M_PI/n/2); B[k2+1] = (float) sin((k2+1)*M_PI/n/2); } for (k=k2=0; k < n8; ++k,k2+=2) { C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); } // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" // Note there are bugs in that pseudocode, presumably due to them attempting // to rename the arrays nicely rather than representing the way their actual // implementation bounces buffers back and forth. As a result, even in the // "some formulars corrected" version, a direct implementation fails. These // are noted below as "paper bug". // copy and reflect spectral data for (k=0; k < n2; ++k) u[k] = buffer[k]; for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; // kernel from paper // step 1 for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; } // step 2 for (k=k4=0; k < n8; k+=1, k4+=4) { w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; } // step 3 ld = ilog(n) - 1; // ilog is off-by-one from normal definitions for (l=0; l < ld-3; ++l) { int k0 = n >> (l+2), k1 = 1 << (l+3); int rlim = n >> (l+4), r4, r; int s2lim = 1 << (l+2), s2; for (r=r4=0; r < rlim; r4+=4,++r) { for (s2=0; s2 < s2lim; s2+=2) { u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; } } if (l+1 < ld-3) { // paper bug: ping-ponging of u&w here is omitted memcpy(w, u, sizeof(u)); } } // step 4 for (i=0; i < n8; ++i) { int j = bit_reverse(i) >> (32-ld+3); assert(j < n8); if (i == j) { // paper bug: original code probably swapped in place; if copying, // need to directly copy in this case int i8 = i << 3; v[i8+1] = u[i8+1]; v[i8+3] = u[i8+3]; v[i8+5] = u[i8+5]; v[i8+7] = u[i8+7]; } else if (i < j) { int i8 = i << 3, j8 = j << 3; v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; } } // step 5 for (k=0; k < n2; ++k) { w[k] = v[k*2+1]; } // step 6 for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { u[n-1-k2] = w[k4]; u[n-2-k2] = w[k4+1]; u[n3_4 - 1 - k2] = w[k4+2]; u[n3_4 - 2 - k2] = w[k4+3]; } // step 7 for (k=k2=0; k < n8; ++k, k2 += 2) { v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; } // step 8 for (k=k2=0; k < n4; ++k,k2 += 2) { X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; } // decode kernel to output // determined the following value experimentally // (by first figuring out what made inverse_mdct_slow work); then matching that here // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) s = 0.5; // theoretically would be n4 // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, // so it needs to use the "old" B values to behave correctly, or else // set s to 1.0 ]]] for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; } #endif static float *get_window(vorb *f, int len) { len <<= 1; if (len == f->blocksize_0) return f->window[0]; if (len == f->blocksize_1) return f->window[1]; return NULL; } #ifndef STB_VORBIS_NO_DEFER_FLOOR typedef int16 YTYPE; #else typedef int YTYPE; #endif static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) { int n2 = n >> 1; int s = map->chan[i].mux, floor; floor = map->submap_floor[s]; if (f->floor_types[floor] == 0) { return error(f, VORBIS_invalid_stream); } else { Floor1 *g = &f->floor_config[floor].floor1; int j,q; int lx = 0, ly = finalY[0] * g->floor1_multiplier; for (q=1; q < g->values; ++q) { j = g->sorted_order[q]; #ifndef STB_VORBIS_NO_DEFER_FLOOR STBV_NOTUSED(step2_flag); if (finalY[j] >= 0) #else if (step2_flag[j]) #endif { int hy = finalY[j] * g->floor1_multiplier; int hx = g->Xlist[j]; if (lx != hx) draw_line(target, lx,ly, hx,hy, n2); CHECK(f); lx = hx, ly = hy; } } if (lx < n2) { // optimization of: draw_line(target, lx,ly, n,ly, n2); for (j=lx; j < n2; ++j) LINE_OP(target[j], inverse_db_table[ly]); CHECK(f); } } return TRUE; } // The meaning of "left" and "right" // // For a given frame: // we compute samples from 0..n // window_center is n/2 // we'll window and mix the samples from left_start to left_end with data from the previous frame // all of the samples from left_end to right_start can be output without mixing; however, // this interval is 0-length except when transitioning between short and long frames // all of the samples from right_start to right_end need to be mixed with the next frame, // which we don't have, so those get saved in a buffer // frame N's right_end-right_start, the number of samples to mix with the next frame, // has to be the same as frame N+1's left_end-left_start (which they are by // construction) static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) { Mode *m; int i, n, prev, next, window_center; f->channel_buffer_start = f->channel_buffer_end = 0; retry: if (f->eof) return FALSE; if (!maybe_start_packet(f)) return FALSE; // check packet type if (get_bits(f,1) != 0) { if (IS_PUSH_MODE(f)) return error(f,VORBIS_bad_packet_type); while (EOP != get8_packet(f)); goto retry; } if (f->alloc.alloc_buffer) assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); i = get_bits(f, ilog(f->mode_count-1)); if (i == EOP) return FALSE; if (i >= f->mode_count) return FALSE; *mode = i; m = f->mode_config + i; if (m->blockflag) { n = f->blocksize_1; prev = get_bits(f,1); next = get_bits(f,1); } else { prev = next = 0; n = f->blocksize_0; } // WINDOWING window_center = n >> 1; if (m->blockflag && !prev) { *p_left_start = (n - f->blocksize_0) >> 2; *p_left_end = (n + f->blocksize_0) >> 2; } else { *p_left_start = 0; *p_left_end = window_center; } if (m->blockflag && !next) { *p_right_start = (n*3 - f->blocksize_0) >> 2; *p_right_end = (n*3 + f->blocksize_0) >> 2; } else { *p_right_start = window_center; *p_right_end = n; } return TRUE; } static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) { Mapping *map; int i,j,k,n,n2; int zero_channel[256]; int really_zero_channel[256]; // WINDOWING STBV_NOTUSED(left_end); n = f->blocksize[m->blockflag]; map = &f->mapping[m->mapping]; // FLOORS n2 = n >> 1; CHECK(f); for (i=0; i < f->channels; ++i) { int s = map->chan[i].mux, floor; zero_channel[i] = FALSE; floor = map->submap_floor[s]; if (f->floor_types[floor] == 0) { return error(f, VORBIS_invalid_stream); } else { Floor1 *g = &f->floor_config[floor].floor1; if (get_bits(f, 1)) { short *finalY; uint8 step2_flag[256]; static const int range_list[4] = { 256, 128, 86, 64 }; int range = range_list[g->floor1_multiplier-1]; int offset = 2; finalY = f->finalY[i]; finalY[0] = get_bits(f, ilog(range)-1); finalY[1] = get_bits(f, ilog(range)-1); for (j=0; j < g->partitions; ++j) { int pclass = g->partition_class_list[j]; int cdim = g->class_dimensions[pclass]; int cbits = g->class_subclasses[pclass]; int csub = (1 << cbits)-1; int cval = 0; if (cbits) { Codebook *c = f->codebooks + g->class_masterbooks[pclass]; DECODE(cval,f,c); } for (k=0; k < cdim; ++k) { int book = g->subclass_books[pclass][cval & csub]; cval = cval >> cbits; if (book >= 0) { int temp; Codebook *c = f->codebooks + book; DECODE(temp,f,c); finalY[offset++] = temp; } else finalY[offset++] = 0; } } if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec step2_flag[0] = step2_flag[1] = 1; for (j=2; j < g->values; ++j) { int low, high, pred, highroom, lowroom, room, val; low = g->neighbors[j][0]; high = g->neighbors[j][1]; //neighbors(g->Xlist, j, &low, &high); pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); val = finalY[j]; highroom = range - pred; lowroom = pred; if (highroom < lowroom) room = highroom * 2; else room = lowroom * 2; if (val) { step2_flag[low] = step2_flag[high] = 1; step2_flag[j] = 1; if (val >= room) if (highroom > lowroom) finalY[j] = val - lowroom + pred; else finalY[j] = pred - val + highroom - 1; else if (val & 1) finalY[j] = pred - ((val+1)>>1); else finalY[j] = pred + (val>>1); } else { step2_flag[j] = 0; finalY[j] = pred; } } #ifdef STB_VORBIS_NO_DEFER_FLOOR do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); #else // defer final floor computation until _after_ residue for (j=0; j < g->values; ++j) { if (!step2_flag[j]) finalY[j] = -1; } #endif } else { error: zero_channel[i] = TRUE; } // So we just defer everything else to later // at this point we've decoded the floor into buffer } } CHECK(f); // at this point we've decoded all floors if (f->alloc.alloc_buffer) assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); // re-enable coupled channels if necessary memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); for (i=0; i < map->coupling_steps; ++i) if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; } CHECK(f); // RESIDUE DECODE for (i=0; i < map->submaps; ++i) { float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; int r; uint8 do_not_decode[256]; int ch = 0; for (j=0; j < f->channels; ++j) { if (map->chan[j].mux == i) { if (zero_channel[j]) { do_not_decode[ch] = TRUE; residue_buffers[ch] = NULL; } else { do_not_decode[ch] = FALSE; residue_buffers[ch] = f->channel_buffers[j]; } ++ch; } } r = map->submap_residue[i]; decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); } if (f->alloc.alloc_buffer) assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); CHECK(f); // INVERSE COUPLING for (i = map->coupling_steps-1; i >= 0; --i) { int n2 = n >> 1; float *m = f->channel_buffers[map->chan[i].magnitude]; float *a = f->channel_buffers[map->chan[i].angle ]; for (j=0; j < n2; ++j) { float a2,m2; if (m[j] > 0) if (a[j] > 0) m2 = m[j], a2 = m[j] - a[j]; else a2 = m[j], m2 = m[j] + a[j]; else if (a[j] > 0) m2 = m[j], a2 = m[j] + a[j]; else a2 = m[j], m2 = m[j] - a[j]; m[j] = m2; a[j] = a2; } } CHECK(f); // finish decoding the floors #ifndef STB_VORBIS_NO_DEFER_FLOOR for (i=0; i < f->channels; ++i) { if (really_zero_channel[i]) { memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); } else { do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); } } #else for (i=0; i < f->channels; ++i) { if (really_zero_channel[i]) { memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); } else { for (j=0; j < n2; ++j) f->channel_buffers[i][j] *= f->floor_buffers[i][j]; } } #endif // INVERSE MDCT CHECK(f); for (i=0; i < f->channels; ++i) inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); CHECK(f); // this shouldn't be necessary, unless we exited on an error // and want to flush to get to the next packet flush_packet(f); if (f->first_decode) { // assume we start so first non-discarded sample is sample 0 // this isn't to spec, but spec would require us to read ahead // and decode the size of all current frames--could be done, // but presumably it's not a commonly used feature f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around) // we might have to discard samples "from" the next frame too, // if we're lapping a large block then a small at the start? f->discard_samples_deferred = n - right_end; f->current_loc_valid = TRUE; f->first_decode = FALSE; } else if (f->discard_samples_deferred) { if (f->discard_samples_deferred >= right_start - left_start) { f->discard_samples_deferred -= (right_start - left_start); left_start = right_start; *p_left = left_start; } else { left_start += f->discard_samples_deferred; *p_left = left_start; f->discard_samples_deferred = 0; } } else if (f->previous_length == 0 && f->current_loc_valid) { // we're recovering from a seek... that means we're going to discard // the samples from this packet even though we know our position from // the last page header, so we need to update the position based on // the discarded samples here // but wait, the code below is going to add this in itself even // on a discard, so we don't need to do it here... } // check if we have ogg information about the sample # for this packet if (f->last_seg_which == f->end_seg_with_known_loc) { // if we have a valid current loc, and this is final: if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { uint32 current_end = f->known_loc_for_packet; // then let's infer the size of the (probably) short final frame if (current_end < f->current_loc + (right_end-left_start)) { if (current_end < f->current_loc) { // negative truncation, that's impossible! *len = 0; } else { *len = current_end - f->current_loc; } *len += left_start; // this doesn't seem right, but has no ill effect on my test files if (*len > right_end) *len = right_end; // this should never happen f->current_loc += *len; return TRUE; } } // otherwise, just set our sample loc // guess that the ogg granule pos refers to the _middle_ of the // last frame? // set f->current_loc to the position of left_start f->current_loc = f->known_loc_for_packet - (n2-left_start); f->current_loc_valid = TRUE; } if (f->current_loc_valid) f->current_loc += (right_start - left_start); if (f->alloc.alloc_buffer) assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); *len = right_end; // ignore samples after the window goes to 0 CHECK(f); return TRUE; } static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) { int mode, left_end, right_end; if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); } static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) { int prev,i,j; // we use right&left (the start of the right- and left-window sin()-regions) // to determine how much to return, rather than inferring from the rules // (same result, clearer code); 'left' indicates where our sin() window // starts, therefore where the previous window's right edge starts, and // therefore where to start mixing from the previous buffer. 'right' // indicates where our sin() ending-window starts, therefore that's where // we start saving, and where our returned-data ends. // mixin from previous window if (f->previous_length) { int i,j, n = f->previous_length; float *w = get_window(f, n); if (w == NULL) return 0; for (i=0; i < f->channels; ++i) { for (j=0; j < n; ++j) f->channel_buffers[i][left+j] = f->channel_buffers[i][left+j]*w[ j] + f->previous_window[i][ j]*w[n-1-j]; } } prev = f->previous_length; // last half of this data becomes previous window f->previous_length = len - right; // @OPTIMIZE: could avoid this copy by double-buffering the // output (flipping previous_window with channel_buffers), but // then previous_window would have to be 2x as large, and // channel_buffers couldn't be temp mem (although they're NOT // currently temp mem, they could be (unless we want to level // performance by spreading out the computation)) for (i=0; i < f->channels; ++i) for (j=0; right+j < len; ++j) f->previous_window[i][j] = f->channel_buffers[i][right+j]; if (!prev) // there was no previous packet, so this data isn't valid... // this isn't entirely true, only the would-have-overlapped data // isn't valid, but this seems to be what the spec requires return 0; // truncate a short frame if (len < right) right = len; f->samples_output += right-left; return right - left; } static int vorbis_pump_first_frame(stb_vorbis *f) { int len, right, left, res; res = vorbis_decode_packet(f, &len, &left, &right); if (res) vorbis_finish_frame(f, len, left, right); f->current_playback_loc = 0; f->current_playback_loc_valid = TRUE; return res; } #ifndef STB_VORBIS_NO_PUSHDATA_API static int is_whole_packet_present(stb_vorbis *f) { // make sure that we have the packet available before continuing... // this requires a full ogg parse, but we know we can fetch from f->stream // instead of coding this out explicitly, we could save the current read state, // read the next packet with get8() until end-of-packet, check f->eof, then // reset the state? but that would be slower, esp. since we'd have over 256 bytes // of state to restore (primarily the page segment table) int s = f->next_seg, first = TRUE; const uint8 *p = f->stream; if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag for (; s < f->segment_count; ++s) { p += f->segments[s]; if (f->segments[s] < 255) // stop at first short segment break; } // either this continues, or it ends it... if (s == f->segment_count) s = -1; // set 'crosses page' flag if (p > f->stream_end) return error(f, VORBIS_need_more_data); first = FALSE; } for (; s == -1;) { const uint8 *q; int n; // check that we have the page header ready if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); // validate the page if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); if (p[4] != 0) return error(f, VORBIS_invalid_stream); if (first) { // the first segment must NOT have 'continued_packet', later ones MUST if (f->previous_length) if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); // if no previous length, we're resynching, so we can come in on a continued-packet, // which we'll just drop } else { if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); } n = p[26]; // segment counts q = p+27; // q points to segment table p = q + n; // advance past header // make sure we've read the segment table if (p > f->stream_end) return error(f, VORBIS_need_more_data); for (s=0; s < n; ++s) { p += q[s]; if (q[s] < 255) break; } if (s == n) s = -1; // set 'crosses page' flag if (p > f->stream_end) return error(f, VORBIS_need_more_data); first = FALSE; } return TRUE; } #endif // !STB_VORBIS_NO_PUSHDATA_API static int start_decoder(vorb *f) { uint8 header[6], x,y; int len,i,j,k, max_submaps = 0; int longest_floorlist=0; // first page, first packet f->first_decode = TRUE; if (!start_page(f)) return FALSE; // validate page flag if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); // check for expected packet length if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); if (f->segments[0] != 30) { // check for the Ogg skeleton fishead identifying header to refine our error if (f->segments[0] == 64 && getn(f, header, 6) && header[0] == 'f' && header[1] == 'i' && header[2] == 's' && header[3] == 'h' && header[4] == 'e' && header[5] == 'a' && get8(f) == 'd' && get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); else return error(f, VORBIS_invalid_first_page); } // read packet // check packet header if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); // vorbis_version if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); get32(f); // bitrate_maximum get32(f); // bitrate_nominal get32(f); // bitrate_minimum x = get8(f); { int log0,log1; log0 = x & 15; log1 = x >> 4; f->blocksize_0 = 1 << log0; f->blocksize_1 = 1 << log1; if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); if (log0 > log1) return error(f, VORBIS_invalid_setup); } // framing_flag x = get8(f); if (!(x & 1)) return error(f, VORBIS_invalid_first_page); // second packet! if (!start_page(f)) return FALSE; if (!start_packet(f)) return FALSE; #ifndef STB_VORBIS_NO_COMMENTS if (!next_segment(f)) return FALSE; if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); for (i=0; i < 6; ++i) header[i] = get8_packet(f); if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); //file vendor len = get32_packet(f); f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); if (f->vendor == NULL) return error(f, VORBIS_outofmem); for(i=0; i < len; ++i) { f->vendor[i] = get8_packet(f); } f->vendor[len] = (char)'\0'; //user comments f->comment_list_length = get32_packet(f); f->comment_list = NULL; if (f->comment_list_length > 0) { if (INT_MAX / (int)sizeof(char*) < f->comment_list_length) goto no_comment; len = sizeof(char*) * f->comment_list_length; f->comment_list = (char**) setup_malloc(f, len); if (f->comment_list == NULL) { no_comment: f->comment_list_length = 0; return error(f, VORBIS_outofmem); } memset(f->comment_list, 0, len); } for(i=0; i < f->comment_list_length; ++i) { len = get32_packet(f); f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); for(j=0; j < len; ++j) { f->comment_list[i][j] = get8_packet(f); } f->comment_list[i][len] = (char)'\0'; } // framing_flag x = get8_packet(f); if (!(x & 1)) return error(f, VORBIS_invalid_setup); skip(f, f->bytes_in_seg); f->bytes_in_seg = 0; #endif // STB_VORBIS_NO_COMMENTS do { len = next_segment(f); skip(f, len); f->bytes_in_seg = 0; } while (len); // third packet! if (!start_packet(f)) return FALSE; #ifndef STB_VORBIS_NO_PUSHDATA_API if (IS_PUSH_MODE(f)) { if (!is_whole_packet_present(f)) { // convert error in ogg header to write type if (f->error == VORBIS_invalid_stream) f->error = VORBIS_invalid_setup; return FALSE; } } #endif crc32_init(); // always init it, to avoid multithread race conditions if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); for (i=0; i < 6; ++i) header[i] = get8_packet(f); if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); // codebooks f->codebook_count = get_bits(f,8) + 1; if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); if (f->codebooks == NULL) return error(f, VORBIS_outofmem); memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); for (i=0; i < f->codebook_count; ++i) { uint32 *values; int ordered, sorted_count; int total=0; uint8 *lengths; Codebook *c = f->codebooks+i; CHECK(f); x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); x = get_bits(f, 8); c->dimensions = (get_bits(f, 8)<<8) + x; x = get_bits(f, 8); y = get_bits(f, 8); c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; ordered = get_bits(f,1); c->sparse = ordered ? 0 : get_bits(f,1); if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (c->sparse) { lengths = (uint8 *) setup_temp_malloc(f, c->entries); f->temp_lengths = lengths; } else lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); if (!lengths) return error(f, VORBIS_outofmem); if (ordered) { int current_entry = 0; int current_length = get_bits(f,5) + 1; while (current_entry < c->entries) { int limit = c->entries - current_entry; int n = get_bits(f, ilog(limit)); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (current_length >= 32) return error(f, VORBIS_invalid_setup); if (current_entry + n > (int) c->entries) return error(f, VORBIS_invalid_setup); memset(lengths + current_entry, current_length, n); current_entry += n; ++current_length; } } else { for (j=0; j < c->entries; ++j) { int present = c->sparse ? get_bits(f,1) : 1; if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (present) { lengths[j] = get_bits(f, 5) + 1; ++total; if (lengths[j] == 32) return error(f, VORBIS_invalid_setup); } else { lengths[j] = NO_CODE; } } } if (c->sparse && total >= c->entries >> 2) { // convert sparse items to non-sparse! if (c->entries > (int) f->setup_temp_memory_required) f->setup_temp_memory_required = c->entries; c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); memcpy(c->codeword_lengths, lengths, c->entries); setup_temp_free(f, &f->temp_lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! lengths = c->codeword_lengths; c->sparse = 0; } // compute the size of the sorted tables if (c->sparse) { sorted_count = total; } else { sorted_count = 0; #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH for (j=0; j < c->entries; ++j) if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) ++sorted_count; #endif } c->sorted_entries = sorted_count; values = NULL; CHECK(f); if (!c->sparse) { c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); if (!c->codewords) return error(f, VORBIS_outofmem); } else { unsigned int size; if (c->sorted_entries) { c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); if (!c->codeword_lengths) return error(f, VORBIS_outofmem); c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); f->temp_codewords = c->codewords; if (!c->codewords) return error(f, VORBIS_outofmem); values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); f->temp_values = values; if (!values) return error(f, VORBIS_outofmem); } size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; if (size > f->setup_temp_memory_required) f->setup_temp_memory_required = size; } if (!compute_codewords(c, lengths, c->entries, values)) { return error(f, VORBIS_invalid_setup); } if (c->sorted_entries) { // allocate an extra slot for sentinels c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); // allocate an extra slot at the front so that c->sorted_values[-1] is defined // so that we can catch that case without an extra if c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); ++c->sorted_values; c->sorted_values[-1] = -1; compute_sorted_huffman(c, lengths, values); } if (c->sparse) { setup_temp_free(f, &f->temp_values, sizeof(*values)*c->sorted_entries); setup_temp_free(f, &f->temp_codewords, sizeof(*c->codewords)*c->sorted_entries); setup_temp_free(f, &f->temp_lengths, c->entries); c->codewords = NULL; } compute_accelerated_huffman(c); CHECK(f); c->lookup_type = get_bits(f, 4); if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); if (c->lookup_type > 0) { uint16 *mults; c->minimum_value = float32_unpack(get_bits(f, 32)); c->delta_value = float32_unpack(get_bits(f, 32)); c->value_bits = get_bits(f, 4)+1; c->sequence_p = get_bits(f,1); if (c->lookup_type == 1) { int values = lookup1_values(c->entries, c->dimensions); if (values < 0) return error(f, VORBIS_invalid_setup); c->lookup_values = (uint32) values; } else { /* unsigned multiply to suppress (legitimate) warning. * https://github.com/nothings/stb/issues/1168 */ c->lookup_values = (unsigned)c->entries * (unsigned)c->dimensions; } if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); f->temp_mults = mults; if (mults == NULL) return error(f, VORBIS_outofmem); for (j=0; j < (int) c->lookup_values; ++j) { int q = get_bits(f, c->value_bits); if (f->valid_bits < 0) return error(f, VORBIS_invalid_setup); mults[j] = q; } #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK if (c->lookup_type == 1) { int len, sparse = c->sparse; float last=0; // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop if (sparse) { if (c->sorted_entries == 0) goto skip; c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); } else c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); if (c->multiplicands == NULL) return error(f, VORBIS_outofmem); len = sparse ? c->sorted_entries : c->entries; for (j=0; j < len; ++j) { unsigned int z = sparse ? c->sorted_values[j] : j; unsigned int div=1; for (k=0; k < c->dimensions; ++k) { int off = (z / div) % c->lookup_values; float val = mults[off]*c->delta_value + c->minimum_value + last; c->multiplicands[j*c->dimensions + k] = val; if (c->sequence_p) last = val; if (k+1 < c->dimensions) { if (div > UINT_MAX / (unsigned int) c->lookup_values) { return error(f, VORBIS_invalid_setup); } div *= c->lookup_values; } } } c->lookup_type = 2; } else #endif { float last=0; CHECK(f); c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); if (c->multiplicands == NULL) return error(f, VORBIS_outofmem); for (j=0; j < (int) c->lookup_values; ++j) { float val = mults[j] * c->delta_value + c->minimum_value + last; c->multiplicands[j] = val; if (c->sequence_p) last = val; } } #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK skip:; #endif setup_temp_free(f, &f->temp_mults, sizeof(mults[0])*c->lookup_values); CHECK(f); } CHECK(f); } // time domain transfers (notused) x = get_bits(f, 6) + 1; for (i=0; i < x; ++i) { uint32 z = get_bits(f, 16); if (z != 0) return error(f, VORBIS_invalid_setup); } // Floors f->floor_count = get_bits(f, 6)+1; if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); if (f->floor_config == NULL) return error(f, VORBIS_outofmem); for (i=0; i < f->floor_count; ++i) { f->floor_types[i] = get_bits(f, 16); if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); if (f->floor_types[i] == 0) { Floor0 *g = &f->floor_config[i].floor0; g->order = get_bits(f,8); g->rate = get_bits(f,16); g->bark_map_size = get_bits(f,16); g->amplitude_bits = get_bits(f,6); g->amplitude_offset = get_bits(f,8); g->number_of_books = get_bits(f,4) + 1; for (j=0; j < g->number_of_books; ++j) g->book_list[j] = get_bits(f,8); return error(f, VORBIS_feature_not_supported); } else { stbv__floor_ordering p[31*8+2]; Floor1 *g = &f->floor_config[i].floor1; int max_class = -1; g->partitions = get_bits(f, 5); for (j=0; j < g->partitions; ++j) { g->partition_class_list[j] = get_bits(f, 4); if (g->partition_class_list[j] > max_class) max_class = g->partition_class_list[j]; } for (j=0; j <= max_class; ++j) { g->class_dimensions[j] = get_bits(f, 3)+1; g->class_subclasses[j] = get_bits(f, 2); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (g->class_subclasses[j]) { g->class_masterbooks[j] = get_bits(f, 8); if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); } for (k=0; k < 1 << g->class_subclasses[j]; ++k) { g->subclass_books[j][k] = (int16)get_bits(f,8)-1; if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); } } g->floor1_multiplier = get_bits(f,2)+1; g->rangebits = get_bits(f,4); g->Xlist[0] = 0; g->Xlist[1] = 1 << g->rangebits; g->values = 2; for (j=0; j < g->partitions; ++j) { int c = g->partition_class_list[j]; for (k=0; k < g->class_dimensions[c]; ++k) { g->Xlist[g->values] = get_bits(f, g->rangebits); ++g->values; } } // precompute the sorting for (j=0; j < g->values; ++j) { p[j].x = g->Xlist[j]; p[j].id = j; } qsort(p, g->values, sizeof(p[0]), point_compare); for (j=0; j < g->values-1; ++j) if (p[j].x == p[j+1].x) return error(f, VORBIS_invalid_setup); for (j=0; j < g->values; ++j) g->sorted_order[j] = (uint8) p[j].id; // precompute the neighbors for (j=2; j < g->values; ++j) { int low = 0,hi = 0; neighbors(g->Xlist, j, &low,&hi); g->neighbors[j][0] = low; g->neighbors[j][1] = hi; } if (g->values > longest_floorlist) longest_floorlist = g->values; } } // Residue f->residue_count = get_bits(f, 6)+1; if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); if (f->residue_config == NULL) return error(f, VORBIS_outofmem); memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); for (i=0; i < f->residue_count; ++i) { uint8 residue_cascade[64]; Residue *r = f->residue_config+i; f->residue_types[i] = get_bits(f, 16); if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); r->begin = get_bits(f, 24); r->end = get_bits(f, 24); if (r->end < r->begin) return error(f, VORBIS_invalid_setup); r->part_size = get_bits(f,24)+1; r->classifications = get_bits(f,6)+1; r->classbook = get_bits(f,8); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); for (j=0; j < r->classifications; ++j) { uint8 high_bits=0; uint8 low_bits=get_bits(f,3); if (get_bits(f,1)) high_bits = get_bits(f,5); residue_cascade[j] = high_bits*8 + low_bits; } if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); if (r->residue_books == NULL) return error(f, VORBIS_outofmem); for (j=0; j < r->classifications; ++j) { for (k=0; k < 8; ++k) { if (residue_cascade[j] & (1 << k)) { r->residue_books[j][k] = get_bits(f, 8); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); } else { r->residue_books[j][k] = -1; } } } // precompute the classifications[] array to avoid inner-loop mod/divide // call it 'classdata' since we already have r->classifications r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); if (!r->classdata) return error(f, VORBIS_outofmem); memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); for (j=0; j < f->codebooks[r->classbook].entries; ++j) { int classwords = f->codebooks[r->classbook].dimensions; int temp = j; r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); for (k=classwords-1; k >= 0; --k) { r->classdata[j][k] = temp % r->classifications; temp /= r->classifications; } } } f->mapping_count = get_bits(f,6)+1; if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); if (f->mapping == NULL) return error(f, VORBIS_outofmem); memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); for (i=0; i < f->mapping_count; ++i) { Mapping *m = f->mapping + i; int mapping_type = get_bits(f,16); if (mapping_type != 0) return error(f, VORBIS_invalid_setup); m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); if (m->chan == NULL) return error(f, VORBIS_outofmem); if (get_bits(f,1)) m->submaps = get_bits(f,4)+1; else m->submaps = 1; if (m->submaps > max_submaps) max_submaps = m->submaps; if (get_bits(f,1)) { m->coupling_steps = get_bits(f,8)+1; if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); for (k=0; k < m->coupling_steps; ++k) { m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); m->chan[k].angle = get_bits(f, ilog(f->channels-1)); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); } } else m->coupling_steps = 0; // reserved field if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); if (m->submaps > 1) { for (j=0; j < f->channels; ++j) { m->chan[j].mux = get_bits(f, 4); if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); } } else // @SPECIFICATION: this case is missing from the spec for (j=0; j < f->channels; ++j) m->chan[j].mux = 0; for (j=0; j < m->submaps; ++j) { get_bits(f,8); // discard m->submap_floor[j] = get_bits(f,8); m->submap_residue[j] = get_bits(f,8); if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); } } // Modes f->mode_count = get_bits(f, 6)+1; for (i=0; i < f->mode_count; ++i) { Mode *m = f->mode_config+i; m->blockflag = get_bits(f,1); m->windowtype = get_bits(f,16); m->transformtype = get_bits(f,16); m->mapping = get_bits(f,8); if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); } flush_packet(f); f->previous_length = 0; for (i=0; i < f->channels; ++i) { f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); #ifdef STB_VORBIS_NO_DEFER_FLOOR f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); #endif } if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; f->blocksize[0] = f->blocksize_0; f->blocksize[1] = f->blocksize_1; #ifdef STB_VORBIS_DIVIDE_TABLE if (integer_divide_table[1][1]==0) for (i=0; i < DIVTAB_NUMER; ++i) for (j=1; j < DIVTAB_DENOM; ++j) integer_divide_table[i][j] = i / j; #endif // compute how much temporary memory is needed // 1. { uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); uint32 classify_mem; int i,max_part_read=0; for (i=0; i < f->residue_count; ++i) { Residue *r = f->residue_config + i; unsigned int rtype = f->residue_types[i]; unsigned int actual_size = rtype == 2 ? f->blocksize_1 : f->blocksize_1 / 2; unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; int n_read = limit_r_end - limit_r_begin; int part_read = n_read / r->part_size; if (part_read > max_part_read) max_part_read = part_read; } #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); #else classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); #endif // maximum reasonable partition size is f->blocksize_1 f->temp_memory_required = classify_mem; if (imdct_mem > f->temp_memory_required) f->temp_memory_required = imdct_mem; } if (f->alloc.alloc_buffer) { assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); // check if there's enough temp memory so we don't error later if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) return error(f, VORBIS_outofmem); } else { f->work_buffer = setup_malloc(f, f->temp_memory_required); if (f->work_buffer == NULL) return error(f, VORBIS_outofmem); } // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page // without PAGEFLAG_continued_packet, so this either points to the first page, or // the page after the end of the headers. It might be cleaner to point to a page // in the middle of the headers, when that's the page where the first audio packet // starts, but we'd have to also correctly skip the end of any continued packet in // stb_vorbis_seek_start. if (f->next_seg == -1) { f->first_audio_page_offset = stb_vorbis_get_file_offset(f); } else { f->first_audio_page_offset = 0; } return TRUE; } static void vorbis_deinit(stb_vorbis *p) { int i,j; #ifndef STB_VORBIS_NO_COMMENTS setup_free(p, p->vendor); for (i=0; i < p->comment_list_length; ++i) { setup_free(p, p->comment_list[i]); } setup_free(p, p->comment_list); #endif if (p->residue_config) { for (i=0; i < p->residue_count; ++i) { Residue *r = p->residue_config+i; if (r->classdata) { for (j=0; j < p->codebooks[r->classbook].entries; ++j) setup_free(p, r->classdata[j]); setup_free(p, r->classdata); } setup_free(p, r->residue_books); } } if (p->codebooks) { CHECK(p); for (i=0; i < p->codebook_count; ++i) { Codebook *c = p->codebooks + i; setup_free(p, c->codeword_lengths); setup_free(p, c->multiplicands); if (c->codewords != p->temp_codewords) // Might be the temporary buffer-allocated array. setup_free(p, c->codewords); setup_free(p, c->sorted_codewords); // c->sorted_values[-1] is the first entry in the array setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); } setup_free(p, p->codebooks); } setup_free(p, p->floor_config); setup_free(p, p->residue_config); if (p->mapping) { for (i=0; i < p->mapping_count; ++i) setup_free(p, p->mapping[i].chan); setup_free(p, p->mapping); } CHECK(p); for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { setup_free(p, p->channel_buffers[i]); setup_free(p, p->previous_window[i]); #ifdef STB_VORBIS_NO_DEFER_FLOOR setup_free(p, p->floor_buffers[i]); #endif setup_free(p, p->finalY[i]); } for (i=0; i < 2; ++i) { setup_free(p, p->A[i]); setup_free(p, p->B[i]); setup_free(p, p->C[i]); setup_free(p, p->window[i]); setup_free(p, p->bit_reverse[i]); } if (!p->alloc.alloc_buffer) { setup_free(p, p->work_buffer); setup_temp_free(p, &p->temp_lengths, 0); setup_temp_free(p, &p->temp_codewords, 0); setup_temp_free(p, &p->temp_values, 0); setup_temp_free(p, &p->temp_mults, 0); } #ifdef STB_VORBIS_SDL if (p->close_on_free) SDL_CloseIO(p->io); #endif #ifndef STB_VORBIS_NO_STDIO if (p->close_on_free) fclose(p->f); #endif } void stb_vorbis_close(stb_vorbis *p) { if (p == NULL) return; vorbis_deinit(p); setup_free(p,p); } static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) { memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start if (z) { p->alloc = *z; p->alloc.alloc_buffer_length_in_bytes &= ~7; p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; } p->eof = 0; p->error = VORBIS__no_error; p->stream = NULL; p->codebooks = NULL; p->page_crc_tests = -1; #ifdef STB_VORBIS_SDL p->close_on_free = FALSE; p->io = NULL; p->io_start = 0; p->io_virtual_pos = 0; p->io_buffer_pos = 0; p->io_buffer_fill = 0; #endif #ifndef STB_VORBIS_NO_STDIO p->close_on_free = FALSE; p->f = NULL; #endif } int stb_vorbis_get_sample_offset(stb_vorbis *f) { if (f->current_loc_valid) return f->current_loc; else return -1; } int stb_vorbis_get_playback_sample_offset(stb_vorbis *f) { if (f->current_playback_loc_valid) return f->current_playback_loc; else return -1; } stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) { stb_vorbis_info d; d.channels = f->channels; d.sample_rate = f->sample_rate; d.setup_memory_required = f->setup_memory_required; d.setup_temp_memory_required = f->setup_temp_memory_required; d.temp_memory_required = f->temp_memory_required; d.max_frame_size = f->blocksize_1 >> 1; return d; } #ifndef STB_VORBIS_NO_COMMENTS stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) { stb_vorbis_comment d; d.vendor = f->vendor; d.comment_list_length = f->comment_list_length; d.comment_list = f->comment_list; return d; } #endif int stb_vorbis_get_error(stb_vorbis *f) { int e = f->error; f->error = VORBIS__no_error; return e; } static stb_vorbis * vorbis_alloc(stb_vorbis *f) { stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); return p; } #ifndef STB_VORBIS_NO_PUSHDATA_API void stb_vorbis_flush_pushdata(stb_vorbis *f) { f->previous_length = 0; f->page_crc_tests = 0; f->discard_samples_deferred = 0; f->current_loc_valid = FALSE; f->first_decode = FALSE; f->samples_output = 0; f->channel_buffer_start = 0; f->channel_buffer_end = 0; } static int vorbis_search_for_page_pushdata(vorb *f, const uint8 *data, int data_len) { int i,n; for (i=0; i < f->page_crc_tests; ++i) f->scan[i].bytes_done = 0; // if we have room for more scans, search for them first, because // they may cause us to stop early if their header is incomplete if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { if (data_len < 4) return 0; data_len -= 3; // need to look for 4-byte sequence, so don't miss // one that straddles a boundary for (i=0; i < data_len; ++i) { if (data[i] == 0x4f) { if (0==memcmp(data+i, ogg_page_header, 4)) { int j,len; uint32 crc; // make sure we have the whole page header if (i+26 >= data_len || i+27+data[i+26] >= data_len) { // only read up to this page start, so hopefully we'll // have the whole page header start next time data_len = i; break; } // ok, we have it all; compute the length of the page len = 27 + data[i+26]; for (j=0; j < data[i+26]; ++j) len += data[i+27+j]; // scan everything up to the embedded crc (which we must 0) crc = 0; for (j=0; j < 22; ++j) crc = crc32_update(crc, data[i+j]); // now process 4 0-bytes for ( ; j < 26; ++j) crc = crc32_update(crc, 0); // len is the total number of bytes we need to scan n = f->page_crc_tests++; f->scan[n].bytes_left = len-j; f->scan[n].crc_so_far = crc; f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); // if the last frame on a page is continued to the next, then // we can't recover the sample_loc immediately if (data[i+27+data[i+26]-1] == 255) f->scan[n].sample_loc = ~0; else f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); f->scan[n].bytes_done = i+j; if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) break; // keep going if we still have room for more } } } } for (i=0; i < f->page_crc_tests;) { uint32 crc; int j; int n = f->scan[i].bytes_done; int m = f->scan[i].bytes_left; if (m > data_len - n) m = data_len - n; // m is the bytes to scan in the current chunk crc = f->scan[i].crc_so_far; for (j=0; j < m; ++j) crc = crc32_update(crc, data[n+j]); f->scan[i].bytes_left -= m; f->scan[i].crc_so_far = crc; if (f->scan[i].bytes_left == 0) { // does it match? if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { // Houston, we have page data_len = n+m; // consumption amount is wherever that scan ended f->page_crc_tests = -1; // drop out of page scan mode f->previous_length = 0; // decode-but-don't-output one frame f->next_seg = -1; // start a new page f->current_loc = f->scan[i].sample_loc; // set the current sample location // to the amount we'd have decoded had we decoded this page f->current_loc_valid = f->current_loc != ~0U; return data_len; } // delete entry f->scan[i] = f->scan[--f->page_crc_tests]; } else { ++i; } } return data_len; } // return value: number of bytes we used int stb_vorbis_decode_frame_pushdata( stb_vorbis *f, // the file we're decoding const uint8 *data, int data_len, // the memory available for decoding int *channels, // place to write number of float * buffers float ***output, // place to write float ** array of float * buffers int *samples // place to write number of output samples ) { int i; int len,right,left; if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); if (f->page_crc_tests >= 0) { *samples = 0; return vorbis_search_for_page_pushdata(f, data, data_len); } f->stream = (const uint8 *) data; f->stream_end = (const uint8 *) data + data_len; f->error = VORBIS__no_error; // check that we have the entire packet in memory if (!is_whole_packet_present(f)) { *samples = 0; return 0; } if (!vorbis_decode_packet(f, &len, &left, &right)) { // save the actual error we encountered enum STBVorbisError error = f->error; if (error == VORBIS_bad_packet_type) { // flush and resynch f->error = VORBIS__no_error; while (get8_packet(f) != EOP) if (f->eof) break; *samples = 0; return (int) (f->stream - data); } if (error == VORBIS_continued_packet_flag_invalid) { if (f->previous_length == 0) { // we may be resynching, in which case it's ok to hit one // of these; just discard the packet f->error = VORBIS__no_error; while (get8_packet(f) != EOP) if (f->eof) break; *samples = 0; return (int) (f->stream - data); } } // if we get an error while parsing, what to do? // well, it DEFINITELY won't work to continue from where we are! stb_vorbis_flush_pushdata(f); // restore the error that actually made us bail f->error = error; *samples = 0; return 1; } // success! len = vorbis_finish_frame(f, len, left, right); for (i=0; i < f->channels; ++i) f->outputs[i] = f->channel_buffers[i] + left; if (channels) *channels = f->channels; *samples = len; *output = f->outputs; return (int) (f->stream - data); } stb_vorbis *stb_vorbis_open_pushdata( const unsigned char *data, int data_len, // the memory available for decoding int *data_used, // only defined if result is not NULL int *error, const stb_vorbis_alloc *alloc) { stb_vorbis *f, p; vorbis_init(&p, alloc); p.stream = (const uint8 *) data; p.stream_end = (const uint8 *) data + data_len; p.push_mode = TRUE; if (!start_decoder(&p)) { if (p.eof) *error = VORBIS_need_more_data; else *error = p.error; vorbis_deinit(&p); return NULL; } f = vorbis_alloc(&p); if (f) { *f = p; *data_used = (int) (f->stream - data); *error = 0; return f; } else { vorbis_deinit(&p); return NULL; } } #endif // STB_VORBIS_NO_PUSHDATA_API unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) { #ifndef STB_VORBIS_NO_PUSHDATA_API if (f->push_mode) return 0; #endif #ifdef STB_VORBIS_SDL return f->io_virtual_pos; #else if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); #endif #ifndef STB_VORBIS_NO_STDIO return (unsigned int) (ftell(f->f) - f->f_start); #endif } #ifndef STB_VORBIS_NO_PULLDATA_API // // DATA-PULLING API // static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) { for(;;) { int n; if (f->eof) return 0; n = get8(f); if (n == 0x4f) { // page header candidate unsigned int retry_loc = stb_vorbis_get_file_offset(f); int i; // check if we're off the end of a file_section stream if (retry_loc - 25 > f->stream_len) return 0; // check the rest of the header for (i=1; i < 4; ++i) if (get8(f) != ogg_page_header[i]) break; if (f->eof) return 0; if (i == 4) { uint8 header[27]; uint32 i, crc, goal, len; for (i=0; i < 4; ++i) header[i] = ogg_page_header[i]; for (; i < 27; ++i) header[i] = get8(f); if (f->eof) return 0; if (header[4] != 0) goto invalid; goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24); for (i=22; i < 26; ++i) header[i] = 0; crc = 0; for (i=0; i < 27; ++i) crc = crc32_update(crc, header[i]); len = 0; for (i=0; i < header[26]; ++i) { int s = get8(f); crc = crc32_update(crc, s); len += s; } if (len && f->eof) return 0; for (i=0; i < len; ++i) crc = crc32_update(crc, get8(f)); // finished parsing probable page if (crc == goal) { // we could now check that it's either got the last // page flag set, OR it's followed by the capture // pattern, but I guess TECHNICALLY you could have // a file with garbage between each ogg page and recover // from it automatically? So even though that paranoia // might decrease the chance of an invalid decode by // another 2^32, not worth it since it would hose those // invalid-but-useful files? if (end) *end = stb_vorbis_get_file_offset(f); if (last) { if (header[5] & 0x04) *last = 1; else *last = 0; } set_file_offset(f, retry_loc-1); return 1; } } invalid: // not a valid page, so rewind and look for next one set_file_offset(f, retry_loc); } } } #define SAMPLE_unknown 0xffffffff // seeking is implemented with a binary search, which narrows down the range to // 64K, before using a linear search (because finding the synchronization // pattern can be expensive, and the chance we'd find the end page again is // relatively high for small ranges) // // two initial interpolation-style probes are used at the start of the search // to try to bound either side of the binary search sensibly, while still // working in O(log n) time if they fail. static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) { uint8 header[27], lacing[255]; int i,len; // record where the page starts z->page_start = stb_vorbis_get_file_offset(f); // parse the header if (!getn(f, header, 27)) return 0; if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') return 0; if (!getn(f, lacing, header[26])) return 0; // determine the length of the payload len = 0; for (i=0; i < header[26]; ++i) len += lacing[i]; // this implies where the page ends z->page_end = z->page_start + 27 + header[26] + len; // read the last-decoded sample out of the data z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); // restore file state to where we were set_file_offset(f, z->page_start); return 1; } // rarely used function to seek back to the preceding page while finding the // start of a packet static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) { unsigned int previous_safe; uint32 end; // now we want to seek back 64K from the limit if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) previous_safe = limit_offset - 65536; else previous_safe = f->first_audio_page_offset; set_file_offset(f, previous_safe); while (vorbis_find_page(f, &end, NULL)) { if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) return 1; set_file_offset(f, end); } return 0; } // implements the search logic for finding a page and starting decoding. if // the function succeeds, current_loc_valid will be true and current_loc will // be less than or equal to the provided sample number (the closer the // better). static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) { ProbedPage left, right, mid; int i, start_seg_with_known_loc, end_pos, page_start; uint32 delta, stream_length, padding, last_sample_limit; double offset = 0.0, bytes_per_sample = 0.0; int probe = 0; // find the last page and validate the target sample stream_length = stb_vorbis_stream_length_in_samples(f); if (stream_length == 0) return error(f, VORBIS_seek_without_length); if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); // this is the maximum difference between the window-center (which is the // actual granule position value), and the right-start (which the spec // indicates should be the granule position (give or take one)). padding = ((f->blocksize_1 - f->blocksize_0) >> 2); if (sample_number < padding) last_sample_limit = 0; else last_sample_limit = sample_number - padding; left = f->p_first; while (left.last_decoded_sample == ~0U) { // (untested) the first page does not have a 'last_decoded_sample' set_file_offset(f, left.page_end); if (!get_seek_page_info(f, &left)) goto error; } right = f->p_last; assert(right.last_decoded_sample != ~0U); // starting from the start is handled differently if (last_sample_limit <= left.last_decoded_sample) { if (stb_vorbis_seek_start(f)) { if (f->current_loc > sample_number) return error(f, VORBIS_seek_failed); return 1; } return 0; } while (left.page_end != right.page_start) { assert(left.page_end < right.page_start); // search range in bytes delta = right.page_start - left.page_end; if (delta <= 65536) { // there's only 64K left to search - handle it linearly set_file_offset(f, left.page_end); } else { if (probe < 2) { if (probe == 0) { // first probe (interpolate) double data_bytes = right.page_end - left.page_start; bytes_per_sample = data_bytes / right.last_decoded_sample; offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); } else { // second probe (try to bound the other side) double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; if (error >= 0 && error < 8000) error = 8000; if (error < 0 && error > -8000) error = -8000; offset += error * 2; } // ensure the offset is valid if (offset < left.page_end) offset = left.page_end; if (offset > right.page_start - 65536) offset = right.page_start - 65536; set_file_offset(f, (unsigned int) offset); } else { // binary search for large ranges (offset by 32K to ensure // we don't hit the right page) set_file_offset(f, left.page_end + (delta / 2) - 32768); } if (!vorbis_find_page(f, NULL, NULL)) goto error; } for (;;) { if (!get_seek_page_info(f, &mid)) goto error; if (mid.last_decoded_sample != ~0U) break; // (untested) no frames end on this page set_file_offset(f, mid.page_end); assert(mid.page_start < right.page_start); } // if we've just found the last page again then we're in a tricky file, // and we're close enough (if it wasn't an interpolation probe). if (mid.page_start == right.page_start) { if (probe >= 2 || delta <= 65536) break; } else { if (last_sample_limit < mid.last_decoded_sample) right = mid; else left = mid; } ++probe; } // seek back to start of the last packet page_start = left.page_start; set_file_offset(f, page_start); if (!start_page(f)) return error(f, VORBIS_seek_failed); end_pos = f->end_seg_with_known_loc; assert(end_pos >= 0); for (;;) { for (i = end_pos; i > 0; --i) if (f->segments[i-1] != 255) break; start_seg_with_known_loc = i; if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) break; // (untested) the final packet begins on an earlier page if (!go_to_page_before(f, page_start)) goto error; page_start = stb_vorbis_get_file_offset(f); if (!start_page(f)) goto error; end_pos = f->segment_count - 1; } // prepare to start decoding f->current_loc_valid = FALSE; f->last_seg = FALSE; f->valid_bits = 0; f->packet_bytes = 0; f->bytes_in_seg = 0; f->previous_length = 0; f->next_seg = start_seg_with_known_loc; for (i = 0; i < start_seg_with_known_loc; i++) skip(f, f->segments[i]); // start decoding (optimizable - this frame is generally discarded) if (!vorbis_pump_first_frame(f)) return 0; if (f->current_loc > sample_number) return error(f, VORBIS_seek_failed); return 1; error: // try to restore the file to a valid state stb_vorbis_seek_start(f); return error(f, VORBIS_seek_failed); } // the same as vorbis_decode_initial, but without advancing static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) { int bits_read, bytes_read; if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) return 0; // either 1 or 2 bytes were read, figure out which so we can rewind bits_read = 1 + ilog(f->mode_count-1); if (f->mode_config[*mode].blockflag) bits_read += 2; bytes_read = (bits_read + 7) / 8; f->bytes_in_seg += bytes_read; f->packet_bytes -= bytes_read; skip(f, -bytes_read); if (f->next_seg == -1) f->next_seg = f->segment_count - 1; else f->next_seg--; f->valid_bits = 0; return 1; } int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) { uint32 max_frame_samples; if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); // fast page-level search if (!seek_to_sample_coarse(f, sample_number)) return 0; assert(f->current_loc_valid); assert(f->current_loc <= sample_number); // linear search for the relevant packet max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; while (f->current_loc < sample_number) { int left_start, left_end, right_start, right_end, mode, frame_samples; if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) return error(f, VORBIS_seek_failed); // calculate the number of samples returned by the next frame frame_samples = right_start - left_start; if (f->current_loc + frame_samples > sample_number) { return 1; // the next frame will contain the sample } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { // there's a chance the frame after this could contain the sample vorbis_pump_first_frame(f); } else { // this frame is too early to be relevant f->current_loc += frame_samples; f->previous_length = 0; maybe_start_packet(f); flush_packet(f); } } // the next frame should start with the sample if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); f->current_playback_loc = sample_number; return 1; } int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) { if (!stb_vorbis_seek_frame(f, sample_number)) { f->current_playback_loc_valid = FALSE; return 0; } if (sample_number != f->current_loc) { int n; uint32 frame_start = f->current_loc; stb_vorbis_get_frame_float(f, &n, NULL); assert(sample_number > frame_start); assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); f->channel_buffer_start += (sample_number - frame_start); } f->current_playback_loc_valid = TRUE; f->current_playback_loc = sample_number; return 1; } int stb_vorbis_seek_start(stb_vorbis *f) { if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } set_file_offset(f, f->first_audio_page_offset); f->previous_length = 0; f->first_decode = TRUE; f->next_seg = -1; return vorbis_pump_first_frame(f); } unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) { unsigned int restore_offset, previous_safe; unsigned int last_page_loc; if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); if (!f->total_samples) { uint32 end,last; uint32 lo,hi; char header[6]; // first, store the current decode position so we can restore it restore_offset = stb_vorbis_get_file_offset(f); // now we want to seek back 64K from the end (the last page must // be at most a little less than 64K, but let's allow a little slop) if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) previous_safe = f->stream_len - 65536; else previous_safe = f->first_audio_page_offset; set_file_offset(f, previous_safe); // previous_safe is now our candidate 'earliest known place that seeking // to will lead to the final page' if (!vorbis_find_page(f, &end, &last)) { // if we can't find a page, we're hosed! f->error = VORBIS_cant_find_last_page; f->total_samples = 0xffffffff; goto done; } // check if there are more pages last_page_loc = stb_vorbis_get_file_offset(f); // stop when the last_page flag is set, not when we reach eof; // this allows us to stop short of a 'file_section' end without // explicitly checking the length of the section while (!last) { set_file_offset(f, end); if (!vorbis_find_page(f, &end, &last)) { // the last page we found didn't have the 'last page' flag // set. whoops! break; } //previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging last_page_loc = stb_vorbis_get_file_offset(f); } set_file_offset(f, last_page_loc); // parse the header getn(f, (unsigned char *)header, 6); // extract the absolute granule position lo = get32(f); hi = get32(f); if (lo == 0xffffffff && hi == 0xffffffff) { f->error = VORBIS_cant_find_last_page; f->total_samples = SAMPLE_unknown; goto done; } if (hi) lo = 0xfffffffe; // saturate f->total_samples = lo; f->p_last.page_start = last_page_loc; f->p_last.page_end = end; f->p_last.last_decoded_sample = lo; done: set_file_offset(f, restore_offset); } return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; } float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) { return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; } int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) { int len, right,left,i; if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); if (!vorbis_decode_packet(f, &len, &left, &right)) { f->channel_buffer_start = f->channel_buffer_end = 0; return 0; } len = vorbis_finish_frame(f, len, left, right); for (i=0; i < f->channels; ++i) f->outputs[i] = f->channel_buffers[i] + left; f->channel_buffer_start = left; f->channel_buffer_end = left+len; if (channels) *channels = f->channels; if (output) *output = f->outputs; return len; } #ifndef STB_VORBIS_NO_STDIO stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) { stb_vorbis *f, p; vorbis_init(&p, alloc); p.f = file; p.f_start = (uint32) ftell(file); p.stream_len = length; p.close_on_free = close_on_free; if (start_decoder(&p)) { f = vorbis_alloc(&p); if (f) { *f = p; vorbis_pump_first_frame(f); return f; } } if (error) *error = p.error; vorbis_deinit(&p); return NULL; } stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) { unsigned int len, start; start = (unsigned int) ftell(file); fseek(file, 0, SEEK_END); len = (unsigned int) (ftell(file) - start); fseek(file, start, SEEK_SET); return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); } stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) { FILE *f; #if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) if (0 != fopen_s(&f, filename, "rb")) f = NULL; #else f = fopen(filename, "rb"); #endif if (f) return stb_vorbis_open_file(f, TRUE, error, alloc); if (error) *error = VORBIS_file_open_failure; return NULL; } #endif // STB_VORBIS_NO_STDIO #ifdef STB_VORBIS_SDL stb_vorbis * stb_vorbis_open_io_section(SDL_IOStream *io, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) { stb_vorbis *f, p; vorbis_init(&p, alloc); p.io = io; p.io_start = (uint32) SDL_TellIO(io); p.stream_len = length; p.close_on_free = close_on_free; if (start_decoder(&p)) { f = vorbis_alloc(&p); if (f) { memcpy(f, &p, sizeof (stb_vorbis)); vorbis_pump_first_frame(f); return f; } } if (error) *error = p.error; vorbis_deinit(&p); return NULL; } stb_vorbis * stb_vorbis_open_io(SDL_IOStream *io, int close_on_free, int *error, const stb_vorbis_alloc *alloc) { const unsigned int start = (unsigned int) SDL_TellIO(io); const unsigned int len = (unsigned int) (SDL_GetIOSize(io) - start); return stb_vorbis_open_io_section(io, close_on_free, error, alloc, len); } #endif stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) { stb_vorbis *f, p; if (!data) { if (error) *error = VORBIS_unexpected_eof; return NULL; } vorbis_init(&p, alloc); p.stream = (const uint8 *) data; p.stream_end = (const uint8 *) data + len; p.stream_start = (const uint8 *) p.stream; p.stream_len = len; p.push_mode = FALSE; if (start_decoder(&p)) { f = vorbis_alloc(&p); if (f) { memcpy(f, &p, sizeof (stb_vorbis)); vorbis_pump_first_frame(f); if (error) *error = VORBIS__no_error; return f; } } if (error) *error = p.error; vorbis_deinit(&p); return NULL; } #ifndef STB_VORBIS_NO_INTEGER_CONVERSION #define PLAYBACK_MONO 1 #define PLAYBACK_LEFT 2 #define PLAYBACK_RIGHT 4 #define L (PLAYBACK_LEFT | PLAYBACK_MONO) #define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) #define R (PLAYBACK_RIGHT | PLAYBACK_MONO) static const int8 channel_position[7][6] = { { 0 }, { C }, { L, R }, { L, C, R }, { L, R, L, R }, { L, C, R, L, R }, { L, C, R, L, R, C }, }; #ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT typedef union { float f; // changed this to unsigned to suppress an UBSan error. // upstream: https://github.com/nothings/stb/issues/1168. unsigned int i; } float_conv; typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; #define FASTDEF(x) float_conv x // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (int)(temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) #define check_endianness() #else #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) #define check_endianness() #define FASTDEF(x) #endif static void copy_samples(short *dest, float *src, int len) { int i; check_endianness(); for (i=0; i < len; ++i) { FASTDEF(temp); int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); if ((unsigned int)v + 32768 > 65535) v = v < 0 ? -32768 : 32767; dest[i] = v; } } static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) { #define STB_BUFFER_SIZE 32 float buffer[STB_BUFFER_SIZE]; int i,j,o,n = STB_BUFFER_SIZE; check_endianness(); for (o = 0; o < len; o += STB_BUFFER_SIZE) { memset(buffer, 0, sizeof(buffer)); if (o + n > len) n = len - o; for (j=0; j < num_c; ++j) { if (channel_position[num_c][j] & mask) { for (i=0; i < n; ++i) buffer[i] += data[j][d_offset+o+i]; } } for (i=0; i < n; ++i) { FASTDEF(temp); int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); if ((unsigned int)v + 32768 > 65535) v = v < 0 ? -32768 : 32767; output[o+i] = v; } } #undef STB_BUFFER_SIZE } static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) { #define STB_BUFFER_SIZE 32 float buffer[STB_BUFFER_SIZE]; int i,j,o,n = STB_BUFFER_SIZE >> 1; // o is the offset in the source data check_endianness(); for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) { // o2 is the offset in the output data int o2 = o << 1; memset(buffer, 0, sizeof(buffer)); if (o + n > len) n = len - o; for (j=0; j < num_c; ++j) { int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { for (i=0; i < n; ++i) { buffer[i*2+0] += data[j][d_offset+o+i]; buffer[i*2+1] += data[j][d_offset+o+i]; } } else if (m == PLAYBACK_LEFT) { for (i=0; i < n; ++i) { buffer[i*2+0] += data[j][d_offset+o+i]; } } else if (m == PLAYBACK_RIGHT) { for (i=0; i < n; ++i) { buffer[i*2+1] += data[j][d_offset+o+i]; } } } for (i=0; i < (n<<1); ++i) { FASTDEF(temp); int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); if ((unsigned int)v + 32768 > 65535) v = v < 0 ? -32768 : 32767; output[o2+i] = v; } } #undef STB_BUFFER_SIZE } static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) { int i; if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { static const int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; for (i=0; i < buf_c; ++i) compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); } else { int limit = buf_c < data_c ? buf_c : data_c; for (i=0; i < limit; ++i) copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); for ( ; i < buf_c; ++i) memset(buffer[i]+b_offset, 0, sizeof(short) * samples); } } int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) { float **output = NULL; int len = stb_vorbis_get_frame_float(f, NULL, &output); if (len > num_samples) len = num_samples; if (len) convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); return len; } static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) { int i; check_endianness(); if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { assert(buf_c == 2); for (i=0; i < buf_c; ++i) compute_stereo_samples(buffer, data_c, data, d_offset, len); } else { int limit = buf_c < data_c ? buf_c : data_c; int j; for (j=0; j < len; ++j) { for (i=0; i < limit; ++i) { FASTDEF(temp); float f = data[i][d_offset+j]; int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); if ((unsigned int)v + 32768 > 65535) v = v < 0 ? -32768 : 32767; *buffer++ = v; } for ( ; i < buf_c; ++i) *buffer++ = 0; } } } int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) { float **output; int len; if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); len = stb_vorbis_get_frame_float(f, NULL, &output); if (len) { if (len*num_c > num_shorts) len = num_shorts / num_c; convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); } return len; } static int fixup_current_playback_loc(stb_vorbis *f, int n) { unsigned int lgs; f->current_playback_loc += n; lgs = stb_vorbis_stream_length_in_samples(f); if (lgs != 0 && lgs != SAMPLE_unknown && f->current_playback_loc > (int)lgs) { int r = n - (f->current_playback_loc - (int)lgs); f->current_playback_loc = lgs; return r; } return n; } int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) { float **outputs; int len = num_shorts / channels; int n=0; while (n < len) { int k = f->channel_buffer_end - f->channel_buffer_start; if (n+k >= len) k = len - n; if (k) convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); buffer += k*channels; n += k; f->channel_buffer_start += k; if (n == len) break; if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; } return fixup_current_playback_loc(f, n); } int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) { float **outputs; int n=0; while (n < len) { int k = f->channel_buffer_end - f->channel_buffer_start; if (n+k >= len) k = len - n; if (k) convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); n += k; f->channel_buffer_start += k; if (n == len) break; if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; } return fixup_current_playback_loc(f, n); } #ifndef STB_VORBIS_NO_STDIO int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) { int data_len, offset, total, limit, error; short *data; stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); if (v == NULL) return -1; limit = v->channels * 4096; *channels = v->channels; if (sample_rate) *sample_rate = v->sample_rate; offset = data_len = 0; total = limit; data = (short *) malloc(total * sizeof(*data)); if (data == NULL) { stb_vorbis_close(v); return -2; } for (;;) { int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); if (n == 0) break; data_len += n; offset += n * v->channels; if (offset + limit > total) { short *data2; total *= 2; data2 = (short *) realloc(data, total * sizeof(*data)); if (data2 == NULL) { free(data); stb_vorbis_close(v); return -2; } data = data2; } } *output = data; stb_vorbis_close(v); return data_len; } #endif // NO_STDIO int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) { int data_len, offset, total, limit, error; short *data; stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); if (v == NULL) return -1; limit = v->channels * 4096; *channels = v->channels; if (sample_rate) *sample_rate = v->sample_rate; offset = data_len = 0; total = limit; data = (short *) malloc(total * sizeof(*data)); if (data == NULL) { stb_vorbis_close(v); return -2; } for (;;) { int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); if (n == 0) break; data_len += n; offset += n * v->channels; if (offset + limit > total) { short *data2; total *= 2; data2 = (short *) realloc(data, total * sizeof(*data)); if (data2 == NULL) { free(data); stb_vorbis_close(v); return -2; } data = data2; } } *output = data; stb_vorbis_close(v); return data_len; } #endif // STB_VORBIS_NO_INTEGER_CONVERSION int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) { float **outputs; int len = num_floats / channels; int n=0; int z = f->channels; if (z > channels) z = channels; while (n < len) { int i,j; int k = f->channel_buffer_end - f->channel_buffer_start; if (n+k >= len) k = len - n; for (j=0; j < k; ++j) { for (i=0; i < z; ++i) *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; for ( ; i < channels; ++i) *buffer++ = 0; } n += k; f->channel_buffer_start += k; if (n == len) break; if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; } return fixup_current_playback_loc(f, n); } int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) { float **outputs; int n=0; int z = f->channels; if (z > channels) z = channels; while (n < num_samples) { int i; int k = f->channel_buffer_end - f->channel_buffer_start; if (n+k >= num_samples) k = num_samples - n; if (k) { for (i=0; i < z; ++i) memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); for ( ; i < channels; ++i) memset(buffer[i]+n, 0, sizeof(float) * k); } n += k; f->channel_buffer_start += k; if (n == num_samples) break; if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; } return fixup_current_playback_loc(f, n); } #endif // STB_VORBIS_NO_PULLDATA_API /* Version history 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 found with Mayhem by ForAllSecure 1.16 - 2019-03-04 - fix warnings 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found 1.14 - 2018-02-11 - delete bogus dealloca usage 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files 1.11 - 2017-07-23 - fix MinGW compilation 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; avoid discarding last frame of audio data 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API some more crash fixes when out of memory or with corrupt files 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) some crash fixes when out of memory or with corrupt files 1.05 - 2015-04-19 - don't define __forceinline if it's redundant 1.04 - 2014-08-27 - fix missing const-correct case in API 1.03 - 2014-08-07 - Warning fixes 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel (API change) report sample rate for decode-full-file funcs 0.99996 - bracket #include for macintosh compilation by Laurent Gomila 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence 0.99993 - remove assert that fired on legal files with empty tables 0.99992 - rewind-to-start 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ 0.9998 - add a full-decode function with a memory source 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition 0.9996 - query length of vorbis stream in samples/seconds 0.9995 - bugfix to another optimization that only happened in certain files 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation 0.9992 - performance improvement of IMDCT; now performs close to reference implementation 0.9991 - performance improvement of IMDCT 0.999 - (should have been 0.9990) performance improvement of IMDCT 0.998 - no-CRT support from Casey Muratori 0.997 - bugfixes for bugs found by Terje Mathisen 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen 0.992 - fixes for MinGW warning 0.991 - turn fast-float-conversion on by default 0.990 - fix push-mode seek recovery if you seek into the headers 0.98b - fix to bad release of 0.98 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode 0.97 - builds under c++ (typecasting, don't use 'class' keyword) 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code 0.95 - clamping code for 16-bit functions 0.94 - not publically released 0.93 - fixed all-zero-floor case (was decoding garbage) 0.92 - fixed a memory leak 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION 0.90 - first public release */ #endif // STB_VORBIS_HEADER_ONLY /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/000077500000000000000000000000001501405355700230745ustar00rootroot00000000000000libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/Android.mk000066400000000000000000000005261501405355700250100ustar00rootroot00000000000000LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := timidity LOCAL_C_INCLUDES := LOCAL_CFLAGS := LOCAL_SRC_FILES += \ common.c \ instrum.c \ mix.c \ output.c \ playmidi.c \ readmidi.c \ resample.c \ tables.c \ timidity.c LOCAL_SHARED_LIBRARIES := SDL3 include $(BUILD_STATIC_LIBRARY) libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/CHANGES000066400000000000000000000061671501405355700241010ustar00rootroot00000000000000This version of TiMidity should contain all the fixes from the September 25 2003 SDL_mixer CVS snapshot. In addition, I've made some changes of my own, e.g.: * All file access is done through SDL_IOStream. This means the MIDI stream no longer has to be a file. (The config file and instruments still have to be though.) * Replacing of TiMidity's endian-handling with SDL's. * Removal of much unused or unnecessary code, such as + The "hooks" for putting a user interface onto TiMidity. + The antialias filter. It wasn't active, and even at 4 kHz I couldn't hear any difference when activating it. + Removed all traces of LOOKUP_HACK and LOOKUP_INTERPOLATION. According to the code comments they weren't very good anyway. ("degrades sound quality noticeably"). I also removed the disclaimer about the "8-bit uLaw to 16-bit PCM and the 13-bit-PCM to 8-bit uLaw tables" disclaimer, since I believe those were the tables I removed. + Removed LOOKUP_SINE since it was already commented out. I think we can count on our target audience having math co-processors nowadays. + Removed USE_LDEXP since it wasn't being used and "it doesn't make much of a difference either way". + Removed decompress hack from open_file() since it didn't look very portable. + Removed heaps of unnecessary constants. + Removed unused functions. + Assume that LINEAR_INTERPOLATION is always used, so remove all code dealing with it not being so. It's not that I think the difference in audio quality is that great, but since it wouldn't compile without code changes I assume no one's used it for quite some time... + Assume PRECALC_LOOPS is always defined. Judging by the comments it may not make much of a difference either way, so why maintain two versions of the same code? * Moving several static globals into the MidiSong struct. This includes sample rate, formate, etc. which are now all per-song. * Moved some typedefs (e.g. MidiSong) to timidity.h for easy inclusion into the MIDI decoder. * Added free_pathlist(). * Replaced TiMidity's own 8, 16 and 32-bit types with SDL's. * Made TiMidity look for its configuration file in both /etc and /usr/local/lib/timidity. (Windows version remains unchanged.) * Timidity_PlaySome() now takes three arguments. A MidiSong, a decode buffer and decode buffer size in bytes. (MidiSong is a new argument, and buffer size used to be in samples.) In addition, it will return the number of bytes decoded. * Added Timidity_Exit(). * Removed Timidity_Stop() and Timidity_Active(). Stopping playback should be handled by SDL_sound, and Timidity_PlaySome() will return 0 when the MIDI stream is finished. * Modified the ToneBank stuff to allow some data to be shared between MidiSongs. * The following files have been removed: controls.c, controls.h, filter.c, filter.h, sdl_a.c, sdl_c.c * config.h has been renamed as options.h to avoid confusion with the automatically generated config.h for SDL_sound. * Added support for loading DLS format instruments: Timidity_LoadDLS(), Timidity_FreeDLS(), Timidity_LoadDLSSong() * Added Timidity_Init_NoConfig() libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/COPYING000066400000000000000000000137321501405355700241350ustar00rootroot00000000000000 The "Artistic License" Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder as specified below. "Copyright Holder" is whoever is named in the copyright or copyrights for the package. "You" is you, if you're thinking about copying or distributing this Package. "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as uunet.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) give non-standard executables non-standard names, and clearly document the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. 8. Aggregation of this Package with a commercial distribution is always permitted provided that the use of this Package is embedded; that is, when no overt attempt is made to make this Package's interfaces visible to the end user of the commercial distribution. Such use shall not be construed as a distribution of this Package. 9. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/FAQ000066400000000000000000000112651501405355700234330ustar00rootroot00000000000000---------------------------*-indented-text-*------------------------------ TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen -------------------------------------------------------------------------- Frequently Asked Questions with answers: -------------------------------------------------------------------------- Q: What is it? A: Where? Well Chris, TiMidity is a software-only synthesizer, MIDI renderer, MIDI to WAVE converter, realtime MIDI player for UNIX machines, even (I've heard) a Netscape helper application. It takes a MIDI file and writes a WAVE or raw PCM data or plays it on your digital audio device. It sounds much more realistic than FM synthesis, but you need a ~100Mhz processor to listen to 32kHz stereo music in the background while you work. 11kHz mono can be played on a low-end 486, and, to some, it still sounds better than FM. -------------------------------------------------------------------------- Q: I don't have a GUS, can I use TiMidity? A: Yes. That's the point. You don't need a Gravis Ultrasound to use TiMidity, you just need GUS-compatible patches, which are freely available on the Internet. See below for pointers. -------------------------------------------------------------------------- Q: I have a GUS, can I use TiMidity? A: The DOS port doesn't have GUS support, and TiMidity won't be taking advantage of the board's internal synthesizer under other operating systems either. So it kind of defeats the purpose. But you can use it. -------------------------------------------------------------------------- Q: I tried playing a MIDI file I got off the Net but all I got was a dozen warnings saying "No instrument mapped to tone bank 0, program xx - this instrument will not be heard". What's wrong? A: The General MIDI standard specifies 128 melodic instruments and some sixty percussion sounds. If you wish to play arbitrary General MIDI files, you'll need to get more patch files. There's a program called Midia for SGI's, which also plays MIDI files and has a lot more bells and whistles than TiMidity. It uses GUS-compatible patches, too -- so you can get the 8 MB set at ftp://archive.cs.umbc.edu/pub/midia for pretty good GM compatibility. There are also many excellent patches on the Ultrasound FTP sites. I can recommend Dustin McCartney's collections gsdrum*.zip and wow*.zip in the "[.../]sound/patches/files" directory. The huge ProPats series (pp3-*.zip) contains good patches as well. General MIDI files can also be found on these sites. This site list is from the GUS FAQ: > FTP Sites Archive Directories > --------- ------------------- > Main N.American Site: archive.orst.edu pub/packages/gravis > wuarchive.wustl.edu systems/ibmpc/ultrasound > Main Asian Site: nctuccca.edu.tw PC/ultrasound > Main European Site: src.doc.ic.ac.uk packages/ultrasound > Main Australian Site: ftp.mpx.com.au /ultrasound/general > /ultrasound/submit > South African Site: ftp.sun.ac.za /pub/packages/ultrasound > Submissions: archive.epas.utoronto.ca pub/pc/ultrasound/submit > Newly Validated Files: archive.epas.utoronto.ca pub/pc/ultrasound > > Mirrors: garbo.uwasa.fi mirror/ultrasound > ftp.st.nepean.uws.edu.au pc/ultrasound > ftp.luth.se pub/msdos/ultrasound -------------------------------------------------------------------------- Q: Some files have awful clicks and pops. A: Find out which patch is responsible for the clicking (try "timidity -P ". Add "strip=tail" in the config file after its name. If this doesn't fix it, mail me the patch. -------------------------------------------------------------------------- Q: I'm playing Fantasie Impromptu in the background. When I run Netscape, the sound gets choppy and it takes ten minutes to load. What can I do? A: Here are some things to try: - Use a lower sampling rate. - Use mono output. This can improve performance by 10-30%. (Using 8-bit instead of 16-bit output makes no difference.) - Use a smaller number of simultaneous voices. - Make sure you compiled with FAST_DECAY enabled in options.h - Recompile with an Intel-optimized gcc for a 5-15% performance increase. -------------------------------------------------------------------------- libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/README000066400000000000000000000044161501405355700237610ustar00rootroot00000000000000[This version of timidity has been stripped for simplicity in porting to SDL, and then even further for SDL_sound] ---------------------------------*-text-*--------------------------------- From http://www.cgs.fi/~tt/discontinued.html : If you'd like to continue hacking on TiMidity, feel free. I'm hereby extending the TiMidity license agreement: you can now select the most convenient license for your needs from (1) the GNU GPL, (2) the GNU LGPL, or (3) the Perl Artistic License. -------------------------------------------------------------------------- This is the README file for TiMidity v0.2i TiMidity is a MIDI to WAVE converter that uses Gravis Ultrasound(*)-compatible patch files to generate digital audio data from General MIDI files. The audio data can be played through any sound device or stored on disk. On a fast machine, music can be played in real time. TiMidity runs under Linux, FreeBSD, HP-UX, SunOS, and Win32, and porting to other systems with gcc should be easy. TiMidity Features: * 32 or more dynamically allocated fully independent voices * Compatibility with GUS patch files * Output to 16- or 8-bit PCM or uLaw audio device, file, or stdout at any sampling rate * Optional interactive mode with real-time status display under ncurses and SLang terminal control libraries. Also a user friendly motif interface since version 0.2h * Support for transparent loading of compressed MIDI files and patch files * Support for the following MIDI events: - Program change - Key pressure - Channel main volume - Tempo - Panning - Damper pedal (Sustain) - Pitch wheel - Pitch wheel sensitivity - Change drum set * TiMidity requires sampled instruments (patches) to play MIDI files. You should get the file "timidity-lib-0.1.tar.gz" and unpack it in the same directory where you unpacked the source code archive. You'll want more patches later -- read the file "FAQ" for pointers. * Timidity is no longer supported, but can be found by searching the web. Tuukka Toivonen [(*) Any Registered Trademarks used anywhere in the documentation or source code for TiMidity are acknowledged as belonging to their respective owners.] libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/TODO000066400000000000000000000024631501405355700235710ustar00rootroot00000000000000* I don't like the indentation style at all, but for the most part I've left it alone. * Much of the code looks ugly to me. * Group the members of MidiSong into logical units, i.e. structs? * The debug messages are probably a bit too noisy. I've removed one particularly annoying one, but... Some of them should be turned into error messages instead. * Can the instrument handling be made more efficient? At the moment different MidiSongs may separately load the same instrument. Note that the MidiSong's audio format affects how the instrument is loaded, so it's not as easy as just letting all MidiSongs share tone and drum banks. At the moment they do share the data that is simply read from the config file, but that's just a quick hack to avoid having to read the config file every time a MIDI song is loaded. * Check if any of MidiStruct's members can safely be made into static globals again. * TiMidity++ adds a number of undocumented (?) extensions to the configuration syntax. These are not implemented here. In particular, the "map" keyword used by the "eawpats". * The other decoders generally only read as much of the file as is necessary. Could we do that in this decoder as well? (Currently it seems to convert the entire file into MIDI events first.) * Can it be optimized? libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/common.c000066400000000000000000000047331501405355700245370ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. common.c */ #include #include "options.h" #include "common.h" #if defined(_WIN32) #define CHAR_DIRSEP '\\' #define is_dirsep(c) ((c) == '/' || (c) == '\\') #define is_abspath(p) ((p)[0] == '/' || (p)[0] == '\\' || ((p)[0] && (p)[1] == ':')) #else /* unix: */ #define CHAR_DIRSEP '/' #define is_dirsep(c) ((c) == '/') #define is_abspath(p) ((p)[0] == '/') #endif /* The paths in this list will be tried whenever we're reading a file */ typedef struct _PathList { char *path; struct _PathList *next; } PathList; static PathList *pathlist = NULL; /* This is meant to find and open files for reading */ SDL_IOStream *timi_openfile(const char *name) { SDL_IOStream *io; if (!name || !(*name)) { SNDDBG(("Attempted to open nameless file.\n")); return NULL; } /* First try the given name */ SNDDBG(("Trying to open %s\n", name)); if ((io = SDL_IOFromFile(name, "rb")) != NULL) return io; if (!is_abspath(name)) { char current_filename[1024]; PathList *plp = pathlist; char *p; size_t l; while (plp) { /* Try along the path then */ *current_filename = 0; p = current_filename; l = SDL_strlen(plp->path); if(l >= sizeof(current_filename) - 3) l = 0; if(l) { SDL_memcpy(current_filename, plp->path, l); p += l; if(!is_dirsep(p[-1])) { *p++ = CHAR_DIRSEP; l++; } } SDL_strlcpy(p, name, sizeof(current_filename) - l); SNDDBG(("Trying to open %s\n", current_filename)); if ((io = SDL_IOFromFile(current_filename, "rb"))) return io; plp = plp->next; } } /* Nothing could be opened. */ SNDDBG(("Could not open %s\n", name)); return NULL; } /* This adds a directory to the path list */ int timi_add_pathlist(const char *s, size_t l) { PathList *plp = SDL_malloc(sizeof(PathList)); if (plp == NULL) return -2; plp->path = SDL_malloc(l + 1); if (plp->path == NULL) { SDL_free (plp); return -2; } SDL_memcpy(plp->path, s, l); plp->path[l] = 0; plp->next = pathlist; pathlist = plp; return 0; } void timi_free_pathlist(void) { PathList *plp = pathlist; PathList *next; while (plp) { next = plp->next; SDL_free(plp->path); SDL_free(plp); plp = next; } pathlist = NULL; } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/common.h000066400000000000000000000014451501405355700245410ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. common.h */ #ifndef TIMIDITY_COMMON_H #define TIMIDITY_COMMON_H extern SDL_IOStream *timi_openfile(const char *name); /* pathlist funcs only to be used during Timidity_Init/Timidity_Exit */ extern int timi_add_pathlist(const char *s, size_t len); extern void timi_free_pathlist(void); /* hide private symbols by prefixing with "_timi_" */ #undef TIMI_NAMESPACE #define TIMI_NAMESPACE(x) _timi_ ## x /* debug output */ #ifdef DEBUG_CHATTER #define SNDDBG(X) SDL_Log X #else #define SNDDBG(X) #endif #endif /* TIMIDITY_COMMON_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/instrum.c000066400000000000000000000363541501405355700247540ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. instrum.c Code to load and unload GUS-compatible instrument patches. */ #include #include "timidity.h" #include "options.h" #include "common.h" #include "instrum.h" #include "resample.h" #include "tables.h" static void free_instrument(Instrument *ip) { Sample *sp; int i; if (!ip) return; if (ip->sample) { for (i=0; isamples; i++) { sp=&(ip->sample[i]); SDL_free(sp->data); } SDL_free(ip->sample); } SDL_free(ip); } static void free_bank(MidiSong *song, int dr, int b) { int i; ToneBank *bank=((dr) ? song->drumset[b] : song->tonebank[b]); for (i=0; iinstrument[i]) { if (bank->instrument[i] != MAGIC_LOAD_INSTRUMENT) free_instrument(bank->instrument[i]); bank->instrument[i] = NULL; } } static Sint32 convert_envelope_rate(MidiSong *song, Uint8 rate) { Sint32 r; r = 3 - ((rate >> 6) & 0x3); r *= 3; r = (Sint32) (rate & 0x3f) << r; /* 6.9 fixed point */ /* 15.15 fixed point. */ r = ((r * 44100) / song->rate) * song->control_ratio; #ifdef FAST_DECAY return r << 10; #else return r << 9; #endif } static Sint32 convert_envelope_offset(Uint8 offset) { /* This is not too good... Can anyone tell me what these values mean? Are they GUS-style "exponential" volumes? And what does that mean? */ /* 15.15 fixed point */ return offset << (7+15); } static Sint32 convert_tremolo_sweep(MidiSong *song, Uint8 sweep) { if (!sweep) return 0; return ((song->control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep); } static Sint32 convert_vibrato_sweep(MidiSong *song, Uint8 sweep, Sint32 vib_control_ratio) { if (!sweep) return 0; return (Sint32) (TIM_FSCALE((double) (vib_control_ratio) * SWEEP_TUNING, SWEEP_SHIFT) / (double)(song->rate * sweep)); /* this was overflowing with seashore.pat ((vib_control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep); */ } static Sint32 convert_tremolo_rate(MidiSong *song, Uint8 rate) { return ((SINE_CYCLE_LENGTH * song->control_ratio * rate) << RATE_SHIFT) / (TREMOLO_RATE_TUNING * song->rate); } static Sint32 convert_vibrato_rate(MidiSong *song, Uint8 rate) { /* Return a suitable vibrato_control_ratio value */ return (VIBRATO_RATE_TUNING * song->rate) / (rate * 2 * VIBRATO_SAMPLE_INCREMENTS); } static void reverse_data(Sint16 *sp, Sint32 ls, Sint32 le) { Sint16 s, *ep=sp+le; sp+=ls; le-=ls; le/=2; while (le--) { s=*sp; *sp++=*ep; *ep--=s; } } /* If panning or note_to_use != -1, it will be used for all samples, instead of the sample-specific values in the instrument file. For note_to_use, any value <0 or >127 will be forced to 0. For other parameters, 1 means yes, 0 means no, other values are undefined. TODO: do reverse loops right */ static void load_instrument(MidiSong *song, const char *name, Instrument **out, int percussion, int panning, int amp, int note_to_use, int strip_loop, int strip_envelope, int strip_tail) { Instrument *ip; Sample *sp; SDL_IOStream *io; char tmp[1024]; int i,j; static char *patch_ext[] = PATCH_EXT_LIST; (void)percussion; /* unused */ *out = NULL; if (!name) return; /* Open patch file */ i = -1; if ((io=timi_openfile(name)) == NULL) { /* Try with various extensions */ for (i=0; patch_ext[i]; i++) { SDL_snprintf(tmp, sizeof(tmp), "%s%s", name, patch_ext[i]); if ((io=timi_openfile(tmp)) != NULL) break; } } if (io == NULL) { SNDDBG(("Instrument `%s' can't be found.\n", name)); return; } SNDDBG(("Loading instrument %s\n", (i < 0)? name : tmp)); /* Read some headers and do cursory sanity checks. There are loads of magic offsets. This could be rewritten... */ if ((239 != SDL_ReadIO(io, tmp, 239)) || (SDL_memcmp(tmp, "GF1PATCH110\0ID#000002", 22) && SDL_memcmp(tmp, "GF1PATCH100\0ID#000002", 22))) /* don't know what the differences are */ { SNDDBG(("%s: not an instrument\n", name)); goto badpat; } if (tmp[82] != 1 && tmp[82] != 0) /* instruments. To some patch makers, 0 means 1 */ { SNDDBG(("Can't handle patches with %d instruments\n", tmp[82])); goto badpat; } if (tmp[151] != 1 && tmp[151] != 0) /* layers. What's a layer? */ { SNDDBG(("Can't handle instruments with %d layers\n", tmp[151])); goto badpat; } *out=SDL_malloc(sizeof(Instrument)); ip = *out; if (!ip) goto nomem; ip->samples = tmp[198]; ip->sample = SDL_malloc(sizeof(Sample) * ip->samples); if (!ip->sample) goto nomem; for (i=0; isamples; i++) { Uint8 fractions; Sint32 tmplong; Uint16 tmpshort; Uint8 tmpchar; #define READ_CHAR(thing) \ if (1 != SDL_ReadIO(io, &tmpchar, 1)) goto badread; \ thing = tmpchar; #define READ_SHORT(thing) \ if (2 != SDL_ReadIO(io, &tmpshort, 2)) goto badread; \ thing = SDL_Swap16LE(tmpshort); #define READ_LONG(thing) \ if (4 != SDL_ReadIO(io, &tmplong, 4)) goto badread; \ thing = (Sint32)SDL_Swap32LE((Uint32)tmplong); SDL_SeekIO(io, 7, SDL_IO_SEEK_CUR); /* Skip the wave name */ if (1 != SDL_ReadIO(io, &fractions, 1)) goto badread; sp=&(ip->sample[i]); READ_LONG(sp->data_length); READ_LONG(sp->loop_start); READ_LONG(sp->loop_end); READ_SHORT(sp->sample_rate); READ_LONG(sp->low_freq); READ_LONG(sp->high_freq); READ_LONG(sp->root_freq); SDL_SeekIO(io, 2, SDL_IO_SEEK_CUR); /* Why have a "root frequency" and then * "tuning"?? */ READ_CHAR(tmp[0]); if (panning==-1) sp->panning = (tmp[0] * 8 + 4) & 0x7f; else sp->panning=(Uint8)(panning & 0x7F); /* envelope, tremolo, and vibrato */ if (18 != SDL_ReadIO(io, tmp, 18)) goto badread; if (!tmp[13] || !tmp[14]) { sp->tremolo_sweep_increment= sp->tremolo_phase_increment=sp->tremolo_depth=0; SNDDBG((" * no tremolo\n")); } else { sp->tremolo_sweep_increment=convert_tremolo_sweep(song, tmp[12]); sp->tremolo_phase_increment=convert_tremolo_rate(song, tmp[13]); sp->tremolo_depth=tmp[14]; SNDDBG((" * tremolo: sweep %d, phase %d, depth %d\n", sp->tremolo_sweep_increment, sp->tremolo_phase_increment, sp->tremolo_depth)); } if (!tmp[16] || !tmp[17]) { sp->vibrato_sweep_increment= sp->vibrato_control_ratio=sp->vibrato_depth=0; SNDDBG((" * no vibrato\n")); } else { sp->vibrato_control_ratio=convert_vibrato_rate(song, tmp[16]); sp->vibrato_sweep_increment= convert_vibrato_sweep(song, tmp[15], sp->vibrato_control_ratio); sp->vibrato_depth=tmp[17]; SNDDBG((" * vibrato: sweep %d, ctl %d, depth %d\n", sp->vibrato_sweep_increment, sp->vibrato_control_ratio, sp->vibrato_depth)); } READ_CHAR(sp->modes); SDL_SeekIO(io, 40, SDL_IO_SEEK_CUR); /* skip the useless scale frequency, scale factor (what's it mean?), and reserved space */ /* Mark this as a fixed-pitch instrument if such a deed is desired. */ if (note_to_use!=-1) sp->note_to_use=(Uint8)(note_to_use); else sp->note_to_use=0; /* seashore.pat in the Midia patch set has no Sustain. I don't understand why, and fixing it by adding the Sustain flag to all looped patches probably breaks something else. We do it anyway. */ if (sp->modes & MODES_LOOPING) sp->modes |= MODES_SUSTAIN; /* Strip any loops and envelopes we're permitted to */ if ((strip_loop==1) && (sp->modes & (MODES_SUSTAIN | MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE))) { SNDDBG((" - Removing loop and/or sustain\n")); sp->modes &=~(MODES_SUSTAIN | MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE); } if (strip_envelope==1) { if (sp->modes & MODES_ENVELOPE) { SNDDBG((" - Removing envelope\n")); } sp->modes &= ~MODES_ENVELOPE; } else if (strip_envelope != 0) { /* Have to make a guess. */ if (!(sp->modes & (MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE))) { /* No loop? Then what's there to sustain? No envelope needed either... */ sp->modes &= ~(MODES_SUSTAIN|MODES_ENVELOPE); SNDDBG((" - No loop, removing sustain and envelope\n")); } else if (!SDL_memcmp(tmp, "??????", 6) || tmp[11] >= 100) { /* Envelope rates all maxed out? Envelope end at a high "offset"? That's a weird envelope. Take it out. */ sp->modes &= ~MODES_ENVELOPE; SNDDBG((" - Weirdness, removing envelope\n")); } else if (!(sp->modes & MODES_SUSTAIN)) { /* No sustain? Then no envelope. I don't know if this is justified, but patches without sustain usually don't need the envelope either... at least the Gravis ones. They're mostly drums. I think. */ sp->modes &= ~MODES_ENVELOPE; SNDDBG((" - No sustain, removing envelope\n")); } } for (j=0; j<6; j++) { sp->envelope_rate[j]= convert_envelope_rate(song, tmp[j]); sp->envelope_offset[j]= convert_envelope_offset(tmp[6+j]); } /* Then read the sample data */ sp->data = (sample_t *) SDL_malloc(sp->data_length+4); if (!sp->data) goto nomem; if (SDL_ReadIO(io, sp->data, sp->data_length) != (size_t)sp->data_length) goto badread; if (!(sp->modes & MODES_16BIT)) /* convert to 16-bit data */ { Sint32 k=sp->data_length; Uint8 *cp=(Uint8 *)(sp->data); Uint16 *tmp16,*new16; sp->data_length *= 2; sp->loop_start *= 2; sp->loop_end *= 2; tmp16 = new16 = (Uint16 *) SDL_malloc(sp->data_length+4); if (!new16) goto nomem; while (k--) *tmp16++ = (Uint16)(*cp++) << 8; SDL_free(sp->data); sp->data = (sample_t *)new16; } #if SDL_BYTEORDER == SDL_BIG_ENDIAN else /* convert to machine byte order */ { Sint32 k=sp->data_length/2; Sint16 *tmp16=(Sint16 *)sp->data,s; while (k--) { s=SDL_Swap16LE(*tmp16); *tmp16++=s; } } #endif if (sp->modes & MODES_UNSIGNED) /* convert to signed data */ { Sint32 k=sp->data_length/2; Sint16 *tmp16=(Sint16 *)sp->data; while (k--) *tmp16++ ^= 0x8000; } /* Reverse reverse loops and pass them off as normal loops */ if (sp->modes & MODES_REVERSE) { Sint32 t; /* The GUS apparently plays reverse loops by reversing the whole sample. We do the same because the GUS does not SUCK. */ SNDDBG(("Reverse loop in %s\n", name)); reverse_data((Sint16 *)sp->data, 0, sp->data_length/2); t=sp->loop_start; sp->loop_start=sp->data_length - sp->loop_end; sp->loop_end=sp->data_length - t; sp->modes &= ~MODES_REVERSE; sp->modes |= MODES_LOOPING; /* just in case */ } #ifdef ADJUST_SAMPLE_VOLUMES if (amp!=-1) sp->volume=(float)((amp) / 100.0); else { /* Try to determine a volume scaling factor for the sample. This is a very crude adjustment, but things sound more balanced with it. Still, this should be a runtime option. */ Sint32 k=sp->data_length/2; Sint16 maxamp=0,a; Sint16 *tmp16=(Sint16 *)sp->data; while (k--) { a=*tmp16++; if (a<0) a=-a; if (a>maxamp) maxamp=a; } sp->volume=(float)(32768.0 / maxamp); SNDDBG((" * volume comp: %f\n", sp->volume)); } #else if (amp!=-1) sp->volume=(double)(amp) / 100.0; else sp->volume=1.0; #endif sp->data_length /= 2; /* These are in bytes. Convert into samples. */ sp->loop_start /= 2; sp->loop_end /= 2; /* initialize the added extra sample space (see the +4 bytes) */ sp->data[sp->data_length] = sp->data[sp->data_length+1] = 0; /* Then fractional samples */ sp->data_length <<= FRACTION_BITS; sp->loop_start <<= FRACTION_BITS; sp->loop_end <<= FRACTION_BITS; /* Adjust for fractional loop points. This is a guess. Does anyone know what "fractions" really stands for? */ sp->loop_start |= (fractions & 0x0F) << (FRACTION_BITS-4); sp->loop_end |= ((fractions>>4) & 0x0F) << (FRACTION_BITS-4); /* If this instrument will always be played on the same note, and it's not looped, we can resample it now. */ if (sp->note_to_use && !(sp->modes & MODES_LOOPING)) { pre_resample(song, sp); if (song->oom) goto fail; } if (strip_tail==1) { /* Let's not really, just say we did. */ SNDDBG((" - Stripping tail\n")); sp->data_length = sp->loop_end; } } SDL_CloseIO(io); return; nomem: song->oom=1; goto fail; badread: SNDDBG(("Error reading sample %d\n", i)); fail: free_instrument (ip); badpat: SDL_CloseIO(io); *out = NULL; } static int fill_bank(MidiSong *song, int dr, int b) { int i, errors=0; ToneBank *bank=((dr) ? song->drumset[b] : song->tonebank[b]); if (!bank) { SNDDBG(("Huh. Tried to load instruments in non-existent %s %d\n", (dr) ? "drumset" : "tone bank", b)); return 0; } for (i=0; iinstrument[i]==MAGIC_LOAD_INSTRUMENT) { if (!(bank->tone[i].name)) { SNDDBG(("No instrument mapped to %s %d, program %d%s\n", (dr)? "drum set" : "tone bank", b, i, (b!=0) ? "" : " - this instrument will not be heard")); if (b!=0) { /* Mark the corresponding instrument in the default bank / drumset for loading (if it isn't already) */ if (!dr) { if (!(song->tonebank[0]->instrument[i])) song->tonebank[0]->instrument[i] = MAGIC_LOAD_INSTRUMENT; } else { if (!(song->drumset[0]->instrument[i])) song->drumset[0]->instrument[i] = MAGIC_LOAD_INSTRUMENT; } } bank->instrument[i] = NULL; errors++; } else { load_instrument(song, bank->tone[i].name, &bank->instrument[i], (dr) ? 1 : 0, bank->tone[i].pan, bank->tone[i].amp, (bank->tone[i].note!=-1) ? bank->tone[i].note : ((dr) ? i : -1), (bank->tone[i].strip_loop!=-1) ? bank->tone[i].strip_loop : ((dr) ? 1 : -1), (bank->tone[i].strip_envelope != -1) ? bank->tone[i].strip_envelope : ((dr) ? 1 : -1), bank->tone[i].strip_tail); if (!bank->instrument[i]) { SNDDBG(("Couldn't load instrument %s (%s %d, program %d)\n", bank->tone[i].name, (dr)? "drum set" : "tone bank", b, i)); errors++; } } } } return errors; } int load_missing_instruments(MidiSong *song) { int i=MAXBANK,errors=0; while (i--) { if (song->tonebank[i]) errors+=fill_bank(song,0,i); if (song->drumset[i]) errors+=fill_bank(song,1,i); } return errors; } void free_instruments(MidiSong *song) { int i=MAXBANK; while(i--) { if (song->tonebank[i]) free_bank(song, 0, i); if (song->drumset[i]) free_bank(song, 1, i); } } int set_default_instrument(MidiSong *song, const char *name) { load_instrument(song, name, &song->default_instrument, 0, -1, -1, -1, 0, 0, 0); if (!song->default_instrument) return -1; song->default_program = SPECIAL_PROGRAM; return 0; } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/instrum.h000066400000000000000000000021571501405355700247530ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. instrum.h */ #ifndef TIMIDITY_INSTRUM_H #define TIMIDITY_INSTRUM_H /* Bits in modes: */ #define MODES_16BIT (1<<0) #define MODES_UNSIGNED (1<<1) #define MODES_LOOPING (1<<2) #define MODES_PINGPONG (1<<3) #define MODES_REVERSE (1<<4) #define MODES_SUSTAIN (1<<5) #define MODES_ENVELOPE (1<<6) /* A hack to delay instrument loading until after reading the entire MIDI file. */ #define MAGIC_LOAD_INSTRUMENT ((Instrument *) (-1)) #define SPECIAL_PROGRAM -1 #define load_missing_instruments TIMI_NAMESPACE(load_missing_instruments) #define free_instruments TIMI_NAMESPACE(free_instruments) #define set_default_instrument TIMI_NAMESPACE(set_default_instrument) extern int load_missing_instruments(MidiSong *song); extern void free_instruments(MidiSong *song); extern int set_default_instrument(MidiSong *song, const char *name); #endif /* TIMIDITY_INSTRUM_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/mix.c000066400000000000000000000271251501405355700240440ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. mix.c */ #include #include "timidity.h" #include "options.h" #include "common.h" #include "instrum.h" #include "playmidi.h" #include "output.h" #include "tables.h" #include "resample.h" #include "mix.h" /* Returns 1 if envelope runs out */ int recompute_envelope(MidiSong *song, int v) { int stage; stage = song->voice[v].envelope_stage; if (stage>5) { /* Envelope ran out. */ song->voice[v].status = VOICE_FREE; return 1; } if (song->voice[v].sample->modes & MODES_ENVELOPE) { if (song->voice[v].status==VOICE_ON || song->voice[v].status==VOICE_SUSTAINED) { if (stage>2) { /* Freeze envelope until note turns off. Trumpets want this. */ song->voice[v].envelope_increment=0; return 0; } } } song->voice[v].envelope_stage=stage+1; if (song->voice[v].envelope_volume==song->voice[v].sample->envelope_offset[stage] || (stage > 2 && song->voice[v].envelope_volume < song->voice[v].sample->envelope_offset[stage])) return recompute_envelope(song, v); song->voice[v].envelope_target = song->voice[v].sample->envelope_offset[stage]; song->voice[v].envelope_increment = song->voice[v].sample->envelope_rate[stage]; if (song->voice[v].envelope_target < song->voice[v].envelope_volume) song->voice[v].envelope_increment = -song->voice[v].envelope_increment; return 0; } void apply_envelope_to_amp(MidiSong *song, int v) { float lamp = song->voice[v].left_amp, ramp; Sint32 la,ra; if (song->voice[v].panned == PANNED_MYSTERY) { ramp = song->voice[v].right_amp; if (song->voice[v].tremolo_phase_increment) { lamp *= song->voice[v].tremolo_volume; ramp *= song->voice[v].tremolo_volume; } if (song->voice[v].sample->modes & MODES_ENVELOPE) { lamp *= (float)vol_table[song->voice[v].envelope_volume>>23]; ramp *= (float)vol_table[song->voice[v].envelope_volume>>23]; } la = (Sint32)TIM_FSCALE(lamp,AMP_BITS); if (la>MAX_AMP_VALUE) la=MAX_AMP_VALUE; ra = (Sint32)TIM_FSCALE(ramp,AMP_BITS); if (ra>MAX_AMP_VALUE) ra=MAX_AMP_VALUE; song->voice[v].left_mix = la; song->voice[v].right_mix = ra; } else { if (song->voice[v].tremolo_phase_increment) lamp *= song->voice[v].tremolo_volume; if (song->voice[v].sample->modes & MODES_ENVELOPE) lamp *= (float)vol_table[song->voice[v].envelope_volume>>23]; la = (Sint32)TIM_FSCALE(lamp,AMP_BITS); if (la>MAX_AMP_VALUE) la=MAX_AMP_VALUE; song->voice[v].left_mix = la; } } static int update_envelope(MidiSong *song, int v) { song->voice[v].envelope_volume += song->voice[v].envelope_increment; /* Why is there no ^^ operator?? */ if (((song->voice[v].envelope_increment < 0) && (song->voice[v].envelope_volume <= song->voice[v].envelope_target)) || ((song->voice[v].envelope_increment > 0) && (song->voice[v].envelope_volume >= song->voice[v].envelope_target))) { song->voice[v].envelope_volume = song->voice[v].envelope_target; if (recompute_envelope(song, v)) return 1; } return 0; } static void update_tremolo(MidiSong *song, int v) { Sint32 depth = song->voice[v].sample->tremolo_depth << 7; if (song->voice[v].tremolo_sweep) { /* Update sweep position */ song->voice[v].tremolo_sweep_position += song->voice[v].tremolo_sweep; if (song->voice[v].tremolo_sweep_position >= (1 << SWEEP_SHIFT)) song->voice[v].tremolo_sweep=0; /* Swept to max amplitude */ else { /* Need to adjust depth */ depth *= song->voice[v].tremolo_sweep_position; depth >>= SWEEP_SHIFT; } } song->voice[v].tremolo_phase += song->voice[v].tremolo_phase_increment; /* if (song->voice[v].tremolo_phase >= (SINE_CYCLE_LENGTH<voice[v].tremolo_phase -= SINE_CYCLE_LENGTH<voice[v].tremolo_volume = (float) (1.0 - TIM_FSCALENEG((timi_sine(song->voice[v].tremolo_phase >> RATE_SHIFT) + 1.0) * depth * TREMOLO_AMPLITUDE_TUNING, 17)); /* I'm not sure about the +1.0 there -- it makes tremoloed voices' volumes on average the lower the higher the tremolo amplitude. */ } /* Returns 1 if the note died */ static int update_signal(MidiSong *song, int v) { if (song->voice[v].envelope_increment && update_envelope(song, v)) return 1; if (song->voice[v].tremolo_phase_increment) update_tremolo(song, v); apply_envelope_to_amp(song, v); return 0; } #define MIXATION(a) *lp++ += (a)*s; static void mix_mystery_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { Voice *vp = song->voice + v; final_volume_t left=vp->left_mix, right=vp->right_mix; int cc; sample_t s; if (!(cc = vp->control_counter)) { cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; right = vp->right_mix; } while (count) if (cc < count) { count -= cc; while (cc--) { s = *sp++; MIXATION(left); MIXATION(right); } cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; right = vp->right_mix; } else { vp->control_counter = cc - count; while (count--) { s = *sp++; MIXATION(left); MIXATION(right); } return; } } static void mix_center_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { Voice *vp = song->voice + v; final_volume_t left=vp->left_mix; int cc; sample_t s; if (!(cc = vp->control_counter)) { cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; } while (count) if (cc < count) { count -= cc; while (cc--) { s = *sp++; MIXATION(left); MIXATION(left); } cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; } else { vp->control_counter = cc - count; while (count--) { s = *sp++; MIXATION(left); MIXATION(left); } return; } } static void mix_single_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { Voice *vp = song->voice + v; final_volume_t left=vp->left_mix; int cc; sample_t s; if (!(cc = vp->control_counter)) { cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; } while (count) if (cc < count) { count -= cc; while (cc--) { s = *sp++; MIXATION(left); lp++; } cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; } else { vp->control_counter = cc - count; while (count--) { s = *sp++; MIXATION(left); lp++; } return; } } static void mix_mono_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { Voice *vp = song->voice + v; final_volume_t left=vp->left_mix; int cc; sample_t s; if (!(cc = vp->control_counter)) { cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; } while (count) if (cc < count) { count -= cc; while (cc--) { s = *sp++; MIXATION(left); } cc = song->control_ratio; if (update_signal(song, v)) return; /* Envelope ran out */ left = vp->left_mix; } else { vp->control_counter = cc - count; while (count--) { s = *sp++; MIXATION(left); } return; } } static void mix_mystery(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { final_volume_t left = song->voice[v].left_mix, right = song->voice[v].right_mix; sample_t s; while (count--) { s = *sp++; MIXATION(left); MIXATION(right); } } static void mix_center(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { final_volume_t left = song->voice[v].left_mix; sample_t s; while (count--) { s = *sp++; MIXATION(left); MIXATION(left); } } static void mix_single(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { final_volume_t left = song->voice[v].left_mix; sample_t s; while (count--) { s = *sp++; MIXATION(left); lp++; } } static void mix_mono(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count) { final_volume_t left = song->voice[v].left_mix; sample_t s; while (count--) { s = *sp++; MIXATION(left); } } /* Ramp a note out in c samples */ static void ramp_out(MidiSong *song, sample_t *sp, Sint32 *lp, int v, Sint32 c) { /* should be final_volume_t, but Uint8 gives trouble. */ Sint32 left, right, li, ri; sample_t s=0; /* silly warning about uninitialized s */ left=song->voice[v].left_mix; li=-(left/c); if (!li) li=-1; /* printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */ if (!(song->encoding & PE_MONO)) { if (song->voice[v].panned==PANNED_MYSTERY) { right=song->voice[v].right_mix; ri=-(right/c); while (c--) { left += li; if (left<0) left=0; right += ri; if (right<0) right=0; s=*sp++; MIXATION(left); MIXATION(right); } } else if (song->voice[v].panned==PANNED_CENTER) { while (c--) { left += li; if (left<0) return; s=*sp++; MIXATION(left); MIXATION(left); } } else if (song->voice[v].panned==PANNED_LEFT) { while (c--) { left += li; if (left<0) return; s=*sp++; MIXATION(left); lp++; } } else if (song->voice[v].panned==PANNED_RIGHT) { while (c--) { left += li; if (left<0) return; s=*sp++; lp++; MIXATION(left); } } } else { /* Mono output. */ while (c--) { left += li; if (left<0) return; s=*sp++; MIXATION(left); } } } /**************** interface function ******************/ void mix_voice(MidiSong *song, Sint32 *buf, int v, Sint32 c) { Voice *vp = song->voice + v; sample_t *sp; if (vp->status==VOICE_DIE) { if (c>=MAX_DIE_TIME) c=MAX_DIE_TIME; sp=resample_voice(song, v, &c); if(c > 0) ramp_out(song, sp, buf, v, c); vp->status=VOICE_FREE; } else { sp=resample_voice(song, v, &c); if (song->encoding & PE_MONO) { /* Mono output. */ if (vp->envelope_increment || vp->tremolo_phase_increment) mix_mono_signal(song, sp, buf, v, c); else mix_mono(song, sp, buf, v, c); } else { if (vp->panned == PANNED_MYSTERY) { if (vp->envelope_increment || vp->tremolo_phase_increment) mix_mystery_signal(song, sp, buf, v, c); else mix_mystery(song, sp, buf, v, c); } else if (vp->panned == PANNED_CENTER) { if (vp->envelope_increment || vp->tremolo_phase_increment) mix_center_signal(song, sp, buf, v, c); else mix_center(song, sp, buf, v, c); } else { /* It's either full left or full right. In either case, every other sample is 0. Just get the offset right: */ if (vp->panned == PANNED_RIGHT) buf++; if (vp->envelope_increment || vp->tremolo_phase_increment) mix_single_signal(song, sp, buf, v, c); else mix_single(song, sp, buf, v, c); } } } } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/mix.h000066400000000000000000000013071501405355700240430ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. mix.h */ #ifndef TIMIDITY_MIX_H #define TIMIDITY_MIX_H #define mix_voice TIMI_NAMESPACE(mix_voice) #define recompute_envelope TIMI_NAMESPACE(recompute_envelope) #define apply_envelope_to_amp TIMI_NAMESPACE(apply_envelope_to_amp) extern void mix_voice(MidiSong *song, Sint32 *buf, int v, Sint32 c); extern int recompute_envelope(MidiSong *song, int v); extern void apply_envelope_to_amp(MidiSong *song, int v); #endif /* TIMIDITY_MIX_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/options.h000066400000000000000000000067661501405355700247570ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. */ #ifndef TIMIDITY_OPTIONS_H #define TIMIDITY_OPTIONS_H /* When a patch file can't be opened, one of these extensions is appended to the filename and the open is tried again. */ #define PATCH_EXT_LIST { ".pat", 0 } /* Acoustic Grand Piano seems to be the usual default instrument. */ #define DEFAULT_PROGRAM 0 /* 9 here is MIDI channel 10, which is the standard percussion channel. Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too. On the other hand, some files know that 16 is not a drum channel and try to play music on it. This is now a runtime option, so this isn't a critical choice anymore. */ #define DEFAULT_DRUMCHANNELS (1<<9) /* In percent. */ #define DEFAULT_AMPLIFICATION 70 /* Default polyphony */ /* #define DEFAULT_VOICES 32 */ #define DEFAULT_VOICES 256 /* 1000 here will give a control ratio of 22:1 with 22 kHz output. Higher CONTROLS_PER_SECOND values allow more accurate rendering of envelopes and tremolo. The cost is CPU time. */ #define CONTROLS_PER_SECOND 1000 /* Make envelopes twice as fast. Saves ~20% CPU time (notes decay faster) and sounds more like a GUS. */ #define FAST_DECAY /* A somewhat arbitrary output frequency range. */ #define MIN_OUTPUT_RATE 4000 #define MAX_OUTPUT_RATE 256000 /* How many bits to use for the fractional part of sample positions. This affects tonal accuracy. The entire position counter must fit in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of a sample is 1048576 samples (2 megabytes in memory). The GUS gets by with just 9 bits and a little help from its friends... "The GUS does not SUCK!!!" -- a happy user :) */ #define FRACTION_BITS 12 /* For some reason the sample volume is always set to maximum in all patch files. Define this for a crude adjustment that may help equalize instrument volumes. */ #define ADJUST_SAMPLE_VOLUMES /* The number of samples to use for ramping out a dying note. Affects click removal. */ #define MAX_DIE_TIME 20 /**************************************************************************/ /* Anything below this shouldn't need to be changed unless you're porting to a new machine with other than 32-bit, big-endian words. */ /**************************************************************************/ /* change FRACTION_BITS above, not these */ #define INTEGER_BITS (32 - FRACTION_BITS) #define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS) #define FRACTION_MASK (~ INTEGER_MASK) /* This is enforced by some computations that must fit in an int */ #define MAX_CONTROL_RATIO 255 #define MAX_AMPLIFICATION 800 /* The TiMidity configuration file */ #ifndef TIMIDITY_CFG #define TIMIDITY_CFG "timidity.cfg" #endif /* These affect general volume */ #define GUARD_BITS 3 #define AMP_BITS (15-GUARD_BITS) #define MAX_AMP_VALUE ((1<<(AMP_BITS+1))-1) #define TIM_FSCALE(a,b) (float)((a) * (double)(1<<(b))) #define TIM_FSCALENEG(a,b) (float)((a) * (1.0L / (double)(1<<(b)))) /* Vibrato and tremolo Choices of the Day */ #define SWEEP_TUNING 38 #define VIBRATO_AMPLITUDE_TUNING 1.0L #define VIBRATO_RATE_TUNING 38 #define TREMOLO_AMPLITUDE_TUNING 1.0L #define TREMOLO_RATE_TUNING 38 #define SWEEP_SHIFT 16 #define RATE_SHIFT 5 #ifndef PI #define PI 3.14159265358979323846 #endif #endif /* TIMIDITY_OPTIONS_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/output.c000066400000000000000000000036551501405355700246110ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. output.c Audio output (to file / device) functions. */ #include #include "options.h" #include "output.h" /*****************************************************************/ /* Some functions to convert signed 32-bit data to other formats */ void timi_s32tos8(void *dp, Sint32 *lp, Sint32 c) { Sint8 *cp=(Sint8 *)(dp); Sint32 l; while (c--) { l=(*lp++)>>(32-8-GUARD_BITS); if (l>127) l=127; else if (l<-128) l=-128; *cp++ = (Sint8) (l); } } void timi_s32tou8(void *dp, Sint32 *lp, Sint32 c) { Uint8 *cp=(Uint8 *)(dp); Sint32 l; while (c--) { l=(*lp++)>>(32-8-GUARD_BITS); if (l>127) l=127; else if (l<-128) l=-128; *cp++ = 0x80 ^ ((Uint8) l); } } void timi_s32tos16(void *dp, Sint32 *lp, Sint32 c) { Sint16 *sp=(Sint16 *)(dp); Sint32 l; while (c--) { l=(*lp++)>>(32-16-GUARD_BITS); if (l > 32767) l=32767; else if (l<-32768) l=-32768; *sp++ = (Sint16)(l); } } void timi_s32tos16x(void *dp, Sint32 *lp, Sint32 c) { Sint16 *sp=(Sint16 *)(dp); Sint32 l; while (c--) { l=(*lp++)>>(32-16-GUARD_BITS); if (l > 32767) l=32767; else if (l<-32768) l=-32768; *sp++ = SDL_Swap16((Sint16)(l)); } } void timi_s32tof32(void *dp, Sint32 *lp, Sint32 c) { float *sp=(float *)(dp); while (c--) { *sp++ = (float)(*lp++) / 2147483647.0f; } } void timi_s32tos32(void *dp, Sint32 *lp, Sint32 c) { Sint32 *sp=(Sint32 *)(dp); while (c--) { *sp++ = (*lp++); } } void timi_s32tos32x(void *dp, Sint32 *lp, Sint32 c) { Sint32 *sp=(Sint32 *)(dp); while (c--) { *sp++ = SDL_Swap32(*lp++); } } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/output.h000066400000000000000000000031131501405355700246030ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. output.h */ #ifndef TIMIDITY_OUTPUT_H #define TIMIDITY_OUTPUT_H /* Data format encoding bits */ #define PE_MONO 0x01 /* versus stereo */ #define PE_SIGNED 0x02 /* versus unsigned */ #define PE_16BIT 0x04 /* versus 8-bit */ #define PE_32BIT 0x08 /* versus 8-bit or 16-bit */ /* Conversion functions -- These overwrite the Sint32 data in *lp with data in another format */ /* 8-bit signed and unsigned*/ extern void timi_s32tos8(void *dp, Sint32 *lp, Sint32 c); extern void timi_s32tou8(void *dp, Sint32 *lp, Sint32 c); /* 16-bit */ extern void timi_s32tos16(void *dp, Sint32 *lp, Sint32 c); /* byte-exchanged 16-bit */ extern void timi_s32tos16x(void *dp, Sint32 *lp, Sint32 c); /* 32-bit */ extern void timi_s32tof32(void *dp, Sint32 *lp, Sint32 c); extern void timi_s32tos32(void *dp, Sint32 *lp, Sint32 c); /* byte-exchanged 32-bit */ extern void timi_s32tos32x(void *dp, Sint32 *lp, Sint32 c); /* little-endian and big-endian specific */ #if SDL_BYTEORDER == SDL_LIL_ENDIAN #define timi_s32tos16l timi_s32tos16 #define timi_s32tos16b timi_s32tos16x #define timi_s32tos32l timi_s32tos32 #define timi_s32tos32b timi_s32tos32x #else #define timi_s32tos16l timi_s32tos16x #define timi_s32tos16b timi_s32tos16 #define timi_s32tos32l timi_s32tos32x #define timi_s32tos32b timi_s32tos32 #endif #endif /* TIMIDITY_OUTPUT_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/playmidi.c000066400000000000000000000512141501405355700250530ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. playmidi.c -- random stuff in need of rearrangement */ #include #include "timidity.h" #include "options.h" #include "common.h" #include "instrum.h" #include "playmidi.h" #include "output.h" #include "mix.h" #include "tables.h" static void adjust_amplification(MidiSong *song) { song->master_volume = (float)(song->amplification) / 100.0f; } static void reset_voices(MidiSong *song) { int i; for (i=0; ivoice[i].status=VOICE_FREE; } /* Process the Reset All Controllers event */ static void reset_controllers(MidiSong *song, int c) { song->channel[c].volume=90; /* Some standard says, although the SCC docs say 0. */ song->channel[c].expression=127; /* SCC-1 does this. */ song->channel[c].sustain=0; song->channel[c].pitchbend=0x2000; song->channel[c].pitchfactor=0; /* to be computed */ } static void reset_midi(MidiSong *song) { int i; for (i=0; ichannel[i].program=song->default_program; song->channel[i].panning=NO_PANNING; song->channel[i].pitchsens=2; song->channel[i].bank=0; /* tone bank or drum set */ } reset_voices(song); } static void select_sample(MidiSong *song, int v, Instrument *ip) { Sint32 f, cdiff, diff; int s,i; Sample *sp, *closest; s=ip->samples; sp=ip->sample; if (s==1) { song->voice[v].sample=sp; return; } f=song->voice[v].orig_frequency; for (i=0; ilow_freq <= f && sp->high_freq >= f) { song->voice[v].sample=sp; return; } } /* No suitable sample found! We'll select the sample whose root frequency is closest to the one we want. (Actually we should probably convert the low, high, and root frequencies to MIDI note values and compare those.) */ cdiff=0x7FFFFFFF; closest=sp=ip->sample; for(i=0; iroot_freq - f; if (diff<0) diff=-diff; if (diffvoice[v].sample=closest; } static void recompute_freq(MidiSong *song, int v) { int sign=(song->voice[v].sample_increment < 0), /* for bidirectional loops */ pb=song->channel[song->voice[v].channel].pitchbend; double a; if (!song->voice[v].sample->sample_rate) return; if (song->voice[v].vibrato_control_ratio) { /* This instrument has vibrato. Invalidate any precomputed sample_increments. */ int i=VIBRATO_SAMPLE_INCREMENTS; while (i--) song->voice[v].vibrato_sample_increment[i]=0; } if (pb==0x2000 || pb<0 || pb>0x3FFF) song->voice[v].frequency = song->voice[v].orig_frequency; else { pb-=0x2000; if (!(song->channel[song->voice[v].channel].pitchfactor)) { /* Damn. Somebody bent the pitch. */ Sint32 i=pb*song->channel[song->voice[v].channel].pitchsens; if (pb<0) i=-i; song->channel[song->voice[v].channel].pitchfactor= (float)(bend_fine[(i>>5) & 0xFF] * bend_coarse[i>>13]); } if (pb>0) song->voice[v].frequency= (Sint32)(song->channel[song->voice[v].channel].pitchfactor * (double)(song->voice[v].orig_frequency)); else song->voice[v].frequency= (Sint32)((double)(song->voice[v].orig_frequency) / song->channel[song->voice[v].channel].pitchfactor); } a = TIM_FSCALE(((double)(song->voice[v].sample->sample_rate) * (double)(song->voice[v].frequency)) / ((double)(song->voice[v].sample->root_freq) * (double)(song->rate)), FRACTION_BITS); if (sign) a = -a; /* need to preserve the loop direction */ song->voice[v].sample_increment = (Sint32)(a); } static void recompute_amp(MidiSong *song, int v) { Sint32 tempamp; /* TODO: use fscale */ tempamp= (song->voice[v].velocity * song->channel[song->voice[v].channel].volume * song->channel[song->voice[v].channel].expression); /* 21 bits */ if (!(song->encoding & PE_MONO)) { if (song->voice[v].panning > 60 && song->voice[v].panning < 68) { song->voice[v].panned=PANNED_CENTER; song->voice[v].left_amp= TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, 21); } else if (song->voice[v].panning<5) { song->voice[v].panned = PANNED_LEFT; song->voice[v].left_amp= TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, 20); } else if (song->voice[v].panning>123) { song->voice[v].panned = PANNED_RIGHT; song->voice[v].left_amp= /* left_amp will be used */ TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, 20); } else { song->voice[v].panned = PANNED_MYSTERY; song->voice[v].left_amp= TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, 27); song->voice[v].right_amp = song->voice[v].left_amp * (song->voice[v].panning); song->voice[v].left_amp *= (float)(127 - song->voice[v].panning); } } else { song->voice[v].panned = PANNED_CENTER; song->voice[v].left_amp= TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, 21); } } static void start_note(MidiSong *song, MidiEvent *e, int i) { Instrument *ip; int j; if (ISDRUMCHANNEL(song, e->channel)) { if (!(ip=song->drumset[song->channel[e->channel].bank]->instrument[e->a])) { if (!(ip=song->drumset[0]->instrument[e->a])) return; /* No instrument? Then we can't play. */ } if (ip->samples != 1) { SNDDBG(("Strange: percussion instrument with %d samples!", ip->samples)); } if (ip->sample->note_to_use) /* Do we have a fixed pitch? */ song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)]; else song->voice[i].orig_frequency = freq_table[e->a & 0x7F]; /* drums are supposed to have only one sample */ song->voice[i].sample = ip->sample; } else { if (song->channel[e->channel].program == SPECIAL_PROGRAM) ip=song->default_instrument; else if (!(ip=song->tonebank[song->channel[e->channel].bank]-> instrument[song->channel[e->channel].program])) { if (!(ip=song->tonebank[0]->instrument[song->channel[e->channel].program])) return; /* No instrument? Then we can't play. */ } if (ip->sample->note_to_use) /* Fixed-pitch instrument? */ song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)]; else song->voice[i].orig_frequency = freq_table[e->a & 0x7F]; select_sample(song, i, ip); } song->voice[i].status = VOICE_ON; song->voice[i].channel = e->channel; song->voice[i].note = e->a; song->voice[i].velocity = e->b; song->voice[i].sample_offset = 0; song->voice[i].sample_increment = 0; /* make sure it isn't negative */ song->voice[i].tremolo_phase = 0; song->voice[i].tremolo_phase_increment = song->voice[i].sample->tremolo_phase_increment; song->voice[i].tremolo_sweep = song->voice[i].sample->tremolo_sweep_increment; song->voice[i].tremolo_sweep_position = 0; song->voice[i].vibrato_sweep = song->voice[i].sample->vibrato_sweep_increment; song->voice[i].vibrato_sweep_position = 0; song->voice[i].vibrato_control_ratio = song->voice[i].sample->vibrato_control_ratio; song->voice[i].vibrato_control_counter = song->voice[i].vibrato_phase = 0; for (j=0; jvoice[i].vibrato_sample_increment[j] = 0; if (song->channel[e->channel].panning != NO_PANNING) song->voice[i].panning = song->channel[e->channel].panning; else song->voice[i].panning = song->voice[i].sample->panning; recompute_freq(song, i); recompute_amp(song, i); if (song->voice[i].sample->modes & MODES_ENVELOPE) { /* Ramp up from 0 */ song->voice[i].envelope_stage = 0; song->voice[i].envelope_volume = 0; song->voice[i].control_counter = 0; recompute_envelope(song, i); apply_envelope_to_amp(song, i); } else { song->voice[i].envelope_increment = 0; apply_envelope_to_amp(song, i); } } static void kill_note(MidiSong *song, int i) { song->voice[i].status = VOICE_DIE; } /* Only one instance of a note can be playing on a single channel. */ static void note_on(MidiSong *song) { int i = song->voices, lowest=-1; Sint32 lv=0x7FFFFFFF, v; MidiEvent *e = song->current_event; while (i--) { if (song->voice[i].status == VOICE_FREE) lowest=i; /* Can't get a lower volume than silence */ else if (song->voice[i].channel==e->channel && (song->voice[i].note==e->a || song->channel[song->voice[i].channel].mono)) kill_note(song, i); } if (lowest != -1) { /* Found a free voice. */ start_note(song,e,lowest); return; } /* Look for the decaying note with the lowest volume */ i = song->voices; while (i--) { if ((song->voice[i].status != VOICE_ON) && (song->voice[i].status != VOICE_DIE)) { v = song->voice[i].left_mix; if ((song->voice[i].panned == PANNED_MYSTERY) && (song->voice[i].right_mix > v)) v = song->voice[i].right_mix; if (vcut_notes++; song->voice[lowest].status=VOICE_FREE; start_note(song,e,lowest); } else song->lost_notes++; } static void finish_note(MidiSong *song, int i) { if (song->voice[i].sample->modes & MODES_ENVELOPE) { /* We need to get the envelope out of Sustain stage */ song->voice[i].envelope_stage = 3; song->voice[i].status = VOICE_OFF; recompute_envelope(song, i); apply_envelope_to_amp(song, i); } else { /* Set status to OFF so resample_voice() will let this voice out of its loop, if any. In any case, this voice dies when it hits the end of its data (ofs>=data_length). */ song->voice[i].status = VOICE_OFF; } } static void note_off(MidiSong *song) { int i = song->voices; MidiEvent *e = song->current_event; while (i--) if (song->voice[i].status == VOICE_ON && song->voice[i].channel == e->channel && song->voice[i].note == e->a) { if (song->channel[e->channel].sustain) { song->voice[i].status = VOICE_SUSTAINED; } else finish_note(song, i); return; } } /* Process the All Notes Off event */ static void all_notes_off(MidiSong *song) { int i = song->voices; int c = song->current_event->channel; SNDDBG(("All notes off on channel %d", c)); while (i--) if (song->voice[i].status == VOICE_ON && song->voice[i].channel == c) { if (song->channel[c].sustain) song->voice[i].status = VOICE_SUSTAINED; else finish_note(song, i); } } /* Process the All Sounds Off event */ static void all_sounds_off(MidiSong *song) { int i = song->voices; int c = song->current_event->channel; while (i--) if (song->voice[i].channel == c && song->voice[i].status != VOICE_FREE && song->voice[i].status != VOICE_DIE) { kill_note(song, i); } } static void adjust_pressure(MidiSong *song) { MidiEvent *e = song->current_event; int i = song->voices; while (i--) if (song->voice[i].status == VOICE_ON && song->voice[i].channel == e->channel && song->voice[i].note == e->a) { song->voice[i].velocity = e->b; recompute_amp(song, i); apply_envelope_to_amp(song, i); return; } } static void drop_sustain(MidiSong *song) { int i = song->voices; int c = song->current_event->channel; while (i--) if (song->voice[i].status == VOICE_SUSTAINED && song->voice[i].channel == c) finish_note(song, i); } static void adjust_pitchbend(MidiSong *song) { int c = song->current_event->channel; int i = song->voices; while (i--) if (song->voice[i].status != VOICE_FREE && song->voice[i].channel == c) { recompute_freq(song, i); } } static void adjust_volume(MidiSong *song) { int c = song->current_event->channel; int i = song->voices; while (i--) if (song->voice[i].channel == c && (song->voice[i].status==VOICE_ON || song->voice[i].status==VOICE_SUSTAINED)) { recompute_amp(song, i); apply_envelope_to_amp(song, i); } } static void seek_forward(MidiSong *song, Sint32 until_time) { reset_voices(song); while (song->current_event->time < until_time) { switch(song->current_event->type) { /* All notes stay off. Just handle the parameter changes. */ case ME_PITCH_SENS: song->channel[song->current_event->channel].pitchsens = song->current_event->a; song->channel[song->current_event->channel].pitchfactor = 0; break; case ME_PITCHWHEEL: song->channel[song->current_event->channel].pitchbend = song->current_event->a + song->current_event->b * 128; song->channel[song->current_event->channel].pitchfactor = 0; break; case ME_MAINVOLUME: song->channel[song->current_event->channel].volume = song->current_event->a; break; case ME_PAN: song->channel[song->current_event->channel].panning = song->current_event->a; break; case ME_EXPRESSION: song->channel[song->current_event->channel].expression = song->current_event->a; break; case ME_PROGRAM: if (ISDRUMCHANNEL(song, song->current_event->channel)) /* Change drum set */ song->channel[song->current_event->channel].bank = song->current_event->a; else song->channel[song->current_event->channel].program = song->current_event->a; break; case ME_SUSTAIN: song->channel[song->current_event->channel].sustain = song->current_event->a; break; case ME_RESET_CONTROLLERS: reset_controllers(song, song->current_event->channel); break; case ME_TONE_BANK: song->channel[song->current_event->channel].bank = song->current_event->a; break; case ME_EOT: song->current_sample = song->current_event->time; return; } song->current_event++; } /*song->current_sample=song->current_event->time;*/ if (song->current_event != song->events) song->current_event--; song->current_sample=until_time; } static void skip_to(MidiSong *song, Sint32 until_time) { if (song->current_sample > until_time) song->current_sample = 0; reset_midi(song); song->buffered_count = 0; song->buffer_pointer = song->common_buffer; song->current_event = song->events; if (until_time) seek_forward(song, until_time); } static void do_compute_data(MidiSong *song, Sint32 count) { int i; SDL_memset(song->buffer_pointer, 0, (song->encoding & PE_MONO) ? (count * 4) : (count * 8)); for (i = 0; i < song->voices; i++) { if(song->voice[i].status != VOICE_FREE) mix_voice(song, song->buffer_pointer, i, count); } song->current_sample += count; } /* count=0 means flush remaining buffered data to output device, then flush the device itself */ static void compute_data(MidiSong *song, void *stream, Sint32 count) { int channels; if ( song->encoding & PE_MONO ) channels = 1; else channels = 2; if (!count) { if (song->buffered_count) song->write(stream, song->common_buffer, channels * song->buffered_count); song->buffer_pointer = song->common_buffer; song->buffered_count = 0; return; } while ((count + song->buffered_count) >= song->buffer_size) { do_compute_data(song, song->buffer_size - song->buffered_count); count -= song->buffer_size - song->buffered_count; song->write(stream, song->common_buffer, channels * song->buffer_size); song->buffer_pointer = song->common_buffer; song->buffered_count = 0; } if (count>0) { do_compute_data(song, count); song->buffered_count += count; song->buffer_pointer += (song->encoding & PE_MONO) ? count : count*2; } } void Timidity_Start(MidiSong *song) { song->playing = 1; adjust_amplification(song); skip_to(song, 0); } void Timidity_Stop(MidiSong *song) { song->playing = 0; } int Timidity_IsActive(MidiSong *song) { return song->playing; } void Timidity_Seek(MidiSong *song, Uint32 ms) { skip_to(song, (ms * (song->rate / 100)) / 10); } Uint32 Timidity_GetSongLength(MidiSong *song) { MidiEvent *last_event = &song->events[song->groomed_event_count - 1]; /* We want last_event->time * 1000 / song->rate */ Uint32 retvalue = (last_event->time / song->rate) * 1000; retvalue += (last_event->time % song->rate) * 1000 / song->rate; return retvalue; } Uint32 Timidity_GetSongTime(MidiSong *song) { Uint32 retvalue = (song->current_sample / song->rate) * 1000; retvalue += (song->current_sample % song->rate) * 1000 / song->rate; return retvalue; } int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len) { Sint32 start_sample, end_sample, samples; int bytes_per_sample; if (!song->playing) return 0; bytes_per_sample = 1; bytes_per_sample *= ((song->encoding & PE_32BIT) ? 4 : ((song->encoding & PE_16BIT) ? 2 : 1)); bytes_per_sample *= ((song->encoding & PE_MONO) ? 1 : 2); samples = len / bytes_per_sample; start_sample = song->current_sample; end_sample = song->current_sample+samples; while ( song->current_sample < end_sample ) { /* Handle all events that should happen at this time */ while (song->current_event->time <= song->current_sample) { switch(song->current_event->type) { /* Effects affecting a single note */ case ME_NOTEON: if (!(song->current_event->b)) /* Velocity 0? */ note_off(song); else note_on(song); break; case ME_NOTEOFF: note_off(song); break; case ME_KEYPRESSURE: adjust_pressure(song); break; /* Effects affecting a single channel */ case ME_PITCH_SENS: song->channel[song->current_event->channel].pitchsens = song->current_event->a; song->channel[song->current_event->channel].pitchfactor = 0; break; case ME_PITCHWHEEL: song->channel[song->current_event->channel].pitchbend = song->current_event->a + song->current_event->b * 128; song->channel[song->current_event->channel].pitchfactor = 0; /* Adjust pitch for notes already playing */ adjust_pitchbend(song); break; case ME_MAINVOLUME: song->channel[song->current_event->channel].volume = song->current_event->a; adjust_volume(song); break; case ME_PAN: song->channel[song->current_event->channel].panning = song->current_event->a; break; case ME_EXPRESSION: song->channel[song->current_event->channel].expression = song->current_event->a; adjust_volume(song); break; case ME_PROGRAM: if (ISDRUMCHANNEL(song, song->current_event->channel)) { /* Change drum set */ song->channel[song->current_event->channel].bank = song->current_event->a; } else song->channel[song->current_event->channel].program = song->current_event->a; break; case ME_SUSTAIN: song->channel[song->current_event->channel].sustain = song->current_event->a; if (!song->current_event->a) drop_sustain(song); break; case ME_RESET_CONTROLLERS: reset_controllers(song, song->current_event->channel); break; case ME_ALL_NOTES_OFF: all_notes_off(song); break; case ME_ALL_SOUNDS_OFF: all_sounds_off(song); break; case ME_TONE_BANK: song->channel[song->current_event->channel].bank = song->current_event->a; break; case ME_EOT: /* Give the last notes a couple of seconds to decay */ SNDDBG(("Playing time: ~%d seconds\n", song->current_sample/song->rate+2)); SNDDBG(("Notes cut: %d\n", song->cut_notes)); SNDDBG(("Notes lost totally: %d\n", song->lost_notes)); song->playing = 0; return (song->current_sample - start_sample) * bytes_per_sample; } song->current_event++; } if (song->current_event->time > end_sample) compute_data(song, stream, end_sample-song->current_sample); else compute_data(song, stream, song->current_event->time-song->current_sample); } return samples * bytes_per_sample; } void Timidity_SetVolume(MidiSong *song, int volume) { int i; if (volume > MAX_AMPLIFICATION) song->amplification = MAX_AMPLIFICATION; else if (volume < 0) song->amplification = 0; else song->amplification = volume; adjust_amplification(song); for (i = 0; i < song->voices; i++) if (song->voice[i].status != VOICE_FREE) { recompute_amp(song, i); apply_envelope_to_amp(song, i); } } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/playmidi.h000066400000000000000000000023671501405355700250650ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. playmidi.h */ #ifndef TIMIDITY_PLAYMIDI_H #define TIMIDITY_PLAYMIDI_H /* Midi events */ #define ME_NONE 0 #define ME_NOTEON 1 #define ME_NOTEOFF 2 #define ME_KEYPRESSURE 3 #define ME_MAINVOLUME 4 #define ME_PAN 5 #define ME_SUSTAIN 6 #define ME_EXPRESSION 7 #define ME_PITCHWHEEL 8 #define ME_PROGRAM 9 #define ME_TEMPO 10 #define ME_PITCH_SENS 11 #define ME_ALL_SOUNDS_OFF 12 #define ME_RESET_CONTROLLERS 13 #define ME_ALL_NOTES_OFF 14 #define ME_TONE_BANK 15 #define ME_LYRIC 16 #define ME_EOT 99 /* Causes the instrument's default panning to be used. */ #define NO_PANNING -1 /* Voice status options: */ #define VOICE_FREE 0 #define VOICE_ON 1 #define VOICE_SUSTAINED 2 #define VOICE_OFF 3 #define VOICE_DIE 4 /* Voice panned options: */ #define PANNED_MYSTERY 0 #define PANNED_LEFT 1 #define PANNED_RIGHT 2 #define PANNED_CENTER 3 /* Anything but PANNED_MYSTERY only uses the left volume */ #define ISDRUMCHANNEL(s, c) (((s)->drumchannels & (1<<(c)))) #endif /* TIMIDITY_PLAYMIDI_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/readmidi.c000066400000000000000000000417441501405355700250300ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. */ #include #include "options.h" #include "timidity.h" #include "common.h" #include "instrum.h" #include "playmidi.h" #include "readmidi.h" /* Computes how many (fractional) samples one MIDI delta-time unit contains */ static void compute_sample_increment(MidiSong *song, Sint32 tempo, Sint32 divisions) { double a; a = (double) (tempo) * (double) (song->rate) * (65536.0/1000000.0) / (double)(divisions); song->sample_correction = (Sint32)(a) & 0xFFFF; song->sample_increment = (Sint32)(a) >> 16; SNDDBG(("Samples per delta-t: %d (correction %d)", song->sample_increment, song->sample_correction)); } /* Read variable-length number (7 bits per byte, MSB first) */ static Sint32 getvl(SDL_IOStream *io) { Sint32 l=0; Uint8 c; for (;;) { if (!SDL_ReadU8(io, &c)) return l; l += (c & 0x7f); if (!(c & 0x80)) return l; l<<=7; } } #if (defined DEBUG_CHATTER) /* Print a string from the file, followed by a newline. Any non-ASCII or unprintable characters will be converted to periods. */ static int dumpstring(SDL_IOStream *io, Sint32 len, Uint8 type) { static const char *label[] = { "Text event: ", "Text: ", "Copyright: ", "Track name: ", "Instrument: ", "Lyric: ", "Marker: ", "Cue point: " }; signed char *s = SDL_malloc(len+1); if (!s) { SDL_SeekIO(io, len, SDL_IO_SEEK_CUR);/* should I ? */ return -1; } if (SDL_ReadIO(io, s, len) != (size_t)len) { SDL_free(s); return -1; } s[len]='\0'; while (len--) { if (s[len]<32) s[len]='.'; } SNDDBG(("%s%s", label[(type>7) ? 0 : type], s)); SDL_free(s); return 0; } #endif #define MIDIEVENT(at,t,ch,pa,pb) \ newlist = (MidiEventList *) SDL_malloc(sizeof(MidiEventList));\ if (!newlist) {song->oom = 1; return NULL;} \ newlist->event.time = at; \ newlist->event.type = t; \ newlist->event.channel = ch; \ newlist->event.a = pa; \ newlist->event.b = pb; \ newlist->next = NULL; \ return newlist; #define MAGIC_EOT ((MidiEventList *)(-1)) /* Read a MIDI event, returning a freshly allocated element that can be linked to the event list */ static MidiEventList *read_midi_event(MidiSong *song) { static Uint8 laststatus, lastchan; static Uint8 nrpn=0, rpn_msb[16], rpn_lsb[16]; /* one per channel */ Uint8 me, type, a = 0, b = 0, c = 0; Sint32 len; MidiEventList *newlist; for (;;) { song->at += getvl(song->io); if (!SDL_ReadU8(song->io, &me)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } if(me==0xF0 || me == 0xF7) /* SysEx event */ { len=getvl(song->io); SDL_SeekIO(song->io, len, SDL_IO_SEEK_CUR); } else if(me==0xFF) /* Meta event */ { if (!SDL_ReadU8(song->io, &type)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } len=getvl(song->io); if (type>0 && type<16) { #if (defined DEBUG_CHATTER) dumpstring(song->io, len, type); #else SDL_SeekIO(song->io, len, SDL_IO_SEEK_CUR); #endif } else switch(type) { case 0x2F: /* End of Track */ return MAGIC_EOT; case 0x51: /* Tempo */ if (!SDL_ReadU8(song->io, &a) || !SDL_ReadU8(song->io, &b) || !SDL_ReadU8(song->io, &c)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } MIDIEVENT(song->at, ME_TEMPO, c, a, b); default: SNDDBG(("(Meta event type 0x%02x, length %d)\n", type, len)); if (SDL_SeekIO(song->io, len, SDL_IO_SEEK_CUR) < 0) { SNDDBG(("read_midi_event: SDL_IOseek() failure\n")); return NULL; } break; } } else { a=me; if (a & 0x80) /* status byte */ { lastchan=a & 0x0F; laststatus=(a>>4) & 0x07; if (!SDL_ReadU8(song->io, &a)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } a &= 0x7F; } switch(laststatus) { case 0: /* Note off */ if (!SDL_ReadU8(song->io, &b)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } b &= 0x7F; MIDIEVENT(song->at, ME_NOTEOFF, lastchan, a,b); case 1: /* Note on */ if (!SDL_ReadU8(song->io, &b)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } b &= 0x7F; MIDIEVENT(song->at, ME_NOTEON, lastchan, a,b); case 2: /* Key Pressure */ if (!SDL_ReadU8(song->io, &b)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } b &= 0x7F; MIDIEVENT(song->at, ME_KEYPRESSURE, lastchan, a, b); case 3: /* Control change */ if (!SDL_ReadU8(song->io, &b)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } b &= 0x7F; { int control=255; switch(a) { case 7: control=ME_MAINVOLUME; break; case 10: control=ME_PAN; break; case 11: control=ME_EXPRESSION; break; case 64: control=ME_SUSTAIN; b = (b >= 64); break; case 120: control=ME_ALL_SOUNDS_OFF; break; case 121: control=ME_RESET_CONTROLLERS; break; case 123: control=ME_ALL_NOTES_OFF; break; /* These should be the SCC-1 tone bank switch commands. I don't know why there are two, or why the latter only allows switching to bank 0. Also, some MIDI files use 0 as some sort of continuous controller. This will cause lots of warnings about undefined tone banks. */ case 0: control=ME_TONE_BANK; break; case 32: if (b!=0) { SNDDBG(("(Strange: tone bank change 0x%02x)\n", b)); } #if 0 /* `Bank Select LSB' is not worked at GS. Please ignore it. */ else control=ME_TONE_BANK; #endif break; case 100: nrpn=0; rpn_msb[lastchan]=b; break; case 101: nrpn=0; rpn_lsb[lastchan]=b; break; case 99: nrpn=1; rpn_msb[lastchan]=b; break; case 98: nrpn=1; rpn_lsb[lastchan]=b; break; case 6: if (nrpn) { SNDDBG(("(Data entry (MSB) for NRPN %02x,%02x: %d)\n", rpn_msb[lastchan], rpn_lsb[lastchan], b)); break; } switch((rpn_msb[lastchan]<<8) | rpn_lsb[lastchan]) { case 0x0000: /* Pitch bend sensitivity */ control=ME_PITCH_SENS; break; case 0x7F7F: /* RPN reset */ /* reset pitch bend sensitivity to 2 */ MIDIEVENT(song->at, ME_PITCH_SENS, lastchan, 2, 0); default: SNDDBG(("(Data entry (MSB) for RPN %02x,%02x: %d)\n", rpn_msb[lastchan], rpn_lsb[lastchan], b)); break; } break; default: SNDDBG(("(Control %d: %d)\n", a, b)); break; } if (control != 255) { MIDIEVENT(song->at, control, lastchan, b, 0); } } break; case 4: /* Program change */ a &= 0x7f; MIDIEVENT(song->at, ME_PROGRAM, lastchan, a, 0); case 5: /* Channel pressure - NOT IMPLEMENTED */ break; case 6: /* Pitch wheel */ if (!SDL_ReadU8(song->io, &b)) { SNDDBG(("read_midi_event: SDL_IOread() failure\n")); return NULL; } b &= 0x7F; MIDIEVENT(song->at, ME_PITCHWHEEL, lastchan, a, b); default: SNDDBG(("*** Can't happen: status 0x%02X, channel 0x%02X\n", laststatus, lastchan)); break; } } } return newlist; } #undef MIDIEVENT /* Read a midi track into the linked list, either merging with any previous tracks or appending to them. */ static int read_track(MidiSong *song, int append) { MidiEventList *meep; MidiEventList *next, *newlist; Sint32 len; Sint64 next_pos, pos; char tmp[4]; meep = song->evlist; if (append && meep) { /* find the last event in the list */ for (; meep->next; meep=meep->next) ; song->at = meep->event.time; } else song->at=0; /* Check the formalities */ if (SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_ReadIO(song->io, &len, 4) != 4) { SNDDBG(("Can't read track header.\n")); return -1; } len=(Sint32)SDL_Swap32BE((Uint32)len); next_pos = SDL_TellIO(song->io) + len; if (SDL_memcmp(tmp, "MTrk", 4)) { SNDDBG(("Corrupt MIDI file.\n")); return -2; } for (;;) { if (!(newlist=read_midi_event(song))) /* Some kind of error */ return -2; if (newlist==MAGIC_EOT) /* End-of-track Hack. */ { /* If the track ends before the size of the * track data, skip any junk at the end. */ pos = SDL_TellIO(song->io); if (pos < next_pos) SDL_SeekIO(song->io, next_pos - pos, SDL_IO_SEEK_CUR); return 0; } next=meep->next; while (next && (next->event.time < newlist->event.time)) { meep=next; next=meep->next; } newlist->next=next; meep->next=newlist; song->event_count++; /* Count the event. (About one?) */ meep=newlist; } } /* Free the linked event list from memory. */ static void free_midi_list(MidiSong *song) { MidiEventList *meep, *next; meep = song->evlist; while (meep) { next=meep->next; SDL_free(meep); meep=next; } song->evlist = NULL; } /* Allocate an array of MidiEvents and fill it from the linked list of events, marking used instruments for loading. Convert event times to samples: handle tempo changes. Strip unnecessary events from the list. Free the linked list. */ static MidiEvent *groom_list(MidiSong *song, Sint32 divisions,Sint32 *eventsp, Sint32 *samplesp) { MidiEvent *groomed_list, *lp; MidiEventList *meep; Sint32 i, our_event_count, tempo, skip_this_event, new_value; Sint32 sample_cum, samples_to_do, at, st, dt, counting_time; int current_bank[MAXCHAN], current_set[MAXCHAN], current_program[MAXCHAN]; /* Or should each bank have its own current program? */ for (i=0; idefault_program; } tempo=500000; compute_sample_increment(song, tempo, divisions); /* This may allocate a bit more than we need */ groomed_list=lp=SDL_malloc(sizeof(MidiEvent) * (song->event_count+1)); if (!groomed_list) { song->oom=1; free_midi_list(song); return NULL; } meep=song->evlist; our_event_count=0; st=at=sample_cum=0; counting_time=2; /* We strip any silence before the first NOTE ON. */ for (i = 0; i < song->event_count; i++) { skip_this_event=0; if (meep->event.type==ME_TEMPO) { skip_this_event=1; } else if (meep->event.channel >= MAXCHAN) skip_this_event=1; else switch (meep->event.type) { case ME_PROGRAM: if (ISDRUMCHANNEL(song, meep->event.channel)) { if (song->drumset[meep->event.a]) /* Is this a defined drumset? */ new_value=meep->event.a; else { SNDDBG(("Drum set %d is undefined\n", meep->event.a)); new_value=meep->event.a=0; } if (current_set[meep->event.channel] != new_value) current_set[meep->event.channel]=new_value; else skip_this_event=1; } else { new_value=meep->event.a; if ((current_program[meep->event.channel] != SPECIAL_PROGRAM) && (current_program[meep->event.channel] != new_value)) current_program[meep->event.channel] = new_value; else skip_this_event=1; } break; case ME_NOTEON: if (counting_time) counting_time=1; if (ISDRUMCHANNEL(song, meep->event.channel)) { /* Mark this instrument to be loaded */ if (!(song->drumset[current_set[meep->event.channel]] ->instrument[meep->event.a])) song->drumset[current_set[meep->event.channel]] ->instrument[meep->event.a] = MAGIC_LOAD_INSTRUMENT; } else { if (current_program[meep->event.channel]==SPECIAL_PROGRAM) break; /* Mark this instrument to be loaded */ if (!(song->tonebank[current_bank[meep->event.channel]] ->instrument[current_program[meep->event.channel]])) song->tonebank[current_bank[meep->event.channel]] ->instrument[current_program[meep->event.channel]] = MAGIC_LOAD_INSTRUMENT; } break; case ME_TONE_BANK: if (ISDRUMCHANNEL(song, meep->event.channel)) { skip_this_event=1; break; } if (song->tonebank[meep->event.a]) /* Is this a defined tone bank? */ new_value=meep->event.a; else { SNDDBG(("Tone bank %d is undefined\n", meep->event.a)); new_value=meep->event.a=0; } if (current_bank[meep->event.channel]!=new_value) current_bank[meep->event.channel]=new_value; else skip_this_event=1; break; } /* Recompute time in samples*/ if ((dt=meep->event.time - at) && !counting_time) { if (song->sample_increment > 2147483647/dt || song->sample_correction > 2147483647/dt) { goto _overflow; } samples_to_do = song->sample_increment * dt; sample_cum += song->sample_correction * dt; if (sample_cum & 0xFFFF0000) { samples_to_do += ((sample_cum >> 16) & 0xFFFF); sample_cum &= 0x0000FFFF; } if (st >= 2147483647 - samples_to_do) { _overflow: SNDDBG(("Overflow in sample counter\n")); free_midi_list(song); SDL_free(groomed_list); return NULL; } st += samples_to_do; } else if (counting_time==1) counting_time=0; if (meep->event.type==ME_TEMPO) { tempo= meep->event.channel + meep->event.b * 256 + meep->event.a * 65536; compute_sample_increment(song, tempo, divisions); } if (!skip_this_event) { /* Add the event to the list */ *lp=meep->event; lp->time=st; lp++; our_event_count++; } at=meep->event.time; meep=meep->next; } /* Add an End-of-Track event */ lp->time=st; lp->type=ME_EOT; our_event_count++; free_midi_list(song); *eventsp=our_event_count; *samplesp=st; return groomed_list; } MidiEvent *read_midi_file(MidiSong *song, Sint32 *count, Sint32 *sp) { Sint32 len, divisions; Sint16 format = 0, tracks = 0, divisions_tmp = 0; int i; char tmp[4]; song->event_count=0; song->at=0; song->evlist = NULL; if (SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_ReadIO(song->io, &len, 4) != 4) { SNDDBG(("Not a MIDI file!\n")); return NULL; } if (SDL_memcmp(tmp, "RIFF", 4) == 0) { /* RMID ?? */ if (SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_memcmp(tmp, "RMID", 4) != 0 || SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_memcmp(tmp, "data", 4) != 0 || SDL_ReadIO(song->io, tmp, 4) != 4 || /* SMF must begin from here onwards: */ SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_ReadIO(song->io, &len, 4) != 4) { SNDDBG(("Not an RMID file!\n")); return NULL; } } len=(Sint32)SDL_Swap32BE((Uint32)len); if (SDL_memcmp(tmp, "MThd", 4) || len < 6) { SNDDBG(("Not a MIDI file!\n")); return NULL; } format=tracks=divisions_tmp = -1; if (!SDL_ReadS16BE(song->io, &format) || !SDL_ReadS16BE(song->io, &tracks) || !SDL_ReadS16BE(song->io, &divisions_tmp)) { SNDDBG(("Not a MIDI file!\n")); return NULL; } if (divisions_tmp<0) { /* SMPTE time -- totally untested. Got a MIDI file that uses this? */ divisions= (Sint32)(-(divisions_tmp/256)) * (Sint32)(divisions_tmp & 0xFF); } else divisions=(Sint32)(divisions_tmp); if (len > 6) { SNDDBG(("MIDI file header size %u bytes", len)); SDL_SeekIO(song->io, len-6, SDL_IO_SEEK_CUR); /* skip the excess */ } if (format<0 || format >2) { SNDDBG(("Unknown MIDI file format %d\n", format)); return NULL; } if (tracks<1) { SNDDBG(("Bad number of tracks %d\n", tracks)); return NULL; } if (format==0 && tracks!=1) { SNDDBG(("%d tracks with Type-0 MIDI (must be 1.)\n", tracks)); return NULL; } SNDDBG(("Format: %d Tracks: %d Divisions: %d\n", format, tracks, divisions)); /* Put a do-nothing event first in the list for easier processing */ song->evlist=SDL_calloc(1, sizeof(MidiEventList)); if (!song->evlist) { song->oom=1; return NULL; } song->event_count++; switch(format) { case 0: if (read_track(song, 0)) { free_midi_list(song); return NULL; } break; case 1: for (i=0; i This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. readmidi.h */ #ifndef TIMIDITY_READMIDI_H #define TIMIDITY_READMIDI_H #define read_midi_file TIMI_NAMESPACE(read_midi_file) extern MidiEvent *read_midi_file(MidiSong *song, Sint32 *count, Sint32 *sp); #endif /* TIMIDITY_READMIDI_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/resample.c000066400000000000000000000344341501405355700250600ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. resample.c */ #include #include "timidity.h" #include "options.h" #include "common.h" #include "instrum.h" #include "playmidi.h" #include "tables.h" #include "resample.h" #define PRECALC_LOOP_COUNT(start, end, incr) (((end) - (start) + (incr) - 1) / (incr)) /*************** resampling with fixed increment *****************/ static sample_t *rs_plain(MidiSong *song, int v, Sint32 *countptr) { /* Play sample until end, then free the voice. */ sample_t v1, v2; Voice *vp=&(song->voice[v]); sample_t *dest=song->resample_buffer, *src=vp->sample->data; Sint32 ofs=vp->sample_offset, incr=vp->sample_increment, le=vp->sample->data_length, count=*countptr; Sint32 i, j; if (incr<0) incr = -incr; /* In case we're coming out of a bidir loop */ /* Precalc how many times we should go through the loop. NOTE: Assumes that incr > 0 and that ofs <= le */ i = PRECALC_LOOP_COUNT(ofs, le, incr); if (i > count) { i = count; count = 0; } else count -= i; for (j = 0; j < i; j++) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } if (ofs >= le) { if (ofs == le) *dest++ = src[(ofs>>FRACTION_BITS)-1]/2; vp->status=VOICE_FREE; *countptr-=count+1; } vp->sample_offset=ofs; /* Update offset */ return song->resample_buffer; } static sample_t *rs_loop(MidiSong *song, Voice *vp, Sint32 count) { /* Play sample until end-of-loop, skip back and continue. */ sample_t v1, v2; Sint32 ofs=vp->sample_offset, incr=vp->sample_increment, le=vp->sample->loop_end, ll=le - vp->sample->loop_start; sample_t *dest=song->resample_buffer, *src=vp->sample->data; Sint32 i, j; while (count) { while (ofs >= le) ofs -= ll; /* Precalc how many times we should go through the loop */ i = PRECALC_LOOP_COUNT(ofs, le, incr); if (i > count) { i = count; count = 0; } else count -= i; for (j = 0; j < i; j++) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } } vp->sample_offset=ofs; /* Update offset */ return song->resample_buffer; } static sample_t *rs_bidir(MidiSong *song, Voice *vp, Sint32 count) { sample_t v1, v2; Sint32 ofs=vp->sample_offset, incr=vp->sample_increment, le=vp->sample->loop_end, ls=vp->sample->loop_start; sample_t *dest=song->resample_buffer, *src=vp->sample->data; Sint32 le2 = le<<1, ls2 = ls<<1, i, j; /* Play normally until inside the loop region */ if (incr > 0 && ofs < ls) { /* NOTE: Assumes that incr > 0, which is NOT always the case when doing bidirectional looping. I have yet to see a case where both ofs <= ls AND incr < 0, however. */ i = PRECALC_LOOP_COUNT(ofs, ls, incr); if (i > count) { i = count; count = 0; } else count -= i; for (j = 0; j < i; j++) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } } /* Then do the bidirectional looping */ while(count) { /* Precalc how many times we should go through the loop */ i = PRECALC_LOOP_COUNT(ofs, incr > 0 ? le : ls, incr); if (i > count) { i = count; count = 0; } else count -= i; for (j = 0; j < i; j++) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } if (ofs>=le) { /* fold the overshoot back in */ ofs = le2 - ofs; incr *= -1; } else if (ofs <= ls) { ofs = ls2 - ofs; incr *= -1; } } vp->sample_increment=incr; vp->sample_offset=ofs; /* Update offset */ return song->resample_buffer; } /*********************** vibrato versions ***************************/ /* We only need to compute one half of the vibrato sine cycle */ static int vib_phase_to_inc_ptr(int phase) { if (phase < VIBRATO_SAMPLE_INCREMENTS/2) return VIBRATO_SAMPLE_INCREMENTS/2-1-phase; else if (phase >= 3*VIBRATO_SAMPLE_INCREMENTS/2) return 5*VIBRATO_SAMPLE_INCREMENTS/2-1-phase; else return phase-VIBRATO_SAMPLE_INCREMENTS/2; } static Sint32 update_vibrato(MidiSong *song, Voice *vp, int sign) { Sint32 depth; int phase, pb; double a; if (vp->vibrato_phase++ >= 2*VIBRATO_SAMPLE_INCREMENTS-1) vp->vibrato_phase=0; phase=vib_phase_to_inc_ptr(vp->vibrato_phase); if (vp->vibrato_sample_increment[phase]) { if (sign) return -vp->vibrato_sample_increment[phase]; else return vp->vibrato_sample_increment[phase]; } /* Need to compute this sample increment. */ depth=vp->sample->vibrato_depth<<7; if (vp->vibrato_sweep) { /* Need to update sweep */ vp->vibrato_sweep_position += vp->vibrato_sweep; if (vp->vibrato_sweep_position >= (1<vibrato_sweep=0; else { /* Adjust depth */ depth *= vp->vibrato_sweep_position; depth >>= SWEEP_SHIFT; } } a = TIM_FSCALE(((double)(vp->sample->sample_rate) * (double)(vp->frequency)) / ((double)(vp->sample->root_freq) * (double)(song->rate)), FRACTION_BITS); pb=(int)((timi_sine(vp->vibrato_phase * (SINE_CYCLE_LENGTH/(2*VIBRATO_SAMPLE_INCREMENTS))) * (double)(depth) * VIBRATO_AMPLITUDE_TUNING)); if (pb<0) { pb=-pb; a /= bend_fine[(pb>>5) & 0xFF] * bend_coarse[pb>>13]; } else a *= bend_fine[(pb>>5) & 0xFF] * bend_coarse[pb>>13]; /* If the sweep's over, we can store the newly computed sample_increment */ if (!vp->vibrato_sweep) vp->vibrato_sample_increment[phase]=(Sint32) a; if (sign) a = -a; /* need to preserve the loop direction */ return (Sint32) a; } static sample_t *rs_vib_plain(MidiSong *song, int v, Sint32 *countptr) { /* Play sample until end, then free the voice. */ sample_t v1, v2; Voice *vp=&(song->voice[v]); sample_t *dest=song->resample_buffer, *src=vp->sample->data; Sint32 le=vp->sample->data_length, ofs=vp->sample_offset, incr=vp->sample_increment, count=*countptr; int cc=vp->vibrato_control_counter; /* This has never been tested */ if (incr<0) incr = -incr; /* In case we're coming out of a bidir loop */ while (count--) { if (!cc--) { cc=vp->vibrato_control_ratio; incr=update_vibrato(song, vp, 0); } v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; if (ofs >= le) { if (ofs == le) *dest++ = src[(ofs>>FRACTION_BITS)-1]/2; vp->status=VOICE_FREE; *countptr-=count+1; break; } } vp->vibrato_control_counter=cc; vp->sample_increment=incr; vp->sample_offset=ofs; /* Update offset */ return song->resample_buffer; } static sample_t *rs_vib_loop(MidiSong *song, Voice *vp, Sint32 count) { /* Play sample until end-of-loop, skip back and continue. */ sample_t v1, v2; Sint32 ofs=vp->sample_offset, incr=vp->sample_increment, le=vp->sample->loop_end, ll=le - vp->sample->loop_start; sample_t *dest=song->resample_buffer, *src=vp->sample->data; int cc=vp->vibrato_control_counter; Sint32 i, j; int vibflag=0; while (count) { /* Hopefully the loop is longer than an increment */ while(ofs >= le) ofs -= ll; /* Precalc how many times to go through the loop, taking the vibrato control ratio into account this time. */ i = PRECALC_LOOP_COUNT(ofs, le, incr); if(i > count) i = count; if(i > cc) { i = cc; vibflag = 1; } else cc -= i; count -= i; for (j = 0; j < i; j++) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } if(vibflag) { cc = vp->vibrato_control_ratio; incr = update_vibrato(song, vp, 0); vibflag = 0; } } vp->vibrato_control_counter=cc; vp->sample_increment=incr; vp->sample_offset=ofs; /* Update offset */ return song->resample_buffer; } static sample_t *rs_vib_bidir(MidiSong *song, Voice *vp, Sint32 count) { sample_t v1, v2; Sint32 ofs=vp->sample_offset, incr=vp->sample_increment, le=vp->sample->loop_end, ls=vp->sample->loop_start; sample_t *dest=song->resample_buffer, *src=vp->sample->data; int cc=vp->vibrato_control_counter; Sint32 le2=le<<1, ls2=ls<<1, i, j; int vibflag = 0; /* Play normally until inside the loop region */ while (count && incr > 0 && ofs < ls) { i = PRECALC_LOOP_COUNT(ofs, ls, incr); if (i > count) i = count; if (i > cc) { i = cc; vibflag = 1; } else cc -= i; count -= i; for (j = 0; j < i; j++) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } if (vibflag) { cc = vp->vibrato_control_ratio; incr = update_vibrato(song, vp, 0); vibflag = 0; } } /* Then do the bidirectional looping */ while (count) { /* Precalc how many times we should go through the loop */ i = PRECALC_LOOP_COUNT(ofs, incr > 0 ? le : ls, incr); if(i > count) i = count; if(i > cc) { i = cc; vibflag = 1; } else cc -= i; count -= i; while (i--) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS)+1]; *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); ofs += incr; } if (vibflag) { cc = vp->vibrato_control_ratio; incr = update_vibrato(song, vp, (incr < 0)); vibflag = 0; } if (ofs >= le) { /* fold the overshoot back in */ ofs = le2 - ofs; incr *= -1; } else if (ofs <= ls) { ofs = ls2 - ofs; incr *= -1; } } vp->vibrato_control_counter=cc; vp->sample_increment=incr; vp->sample_offset=ofs; /* Update offset */ return song->resample_buffer; } sample_t *resample_voice(MidiSong *song, int v, Sint32 *countptr) { Sint32 ofs; Uint8 modes; Voice *vp=&(song->voice[v]); if (!(vp->sample->sample_rate)) { /* Pre-resampled data -- just update the offset and check if we're out of data. */ ofs=vp->sample_offset >> FRACTION_BITS; /* Kind of silly to use FRACTION_BITS here... */ if (*countptr >= (vp->sample->data_length>>FRACTION_BITS) - ofs) { /* Note finished. Free the voice. */ vp->status = VOICE_FREE; /* Let the caller know how much data we had left */ *countptr = (vp->sample->data_length>>FRACTION_BITS) - ofs; } else vp->sample_offset += *countptr << FRACTION_BITS; return vp->sample->data+ofs; } /* Need to resample. Use the proper function. */ modes=vp->sample->modes; if (vp->vibrato_control_ratio) { if ((modes & MODES_LOOPING) && ((modes & MODES_ENVELOPE) || (vp->status==VOICE_ON || vp->status==VOICE_SUSTAINED))) { if (modes & MODES_PINGPONG) return rs_vib_bidir(song, vp, *countptr); else return rs_vib_loop(song, vp, *countptr); } else return rs_vib_plain(song, v, countptr); } else { if ((modes & MODES_LOOPING) && ((modes & MODES_ENVELOPE) || (vp->status==VOICE_ON || vp->status==VOICE_SUSTAINED))) { if (modes & MODES_PINGPONG) return rs_bidir(song, vp, *countptr); else return rs_loop(song, vp, *countptr); } else return rs_plain(song, v, countptr); } } void pre_resample(MidiSong *song, Sample *sp) { double a, xdiff; Sint32 incr, ofs, newlen, count; Sint16 *newdata, *dest, *src = (Sint16 *) sp->data, *vptr; Sint32 v, v1, v2, v3, v4, v5, i; #ifdef DEBUG_CHATTER static const char note_name[12][3] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; SNDDBG((" * pre-resampling for note %d (%s%d)\n", sp->note_to_use, note_name[sp->note_to_use % 12], (sp->note_to_use & 0x7F) / 12)); #endif a = ((double) (sp->root_freq) * song->rate) / ((double) (sp->sample_rate) * freq_table[(int) (sp->note_to_use)]); if(sp->data_length * a >= 0x7fffffffL) { /* Too large to compute */ SNDDBG((" *** Can't pre-resampling for note %d\n", sp->note_to_use)); return; } newlen = (Sint32)(sp->data_length * a); count = (newlen >> FRACTION_BITS) - 1; ofs = incr = (sp->data_length - (1 << FRACTION_BITS)) / count; if((double)newlen + incr >= 0x7fffffffL) { /* Too large to compute */ SNDDBG((" *** Can't pre-resampling for note %d\n", sp->note_to_use)); return; } dest = newdata = (Sint16 *) SDL_malloc((newlen >> (FRACTION_BITS - 1)) + 2); if (!dest) { song->oom = 1; return; } if (--count) *dest++ = src[0]; /* Since we're pre-processing and this doesn't have to be done in real-time, we go ahead and do the full sliding cubic interpolation. */ count--; for(i = 0; i < count; i++) { vptr = src + (ofs >> FRACTION_BITS); v1 = ((vptr>=src+1)? *(vptr - 1):0); v2 = *vptr; v3 = *(vptr + 1); v4 = *(vptr + 2); v5 = v2 - v3; xdiff = TIM_FSCALENEG(ofs & FRACTION_MASK, FRACTION_BITS); v = (Sint32)(v2 + xdiff * (1.0/6.0) * (3 * (v3 - v5) - 2 * v1 - v4 + xdiff * (3 * (v1 - v2 - v5) + xdiff * (3 * v5 + v4 - v1)))); *dest++ = (Sint16)((v > 32767) ? 32767 : ((v < -32768) ? -32768 : v)); ofs += incr; } if (ofs & FRACTION_MASK) { v1 = src[ofs >> FRACTION_BITS]; v2 = src[(ofs >> FRACTION_BITS) + 1]; *dest++ = (Sint16)(v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS)); } else *dest++ = src[ofs >> FRACTION_BITS]; *dest = *(dest - 1) / 2; ++dest; *dest = *(dest - 1) / 2; sp->data_length = newlen; sp->loop_start = (Sint32)(sp->loop_start * a); sp->loop_end = (Sint32)(sp->loop_end * a); SDL_free(sp->data); sp->data = (sample_t *) newdata; sp->sample_rate = 0; } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/resample.h000066400000000000000000000011401501405355700250510ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. resample.h */ #ifndef TIMIDITY_RESAMPLE_H #define TIMIDITY_RESAMPLE_H #define resample_voice TIMI_NAMESPACE(resample_voice) #define pre_resample TIMI_NAMESPACE(pre_resample) extern sample_t *resample_voice(MidiSong *song, int v, Sint32 *countptr); extern void pre_resample(MidiSong *song, Sample *sp); #endif /* TIMIDITY_RESAMPLE_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/tables.c000066400000000000000000000271141501405355700245170ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. */ #include #include "common.h" #include "tables.h" const Sint32 freq_table[128] = { 8176, 8662, 9177, 9723, 10301, 10913, 11562, 12250, 12978, 13750, 14568, 15434, 16352, 17324, 18354, 19445, 20602, 21827, 23125, 24500, 25957, 27500, 29135, 30868, 32703, 34648, 36708, 38891, 41203, 43654, 46249, 48999, 51913, 55000, 58270, 61735, 65406, 69296, 73416, 77782, 82407, 87307, 92499, 97999, 103826, 110000, 116541, 123471, 130813, 138591, 146832, 155563, 164814, 174614, 184997, 195998, 207652, 220000, 233082, 246942, 261626, 277183, 293665, 311127, 329628, 349228, 369994, 391995, 415305, 440000, 466164, 493883, 523251, 554365, 587330, 622254, 659255, 698456, 739989, 783991, 830609, 880000, 932328, 987767, 1046502, 1108731, 1174659, 1244508, 1318510, 1396913, 1479978, 1567982, 1661219, 1760000, 1864655, 1975533, 2093005, 2217461, 2349318, 2489016, 2637020, 2793826, 2959955, 3135963, 3322438, 3520000, 3729310, 3951066, 4186009, 4434922, 4698636, 4978032, 5274041, 5587652, 5919911, 6271927, 6644875, 7040000, 7458620, 7902133, 8372018, 8869844, 9397273, 9956063, 10548082, 11175303, 11839822, 12543854 }; /* v=2.^((x/127-1) * 6) */ const double vol_table[128] = { 0.015625, 0.016145143728351113, 0.016682602624583379, 0.017237953096759438, 0.017811790741104401, 0.01840473098076444, 0.019017409725829021, 0.019650484055324921, 0.020304632921913132, 0.020980557880044631, 0.021678983838355849, 0.02240065983711079, 0.023146359851523596, 0.023916883621822989, 0.024713057510949051, 0.025535735390801884, 0.026385799557992876, 0.027264161680080529, 0.028171763773305786, 0.029109579212875332, 0.030078613776876421, 0.031079906724942836, 0.032114531912828696, 0.033183598944085631, 0.034288254360078256, 0.035429682869614412, 0.036609108619508737, 0.037827796507442342, 0.039087053538526394, 0.040388230227024875, 0.041732722044739302, 0.043121970917609151, 0.044557466772132896, 0.046040749133268132, 0.047573408775524545, 0.049157089429020417, 0.050793489542332405, 0.05248436410402918, 0.054231526524842463, 0.056036850582493913, 0.057902272431264008, 0.059829792678457581, 0.061821478529993396, 0.063879466007418645, 0.066005962238725971, 0.068203247825430205, 0.070473679288442961, 0.072819691595368496, 0.075243800771931268, 0.077748606600335793, 0.080336795407452768, 0.083011142945821612, 0.085774517370559328, 0.088629882315368294, 0.091580300070941839, 0.094628934869176312, 0.097779056276712184, 0.10103404270144323, 0.1043973850157546, 0.1078726903003755, 0.11146368571286204, 0.11517422248485852, 0.11900828005242428, 0.12296997032385605, 0.12706354208958254, 0.13129338557886089, 0.13566403716816194, 0.14018018424629392, 0.14484667024148207, 0.14966849981579558, 0.15465084423249356, 0.15979904690204472, 0.16511862911277009, 0.17061529595225433, 0.17629494242587571, 0.18216365977901747, 0.18822774202974024, 0.19449369271892172, 0.20096823188510385, 0.20765830327152621, 0.21457108177307616, 0.22171398113114205, 0.2290946618846218, 0.23672103958561411, 0.2446012932886038, 0.25274387432224471, 0.26115751535314891, 0.26985123975140174, 0.27883437126784744, 0.28811654403352405, 0.29770771289197112, 0.30761816407549192, 0.31785852623682015, 0.32843978184802081, 0.33937327897885317, 0.3506707434672246, 0.36234429149478936, 0.37440644258117928, 0.38687013301080181, 0.39974872970660535, 0.41305604456569134, 0.42680634927214656, 0.44101439060298442, 0.45569540624360722, 0.47086514112975281, 0.48653986433345225, 0.50273638651110641, 0.51947207793239625, 0.53676488710936021, 0.55463336004561792, 0.57309666012638816, 0.59217458867062556, 0.61188760616732485, 0.63225685421876243, 0.65330417821421161, 0.67505215075844849, 0.69752409588017272, 0.72074411404630734, 0.74473710800900605, 0.76952880951308478, 0.79514580689252357, 0.82161557358563286, 0.84896649759946774, 0.87722791195508854, 0.90643012614631979, 0.93660445864574493, 0.96778327049280244, 1 }; const double bend_fine[256] = { 1, 1.0002256593050698, 1.0004513695322617, 1.0006771306930664, 1.0009029427989777, 1.0011288058614922, 1.0013547198921082, 1.0015806849023274, 1.0018067009036538, 1.002032767907594, 1.0022588859256572, 1.0024850549693551, 1.0027112750502025, 1.0029375461797159, 1.0031638683694153, 1.0033902416308227, 1.0036166659754628, 1.0038431414148634, 1.0040696679605541, 1.0042962456240678, 1.0045228744169397, 1.0047495543507072, 1.0049762854369111, 1.0052030676870944, 1.0054299011128027, 1.0056567857255843, 1.00588372153699, 1.006110708558573, 1.0063377468018897, 1.0065648362784985, 1.0067919769999607, 1.0070191689778405, 1.0072464122237039, 1.0074737067491204, 1.0077010525656616, 1.0079284496849015, 1.0081558981184175, 1.008383397877789, 1.008610948974598, 1.0088385514204294, 1.0090662052268706, 1.0092939104055114, 1.0095216669679448, 1.0097494749257656, 1.009977334290572, 1.0102052450739643, 1.0104332072875455, 1.0106612209429215, 1.0108892860517005, 1.0111174026254934, 1.0113455706759138, 1.0115737902145781, 1.0118020612531047, 1.0120303838031153, 1.0122587578762337, 1.012487183484087, 1.0127156606383041, 1.0129441893505169, 1.0131727696323602, 1.0134014014954713, 1.0136300849514894, 1.0138588200120575, 1.0140876066888203, 1.0143164449934257, 1.0145453349375237, 1.0147742765327674, 1.0150032697908125, 1.0152323147233171, 1.015461411341942, 1.0156905596583505, 1.0159197596842091, 1.0161490114311862, 1.0163783149109531, 1.0166076701351838, 1.0168370771155553, 1.0170665358637463, 1.0172960463914391, 1.0175256087103179, 1.0177552228320703, 1.0179848887683858, 1.0182146065309567, 1.0184443761314785, 1.0186741975816487, 1.0189040708931674, 1.0191339960777379, 1.0193639731470658, 1.0195940021128593, 1.0198240829868295, 1.0200542157806898, 1.0202844005061564, 1.0205146371749483, 1.0207449257987866, 1.0209752663893958, 1.0212056589585028, 1.0214361035178368, 1.0216666000791297, 1.0218971486541166, 1.0221277492545349, 1.0223584018921241, 1.0225891065786274, 1.0228198633257899, 1.0230506721453596, 1.023281533049087, 1.0235124460487257, 1.0237434111560313, 1.0239744283827625, 1.0242054977406807, 1.0244366192415495, 1.0246677928971357, 1.0248990187192082, 1.025130296719539, 1.0253616269099028, 1.0255930093020766, 1.0258244439078401, 1.0260559307389761, 1.0262874698072693, 1.0265190611245079, 1.0267507047024822, 1.0269824005529853, 1.027214148687813, 1.0274459491187637, 1.0276778018576387, 1.0279097069162415, 1.0281416643063788, 1.0283736740398595, 1.0286057361284953, 1.0288378505841009, 1.0290700174184932, 1.0293022366434921, 1.0295345082709197, 1.0297668323126017, 1.0299992087803651, 1.030231637686041, 1.0304641190414621, 1.0306966528584645, 1.0309292391488862, 1.0311618779245688, 1.0313945691973556, 1.0316273129790936, 1.0318601092816313, 1.0320929581168212, 1.0323258594965172, 1.0325588134325767, 1.0327918199368598, 1.0330248790212284, 1.0332579906975481, 1.0334911549776868, 1.033724371873515, 1.0339576413969056, 1.0341909635597348, 1.0344243383738811, 1.0346577658512259, 1.034891246003653, 1.0351247788430489, 1.0353583643813031, 1.0355920026303078, 1.0358256936019572, 1.0360594373081489, 1.0362932337607829, 1.0365270829717617, 1.0367609849529913, 1.0369949397163791, 1.0372289472738365, 1.0374630076372766, 1.0376971208186156, 1.0379312868297725, 1.0381655056826686, 1.0383997773892284, 1.0386341019613787, 1.0388684794110492, 1.0391029097501721, 1.0393373929906822, 1.0395719291445176, 1.0398065182236185, 1.0400411602399278, 1.0402758552053915, 1.0405106031319582, 1.0407454040315787, 1.0409802579162071, 1.0412151647977996, 1.0414501246883161, 1.0416851375997183, 1.0419202035439705, 1.0421553225330404, 1.042390494578898, 1.042625719693516, 1.0428609978888699, 1.043096329176938, 1.0433317135697009, 1.0435671510791424, 1.0438026417172486, 1.0440381854960086, 1.0442737824274138, 1.044509432523459, 1.044745135796141, 1.0449808922574599, 1.0452167019194181, 1.0454525647940205, 1.0456884808932754, 1.0459244502291931, 1.0461604728137874, 1.0463965486590741, 1.046632677777072, 1.0468688601798024, 1.0471050958792898, 1.047341384887561, 1.0475777272166455, 1.047814122878576, 1.048050571885387, 1.0482870742491166, 1.0485236299818055, 1.0487602390954964, 1.0489969016022356, 1.0492336175140715, 1.0494703868430555, 1.0497072096012419, 1.0499440858006872, 1.0501810154534512, 1.050417998571596, 1.0506550351671864, 1.0508921252522903, 1.0511292688389782, 1.0513664659393229, 1.0516037165654004, 1.0518410207292894, 1.0520783784430709, 1.0523157897188296, 1.0525532545686513, 1.0527907730046264, 1.0530283450388465, 1.0532659706834067, 1.0535036499504049, 1.0537413828519411, 1.0539791694001188, 1.0542170096070436, 1.0544549034848243, 1.0546928510455722, 1.0549308523014012, 1.0551689072644284, 1.0554070159467728, 1.0556451783605572, 1.0558833945179062, 1.0561216644309479, 1.0563599881118126, 1.0565983655726334, 1.0568367968255465, 1.0570752818826903, 1.0573138207562065, 1.057552413458239, 1.0577910600009348, 1.0580297603964437, 1.058268514656918, 1.0585073227945128, 1.0587461848213857, 1.058985100749698, 1.0592240705916123 }; const double bend_coarse[128] = { 1, 1.0594630943592953, 1.122462048309373, 1.189207115002721, 1.2599210498948732, 1.3348398541700344, 1.4142135623730951, 1.4983070768766815, 1.5874010519681994, 1.681792830507429, 1.7817974362806785, 1.8877486253633868, 2, 2.1189261887185906, 2.244924096618746, 2.3784142300054421, 2.5198420997897464, 2.6696797083400687, 2.8284271247461903, 2.996614153753363, 3.1748021039363992, 3.363585661014858, 3.5635948725613571, 3.7754972507267741, 4, 4.2378523774371812, 4.4898481932374912, 4.7568284600108841, 5.0396841995794928, 5.3393594166801366, 5.6568542494923806, 5.993228307506727, 6.3496042078727974, 6.727171322029716, 7.1271897451227151, 7.5509945014535473, 8, 8.4757047548743625, 8.9796963864749824, 9.5136569200217682, 10.079368399158986, 10.678718833360273, 11.313708498984761, 11.986456615013454, 12.699208415745595, 13.454342644059432, 14.25437949024543, 15.101989002907095, 16, 16.951409509748721, 17.959392772949972, 19.027313840043536, 20.158736798317967, 21.357437666720553, 22.627416997969522, 23.972913230026901, 25.398416831491197, 26.908685288118864, 28.508758980490853, 30.203978005814196, 32, 33.902819019497443, 35.918785545899944, 38.054627680087073, 40.317473596635935, 42.714875333441107, 45.254833995939045, 47.945826460053802, 50.796833662982394, 53.817370576237728, 57.017517960981706, 60.407956011628393, 64, 67.805638038994886, 71.837571091799887, 76.109255360174146, 80.63494719327187, 85.429750666882214, 90.509667991878089, 95.891652920107603, 101.59366732596479, 107.63474115247546, 114.03503592196341, 120.81591202325679, 128, 135.61127607798977, 143.67514218359977, 152.21851072034829, 161.26989438654374, 170.85950133376443, 181.01933598375618, 191.78330584021521, 203.18733465192958, 215.26948230495091, 228.07007184392683, 241.63182404651357, 256, 271.22255215597971, 287.35028436719938, 304.43702144069658, 322.53978877308765, 341.71900266752868, 362.03867196751236, 383.56661168043064, 406.37466930385892, 430.53896460990183, 456.14014368785394, 483.26364809302686, 512, 542.44510431195943, 574.70056873439876, 608.87404288139317, 645.0795775461753, 683.43800533505737, 724.07734393502471, 767.13322336086128, 812.74933860771785, 861.07792921980365, 912.28028737570787, 966.52729618605372, 1024, 1084.8902086239189, 1149.4011374687975, 1217.7480857627863, 1290.1591550923506, 1366.8760106701147, 1448.1546878700494, 1534.2664467217226 }; libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/tables.h000066400000000000000000000014021501405355700245140ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. tables.h */ #ifndef TIMIDITY_TABLES_H #define TIMIDITY_TABLES_H #define timi_sine(x) (SDL_sin((2*PI/1024.0) * (x))) #define SINE_CYCLE_LENGTH 1024 #define freq_table TIMI_NAMESPACE(freq_table) #define vol_table TIMI_NAMESPACE(vol_table) #define bend_fine TIMI_NAMESPACE(bend_fine) #define bend_coarse TIMI_NAMESPACE(bend_coarse) extern const Sint32 freq_table[]; extern const double vol_table[]; extern const double bend_fine[]; extern const double bend_coarse[]; #endif /* TIMIDITY_TABLES_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/timidity.c000066400000000000000000000433501501405355700251010ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. */ #include #include "timidity.h" #include "options.h" #include "common.h" #include "instrum.h" #include "playmidi.h" #include "readmidi.h" #include "output.h" #include "tables.h" static ToneBank *master_tonebank[MAXBANK], *master_drumset[MAXBANK]; static char def_instr_name[256] = ""; #define MAXWORDS 10 #define MAX_RCFCOUNT 50 /* Quick-and-dirty fgets() replacement. */ static char *IOgets(SDL_IOStream *io, char *s, int size) { int num_read = 0; char *p = s; --size;/* so that we nul terminate properly */ for (; num_read < size; ++p) { if (SDL_ReadIO(io, p, 1) != 1) break; num_read++; /* Unlike fgets(), don't store newline. Under Windows/DOS we'll * probably get an extra blank line for every line that's being * read, but that should be ok. */ if (*p == '\n' || *p == '\r') { *p = '\0'; return s; } } *p = '\0'; return (num_read != 0) ? s : NULL; } static int read_config_file(const char *name, int rcf_count) { SDL_IOStream *io; char tmp[1024]; char *w[MAXWORDS], *cp; char *endp; ToneBank *bank; int i, j, k, r, words; #ifdef DEBUG_CHATTER int line; #endif if (rcf_count >= MAX_RCFCOUNT) { SNDDBG(("Probable source loop in configuration files\n")); return -1; } if (!(io=timi_openfile(name))) return -1; bank = NULL; #ifdef DEBUG_CHATTER line = 0; #endif r = -1; /* start by assuming failure, */ while (IOgets(io, tmp, sizeof(tmp))) { #ifdef DEBUG_CHATTER line++; #endif words=0; w[0]=SDL_strtok_r(tmp, " \t\240", &endp); if (!w[0]) continue; /* Originally the TiMidity++ extensions were prefixed like this */ if (SDL_strcmp(w[0], "#extension") == 0) { w[0]=SDL_strtok_r(0, " \t\240", &endp); if (!w[0]) continue; } if (*w[0] == '#') continue; while (words < MAXWORDS - 1) /* -1 : next arg */ { while (*endp == ' ' || *endp == '\t' || *endp == '\240') endp++; if (*endp == '\0' || *endp == '#') break; if (*endp == '"' || *endp == '\'') { /* quoted string */ char *terminator = SDL_strchr(endp + 1, *endp); if (terminator != NULL) { /* terminated */ if (terminator[1] == ' ' || terminator[1] == '\t' || terminator[1] == '\240' || terminator[1] == '\0') { char *extraQuote = SDL_strchr(endp + 1, *endp == '"' ? '\'' : '"'); if (extraQuote != NULL && extraQuote < terminator) { SNDDBG(("%s: line %d: Quote characters are not allowed inside a quoted string", name, line)); goto fail; } w[++words] = endp + 1; endp = terminator + 1; *terminator = '\0'; } else { /* no space after quoted string */ SNDDBG(("%s: line %d: There must be at least one whitespace between string terminator (%c) and the next parameter", name, line, *endp)); goto fail; } } else { /* not terminated */ SNDDBG(("%s: line %d: The quoted string is not terminated", name, line)); goto fail; } } else { /* not quoted string */ w[++words] = endp; while (!(*endp == ' ' || *endp == '\t' || *endp == '\240' || *endp == '\0')) { if (*endp == '"' || *endp == '\'') { /* no space before quoted string */ SNDDBG(("%s: line %d: There must be at least one whitespace between previous parameter and a beginning of the quoted string (%c)", name, line, *endp)); goto fail; } endp++; } if (*endp != '\0') { /* unless at the end-of-string (i.e. EOF) */ *endp = '\0'; /* terminate the token */ endp++; } } } w[++words] = NULL; /* TiMidity++ adds a number of extensions to the config file format. * Many of them are completely irrelevant to SDL_sound, but at least * we shouldn't choke on them. * * Unfortunately the documentation for these extensions is often quite * vague, gramatically strange or completely absent. */ if (!SDL_strcmp(w[0], "comm") /* "comm" program second */ || !SDL_strcmp(w[0], "HTTPproxy") /* "HTTPproxy" hostname:port */ || !SDL_strcmp(w[0], "FTPproxy") /* "FTPproxy" hostname:port */ || !SDL_strcmp(w[0], "mailaddr") /* "mailaddr" your-mail-address */ || !SDL_strcmp(w[0], "opt") /* "opt" timidity-options */ ) { /* + "comm" sets some kind of comment -- the documentation is too * vague for me to understand at this time. * + "HTTPproxy", "FTPproxy" and "mailaddr" are for reading data * over a network, rather than from the file system. * + "opt" specifies default options for TiMidity++. * * Quite useless for us, so they can safely remain no-ops. */ } else if (!SDL_strcmp(w[0], "timeout")) /* "timeout" program second */ { /* Specifies a timeout value of the program. A number of seconds * before TiMidity kills the note. No urgent need for it. */ SNDDBG(("FIXME: Implement \"timeout\" in TiMidity config.\n")); } else if (!SDL_strcmp(w[0], "copydrumset") /* "copydrumset" drumset */ || !SDL_strcmp(w[0], "copybank")) /* "copybank" bank */ { /* Copies all the settings of the specified drumset or bank to * the current drumset or bank. May be useful later, but not a * high priority. */ SNDDBG(("FIXME: Implement \"%s\" in TiMidity config.\n", w[0])); } else if (!SDL_strcmp(w[0], "undef")) /* "undef" progno */ { /* Undefines the tone "progno" of the current tone bank (or * drum set?). Not a high priority. */ SNDDBG(("FIXME: Implement \"undef\" in TiMidity config.\n")); } else if (!SDL_strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */ { /* Sets the alternate assign for drum set. Whatever that's * supposed to mean. */ SNDDBG(("FIXME: Implement \"altassign\" in TiMidity config.\n")); } else if (!SDL_strcmp(w[0], "soundfont") || !SDL_strcmp(w[0], "font")) { /* "soundfont" sf_file "remove" * "soundfont" sf_file ["order=" order] ["cutoff=" cutoff] * ["reso=" reso] ["amp=" amp] * "font" "exclude" bank preset keynote * "font" "order" order bank preset keynote */ SNDDBG(("FIXME: Implmement \"%s\" in TiMidity config.\n", w[0])); } else if (!SDL_strcmp(w[0], "progbase")) { /* The documentation for this makes absolutely no sense to me, but * apparently it sets some sort of base offset for tone numbers. */ SNDDBG(("FIXME: Implement \"progbase\" in TiMidity config.\n")); } else if (!SDL_strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */ { /* This one is used by the "eawpats". Looks like it's used * for remapping one instrument to another somehow. */ SNDDBG(("FIXME: Implement \"map\" in TiMidity config.\n")); } /* Standard TiMidity config */ else if (!SDL_strcmp(w[0], "dir")) { if (words < 2) { SNDDBG(("%s: line %d: No directory given\n", name, line)); goto fail; } for (i=1; i(MAXBANK-1)) { SNDDBG(("%s: line %d: Drum set must be between 0 and %d\n", name, line, MAXBANK-1)); goto fail; } if (!master_drumset[i]) { master_drumset[i] = SDL_calloc(1, sizeof(ToneBank)); if (!master_drumset[i]) goto fail; master_drumset[i]->tone = SDL_calloc(128, sizeof(ToneBankElement)); if (!master_drumset[i]->tone) goto fail; } bank=master_drumset[i]; } else if (!SDL_strcmp(w[0], "bank")) { if (words < 2) { SNDDBG(("%s: line %d: No bank number given\n", name, line)); goto fail; } i=SDL_atoi(w[1]); if (i<0 || i>(MAXBANK-1)) { SNDDBG(("%s: line %d: Tone bank must be between 0 and %d\n", name, line, MAXBANK-1)); goto fail; } if (!master_tonebank[i]) { master_tonebank[i] = SDL_calloc(1, sizeof(ToneBank)); if (!master_tonebank[i]) goto fail; master_tonebank[i]->tone = SDL_calloc(128, sizeof(ToneBankElement)); if (!master_tonebank[i]->tone) goto fail; } bank=master_tonebank[i]; } else { size_t sz; if ((words < 2) || (*w[0] < '0' || *w[0] > '9')) { SNDDBG(("%s: line %d: syntax error\n", name, line)); goto fail; } i=SDL_atoi(w[0]); if (i<0 || i>127) { SNDDBG(("%s: line %d: Program must be between 0 and 127\n", name, line)); goto fail; } if (!bank) { SNDDBG(("%s: line %d: Must specify tone bank or drum set before assignment\n", name, line)); goto fail; } SDL_free(bank->tone[i].name); sz = SDL_strlen(w[1])+1; bank->tone[i].name = SDL_malloc(sz); if (!bank->tone[i].name) goto fail; SDL_memcpy(bank->tone[i].name,w[1],sz); bank->tone[i].note=bank->tone[i].amp=bank->tone[i].pan= bank->tone[i].strip_loop=bank->tone[i].strip_envelope= bank->tone[i].strip_tail=-1; for (j=2; j '9')) { SNDDBG(("%s: line %d: amplification must be between 0 and %d\n", name, line, MAX_AMPLIFICATION)); goto fail; } bank->tone[i].amp=k; } else if (!SDL_strcmp(w[j], "note")) { k=SDL_atoi(cp); if ((k<0 || k>127) || (*cp < '0' || *cp > '9')) { SNDDBG(("%s: line %d: note must be between 0 and 127\n", name, line)); goto fail; } bank->tone[i].note=k; } else if (!SDL_strcmp(w[j], "pan")) { if (!SDL_strcmp(cp, "center")) k=64; else if (!SDL_strcmp(cp, "left")) k=0; else if (!SDL_strcmp(cp, "right")) k=127; else k=((SDL_atoi(cp)+100) * 100) / 157; if ((k<0 || k>127) || (k==0 && *cp!='-' && (*cp < '0' || *cp > '9'))) { SNDDBG(("%s: line %d: panning must be left, right, center, or between -100 and 100\n", name, line)); goto fail; } bank->tone[i].pan=k; } else if (!SDL_strcmp(w[j], "keep")) { if (!SDL_strcmp(cp, "env")) bank->tone[i].strip_envelope=0; else if (!SDL_strcmp(cp, "loop")) bank->tone[i].strip_loop=0; else { SNDDBG(("%s: line %d: keep must be env or loop\n", name, line)); goto fail; } } else if (!SDL_strcmp(w[j], "strip")) { if (!SDL_strcmp(cp, "env")) bank->tone[i].strip_envelope=1; else if (!SDL_strcmp(cp, "loop")) bank->tone[i].strip_loop=1; else if (!SDL_strcmp(cp, "tail")) bank->tone[i].strip_tail=1; else { SNDDBG(("%s: line %d: strip must be env, loop, or tail\n", name, line)); goto fail; } } else { SNDDBG(("%s: line %d: bad patch option %s\n", name, line, w[j])); goto fail; } } } } r = 0; /* we're good. */ fail: SDL_CloseIO(io); return r; } #if defined(_WIN32) || defined(__CYGWIN__) /* FIXME: What about C:FOO ? */ static SDL_INLINE char *get_last_dirsep (const char *p) { char *p1 = SDL_strrchr(p, '/'); char *p2 = SDL_strrchr(p, '\\'); if (!p1) return p2; if (!p2) return p1; return (p1 > p2)? p1 : p2; } #else /* assumed UNIX-ish : */ static SDL_INLINE char *get_last_dirsep (const char *p) { return SDL_strrchr(p, '/'); } #endif static int init_alloc_banks(void) { /* Allocate memory for the standard tonebank and drumset */ master_tonebank[0] = SDL_calloc(1, sizeof(ToneBank)); if (!master_tonebank[0]) goto _nomem; master_tonebank[0]->tone = SDL_calloc(128, sizeof(ToneBankElement)); if (!master_tonebank[0]->tone) goto _nomem; master_drumset[0] = SDL_calloc(1, sizeof(ToneBank)); if (!master_drumset[0]) goto _nomem; master_drumset[0]->tone = SDL_calloc(128, sizeof(ToneBankElement)); if (!master_drumset[0]->tone) goto _nomem; return 0; _nomem: SNDDBG(("Out of memory\n")); Timidity_Exit (); return -2; } static int init_begin_config(const char *cf) { const char *p = get_last_dirsep(cf); if (p != NULL) return timi_add_pathlist(cf, p - cf + 1); /* including DIRSEP */ return 0; } static int init_with_config(const char *cf) { int rc = init_begin_config(cf); if (rc != 0) { Timidity_Exit (); return rc; } rc = read_config_file(cf, 0); if (rc != 0) { Timidity_Exit (); } return rc; } int Timidity_Init_NoConfig(void) { master_tonebank[0] = NULL; master_drumset[0] = NULL; return init_alloc_banks(); } int Timidity_Init(const char *config_file) { int rc = Timidity_Init_NoConfig(); if (rc != 0) { return rc; } if (config_file == NULL || *config_file == '\0') { return init_with_config(TIMIDITY_CFG); } return init_with_config(config_file); } static void do_song_load(SDL_IOStream *io, SDL_AudioSpec *audio, MidiSong **out) { MidiSong *song; int i; *out = NULL; if (io == NULL) return; /* Allocate memory for the song */ song = (MidiSong *)SDL_calloc(1, sizeof(*song)); if (song == NULL) return; for (i = 0; i < MAXBANK; i++) { if (master_tonebank[i]) { song->tonebank[i] = SDL_calloc(1, sizeof(ToneBank)); if (!song->tonebank[i]) goto fail; song->tonebank[i]->tone = master_tonebank[i]->tone; } if (master_drumset[i]) { song->drumset[i] = SDL_calloc(1, sizeof(ToneBank)); if (!song->drumset[i]) goto fail; song->drumset[i]->tone = master_drumset[i]->tone; } } song->amplification = DEFAULT_AMPLIFICATION; song->voices = DEFAULT_VOICES; song->drumchannels = DEFAULT_DRUMCHANNELS; song->io = io; song->rate = audio->freq; song->encoding = 0; if ((audio->format & 0xFF) == 16) song->encoding |= PE_16BIT; else if ((audio->format & 0xFF) == 32) song->encoding |= PE_32BIT; if (audio->format & 0x8000) song->encoding |= PE_SIGNED; if (audio->channels == 1) song->encoding |= PE_MONO; else if (audio->channels > 2) { SDL_SetError("Surround sound not supported"); goto fail; } switch (audio->format) { case SDL_AUDIO_S8: song->write = timi_s32tos8; break; case SDL_AUDIO_U8: song->write = timi_s32tou8; break; case SDL_AUDIO_S16LE: song->write = timi_s32tos16l; break; case SDL_AUDIO_S16BE: song->write = timi_s32tos16b; break; case SDL_AUDIO_S32LE: song->write = timi_s32tos32l; break; case SDL_AUDIO_S32BE: song->write = timi_s32tos32b; break; case SDL_AUDIO_F32: song->write = timi_s32tof32; break; default: SDL_SetError("Unsupported audio format"); goto fail; } song->buffer_size = 4096/*audio->samples*/; song->resample_buffer = SDL_malloc(4096/*audio->samples*/ * sizeof(sample_t)); if (!song->resample_buffer) goto fail; song->common_buffer = SDL_malloc(4096/*audio->samples*/ * 2 * sizeof(Sint32)); if (!song->common_buffer) goto fail; song->control_ratio = audio->freq / CONTROLS_PER_SECOND; if (song->control_ratio < 1) song->control_ratio = 1; else if (song->control_ratio > MAX_CONTROL_RATIO) song->control_ratio = MAX_CONTROL_RATIO; song->lost_notes = 0; song->cut_notes = 0; song->events = read_midi_file(song, &(song->groomed_event_count), &song->samples); /* Make sure everything is okay */ if (!song->events) goto fail; song->default_instrument = NULL; song->default_program = DEFAULT_PROGRAM; if (*def_instr_name) set_default_instrument(song, def_instr_name); load_missing_instruments(song); if (! song->oom) *out = song; else { fail: Timidity_FreeSong(song); } } MidiSong *Timidity_LoadSong(SDL_IOStream *io, SDL_AudioSpec *audio) { MidiSong *song; do_song_load(io, audio, &song); return song; } void Timidity_FreeSong(MidiSong *song) { int i; if (!song) return; free_instruments(song); for (i = 0; i < 128; i++) { SDL_free(song->tonebank[i]); SDL_free(song->drumset[i]); } SDL_free(song->common_buffer); SDL_free(song->resample_buffer); SDL_free(song->events); SDL_free(song); } void Timidity_Exit(void) { int i, j; for (i = 0; i < MAXBANK; i++) { if (master_tonebank[i]) { ToneBankElement *e = master_tonebank[i]->tone; if (e != NULL) { for (j = 0; j < 128; j++) { SDL_free(e[j].name); } SDL_free(e); } SDL_free(master_tonebank[i]); master_tonebank[i] = NULL; } if (master_drumset[i]) { ToneBankElement *e = master_drumset[i]->tone; if (e != NULL) { for (j = 0; j < 128; j++) { SDL_free(e[j].name); } SDL_free(e); } SDL_free(master_drumset[i]); master_drumset[i] = NULL; } } timi_free_pathlist(); } libsdl3-mixer-3~git20250523~daf0503+ds/src/codecs/timidity/timidity.h000066400000000000000000000105171501405355700251050ustar00rootroot00000000000000/* TiMidity -- Experimental MIDI to WAVE converter Copyright (C) 1995 Tuukka Toivonen This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, available in COPYING. */ #ifndef TIMIDITY_H #define TIMIDITY_H #ifdef __cplusplus extern "C" { #endif typedef Sint16 sample_t; typedef Sint32 final_volume_t; #define VIBRATO_SAMPLE_INCREMENTS 32 /* Maximum polyphony. */ /* #define MAX_VOICES 48 */ #define MAX_VOICES 256 #define MAXCHAN 16 /* #define MAXCHAN 64 */ #define MAXBANK 128 typedef struct { Sint32 loop_start, loop_end, data_length, sample_rate, low_freq, high_freq, root_freq; Sint32 envelope_rate[6], envelope_offset[6]; float volume; sample_t *data; Sint32 tremolo_sweep_increment, tremolo_phase_increment, vibrato_sweep_increment, vibrato_control_ratio; Uint8 tremolo_depth, vibrato_depth, modes; Sint8 panning, note_to_use; } Sample; typedef struct { int bank, program, volume, sustain, panning, pitchbend, expression, mono, /* one note only on this channel -- not implemented yet */ pitchsens; /* chorus, reverb... Coming soon to a 300-MHz, eight-way superscalar processor near you */ float pitchfactor; /* precomputed pitch bend factor to save some fdiv's */ } Channel; typedef struct { Uint8 status, channel, note, velocity; Sample *sample; Sint32 orig_frequency, frequency, sample_offset, sample_increment, envelope_volume, envelope_target, envelope_increment, tremolo_sweep, tremolo_sweep_position, tremolo_phase, tremolo_phase_increment, vibrato_sweep, vibrato_sweep_position; final_volume_t left_mix, right_mix; float left_amp, right_amp, tremolo_volume; Sint32 vibrato_sample_increment[VIBRATO_SAMPLE_INCREMENTS]; int vibrato_phase, vibrato_control_ratio, vibrato_control_counter, envelope_stage, control_counter, panning, panned; } Voice; typedef struct { int samples; Sample *sample; } Instrument; /* Shared data */ typedef struct { char *name; int note, amp, pan, strip_loop, strip_envelope, strip_tail; } ToneBankElement; typedef struct { ToneBankElement *tone; Instrument *instrument[128]; } ToneBank; typedef struct { Sint32 time; Uint8 channel, type, a, b; } MidiEvent; typedef struct _MidiEventList { MidiEvent event; struct _MidiEventList *next; } MidiEventList; typedef struct { int oom; /* malloc() failed */ int playing; SDL_IOStream *io; Sint32 rate; Sint32 encoding; float master_volume; Sint32 amplification; ToneBank *tonebank[MAXBANK]; ToneBank *drumset[MAXBANK]; Instrument *default_instrument; int default_program; void (*write)(void *dp, Sint32 *lp, Sint32 c); int buffer_size; sample_t *resample_buffer; Sint32 *common_buffer; Sint32 *buffer_pointer; /* These would both fit into 32 bits, but they are often added in large multiples, so it's simpler to have two roomy ints */ /* samples per MIDI delta-t */ Sint32 sample_increment; Sint32 sample_correction; Channel channel[MAXCHAN]; Voice voice[MAX_VOICES]; int voices; Sint32 drumchannels; Sint32 buffered_count; Sint32 control_ratio; Sint32 lost_notes; Sint32 cut_notes; Sint32 samples; MidiEvent *events; MidiEvent *current_event; MidiEventList *evlist; Sint32 current_sample; Sint32 event_count; Sint32 at; Sint32 groomed_event_count; } MidiSong; /* Some of these are not defined in timidity.c but are here for convenience */ extern int Timidity_Init(const char *config_file); extern int Timidity_Init_NoConfig(void); extern void Timidity_SetVolume(MidiSong *song, int volume); extern int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len); extern MidiSong *Timidity_LoadSong(SDL_IOStream *io, SDL_AudioSpec *audio); extern void Timidity_Start(MidiSong *song); extern void Timidity_Seek(MidiSong *song, Uint32 ms); extern Uint32 Timidity_GetSongLength(MidiSong *song); /* returns millseconds */ extern Uint32 Timidity_GetSongTime(MidiSong *song); /* returns millseconds */ extern void Timidity_Stop(MidiSong *song); extern int Timidity_IsActive(MidiSong *song); extern void Timidity_FreeSong(MidiSong *song); extern void Timidity_Exit(void); #ifdef __cplusplus } #endif #endif /* TIMIDITY_H */ libsdl3-mixer-3~git20250523~daf0503+ds/src/effect_position.c000066400000000000000000002017371501405355700233360ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file by Ryan C. Gordon (icculus@icculus.org) These are some internally supported special effects that use SDL_mixer's effect callback API. They are meant for speed over quality. :) */ #include #include #include "mixer.h" #define MIX_INTERNAL_EFFECT__ #include "effects_internal.h" /* profile code: #include #include struct timeval tv1; struct timeval tv2; gettimeofday(&tv1, NULL); ... do your thing here ... gettimeofday(&tv2, NULL); printf("%ld\n", tv2.tv_usec - tv1.tv_usec); */ /* * Positional effects...panning, distance attenuation, etc. */ typedef struct _Eff_positionargs { float left_f; float right_f; Uint8 left_u8; Uint8 right_u8; float left_rear_f; float right_rear_f; float center_f; float lfe_f; Uint8 left_rear_u8; Uint8 right_rear_u8; Uint8 center_u8; Uint8 lfe_u8; float distance_f; Uint8 distance_u8; Sint16 room_angle; int in_use; int channels; } position_args; static position_args **pos_args_array = NULL; static position_args *pos_args_global = NULL; static int position_channels = 0; void _Eff_PositionDeinit(void) { int i; for (i = 0; i < position_channels; i++) { SDL_free(pos_args_array[i]); } position_channels = 0; SDL_free(pos_args_global); pos_args_global = NULL; SDL_free(pos_args_array); pos_args_array = NULL; } /* This just frees up the callback-specific data. */ static void SDLCALL _Eff_PositionDone(int channel, void *udata) { (void)udata; if (channel < 0) { if (pos_args_global != NULL) { SDL_free(pos_args_global); pos_args_global = NULL; } } else if (pos_args_array[channel] != NULL) { SDL_free(pos_args_array[channel]); pos_args_array[channel] = NULL; } } static void SDLCALL _Eff_position_u8(int chan, void *stream, int len, void *udata) { Uint8 *ptr = (Uint8 *) stream; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; /* * if there's only a mono channnel (the only way we wouldn't have * a len divisible by 2 here), then left_f and right_f are always * 1.0, and are therefore throwaways. */ if (len % (int)sizeof(Uint16) != 0) { *ptr = (Uint8) (((float) *ptr) * dist_f); ptr++; len--; } if (((position_args *)udata)->room_angle == 180) { for (i = 0; i < len; i += sizeof(Uint8) * 2) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * right_f) * dist_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * left_f) * dist_f) + 128); ptr++; } } else { for (i = 0; i < len; i += sizeof(Uint8) * 2) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * left_f) * dist_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * right_f) * dist_f) + 128); ptr++; } } } static void SDLCALL _Eff_position_u8_c4(int chan, void *stream, int len, void *udata) { position_args *args = (position_args *) udata; Uint8 *ptr = (Uint8 *) stream; int i; (void)chan; /* * if there's only a mono channnel (the only way we wouldn't have * a len divisible by 2 here), then left_f and right_f are always * 1.0, and are therefore throwaways. */ if (len % (int)sizeof(Uint16) != 0) { *ptr = (Uint8) (((float) *ptr) * args->distance_f); ptr++; len--; } if (args->room_angle == 0) { for (i = 0; i < len; i += sizeof(Uint8) * 4) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; } } else if (args->room_angle == 90) { for (i = 0; i < len; i += sizeof(Uint8) * 4) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; } } else if (args->room_angle == 180) { for (i = 0; i < len; i += sizeof(Uint8) * 4) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; } } else if (args->room_angle == 270) { for (i = 0; i < len; i += sizeof(Uint8) * 4) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; } } } static void SDLCALL _Eff_position_u8_c6(int chan, void *stream, int len, void *udata) { position_args *args = (position_args *) udata; Uint8 *ptr = (Uint8 *) stream; int i; (void)chan; (void)len; /* * if there's only a mono channnel (the only way we wouldn't have * a len divisible by 2 here), then left_f and right_f are always * 1.0, and are therefore throwaways. */ if (len % (int)sizeof(Uint16) != 0) { *ptr = (Uint8) (((float) *ptr) * args->distance_f); ptr++; len--; } if (args->room_angle == 0) { for (i = 0; i < len; i += sizeof(Uint8) * 6) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->center_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->lfe_f) * args->distance_f) + 128); ptr++; } } else if (args->room_angle == 90) { for (i = 0; i < len; i += sizeof(Uint8) * 6) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f/2) + 128) + (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f/2) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->lfe_f) * args->distance_f) + 128); ptr++; } } else if (args->room_angle == 180) { for (i = 0; i < len; i += sizeof(Uint8) * 6) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f/2) + 128) + (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f/2) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->lfe_f) * args->distance_f) + 128); ptr++; } } else if (args->room_angle == 270) { for (i = 0; i < len; i += sizeof(Uint8) * 6) { /* must adjust the sample so that 0 is the center */ *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_rear_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->right_f) * args->distance_f) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_f) * args->distance_f/2) + 128) + (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->left_rear_f) * args->distance_f/2) + 128); ptr++; *ptr = (Uint8) ((Sint8) ((((float) (Sint8) (*ptr - 128)) * args->lfe_f) * args->distance_f) + 128); ptr++; } } } /* * This one runs about 10.1 times faster than the non-table version, with * no loss in quality. It does, however, require 64k of memory for the * lookup table. Also, this will only update position information once per * call; the non-table version always checks the arguments for each sample, * in case the user has called Mix_SetPanning() or whatnot again while this * callback is running. */ static void SDLCALL _Eff_position_table_u8(int chan, void *stream, int len, void *udata) { position_args *args = (position_args *) udata; Uint8 *ptr = (Uint8 *) stream; Uint32 *p; int i; Uint8 *l = ((Uint8 *) _Eff_volume_table) + (256 * args->left_u8); Uint8 *r = ((Uint8 *) _Eff_volume_table) + (256 * args->right_u8); Uint8 *d = ((Uint8 *) _Eff_volume_table) + (256 * args->distance_u8); (void)chan; if (args->room_angle == 180) { Uint8 *temp = l; l = r; r = temp; } /* * if there's only a mono channnel, then l[] and r[] are always * volume 255, and are therefore throwaways. Still, we have to * be sure not to overrun the audio buffer... */ while (len % (int)sizeof(Uint32) != 0) { *ptr = d[l[*ptr]]; ptr++; if (args->channels > 1) { *ptr = d[r[*ptr]]; ptr++; } len -= args->channels; } p = (Uint32 *) ptr; for (i = 0; i < len; i += sizeof(Uint32)) { #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) *p = (d[l[(*p & 0xFF000000) >> 24]] << 24) | (d[r[(*p & 0x00FF0000) >> 16]] << 16) | (d[l[(*p & 0x0000FF00) >> 8]] << 8) | (d[r[(*p & 0x000000FF) ]] ) ; #else *p = (d[r[(*p & 0xFF000000) >> 24]] << 24) | (d[l[(*p & 0x00FF0000) >> 16]] << 16) | (d[r[(*p & 0x0000FF00) >> 8]] << 8) | (d[l[(*p & 0x000000FF) ]] ) ; #endif ++p; } } static void SDLCALL _Eff_position_s8(int chan, void *stream, int len, void *udata) { Sint8 *ptr = (Sint8 *) stream; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; /* * if there's only a mono channnel (the only way we wouldn't have * a len divisible by 2 here), then left_f and right_f are always * 1.0, and are therefore throwaways. */ if (len % (int)sizeof(Sint16) != 0) { *ptr = (Sint8) (((float) *ptr) * dist_f); ptr++; len--; } if (((position_args *)udata)->room_angle == 180) { for (i = 0; i < len; i += sizeof(Sint8) * 2) { *ptr = (Sint8)((((float) *ptr) * right_f) * dist_f); ptr++; *ptr = (Sint8)((((float) *ptr) * left_f) * dist_f); ptr++; } } else { for (i = 0; i < len; i += sizeof(Sint8) * 2) { *ptr = (Sint8)((((float) *ptr) * left_f) * dist_f); ptr++; *ptr = (Sint8)((((float) *ptr) * right_f) * dist_f); ptr++; } } } static void SDLCALL _Eff_position_s8_c4(int chan, void *stream, int len, void *udata) { position_args *args = (position_args *) udata; Sint8 *ptr = (Sint8 *) stream; int i; (void)chan; /* * if there's only a mono channnel (the only way we wouldn't have * a len divisible by 2 here), then left_f and right_f are always * 1.0, and are therefore throwaways. */ if (len % (int)sizeof(Sint16) != 0) { *ptr = (Sint8) (((float) *ptr) * args->distance_f); ptr++; len--; } for (i = 0; i < len; i += sizeof(Sint8) * 4) { switch (args->room_angle) { case 0: *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; break; case 90: *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; break; case 180: *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; break; case 270: *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; break; } } } static void SDLCALL _Eff_position_s8_c6(int chan, void *stream, int len, void *udata) { position_args *args = (position_args *) udata; Sint8 *ptr = (Sint8 *) stream; int i; (void)chan; /* * if there's only a mono channnel (the only way we wouldn't have * a len divisible by 2 here), then left_f and right_f are always * 1.0, and are therefore throwaways. */ if (len % (int)sizeof(Sint16) != 0) { *ptr = (Sint8) (((float) *ptr) * args->distance_f); ptr++; len--; } for (i = 0; i < len; i += sizeof(Sint8) * 6) { switch (args->room_angle) { case 0: *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->center_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->lfe_f) * args->distance_f); ptr++; break; case 90: *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f / 2) + (Sint8)((((float) *ptr) * args->right_f) * args->distance_f / 2); ptr++; *ptr = (Sint8)((((float) *ptr) * args->lfe_f) * args->distance_f); ptr++; break; case 180: *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f / 2) + (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f / 2); ptr++; *ptr = (Sint8)((((float) *ptr) * args->lfe_f) * args->distance_f); ptr++; break; case 270: *ptr = (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_rear_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->right_f) * args->distance_f); ptr++; *ptr = (Sint8)((((float) *ptr) * args->left_f) * args->distance_f / 2) + (Sint8)((((float) *ptr) * args->left_rear_f) * args->distance_f / 2); ptr++; *ptr = (Sint8)((((float) *ptr) * args->lfe_f) * args->distance_f); ptr++; break; } } } /* * This one runs about 10.1 times faster than the non-table version, with * no loss in quality. It does, however, require 64k of memory for the * lookup table. Also, this will only update position information once per * call; the non-table version always checks the arguments for each sample, * in case the user has called Mix_SetPanning() or whatnot again while this * callback is running. */ static void SDLCALL _Eff_position_table_s8(int chan, void *stream, int len, void *udata) { position_args *args = (position_args *) udata; Sint8 *ptr = (Sint8 *) stream; Uint32 *p; int i; Sint8 *l = ((Sint8 *) _Eff_volume_table) + (256 * args->left_u8); Sint8 *r = ((Sint8 *) _Eff_volume_table) + (256 * args->right_u8); Sint8 *d = ((Sint8 *) _Eff_volume_table) + (256 * args->distance_u8); (void)chan; if (args->room_angle == 180) { Sint8 *temp = l; l = r; r = temp; } while (len % (int)sizeof(Uint32) != 0) { *ptr = d[l[*ptr]]; ptr++; if (args->channels > 1) { *ptr = d[r[*ptr]]; ptr++; } len -= args->channels; } p = (Uint32 *) ptr; for (i = 0; i < len; i += sizeof(Uint32)) { #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) *p = (d[l[((Sint16)(Sint8)((*p & 0xFF000000) >> 24))+128]] << 24) | (d[r[((Sint16)(Sint8)((*p & 0x00FF0000) >> 16))+128]] << 16) | (d[l[((Sint16)(Sint8)((*p & 0x0000FF00) >> 8))+128]] << 8) | (d[r[((Sint16)(Sint8)((*p & 0x000000FF) ))+128]] ) ; #else *p = (d[r[((Sint16)(Sint8)((*p & 0xFF000000) >> 24))+128]] << 24) | (d[l[((Sint16)(Sint8)((*p & 0x00FF0000) >> 16))+128]] << 16) | (d[r[((Sint16)(Sint8)((*p & 0x0000FF00) >> 8))+128]] << 8) | (d[l[((Sint16)(Sint8)((*p & 0x000000FF) ))+128]] ) ; #endif ++p; } } /* !!! FIXME : Optimize the code for 16-bit samples? */ static void SDLCALL _Eff_position_s16lsb(int chan, void *stream, int len, void *udata) { /* 16 signed bits (lsb) * 2 channels. */ Sint16 *ptr = (Sint16 *) stream; const bool opp = ((position_args *)udata)->room_angle == 180 ? true : false; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; #if 0 if (len % (int)(sizeof(Sint16) * 2)) { fprintf(stderr,"Not an even number of frames! len=%d\n", len); return; } #endif for (i = 0; i < len; i += sizeof(Sint16) * 2) { Sint16 swapl = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+0))) * left_f) * dist_f); Sint16 swapr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+1))) * right_f) * dist_f); if (opp) { *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); } else { *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); } } } static void SDLCALL _Eff_position_s16lsb_c4(int chan, void *stream, int len, void *udata) { /* 16 signed bits (lsb) * 4 channels. */ position_args *args = (position_args *) udata; Sint16 *ptr = (Sint16 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint16) * 4) { Sint16 swapl = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+0))) * args->left_f) * args->distance_f); Sint16 swapr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+1))) * args->right_f) * args->distance_f); Sint16 swaplr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+1))) * args->left_rear_f) * args->distance_f); Sint16 swaprr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+2))) * args->right_rear_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); break; case 90: *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); break; case 180: *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); break; case 270: *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); break; } } } static void SDLCALL _Eff_position_s16lsb_c6(int chan, void *stream, int len, void *udata) { /* 16 signed bits (lsb) * 6 channels. */ position_args *args = (position_args *) udata; Sint16 *ptr = (Sint16 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint16) * 6) { Sint16 swapl = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+0))) * args->left_f) * args->distance_f); Sint16 swapr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+1))) * args->right_f) * args->distance_f); Sint16 swaplr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+2))) * args->left_rear_f) * args->distance_f); Sint16 swaprr = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+3))) * args->right_rear_f) * args->distance_f); Sint16 swapce = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+4))) * args->center_f) * args->distance_f); Sint16 swapwf = (Sint16) ((((float) (Sint16) SDL_Swap16LE(*(ptr+5))) * args->lfe_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swapce); *(ptr++) = (Sint16) SDL_Swap16LE(swapwf); break; case 90: *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swapr)/2 + (Sint16) SDL_Swap16LE(swaprr)/2; *(ptr++) = (Sint16) SDL_Swap16LE(swapwf); break; case 180: *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr)/2 + (Sint16) SDL_Swap16LE(swaplr)/2; *(ptr++) = (Sint16) SDL_Swap16LE(swapwf); break; case 270: *(ptr++) = (Sint16) SDL_Swap16LE(swaplr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl); *(ptr++) = (Sint16) SDL_Swap16LE(swaprr); *(ptr++) = (Sint16) SDL_Swap16LE(swapr); *(ptr++) = (Sint16) SDL_Swap16LE(swapl)/2 + (Sint16) SDL_Swap16LE(swaplr)/2; *(ptr++) = (Sint16) SDL_Swap16LE(swapwf); break; } } } static void SDLCALL _Eff_position_s16msb(int chan, void *stream, int len, void *udata) { /* 16 signed bits (lsb) * 2 channels. */ Sint16 *ptr = (Sint16 *) stream; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint16) * 2) { Sint16 swapl = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+0))) * left_f) * dist_f); Sint16 swapr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+1))) * right_f) * dist_f); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); } } static void SDLCALL _Eff_position_s16msb_c4(int chan, void *stream, int len, void *udata) { /* 16 signed bits (lsb) * 4 channels. */ position_args *args = (position_args *) udata; Sint16 *ptr = (Sint16 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint16) * 4) { Sint16 swapl = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+0))) * args->left_f) * args->distance_f); Sint16 swapr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+1))) * args->right_f) * args->distance_f); Sint16 swaplr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+2))) * args->left_rear_f) * args->distance_f); Sint16 swaprr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+3))) * args->right_rear_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); break; case 90: *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); break; case 180: *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); break; case 270: *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); break; } } } static void SDLCALL _Eff_position_s16msb_c6(int chan, void *stream, int len, void *udata) { /* 16 signed bits (lsb) * 6 channels. */ position_args *args = (position_args *) udata; Sint16 *ptr = (Sint16 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint16) * 6) { Sint16 swapl = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+0))) * args->left_f) * args->distance_f); Sint16 swapr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+1))) * args->right_f) * args->distance_f); Sint16 swaplr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+2))) * args->left_rear_f) * args->distance_f); Sint16 swaprr = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+3))) * args->right_rear_f) * args->distance_f); Sint16 swapce = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+4))) * args->center_f) * args->distance_f); Sint16 swapwf = (Sint16) ((((float) (Sint16) SDL_Swap16BE(*(ptr+5))) * args->lfe_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swapce); *(ptr++) = (Sint16) SDL_Swap16BE(swapwf); break; case 90: *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swapr)/2 + (Sint16) SDL_Swap16BE(swaprr)/2; *(ptr++) = (Sint16) SDL_Swap16BE(swapwf); break; case 180: *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr)/2 + (Sint16) SDL_Swap16BE(swaplr)/2; *(ptr++) = (Sint16) SDL_Swap16BE(swapwf); break; case 270: *(ptr++) = (Sint16) SDL_Swap16BE(swaplr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl); *(ptr++) = (Sint16) SDL_Swap16BE(swaprr); *(ptr++) = (Sint16) SDL_Swap16BE(swapr); *(ptr++) = (Sint16) SDL_Swap16BE(swapl)/2 + (Sint16) SDL_Swap16BE(swaplr)/2; *(ptr++) = (Sint16) SDL_Swap16BE(swapwf); break; } } } static void SDLCALL _Eff_position_s32lsb(int chan, void *stream, int len, void *udata) { /* 32 signed bits (lsb) * 2 channels. */ Sint32 *ptr = (Sint32 *) stream; const bool opp = ((position_args *)udata)->room_angle == 180 ? true : false; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; #if 0 if (len % (int)(sizeof(Sint32) * 2)) { fprintf(stderr,"Not an even number of frames! len=%d\n", len); return; } #endif for (i = 0; i < len; i += sizeof(Sint32) * 2) { Sint32 swapl = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+0))) * left_f) * dist_f); Sint32 swapr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+1))) * right_f) * dist_f); if (opp) { *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); } else { *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); } } } static void SDLCALL _Eff_position_s32lsb_c4(int chan, void *stream, int len, void *udata) { /* 32 signed bits (lsb) * 4 channels. */ position_args *args = (position_args *) udata; Sint32 *ptr = (Sint32 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint32) * 4) { Sint32 swapl = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+0))) * args->left_f) * args->distance_f); Sint32 swapr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+1))) * args->right_f) * args->distance_f); Sint32 swaplr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+1))) * args->left_rear_f) * args->distance_f); Sint32 swaprr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+2))) * args->right_rear_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); break; case 90: *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); break; case 180: *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); break; case 270: *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); break; } } } static void SDLCALL _Eff_position_s32lsb_c6(int chan, void *stream, int len, void *udata) { /* 32 signed bits (lsb) * 6 channels. */ position_args *args = (position_args *) udata; Sint32 *ptr = (Sint32 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint32) * 6) { Sint32 swapl = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+0))) * args->left_f) * args->distance_f); Sint32 swapr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+1))) * args->right_f) * args->distance_f); Sint32 swaplr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+2))) * args->left_rear_f) * args->distance_f); Sint32 swaprr = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+3))) * args->right_rear_f) * args->distance_f); Sint32 swapce = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+4))) * args->center_f) * args->distance_f); Sint32 swapwf = (Sint32) ((((float) (Sint32) SDL_Swap32LE(*(ptr+5))) * args->lfe_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swapce); *(ptr++) = (Sint32) SDL_Swap32LE(swapwf); break; case 90: *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swapr)/2 + (Sint32) SDL_Swap32LE(swaprr)/2; *(ptr++) = (Sint32) SDL_Swap32LE(swapwf); break; case 180: *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr)/2 + (Sint32) SDL_Swap32LE(swaplr)/2; *(ptr++) = (Sint32) SDL_Swap32LE(swapwf); break; case 270: *(ptr++) = (Sint32) SDL_Swap32LE(swaplr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl); *(ptr++) = (Sint32) SDL_Swap32LE(swaprr); *(ptr++) = (Sint32) SDL_Swap32LE(swapr); *(ptr++) = (Sint32) SDL_Swap32LE(swapl)/2 + (Sint32) SDL_Swap32LE(swaplr)/2; *(ptr++) = (Sint32) SDL_Swap32LE(swapwf); break; } } } static void SDLCALL _Eff_position_s32msb(int chan, void *stream, int len, void *udata) { /* 32 signed bits (lsb) * 2 channels. */ Sint32 *ptr = (Sint32 *) stream; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint32) * 2) { Sint32 swapl = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+0))) * left_f) * dist_f); Sint32 swapr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+1))) * right_f) * dist_f); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); } } static void SDLCALL _Eff_position_s32msb_c4(int chan, void *stream, int len, void *udata) { /* 32 signed bits (lsb) * 4 channels. */ position_args *args = (position_args *) udata; Sint32 *ptr = (Sint32 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint32) * 4) { Sint32 swapl = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+0))) * args->left_f) * args->distance_f); Sint32 swapr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+1))) * args->right_f) * args->distance_f); Sint32 swaplr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+2))) * args->left_rear_f) * args->distance_f); Sint32 swaprr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+3))) * args->right_rear_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); break; case 90: *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); break; case 180: *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); break; case 270: *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); break; } } } static void SDLCALL _Eff_position_s32msb_c6(int chan, void *stream, int len, void *udata) { /* 32 signed bits (lsb) * 6 channels. */ position_args *args = (position_args *) udata; Sint32 *ptr = (Sint32 *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(Sint32) * 6) { Sint32 swapl = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+0))) * args->left_f) * args->distance_f); Sint32 swapr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+1))) * args->right_f) * args->distance_f); Sint32 swaplr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+2))) * args->left_rear_f) * args->distance_f); Sint32 swaprr = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+3))) * args->right_rear_f) * args->distance_f); Sint32 swapce = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+4))) * args->center_f) * args->distance_f); Sint32 swapwf = (Sint32) ((((float) (Sint32) SDL_Swap32BE(*(ptr+5))) * args->lfe_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swapce); *(ptr++) = (Sint32) SDL_Swap32BE(swapwf); break; case 90: *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swapr)/2 + (Sint32) SDL_Swap32BE(swaprr)/2; *(ptr++) = (Sint32) SDL_Swap32BE(swapwf); break; case 180: *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr)/2 + (Sint32) SDL_Swap32BE(swaplr)/2; *(ptr++) = (Sint32) SDL_Swap32BE(swapwf); break; case 270: *(ptr++) = (Sint32) SDL_Swap32BE(swaplr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl); *(ptr++) = (Sint32) SDL_Swap32BE(swaprr); *(ptr++) = (Sint32) SDL_Swap32BE(swapr); *(ptr++) = (Sint32) SDL_Swap32BE(swapl)/2 + (Sint32) SDL_Swap32BE(swaplr)/2; *(ptr++) = (Sint32) SDL_Swap32BE(swapwf); break; } } } static void SDLCALL _Eff_position_f32sys(int chan, void *stream, int len, void *udata) { /* float * 2 channels. */ float *ptr = (float *) stream; const float dist_f = ((position_args *)udata)->distance_f; const float left_f = ((position_args *)udata)->left_f; const float right_f = ((position_args *)udata)->right_f; int i; (void)chan; for (i = 0; i < len; i += sizeof(float) * 2) { float swapl = ((*(ptr+0) * left_f) * dist_f); float swapr = ((*(ptr+1) * right_f) * dist_f); *(ptr++) = swapl; *(ptr++) = swapr; } } static void SDLCALL _Eff_position_f32sys_c4(int chan, void *stream, int len, void *udata) { /* float * 4 channels. */ position_args *args = (position_args *) udata; float *ptr = (float *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(float) * 4) { float swapl = ((*(ptr+0) * args->left_f) * args->distance_f); float swapr = ((*(ptr+1) * args->right_f) * args->distance_f); float swaplr = ((*(ptr+2) * args->left_rear_f) * args->distance_f); float swaprr = ((*(ptr+3) * args->right_rear_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = swapl; *(ptr++) = swapr; *(ptr++) = swaplr; *(ptr++) = swaprr; break; case 90: *(ptr++) = swapr; *(ptr++) = swaprr; *(ptr++) = swapl; *(ptr++) = swaplr; break; case 180: *(ptr++) = swaprr; *(ptr++) = swaplr; *(ptr++) = swapr; *(ptr++) = swapl; break; case 270: *(ptr++) = swaplr; *(ptr++) = swapl; *(ptr++) = swaprr; *(ptr++) = swapr; break; } } } static void SDLCALL _Eff_position_f32sys_c6(int chan, void *stream, int len, void *udata) { /* float * 6 channels. */ position_args *args = (position_args *) udata; float *ptr = (float *) stream; int i; (void)chan; for (i = 0; i < len; i += sizeof(float) * 6) { float swapl = ((*(ptr+0) * args->left_f) * args->distance_f); float swapr = ((*(ptr+1) * args->right_f) * args->distance_f); float swaplr = ((*(ptr+2) * args->left_rear_f) * args->distance_f); float swaprr = ((*(ptr+3) * args->right_rear_f) * args->distance_f); float swapce = ((*(ptr+4) * args->center_f) * args->distance_f); float swapwf = ((*(ptr+5) * args->lfe_f) * args->distance_f); switch (args->room_angle) { case 0: *(ptr++) = swapl; *(ptr++) = swapr; *(ptr++) = swaplr; *(ptr++) = swaprr; *(ptr++) = swapce; *(ptr++) = swapwf; break; case 90: *(ptr++) = swapr; *(ptr++) = swaprr; *(ptr++) = swapl; *(ptr++) = swaplr; *(ptr++) = swapr/2.0f + swaprr/2.0f; *(ptr++) = swapwf; break; case 180: *(ptr++) = swaprr; *(ptr++) = swaplr; *(ptr++) = swapr; *(ptr++) = swapl; *(ptr++) = swaprr/2.0f + swaplr/2.0f; *(ptr++) = swapwf; break; case 270: *(ptr++) = swaplr; *(ptr++) = swapl; *(ptr++) = swaprr; *(ptr++) = swapr; *(ptr++) = swapl/2.0f + swaplr/2.0f; *(ptr++) = swapwf; break; } } } static void init_position_args(position_args *args) { SDL_memset(args, '\0', sizeof(position_args)); args->in_use = 0; args->room_angle = 0; args->left_u8 = args->right_u8 = args->distance_u8 = 255; args->left_f = args->right_f = args->distance_f = 1.0f; args->left_rear_u8 = args->right_rear_u8 = args->center_u8 = args->lfe_u8 = 255; args->left_rear_f = args->right_rear_f = args->center_f = args->lfe_f = 1.0f; Mix_QuerySpec(NULL, NULL, (int *) &args->channels); } static position_args *get_position_arg(int channel) { void *rc; int i; if (channel < 0) { if (pos_args_global == NULL) { pos_args_global = SDL_malloc(sizeof(position_args)); if (pos_args_global == NULL) { return NULL; } init_position_args(pos_args_global); } return pos_args_global; } if (channel >= position_channels) { rc = SDL_realloc(pos_args_array, (size_t)(channel + 1) * sizeof(position_args *)); if (rc == NULL) { return NULL; } pos_args_array = (position_args **) rc; for (i = position_channels; i <= channel; i++) { pos_args_array[i] = NULL; } position_channels = channel + 1; } if (pos_args_array[channel] == NULL) { pos_args_array[channel] = (position_args *)SDL_malloc(sizeof(position_args)); if (pos_args_array[channel] == NULL) { return NULL; } init_position_args(pos_args_array[channel]); } return pos_args_array[channel]; } static Mix_EffectFunc_t get_position_effect_func(SDL_AudioFormat format, int channels) { Mix_EffectFunc_t f = NULL; switch (format) { case SDL_AUDIO_U8 : switch (channels) { case 1: case 2: f = (_Eff_build_volume_table_u8()) ? _Eff_position_table_u8 : _Eff_position_u8; break; case 4: f = _Eff_position_u8_c4; break; case 6: f = _Eff_position_u8_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; case SDL_AUDIO_S8 : switch (channels) { case 1: case 2: f = (_Eff_build_volume_table_s8()) ? _Eff_position_table_s8 : _Eff_position_s8; break; case 4: f = _Eff_position_s8_c4; break; case 6: f = _Eff_position_s8_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; case SDL_AUDIO_S16LE: switch (channels) { case 1: case 2: f = _Eff_position_s16lsb; break; case 4: f = _Eff_position_s16lsb_c4; break; case 6: f = _Eff_position_s16lsb_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; case SDL_AUDIO_S16BE: switch (channels) { case 1: case 2: f = _Eff_position_s16msb; break; case 4: f = _Eff_position_s16msb_c4; break; case 6: f = _Eff_position_s16msb_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; case SDL_AUDIO_S32BE: switch (channels) { case 1: case 2: f = _Eff_position_s32msb; break; case 4: f = _Eff_position_s32msb_c4; break; case 6: f = _Eff_position_s32msb_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; case SDL_AUDIO_S32LE: switch (channels) { case 1: case 2: f = _Eff_position_s32lsb; break; case 4: f = _Eff_position_s32lsb_c4; break; case 6: f = _Eff_position_s32lsb_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; case SDL_AUDIO_F32: switch (channels) { case 1: case 2: f = _Eff_position_f32sys; break; case 4: f = _Eff_position_f32sys_c4; break; case 6: f = _Eff_position_f32sys_c6; break; default: SDL_SetError("Unsupported audio channels"); break; } break; default: SDL_SetError("Unsupported audio format"); break; } return f; } static Uint8 speaker_amplitude[6]; static void set_amplitudes(int channels, int angle, int room_angle) { int left = 255, right = 255; int left_rear = 255, right_rear = 255, center = 255; /* our only caller Mix_SetPosition() already makes angle between 0 and 359. */ if (channels == 2) { /* * We only attenuate by position if the angle falls on the far side * of center; That is, an angle that's due north would not attenuate * either channel. Due west attenuates the right channel to 0.0, and * due east attenuates the left channel to 0.0. Slightly east of * center attenuates the left channel a little, and the right channel * not at all. I think of this as occlusion by one's own head. :) * * ...so, we split our angle circle into four quadrants... */ if (angle < 90) { left = 255 - ((int) (255.0f * (((float) angle) / 89.0f))); } else if (angle < 180) { left = (int) (255.0f * (((float) (angle - 90)) / 89.0f)); } else if (angle < 270) { right = 255 - ((int) (255.0f * (((float) (angle - 180)) / 89.0f))); } else { right = (int) (255.0f * (((float) (angle - 270)) / 89.0f)); } } if (channels == 4 || channels == 6) { /* * An angle that's due north does not attenuate the center channel. * An angle in the first quadrant, 0-90, does not attenuate the RF. * * ...so, we split our angle circle into 8 ... * * CE * 0 * LF | RF * | * 270<-------|----------->90 * | * LR | RR * 180 * */ if (angle < 45) { left = ((int) (255.0f * (((float) (180 - angle)) / 179.0f))); left_rear = 255 - ((int) (255.0f * (((float) (angle + 45)) / 89.0f))); right_rear = 255 - ((int) (255.0f * (((float) (90 - angle)) / 179.0f))); } else if (angle < 90) { center = ((int) (255.0f * (((float) (225 - angle)) / 179.0f))); left = ((int) (255.0f * (((float) (180 - angle)) / 179.0f))); left_rear = 255 - ((int) (255.0f * (((float) (135 - angle)) / 89.0f))); right_rear = ((int) (255.0f * (((float) (90 + angle)) / 179.0f))); } else if (angle < 135) { center = ((int) (255.0f * (((float) (225 - angle)) / 179.0f))); left = 255 - ((int) (255.0f * (((float) (angle - 45)) / 89.0f))); right = ((int) (255.0f * (((float) (270 - angle)) / 179.0f))); left_rear = ((int) (255.0f * (((float) (angle)) / 179.0f))); } else if (angle < 180) { center = 255 - ((int) (255.0f * (((float) (angle - 90)) / 89.0f))); left = 255 - ((int) (255.0f * (((float) (225 - angle)) / 89.0f))); right = ((int) (255.0f * (((float) (270 - angle)) / 179.0f))); left_rear = ((int) (255.0f * (((float) (angle)) / 179.0f))); } else if (angle < 225) { center = 255 - ((int) (255.0f * (((float) (270 - angle)) / 89.0f))); left = ((int) (255.0f * (((float) (angle - 90)) / 179.0f))); right = 255 - ((int) (255.0f * (((float) (angle - 135)) / 89.0f))); right_rear = ((int) (255.0f * (((float) (360 - angle)) / 179.0f))); } else if (angle < 270) { center = ((int) (255.0f * (((float) (angle - 135)) / 179.0f))); left = ((int) (255.0f * (((float) (angle - 90)) / 179.0f))); right = 255 - ((int) (255.0f * (((float) (315 - angle)) / 89.0f))); right_rear = ((int) (255.0f * (((float) (360 - angle)) / 179.0f))); } else if (angle < 315) { center = ((int) (255.0f * (((float) (angle - 135)) / 179.0f))); right = ((int) (255.0f * (((float) (angle - 180)) / 179.0f))); left_rear = ((int) (255.0f * (((float) (450 - angle)) / 179.0f))); right_rear = 255 - ((int) (255.0f * (((float) (angle - 225)) / 89.0f))); } else { right = ((int) (255.0f * (((float) (angle - 180)) / 179.0f))); left_rear = ((int) (255.0f * (((float) (450 - angle)) / 179.0f))); right_rear = 255 - ((int) (255.0f * (((float) (405 - angle)) / 89.0f))); } } if (left < 0) left = 0; if (left > 255) left = 255; if (right < 0) right = 0; if (right > 255) right = 255; if (left_rear < 0) left_rear = 0; if (left_rear > 255) left_rear = 255; if (right_rear < 0) right_rear = 0; if (right_rear > 255) right_rear = 255; if (center < 0) center = 0; if (center > 255) center = 255; if (room_angle == 90) { speaker_amplitude[0] = (Uint8)left_rear; speaker_amplitude[1] = (Uint8)left; speaker_amplitude[2] = (Uint8)right_rear; speaker_amplitude[3] = (Uint8)right; } else if (room_angle == 180) { if (channels == 2) { speaker_amplitude[0] = (Uint8)right; speaker_amplitude[1] = (Uint8)left; } else { speaker_amplitude[0] = (Uint8)right_rear; speaker_amplitude[1] = (Uint8)left_rear; speaker_amplitude[2] = (Uint8)right; speaker_amplitude[3] = (Uint8)left; } } else if (room_angle == 270) { speaker_amplitude[0] = (Uint8)right; speaker_amplitude[1] = (Uint8)right_rear; speaker_amplitude[2] = (Uint8)left; speaker_amplitude[3] = (Uint8)left_rear; } else { speaker_amplitude[0] = (Uint8)left; speaker_amplitude[1] = (Uint8)right; speaker_amplitude[2] = (Uint8)left_rear; speaker_amplitude[3] = (Uint8)right_rear; } speaker_amplitude[4] = (Uint8)center; speaker_amplitude[5] = 255; } bool Mix_SetPanning(int channel, Uint8 left, Uint8 right) { Mix_EffectFunc_t f = NULL; int channels; SDL_AudioFormat format; position_args *args = NULL; bool retval = true; Mix_QuerySpec(NULL, &format, &channels); if (channels != 2 && channels != 4 && channels != 6) /* it's a no-op; we call that successful. */ return true; if (channels > 2) { /* left = right = 255 => angle = 0, to unregister effect as when channels = 2 */ /* left = 255 => angle = -90; left = 0 => angle = +89 */ int angle = 0; if ((left != 255) || (right != 255)) { angle = (int)left; angle = 127 - angle; angle = -angle; angle = angle * 90 / 128; /* Make it larger for more effect? */ } return Mix_SetPosition(channel, angle, 0); } f = get_position_effect_func(format, channels); if (f == NULL) return false; Mix_LockAudio(); args = get_position_arg(channel); if (!args) { Mix_UnlockAudio(); return false; } /* it's a no-op; unregister the effect, if it's registered. */ if ((args->distance_u8 == 255) && (left == 255) && (right == 255)) { if (args->in_use) { retval = _Mix_UnregisterEffect_locked(channel, f); Mix_UnlockAudio(); return retval; } else { Mix_UnlockAudio(); return true; } } args->left_u8 = left; args->left_f = ((float) left) / 255.0f; args->right_u8 = right; args->right_f = ((float) right) / 255.0f; args->room_angle = 0; if (!args->in_use) { args->in_use = 1; retval = _Mix_RegisterEffect_locked(channel, f, _Eff_PositionDone, (void*)args); } Mix_UnlockAudio(); return retval; } bool Mix_SetDistance(int channel, Uint8 distance) { Mix_EffectFunc_t f = NULL; SDL_AudioFormat format; position_args *args = NULL; int channels; bool retval = true; Mix_QuerySpec(NULL, &format, &channels); f = get_position_effect_func(format, channels); if (f == NULL) return false; Mix_LockAudio(); args = get_position_arg(channel); if (!args) { Mix_UnlockAudio(); return false; } distance = 255 - distance; /* flip it to our scale. */ /* it's a no-op; unregister the effect, if it's registered. */ if ((distance == 255) && (args->left_u8 == 255) && (args->right_u8 == 255)) { if (args->in_use) { retval = _Mix_UnregisterEffect_locked(channel, f); Mix_UnlockAudio(); return retval; } else { Mix_UnlockAudio(); return true; } } args->distance_u8 = distance; args->distance_f = ((float) distance) / 255.0f; if (!args->in_use) { args->in_use = 1; retval = _Mix_RegisterEffect_locked(channel, f, _Eff_PositionDone, (void *) args); } Mix_UnlockAudio(); return retval; } bool Mix_SetPosition(int channel, Sint16 angle, Uint8 distance) { Mix_EffectFunc_t f = NULL; SDL_AudioFormat format; int channels; position_args *args = NULL; Sint16 room_angle = 0; bool retval = true; Mix_QuerySpec(NULL, &format, &channels); f = get_position_effect_func(format, channels); if (f == NULL) return false; /* make angle between 0 and 359. */ angle %= 360; if (angle < 0) angle += 360; Mix_LockAudio(); args = get_position_arg(channel); if (!args) { Mix_UnlockAudio(); return false; } /* it's a no-op; unregister the effect, if it's registered. */ if ((!distance) && (!angle)) { if (args->in_use) { retval = _Mix_UnregisterEffect_locked(channel, f); Mix_UnlockAudio(); return retval; } else { Mix_UnlockAudio(); return true; } } if (channels == 2) { if (angle > 180) room_angle = 180; /* exchange left and right channels */ else room_angle = 0; } if (channels == 4 || channels == 6) { if (angle > 315) room_angle = 0; else if (angle > 225) room_angle = 270; else if (angle > 135) room_angle = 180; else if (angle > 45) room_angle = 90; else room_angle = 0; } distance = 255 - distance; /* flip it to scale Mix_SetDistance() uses. */ set_amplitudes(channels, angle, room_angle); args->left_u8 = speaker_amplitude[0]; args->left_f = ((float) speaker_amplitude[0]) / 255.0f; args->right_u8 = speaker_amplitude[1]; args->right_f = ((float) speaker_amplitude[1]) / 255.0f; args->left_rear_u8 = speaker_amplitude[2]; args->left_rear_f = ((float) speaker_amplitude[2]) / 255.0f; args->right_rear_u8 = speaker_amplitude[3]; args->right_rear_f = ((float) speaker_amplitude[3]) / 255.0f; args->center_u8 = speaker_amplitude[4]; args->center_f = ((float) speaker_amplitude[4]) / 255.0f; args->lfe_u8 = speaker_amplitude[5]; args->lfe_f = ((float) speaker_amplitude[5]) / 255.0f; args->distance_u8 = distance; args->distance_f = ((float) distance) / 255.0f; args->room_angle = room_angle; if (!args->in_use) { args->in_use = 1; retval = _Mix_RegisterEffect_locked(channel, f, _Eff_PositionDone, (void *) args); } Mix_UnlockAudio(); return retval; } libsdl3-mixer-3~git20250523~daf0503+ds/src/effect_stereoreverse.c000066400000000000000000000073301501405355700243600ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file by Ryan C. Gordon (icculus@icculus.org) These are some internally supported special effects that use SDL_mixer's effect callback API. They are meant for speed over quality. :) */ #include #define MIX_INTERNAL_EFFECT__ #include "effects_internal.h" /* profile code: #include #include struct timeval tv1; struct timeval tv2; gettimeofday(&tv1, NULL); ... do your thing here ... gettimeofday(&tv2, NULL); printf("%ld\n", tv2.tv_usec - tv1.tv_usec); */ /* * Stereo reversal effect...this one's pretty straightforward... */ static void SDLCALL _Eff_reversestereo32(int chan, void *stream, int len, void *udata) { /* 16 bits * 2 channels. */ Uint32 *ptr = (Uint32 *) stream; Uint32 tmp; int i; (void)chan; (void)udata; for (i = 0; i < len; i += 2 * sizeof(Uint32), ptr += 2) { tmp = ptr[0]; ptr[0] = ptr[1]; ptr[1] = tmp; } } static void SDLCALL _Eff_reversestereo16(int chan, void *stream, int len, void *udata) { /* 16 bits * 2 channels. */ Uint32 *ptr = (Uint32 *) stream; int i; (void)chan; (void)udata; for (i = 0; i < len; i += sizeof(Uint32), ptr++) { *ptr = (((*ptr) & 0xFFFF0000) >> 16) | (((*ptr) & 0x0000FFFF) << 16); } } static void SDLCALL _Eff_reversestereo8(int chan, void *stream, int len, void *udata) { /* 8 bits * 2 channels. */ Uint32 *ptr = (Uint32 *) stream; int i; (void)chan; (void)udata; /* get the last two bytes if len is not divisible by four... */ if (len % (int)sizeof(Uint32) != 0) { Uint16 *p = (Uint16 *) (((Uint8 *) stream) + (len - 2)); *p = (Uint16)((((*p) & 0xFF00) >> 8) | (((*ptr) & 0x00FF) << 8)); len -= 2; } for (i = 0; i < len; i += sizeof(Uint32), ptr++) { *ptr = (((*ptr) & 0x0000FF00) >> 8) | (((*ptr) & 0x000000FF) << 8) | (((*ptr) & 0xFF000000) >> 8) | (((*ptr) & 0x00FF0000) << 8); } } bool Mix_SetReverseStereo(int channel, int flip) { Mix_EffectFunc_t f = NULL; int channels; SDL_AudioFormat format; Mix_QuerySpec(NULL, &format, &channels); if (channels == 2) { int bits = (format & 0xFF); switch (bits) { case 8: f = _Eff_reversestereo8; break; case 16: f = _Eff_reversestereo16; break; case 32: f = _Eff_reversestereo32; break; default: return SDL_SetError("Unsupported audio format"); } if (!flip) { return Mix_UnregisterEffect(channel, f); } return Mix_RegisterEffect(channel, f, NULL, NULL); } return SDL_SetError("Trying to reverse stereo on a non-stereo stream"); } libsdl3-mixer-3~git20250523~daf0503+ds/src/effects_internal.c000066400000000000000000000064111501405355700234610ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. This file by Ryan C. Gordon (icculus@icculus.org) These are some helper functions for the internal mixer special effects. */ /* ------ These are used internally only. Don't touch. ------ */ #include #define MIX_INTERNAL_EFFECT__ #include "effects_internal.h" /* Should we favor speed over memory usage and/or quality of output? */ int _Mix_effects_max_speed = 0; void _Mix_InitEffects(void) { _Mix_effects_max_speed = (SDL_getenv(MIX_EFFECTSMAXSPEED) != NULL); } void _Mix_DeinitEffects(void) { _Eff_PositionDeinit(); } void *_Eff_volume_table = NULL; /* Build the volume table for Uint8-format samples. * * Each column of the table is a possible sample, while each row of the * table is a volume. Volume is a Uint8, where 0 is silence and 255 is full * volume. So _Eff_volume_table[128][mysample] would be the value of * mysample, at half volume. */ void *_Eff_build_volume_table_u8(void) { int volume; int sample; Uint8 *rc; if (!_Mix_effects_max_speed) { return NULL; } if (!_Eff_volume_table) { rc = SDL_malloc(256 * 256); if (rc) { _Eff_volume_table = (void *) rc; for (volume = 0; volume < 256; volume++) { for (sample = -128; sample < 128; sample ++) { *rc = (Uint8)(((float) sample) * ((float) volume / 255.0f)) + 128; rc++; } } } } return _Eff_volume_table; } /* Build the volume table for Sint8-format samples. * * Each column of the table is a possible sample, while each row of the * table is a volume. Volume is a Uint8, where 0 is silence and 255 is full * volume. So _Eff_volume_table[128][mysample+128] would be the value of * mysample, at half volume. */ void *_Eff_build_volume_table_s8(void) { int volume; int sample; Sint8 *rc; if (!_Eff_volume_table) { rc = SDL_malloc(256 * 256); if (rc) { _Eff_volume_table = (void *) rc; for (volume = 0; volume < 256; volume++) { for (sample = -128; sample < 128; sample ++) { *rc = (Sint8)(((float) sample) * ((float) volume / 255.0f)); rc++; } } } } return _Eff_volume_table; } libsdl3-mixer-3~git20250523~daf0503+ds/src/effects_internal.h000066400000000000000000000032261501405355700234670ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef INCLUDE_EFFECTS_INTERNAL_H_ #define INCLUDE_EFFECTS_INTERNAL_H_ #ifndef MIX_INTERNAL_EFFECT__ #error You should not include this file or use these functions. #endif #include extern int _Mix_effects_max_speed; extern void *_Eff_volume_table; void *_Eff_build_volume_table_u8(void); void *_Eff_build_volume_table_s8(void); void _Mix_InitEffects(void); void _Mix_DeinitEffects(void); void _Eff_PositionDeinit(void); bool _Mix_RegisterEffect_locked(int channel, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg); bool _Mix_UnregisterEffect_locked(int channel, Mix_EffectFunc_t f); bool _Mix_UnregisterAllEffects_locked(int channel); #endif /* _INCLUDE_EFFECTS_INTERNAL_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/mixer.c000066400000000000000000001422351501405355700212770ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include "mixer.h" #include "music.h" #include "load_aiff.h" #include "load_voc.h" #include "load_sndfile.h" #define MIX_INTERNAL_EFFECT__ #include "effects_internal.h" /* Magic numbers for various audio file formats */ #define RIFF 0x46464952 /* "RIFF" */ #define WAVE 0x45564157 /* "WAVE" */ #define FORM 0x4d524f46 /* "FORM" */ #define CREA 0x61657243 /* "Crea" */ #if defined(SDL_BUILD_MAJOR_VERSION) SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION, SDL_MIXER_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION); SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION, SDL_MIXER_MINOR_VERSION == SDL_BUILD_MINOR_VERSION); SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION, SDL_MIXER_MICRO_VERSION == SDL_BUILD_MICRO_VERSION); #endif /* Limited by its encoding in SDL_VERSIONNUM */ SDL_COMPILE_TIME_ASSERT(SDL_MIXER_MAJOR_VERSION_min, SDL_MIXER_MAJOR_VERSION >= 0); SDL_COMPILE_TIME_ASSERT(SDL_MIXER_MAJOR_VERSION_max, SDL_MIXER_MAJOR_VERSION <= 10); SDL_COMPILE_TIME_ASSERT(SDL_MIXER_MINOR_VERSION_min, SDL_MIXER_MINOR_VERSION >= 0); SDL_COMPILE_TIME_ASSERT(SDL_MIXER_MINOR_VERSION_max, SDL_MIXER_MINOR_VERSION <= 999); SDL_COMPILE_TIME_ASSERT(SDL_MIXER_MICRO_VERSION_min, SDL_MIXER_MICRO_VERSION >= 0); SDL_COMPILE_TIME_ASSERT(SDL_MIXER_MICRO_VERSION_max, SDL_MIXER_MICRO_VERSION <= 999); static int audio_opened = 0; static SDL_AudioSpec mixer; static SDL_AudioDeviceID audio_device; static SDL_AudioStream *audio_stream; static Uint8 *audio_mixbuf; static int audio_mixbuflen; typedef struct _Mix_effectinfo { Mix_EffectFunc_t callback; Mix_EffectDone_t done_callback; void *udata; struct _Mix_effectinfo *next; } effect_info; static struct _Mix_Channel { Mix_Chunk *chunk; int playing; Uint64 paused; Uint8 *samples; int volume; int looping; int tag; Uint64 expire; Uint64 start_time; Mix_Fading fading; int fade_volume; int fade_volume_reset; Uint64 fade_length; Uint64 ticks_fade; effect_info *effects; } *mix_channel = NULL; static effect_info *posteffects = NULL; static int num_channels; static int reserved_channels = 0; /* Support for hooking into the mixer callback system */ static Mix_MixCallback mix_postmix = NULL; static void *mix_postmix_data = NULL; /* rcg07062001 callback to alert when channels are done playing. */ static Mix_ChannelFinishedCallback channel_done_callback = NULL; /* Support for user defined music functions */ static Mix_MixCallback mix_music = music_mixer; static void *music_data = NULL; /* rcg06042009 report available decoders at runtime. */ static const char **chunk_decoders = NULL; static int num_decoders = 0; static SDL_AtomicInt master_volume = { MIX_MAX_VOLUME }; int Mix_GetNumChunkDecoders(void) { return num_decoders; } const char *Mix_GetChunkDecoder(int index) { if ((index < 0) || (index >= num_decoders)) { return NULL; } return chunk_decoders[index]; } bool Mix_HasChunkDecoder(const char *name) { int index; for (index = 0; index < num_decoders; ++index) { if (SDL_strcasecmp(name, chunk_decoders[index]) == 0) { return true; } } return false; } void add_chunk_decoder(const char *decoder) { int i; void *ptr; /* Check to see if we already have this decoder */ for (i = 0; i < num_decoders; ++i) { if (SDL_strcmp(chunk_decoders[i], decoder) == 0) { return; } } ptr = SDL_realloc((void *)chunk_decoders, (size_t)(num_decoders + 1) * sizeof(const char *)); if (ptr == NULL) { return; /* oh well, go on without it. */ } chunk_decoders = (const char **) ptr; chunk_decoders[num_decoders++] = decoder; } /* rcg06192001 get linked library's version. */ int Mix_Version(void) { return SDL_MIXER_VERSION; } /* * Returns a bitmask of already loaded modules (MIX_INIT_* flags). * * Note that functions other than Mix_Init() may cause a module to get loaded * (hence the looping over the interfaces instead of maintaining a set of flags * just in Mix_Init() and Mix_Quit()). */ static MIX_InitFlags get_loaded_mix_init_flags(void) { int i; MIX_InitFlags loaded_init_flags = 0; for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface; interface = get_music_interface(i); if (interface->loaded) { switch (interface->type) { case MUS_FLAC: loaded_init_flags |= MIX_INIT_FLAC; break; case MUS_WAVPACK: loaded_init_flags |= MIX_INIT_WAVPACK; break; case MUS_MOD: loaded_init_flags |= MIX_INIT_MOD; break; case MUS_MP3: loaded_init_flags |= MIX_INIT_MP3; break; case MUS_OGG: loaded_init_flags |= MIX_INIT_OGG; break; case MUS_MID: loaded_init_flags |= MIX_INIT_MID; break; case MUS_OPUS: loaded_init_flags |= MIX_INIT_OPUS; break; default: break; } } } return loaded_init_flags; } MIX_InitFlags Mix_Init(MIX_InitFlags flags) { MIX_InitFlags result = 0; MIX_InitFlags already_loaded = get_loaded_mix_init_flags(); if (flags & MIX_INIT_FLAC) { if (load_music_type(MUS_FLAC)) { open_music_type(MUS_FLAC); result |= MIX_INIT_FLAC; } else { SDL_SetError("FLAC support not available"); } } if (flags & MIX_INIT_WAVPACK) { if (load_music_type(MUS_WAVPACK)) { open_music_type(MUS_WAVPACK); result |= MIX_INIT_WAVPACK; } else { SDL_SetError("WavPack support not available"); } } if (flags & MIX_INIT_MOD) { if (load_music_type(MUS_MOD)) { open_music_type(MUS_MOD); result |= MIX_INIT_MOD; } else { SDL_SetError("MOD support not available"); } } if (flags & MIX_INIT_MP3) { if (load_music_type(MUS_MP3)) { open_music_type(MUS_MP3); result |= MIX_INIT_MP3; } else { SDL_SetError("MP3 support not available"); } } if (flags & MIX_INIT_OGG) { if (load_music_type(MUS_OGG)) { open_music_type(MUS_OGG); result |= MIX_INIT_OGG; } else { SDL_SetError("OGG support not available"); } } if (flags & MIX_INIT_OPUS) { if (load_music_type(MUS_OPUS)) { open_music_type(MUS_OPUS); result |= MIX_INIT_OPUS; } else { SDL_SetError("OPUS support not available"); } } if (flags & MIX_INIT_MID) { if (load_music_type(MUS_MID)) { open_music_type(MUS_MID); result |= MIX_INIT_MID; } else { SDL_SetError("MIDI support not available"); } } result |= already_loaded; return result; } void Mix_Quit(void) { unload_music(); SNDFILE_uninit(); } static bool _Mix_remove_all_effects(int channel, effect_info **e); /* * rcg06122001 Cleanup effect callbacks. * MAKE SURE Mix_LockAudio() is called before this (or you're in the * audio callback). */ static void _Mix_channel_done_playing(int channel) { if (channel_done_callback) { channel_done_callback(channel); } /* * Call internal function directly, to avoid locking audio from * inside audio callback. */ _Mix_remove_all_effects(channel, &mix_channel[channel].effects); } static void *Mix_DoEffects(int chan, void *snd, int len) { int posteffect = (chan == MIX_CHANNEL_POST); effect_info *e = ((posteffect) ? posteffects : mix_channel[chan].effects); void *buf = snd; if (e != NULL) { /* are there any registered effects? */ /* if this is the postmix, we can just overwrite the original. */ if (!posteffect) { buf = SDL_malloc((size_t)len); if (buf == NULL) { return snd; } SDL_memcpy(buf, snd, (size_t)len); } for (; e != NULL; e = e->next) { if (e->callback != NULL) { e->callback(chan, buf, len, e->udata); } } } /* be sure to SDL_free() the return value if != snd ... */ return buf; } /* Mixing function */ static void SDLCALL mix_channels(void *udata, SDL_AudioStream *astream, int len, int total) { Uint8 *stream; Uint8 *mix_input; int i, mixable, master_vol; Uint64 sdl_ticks; (void)udata; (void)total; if (audio_mixbuflen < len) { void *ptr = SDL_aligned_alloc(SDL_GetSIMDAlignment(), len); if (!ptr) { return; // oh well. } SDL_aligned_free(audio_mixbuf); audio_mixbuf = (Uint8 *) ptr; audio_mixbuflen = len; } stream = audio_mixbuf; /* Need to initialize the stream in SDL 1.3+ */ SDL_memset(stream, SDL_GetSilenceValueForFormat(mixer.format), (size_t)len); /* Mix the music (must be done before the channels are added) */ mix_music(music_data, stream, len); master_vol = SDL_GetAtomicInt(&master_volume); /* Mix any playing channels... */ sdl_ticks = SDL_GetTicks(); for (i = 0; i < num_channels; ++i) { if (!mix_channel[i].paused) { if (mix_channel[i].expire > 0 && mix_channel[i].expire < sdl_ticks) { /* Expiration delay for that channel is reached */ mix_channel[i].playing = 0; mix_channel[i].looping = 0; mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].expire = 0; _Mix_channel_done_playing(i); } else if (mix_channel[i].fading != MIX_NO_FADING) { Uint64 ticks = sdl_ticks - mix_channel[i].ticks_fade; if (ticks >= mix_channel[i].fade_length) { Mix_Volume(i, mix_channel[i].fade_volume_reset); /* Restore the volume */ if (mix_channel[i].fading == MIX_FADING_OUT) { mix_channel[i].playing = 0; mix_channel[i].looping = 0; mix_channel[i].expire = 0; _Mix_channel_done_playing(i); } mix_channel[i].fading = MIX_NO_FADING; } else { if (mix_channel[i].fading == MIX_FADING_OUT) { int volume = (int)((mix_channel[i].fade_volume * (mix_channel[i].fade_length - ticks)) / mix_channel[i].fade_length); Mix_Volume(i, volume); } else { int volume = (int)((mix_channel[i].fade_volume * ticks) / mix_channel[i].fade_length); Mix_Volume(i, volume); } } } if (mix_channel[i].playing > 0) { int volume = (master_vol * (mix_channel[i].volume * mix_channel[i].chunk->volume)) / (MIX_MAX_VOLUME * MIX_MAX_VOLUME); float fvolume = (float)volume / (float)MIX_MAX_VOLUME; int index = 0; int remaining = len; while (mix_channel[i].playing > 0 && index < len) { remaining = len - index; mixable = mix_channel[i].playing; if (mixable > remaining) { mixable = remaining; } mix_input = Mix_DoEffects(i, mix_channel[i].samples, mixable); SDL_MixAudio(stream+index, mix_input, mixer.format, mixable, fvolume); if (mix_input != mix_channel[i].samples) SDL_free(mix_input); mix_channel[i].samples += mixable; mix_channel[i].playing -= mixable; index += mixable; /* rcg06072001 Alert app if channel is done playing. */ if (!mix_channel[i].playing && !mix_channel[i].looping) { mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].expire = 0; _Mix_channel_done_playing(i); /* Update the volume after the application callback */ volume = (master_vol * (mix_channel[i].volume * mix_channel[i].chunk->volume)) / (MIX_MAX_VOLUME * MIX_MAX_VOLUME); fvolume = (float)volume / (float)MIX_MAX_VOLUME; } } /* If looping the sample and we are at its end, make sure we will still return a full buffer */ while (mix_channel[i].looping && index < len) { int alen = mix_channel[i].chunk->alen; remaining = len - index; if (remaining > alen) { remaining = alen; } mix_input = Mix_DoEffects(i, mix_channel[i].chunk->abuf, remaining); SDL_MixAudio(stream+index, mix_input, mixer.format, remaining, fvolume); if (mix_input != mix_channel[i].chunk->abuf) SDL_free(mix_input); if (mix_channel[i].looping > 0) { --mix_channel[i].looping; } mix_channel[i].samples = mix_channel[i].chunk->abuf + remaining; mix_channel[i].playing = mix_channel[i].chunk->alen - remaining; index += remaining; } if (! mix_channel[i].playing && mix_channel[i].looping) { if (mix_channel[i].looping > 0) { --mix_channel[i].looping; } mix_channel[i].samples = mix_channel[i].chunk->abuf; mix_channel[i].playing = mix_channel[i].chunk->alen; } } } } /* rcg06122001 run posteffects... */ Mix_DoEffects(MIX_CHANNEL_POST, stream, len); if (mix_postmix) { mix_postmix(mix_postmix_data, stream, len); } SDL_PutAudioStreamData(astream, audio_mixbuf, len); } #if 0 static void PrintFormat(char *title, SDL_AudioSpec *fmt) { printf("%s: %d bit %s audio (%s) at %u Hz\n", title, (fmt->format&0xFF), (fmt->format&0x8000) ? "signed" : "unsigned", (fmt->channels > 2) ? "surround" : (fmt->channels > 1) ? "stereo" : "mono", fmt->freq); } #endif /* Open the mixer with a certain desired audio format */ bool Mix_OpenAudio(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { int i; if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { return false; } } /* If the mixer is already opened, increment open count */ if (audio_opened) { if (spec && (spec->format == mixer.format) && (spec->channels == mixer.channels)) { ++audio_opened; return true; } while (audio_opened) { Mix_CloseAudio(); } } if (devid == 0) { devid = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; } if ((audio_device = SDL_OpenAudioDevice(devid, spec)) == 0) { return false; } SDL_GetAudioDeviceFormat(audio_device, &mixer, NULL); audio_stream = SDL_CreateAudioStream(&mixer, &mixer); if (!audio_stream) { SDL_CloseAudioDevice(audio_device); audio_device = 0; return false; } #if 0 PrintFormat("Audio device", &mixer); #endif num_channels = MIX_CHANNELS; mix_channel = (struct _Mix_Channel *) SDL_malloc(num_channels * sizeof(struct _Mix_Channel)); /* Clear out the audio channels */ for (i = 0; i < num_channels; ++i) { mix_channel[i].chunk = NULL; mix_channel[i].playing = 0; mix_channel[i].looping = 0; mix_channel[i].volume = MIX_MAX_VOLUME; mix_channel[i].fade_volume = MIX_MAX_VOLUME; mix_channel[i].fade_volume_reset = MIX_MAX_VOLUME; mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].tag = -1; mix_channel[i].expire = 0; mix_channel[i].effects = NULL; mix_channel[i].paused = 0; } Mix_VolumeMusic(MIX_MAX_VOLUME); _Mix_InitEffects(); add_chunk_decoder("WAVE"); add_chunk_decoder("AIFF"); add_chunk_decoder("VOC"); /* Initialize the music players */ open_music(&mixer); SDL_BindAudioStream(audio_device, audio_stream); SDL_SetAudioStreamGetCallback(audio_stream, mix_channels, NULL); audio_opened = 1; return true; } /* Pause or resume the audio streaming */ void Mix_PauseAudio(int pause_on) { if (pause_on) { SDL_PauseAudioDevice(audio_device); } else { SDL_ResumeAudioDevice(audio_device); } Mix_LockAudio(); pause_async_music(pause_on); Mix_UnlockAudio(); } /* Dynamically change the number of channels managed by the mixer. If decreasing the number of channels, the upper channels are stopped. */ int Mix_AllocateChannels(int numchans) { struct _Mix_Channel *mix_channel_tmp; if (numchans<0 || numchans==num_channels) return num_channels; if (numchans < num_channels) { /* Stop the affected channels */ int i; for (i = numchans; i < num_channels; i++) { Mix_UnregisterAllEffects(i); Mix_HaltChannel(i); } } Mix_LockAudio(); /* Allocate channels into temporary pointer */ if (numchans) { mix_channel_tmp = (struct _Mix_Channel *) SDL_realloc(mix_channel, numchans * sizeof(struct _Mix_Channel)); } else { /* Handle 0 numchans */ SDL_free(mix_channel); mix_channel_tmp = NULL; } /* Check the allocation */ if (mix_channel_tmp || !numchans) { /* Apply the temporary pointer on success */ mix_channel = mix_channel_tmp; if (numchans > num_channels) { /* Initialize the new channels */ int i; for (i = num_channels; i < numchans; i++) { mix_channel[i].chunk = NULL; mix_channel[i].playing = 0; mix_channel[i].looping = 0; mix_channel[i].volume = MIX_MAX_VOLUME; mix_channel[i].fade_volume = MIX_MAX_VOLUME; mix_channel[i].fade_volume_reset = MIX_MAX_VOLUME; mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].tag = -1; mix_channel[i].expire = 0; mix_channel[i].effects = NULL; mix_channel[i].paused = 0; } } num_channels = numchans; } else { /* On error mix_channel remains intact */ SDL_SetError("Channel allocation failed"); } Mix_UnlockAudio(); return num_channels; /* If the return value equals numchans the allocation was successful */ } /* Return the actual mixer parameters */ bool Mix_QuerySpec(int *frequency, SDL_AudioFormat *format, int *channels) { if (audio_opened) { if (frequency) { *frequency = mixer.freq; } if (format) { *format = mixer.format; } if (channels) { *channels = mixer.channels; } } return (audio_opened > 0); } typedef struct _MusicFragment { Uint8 *data; int size; struct _MusicFragment *next; } MusicFragment; static SDL_AudioSpec *Mix_LoadMusic_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { int i; Mix_MusicType music_type; Mix_MusicInterface *interface = NULL; void *music = NULL; Sint64 start; bool playing; MusicFragment *first = NULL, *last = NULL, *fragment = NULL; int count = 0; int fragment_size; music_type = detect_music_type(src); if (!load_music_type(music_type) || !open_music_type(music_type)) { return NULL; } *spec = mixer; /* Use fragments sized on full audio frame boundaries - this'll do */ fragment_size = 4096/*spec->samples*/ * (SDL_AUDIO_BITSIZE(spec->format) / 8) * spec->channels; start = SDL_TellIO(src); for (i = 0; i < get_num_music_interfaces(); ++i) { interface = get_music_interface(i); if (!interface->opened) { continue; } if (interface->type != music_type) { continue; } if (!interface->CreateFromIO || !interface->GetAudio) { continue; } /* These music interfaces are not safe to use while music is playing */ if (interface->api == MIX_MUSIC_NATIVEMIDI) { continue; } music = interface->CreateFromIO(src, closeio); if (music) { /* The interface owns the data source now */ closeio = false; break; } /* Reset the stream for the next decoder */ SDL_SeekIO(src, start, SDL_IO_SEEK_SET); } if (!music) { if (closeio) { SDL_CloseIO(src); } SDL_SetError("Unrecognized audio format"); return NULL; } Mix_LockAudio(); if (interface->Play) { interface->Play(music, 1); } playing = true; while (playing) { int left; fragment = (MusicFragment *)SDL_malloc(sizeof(*fragment)); if (!fragment) { /* Uh oh, out of memory, let's return what we have */ break; } fragment->data = (Uint8 *)SDL_malloc(fragment_size); if (!fragment->data) { /* Uh oh, out of memory, let's return what we have */ SDL_free(fragment); break; } fragment->next = NULL; left = interface->GetAudio(music, fragment->data, fragment_size); if (left > 0) { playing = false; } else if (interface->IsPlaying) { playing = interface->IsPlaying(music); } fragment->size = (fragment_size - left); if (!first) { first = fragment; } if (last) { last->next = fragment; } last = fragment; ++count; } if (interface->Stop) { interface->Stop(music); } if (music) { interface->Delete(music); } Mix_UnlockAudio(); if (count > 0) { *audio_len = (count - 1) * fragment_size + last->size; *audio_buf = (Uint8 *)SDL_malloc(*audio_len); if (*audio_buf) { Uint8 *dst = *audio_buf; for (fragment = first; fragment; fragment = fragment->next) { SDL_memcpy(dst, fragment->data, fragment->size); dst += fragment->size; } } else { spec = NULL; } } else { SDL_SetError("No audio data"); spec = NULL; } while (first) { fragment = first; first = first->next; SDL_free(fragment->data); SDL_free(fragment); } if (closeio) { SDL_CloseIO(src); } return spec; } /* Load a wave file */ Mix_Chunk *Mix_LoadWAV_IO(SDL_IOStream *src, bool closeio) { Uint8 magic[4]; Mix_Chunk *chunk; SDL_AudioSpec wavespec, *loaded; /* rcg06012001 Make sure src is valid */ if (!src) { SDL_SetError("Mix_LoadWAV_IO with NULL src"); return NULL; } /* Make sure audio has been opened */ if (!audio_opened) { SDL_SetError("Audio device hasn't been opened"); if (closeio) { SDL_CloseIO(src); } return NULL; } /* Allocate the chunk memory */ chunk = (Mix_Chunk *)SDL_malloc(sizeof(Mix_Chunk)); if (chunk == NULL) { if (closeio) { SDL_CloseIO(src); } return NULL; } /* Find out what kind of audio file this is */ if (SDL_ReadIO(src, magic, 4) != 4) { SDL_free(chunk); if (closeio) { SDL_CloseIO(src); } SDL_SetError("Couldn't read first 4 bytes of audio data"); return NULL; } /* Seek backwards for compatibility with older loaders */ SDL_SeekIO(src, -4, SDL_IO_SEEK_CUR); /* First try loading via libsndfile */ SDL_zero(wavespec); loaded = Mix_LoadSndFile_IO(src, closeio, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); if (!loaded) { if (SDL_memcmp(magic, "WAVE", 4) == 0 || SDL_memcmp(magic, "RIFF", 4) == 0) { loaded = SDL_LoadWAV_IO(src, closeio, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen) ? &wavespec : NULL; } else if (SDL_memcmp(magic, "FORM", 4) == 0) { loaded = Mix_LoadAIFF_IO(src, closeio, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); } else if (SDL_memcmp(magic, "Crea", 4) == 0) { loaded = Mix_LoadVOC_IO(src, closeio, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); } else { loaded = Mix_LoadMusic_IO(src, closeio, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); } } if (!loaded) { /* The individual loaders have closed src if needed */ SDL_free(chunk); return NULL; } #if 0 PrintFormat("Audio device", &mixer); PrintFormat("-- Wave file", &wavespec); #endif chunk->allocated = 1; chunk->volume = MIX_MAX_VOLUME; /* Build the audio converter and create conversion buffers */ if (wavespec.format != mixer.format || wavespec.channels != mixer.channels || wavespec.freq != mixer.freq) { Uint8 *dst_data = NULL; int dst_len = 0; if (!SDL_ConvertAudioSamples(&wavespec, chunk->abuf, chunk->alen, &mixer, &dst_data, &dst_len)) { SDL_free(chunk->abuf); SDL_free(chunk); return NULL; } SDL_free(chunk->abuf); chunk->abuf = dst_data; chunk->alen = dst_len; } return chunk; } Mix_Chunk *Mix_LoadWAV(const char *file) { return Mix_LoadWAV_IO(SDL_IOFromFile(file, "rb"), 1); } /* Load a wave file of the mixer format from a memory buffer */ Mix_Chunk *Mix_QuickLoad_WAV(Uint8 *mem) { Mix_Chunk *chunk; Uint8 magic[4]; /* Make sure audio has been opened */ if (! audio_opened) { SDL_SetError("Audio device hasn't been opened"); return NULL; } /* Allocate the chunk memory */ chunk = (Mix_Chunk *)SDL_calloc(1,sizeof(Mix_Chunk)); if (chunk == NULL) { return NULL; } /* Essentially just skip to the audio data (no error checking - fast) */ chunk->allocated = 0; mem += 12; /* WAV header */ do { SDL_memcpy(magic, mem, 4); mem += 4; chunk->alen = ((mem[3]<<24)|(mem[2]<<16)|(mem[1]<<8)|(mem[0])); mem += 4; chunk->abuf = mem; mem += chunk->alen; } while (SDL_memcmp(magic, "data", 4) != 0); chunk->volume = MIX_MAX_VOLUME; return chunk; } /* Load raw audio data of the mixer format from a memory buffer */ Mix_Chunk *Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len) { Mix_Chunk *chunk; /* Make sure audio has been opened */ if (! audio_opened) { SDL_SetError("Audio device hasn't been opened"); return NULL; } /* Allocate the chunk memory */ chunk = (Mix_Chunk *)SDL_malloc(sizeof(Mix_Chunk)); if (chunk == NULL) { return NULL; } /* Essentially just point at the audio data (no error checking - fast) */ chunk->allocated = 0; chunk->alen = len; chunk->abuf = mem; chunk->volume = MIX_MAX_VOLUME; return chunk; } /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ static void Mix_HaltChannel_locked(int which) { if (Mix_Playing(which)) { mix_channel[which].playing = 0; mix_channel[which].looping = 0; _Mix_channel_done_playing(which); } mix_channel[which].expire = 0; if (mix_channel[which].fading != MIX_NO_FADING) /* Restore volume */ mix_channel[which].volume = mix_channel[which].fade_volume_reset; mix_channel[which].fading = MIX_NO_FADING; } /* Free an audio chunk previously loaded */ void Mix_FreeChunk(Mix_Chunk *chunk) { int i; /* Caution -- if the chunk is playing, the mixer will crash */ if (chunk) { /* Guarantee that this chunk isn't playing */ Mix_LockAudio(); if (mix_channel) { for (i = 0; i < num_channels; ++i) { if (chunk == mix_channel[i].chunk) { Mix_HaltChannel_locked(i); } } } Mix_UnlockAudio(); /* Actually free the chunk */ if (chunk->allocated) { SDL_free(chunk->abuf); } SDL_free(chunk); } } /* Set a function that is called after all mixing is performed. This can be used to provide real-time visual display of the audio stream or add a custom mixer filter for the stream data. */ void Mix_SetPostMix(Mix_MixCallback mix_func, void *arg) { Mix_LockAudio(); mix_postmix_data = arg; mix_postmix = mix_func; Mix_UnlockAudio(); } /* Add your own music player or mixer function. If 'mix_func' is NULL, the default music player is re-enabled. */ void Mix_HookMusic(Mix_MixCallback mix_func, void *arg) { Mix_LockAudio(); if (mix_func != NULL) { music_data = arg; mix_music = mix_func; } else { music_data = NULL; mix_music = music_mixer; } Mix_UnlockAudio(); } void *Mix_GetMusicHookData(void) { return music_data; } void Mix_ChannelFinished(Mix_ChannelFinishedCallback channel_finished) { Mix_LockAudio(); channel_done_callback = channel_finished; Mix_UnlockAudio(); } /* Reserve the first channels (0 -> n-1) for the application, i.e. don't allocate them dynamically to the next sample if requested with a -1 value below. Returns the number of reserved channels. */ int Mix_ReserveChannels(int num) { if (num < 0) num = 0; if (num > num_channels) num = num_channels; reserved_channels = num; return num; } static int checkchunkintegral(Mix_Chunk *chunk) { int frame_width = 1; if ((mixer.format & 0xFF) == 16) frame_width = 2; frame_width *= mixer.channels; while (chunk->alen % frame_width) chunk->alen--; return chunk->alen; } /* Play an audio chunk on a specific channel. If the specified channel is -1, play on the first free channel. 'ticks' is the number of milliseconds at most to play the sample, or -1 if there is no limit. Returns which channel was used to play the sound. */ int Mix_PlayChannelTimed(int which, Mix_Chunk *chunk, int loops, int ticks) { int i; /* Don't play null pointers :-) */ if (chunk == NULL) { SDL_SetError("Tried to play a NULL chunk"); return -1; } if (!checkchunkintegral(chunk)) { SDL_SetError("Tried to play a chunk with a bad frame"); return -1; } /* Lock the mixer while modifying the playing channels */ Mix_LockAudio(); { /* If which is -1, play on the first free channel */ if (which == -1) { for (i = reserved_channels; i < num_channels; ++i) { if (!Mix_Playing(i)) break; } if (i == num_channels) { SDL_SetError("No free channels available"); which = -1; } else { which = i; } } else { if (Mix_Playing(which)) _Mix_channel_done_playing(which); } /* Queue up the audio data for this channel */ if (which >= 0 && which < num_channels) { Uint64 sdl_ticks = SDL_GetTicks(); mix_channel[which].samples = chunk->abuf; mix_channel[which].playing = (int)chunk->alen; mix_channel[which].looping = loops; mix_channel[which].chunk = chunk; mix_channel[which].paused = 0; mix_channel[which].fading = MIX_NO_FADING; mix_channel[which].start_time = sdl_ticks; mix_channel[which].expire = (ticks > 0) ? (sdl_ticks + ticks) : 0; } } Mix_UnlockAudio(); /* Return the channel on which the sound is being played */ return which; } int Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops) { return Mix_PlayChannelTimed(channel, chunk, loops, -1); } /* Change the expiration delay for a channel */ int Mix_ExpireChannel(int which, int ticks) { int status = 0; if (which == -1) { int i; for (i = 0; i < num_channels; ++i) { status += Mix_ExpireChannel(i, ticks); } } else if (which < num_channels) { Mix_LockAudio(); mix_channel[which].expire = (ticks>0) ? (SDL_GetTicks() + (Uint32)ticks) : 0; Mix_UnlockAudio(); ++status; } return status; } /* Fade in a sound on a channel, over ms milliseconds */ int Mix_FadeInChannelTimed(int which, Mix_Chunk *chunk, int loops, int ms, int ticks) { int i; /* Don't play null pointers :-) */ if (chunk == NULL) { return -1; } if (!checkchunkintegral(chunk)) { SDL_SetError("Tried to play a chunk with a bad frame"); return -1; } /* Lock the mixer while modifying the playing channels */ Mix_LockAudio(); { /* If which is -1, play on the first free channel */ if (which == -1) { for (i = reserved_channels; i < num_channels; ++i) { if (!Mix_Playing(i)) break; } if (i == num_channels) { which = -1; } else { which = i; } } else { if (Mix_Playing(which)) _Mix_channel_done_playing(which); } /* Queue up the audio data for this channel */ if (which >= 0 && which < num_channels) { Uint64 sdl_ticks = SDL_GetTicks(); mix_channel[which].samples = chunk->abuf; mix_channel[which].playing = (int)chunk->alen; mix_channel[which].looping = loops; mix_channel[which].chunk = chunk; mix_channel[which].paused = 0; if (mix_channel[which].fading == MIX_NO_FADING) { mix_channel[which].fade_volume_reset = mix_channel[which].volume; } mix_channel[which].fading = MIX_FADING_IN; mix_channel[which].fade_volume = mix_channel[which].volume; mix_channel[which].volume = 0; mix_channel[which].fade_length = (Uint64)ms; mix_channel[which].start_time = mix_channel[which].ticks_fade = sdl_ticks; mix_channel[which].expire = (ticks > 0) ? (sdl_ticks + ticks) : 0; } } Mix_UnlockAudio(); /* Return the channel on which the sound is being played */ return which; } int Mix_FadeInChannel(int channel, Mix_Chunk *chunk, int loops, int ms) { return Mix_FadeInChannelTimed(channel, chunk, loops, ms, -1); } /* Set volume of a particular channel */ int Mix_Volume(int which, int volume) { int i; int prev_volume = 0; if (which == -1) { for (i = 0; i < num_channels; ++i) { prev_volume += Mix_Volume(i, volume); } prev_volume /= num_channels; } else if (which < num_channels) { prev_volume = mix_channel[which].volume; if (volume >= 0) { if (volume > MIX_MAX_VOLUME) { volume = MIX_MAX_VOLUME; } mix_channel[which].volume = volume; } } return prev_volume; } /* Set volume of a particular chunk */ int Mix_VolumeChunk(Mix_Chunk *chunk, int volume) { int prev_volume; if (chunk == NULL) { return -1; } prev_volume = chunk->volume; if (volume >= 0) { if (volume > MIX_MAX_VOLUME) { volume = MIX_MAX_VOLUME; } chunk->volume = volume; } return prev_volume; } /* Halt playing of a particular channel */ void Mix_HaltChannel(int which) { int i; Mix_LockAudio(); if (which == -1) { for (i = 0; i < num_channels; ++i) { Mix_HaltChannel_locked(i); } } else if (which < num_channels) { Mix_HaltChannel_locked(which); } Mix_UnlockAudio(); } /* Halt playing of a particular group of channels */ void Mix_HaltGroup(int tag) { int i; for (i = 0; i < num_channels; ++i) { if (mix_channel[i].tag == tag) { Mix_HaltChannel(i); } } } /* Fade out a channel and then stop it automatically */ int Mix_FadeOutChannel(int which, int ms) { int status; status = 0; if (audio_opened) { if (which == -1) { int i; for (i = 0; i < num_channels; ++i) { status += Mix_FadeOutChannel(i, ms); } } else if (which < num_channels) { Mix_LockAudio(); if (Mix_Playing(which) && (mix_channel[which].volume > 0) && (mix_channel[which].fading != MIX_FADING_OUT)) { mix_channel[which].fade_volume = mix_channel[which].volume; mix_channel[which].fade_length = (Uint64)ms; mix_channel[which].ticks_fade = SDL_GetTicks(); /* only change fade_volume_reset if we're not fading. */ if (mix_channel[which].fading == MIX_NO_FADING) { mix_channel[which].fade_volume_reset = mix_channel[which].volume; } mix_channel[which].fading = MIX_FADING_OUT; ++status; } Mix_UnlockAudio(); } } return status; } /* Halt playing of a particular group of channels */ int Mix_FadeOutGroup(int tag, int ms) { int i; int status = 0; for (i = 0; i < num_channels; ++i) { if (mix_channel[i].tag == tag) { status += Mix_FadeOutChannel(i,ms); } } return status; } Mix_Fading Mix_FadingChannel(int which) { if (which < 0 || which >= num_channels) { return MIX_NO_FADING; } return mix_channel[which].fading; } /* Check the status of a specific channel. If the specified mix_channel is -1, check all mix channels. */ int Mix_Playing(int which) { int status; status = 0; if (which == -1) { int i; for (i = 0; i < num_channels; ++i) { if ((mix_channel[i].playing > 0) || mix_channel[i].looping) { ++status; } } } else if (which < num_channels) { if ((mix_channel[which].playing > 0) || mix_channel[which].looping) { ++status; } } return status; } /* rcg06072001 Get the chunk associated with a channel. */ Mix_Chunk *Mix_GetChunk(int channel) { Mix_Chunk *retval = NULL; if ((channel >= 0) && (channel < num_channels)) { retval = mix_channel[channel].chunk; } return retval; } /* Close the mixer, halting all playing audio */ void Mix_CloseAudio(void) { int i; if (audio_opened) { if (audio_opened == 1) { for (i = 0; i < num_channels; i++) { Mix_UnregisterAllEffects(i); } Mix_UnregisterAllEffects(MIX_CHANNEL_POST); close_music(); Mix_HaltChannel(-1); _Mix_DeinitEffects(); SDL_DestroyAudioStream(audio_stream); audio_stream = NULL; SDL_CloseAudioDevice(audio_device); audio_device = 0; SDL_free(mix_channel); mix_channel = NULL; SDL_aligned_free(audio_mixbuf); audio_mixbuf = NULL; audio_mixbuflen = 0; /* rcg06042009 report available decoders at runtime. */ SDL_free((void *)chunk_decoders); chunk_decoders = NULL; num_decoders = 0; } --audio_opened; } } /* Pause a particular channel (or all) */ void Mix_Pause(int which) { Uint64 sdl_ticks = SDL_GetTicks(); if (which == -1) { int i; for (i = 0; i < num_channels; ++i) { if (Mix_Playing(i)) { mix_channel[i].paused = sdl_ticks; } } } else if (which < num_channels) { if (Mix_Playing(which)) { mix_channel[which].paused = sdl_ticks; } } } /* Pause playing of a particular group of channels */ void Mix_PauseGroup(int tag) { int i; for (i=0; i 0) { mix_channel[i].expire += sdl_ticks - mix_channel[i].paused; } mix_channel[i].paused = 0; } } } else if (which < num_channels) { if (Mix_Playing(which)) { if (mix_channel[which].expire > 0) { mix_channel[which].expire += sdl_ticks - mix_channel[which].paused; } mix_channel[which].paused = 0; } } Mix_UnlockAudio(); } /* Resume playing of a particular group of channels */ void Mix_ResumeGroup(int tag) { int i; for (i=0; i num_channels) { return false; } Mix_LockAudio(); mix_channel[which].tag = tag; Mix_UnlockAudio(); return true; } /* Assign several consecutive channels to a group */ bool Mix_GroupChannels(int from, int to, int tag) { bool status = true; for (; from <= to; ++from) { status &= Mix_GroupChannel(from, tag); } return status; } /* Finds the first available channel in a group of channels */ int Mix_GroupAvailable(int tag) { int i; for (i = 0; i < num_channels; i++) { if ((tag == -1 || tag == mix_channel[i].tag) && !Mix_Playing(i)) { return i; } } return -1; } int Mix_GroupCount(int tag) { int count = 0; int i; if (tag == -1) { return num_channels; /* minor optimization; no need to go through the loop. */ } for (i = 0; i < num_channels; i++) { if (mix_channel[i].tag == tag) { ++count; } } return count; } /* Finds the "oldest" sample playing in a group of channels */ int Mix_GroupOldest(int tag) { int chan = -1; Uint64 mintime = SDL_GetTicks(); int i; for (i = 0; i < num_channels; i++) { if ((mix_channel[i].tag == tag || tag == -1) && Mix_Playing(i) && mix_channel[i].start_time <= mintime) { mintime = mix_channel[i].start_time; chan = i; } } return chan; } /* Finds the "most recent" (i.e. last) sample playing in a group of channels */ int Mix_GroupNewer(int tag) { int chan = -1; Uint64 maxtime = 0; int i; for (i = 0; i < num_channels; i++) { if ((mix_channel[i].tag == tag || tag == -1) && Mix_Playing(i) && mix_channel[i].start_time >= maxtime) { maxtime = mix_channel[i].start_time; chan = i; } } return chan; } /* * rcg06122001 The special effects exportable API. * Please see effect_*.c for internally-implemented effects, such * as Mix_SetPanning(). */ /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ static bool _Mix_register_effect(effect_info **e, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg) { effect_info *new_e; if (!e) { return SDL_SetError("Internal error"); } if (f == NULL) { return SDL_SetError("NULL effect callback"); } new_e = SDL_malloc(sizeof(effect_info)); if (new_e == NULL) { return false; } new_e->callback = f; new_e->done_callback = d; new_e->udata = arg; new_e->next = NULL; /* add new effect to end of linked list... */ if (*e == NULL) { *e = new_e; } else { effect_info *cur = *e; while (1) { if (cur->next == NULL) { cur->next = new_e; break; } cur = cur->next; } } return true; } /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ static bool _Mix_remove_effect(int channel, effect_info **e, Mix_EffectFunc_t f) { effect_info *cur; effect_info *prev = NULL; effect_info *next = NULL; if (!e) { return SDL_SetError("Internal error"); } for (cur = *e; cur != NULL; cur = cur->next) { if (cur->callback == f) { next = cur->next; if (cur->done_callback != NULL) { cur->done_callback(channel, cur->udata); } SDL_free(cur); if (prev == NULL) { /* removing first item of list? */ *e = next; } else { prev->next = next; } return true; } prev = cur; } return SDL_SetError("No such effect registered"); } /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ static bool _Mix_remove_all_effects(int channel, effect_info **e) { effect_info *cur; effect_info *next; if (!e) { return SDL_SetError("Internal error"); } for (cur = *e; cur != NULL; cur = next) { next = cur->next; if (cur->done_callback != NULL) { cur->done_callback(channel, cur->udata); } SDL_free(cur); } *e = NULL; return true; } /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ bool _Mix_RegisterEffect_locked(int channel, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg) { effect_info **e = NULL; if (channel == MIX_CHANNEL_POST) { e = &posteffects; } else { if ((channel < 0) || (channel >= num_channels)) { return SDL_SetError("Invalid channel number"); } e = &mix_channel[channel].effects; } return _Mix_register_effect(e, f, d, arg); } bool Mix_RegisterEffect(int channel, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg) { bool retval; Mix_LockAudio(); retval = _Mix_RegisterEffect_locked(channel, f, d, arg); Mix_UnlockAudio(); return retval; } /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ bool _Mix_UnregisterEffect_locked(int channel, Mix_EffectFunc_t f) { effect_info **e = NULL; if (channel == MIX_CHANNEL_POST) { e = &posteffects; } else { if ((channel < 0) || (channel >= num_channels)) { return SDL_SetError("Invalid channel number"); } e = &mix_channel[channel].effects; } return _Mix_remove_effect(channel, e, f); } bool Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f) { bool retval; Mix_LockAudio(); retval = _Mix_UnregisterEffect_locked(channel, f); Mix_UnlockAudio(); return retval; } /* MAKE SURE you hold the audio lock (Mix_LockAudio()) before calling this! */ bool _Mix_UnregisterAllEffects_locked(int channel) { effect_info **e = NULL; if (channel == MIX_CHANNEL_POST) { e = &posteffects; } else { if ((channel < 0) || (channel >= num_channels)) { return SDL_SetError("Invalid channel number"); } e = &mix_channel[channel].effects; } return _Mix_remove_all_effects(channel, e); } bool Mix_UnregisterAllEffects(int channel) { bool retval; Mix_LockAudio(); retval = _Mix_UnregisterAllEffects_locked(channel); Mix_UnlockAudio(); return retval; } void Mix_LockAudio(void) { SDL_LockAudioStream(audio_stream); } void Mix_UnlockAudio(void) { SDL_UnlockAudioStream(audio_stream); } int Mix_MasterVolume(int volume) { int prev_volume = SDL_GetAtomicInt(&master_volume); if (volume < 0) { return prev_volume; } if (volume > MIX_MAX_VOLUME) { volume = MIX_MAX_VOLUME; } SDL_SetAtomicInt(&master_volume, volume); return prev_volume; } libsdl3-mixer-3~git20250523~daf0503+ds/src/mixer.h000066400000000000000000000022411501405355700212740ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef MIXER_H_ #define MIXER_H_ /* Locking wrapper functions */ extern void Mix_LockAudio(void); extern void Mix_UnlockAudio(void); extern void add_chunk_decoder(const char *decoder); #endif /* MIXER_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/music.c000066400000000000000000001174151501405355700212750ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include #include #include "mixer.h" #include "music.h" #include "music_wav.h" #include "music_xmp.h" #include "music_nativemidi.h" #include "music_fluidsynth.h" #include "music_timidity.h" #include "music_ogg.h" #include "music_opus.h" #include "music_drmp3.h" #include "music_mpg123.h" #include "music_drflac.h" #include "music_flac.h" #include "music_wavpack.h" #include "music_gme.h" #include "native_midi/native_midi.h" #include "utils.h" /* Check to make sure we are building with a new enough SDL */ #if !SDL_VERSION_ATLEAST(3, 0, 0) #error You need SDL 3.0.0 or newer from http://www.libsdl.org #endif /* Set this hint to true if you want verbose logging of music interfaces */ #define SDL_MIXER_HINT_DEBUG_MUSIC_INTERFACES \ "SDL_MIXER_DEBUG_MUSIC_INTERFACES" static bool music_active = true; static int music_volume = MIX_MAX_VOLUME; static Mix_Music *music_playing = NULL; SDL_AudioSpec music_spec; struct Mix_Music { Mix_MusicInterface *interface; void *context; bool playing; Mix_Fading fading; int fade_step; int fade_steps; char filename[1024]; }; /* Used to calculate fading steps */ static int ms_per_step; /* rcg06042009 report available decoders at runtime. */ static const char **music_decoders = NULL; static int num_decoders = 0; /* Semicolon-separated SoundFont paths */ static char* soundfont_paths = NULL; /* full path of timidity config file */ static char* timidity_cfg = NULL; /* Meta-Tags utility */ void meta_tags_init(Mix_MusicMetaTags *tags) { SDL_memset(tags, 0, sizeof(Mix_MusicMetaTags)); } void meta_tags_clear(Mix_MusicMetaTags *tags) { int i; for (i = 0; i < MIX_META_LAST; i++) { if (tags->tags[i]) { SDL_free(tags->tags[i]); tags->tags[i] = NULL; } } } void meta_tags_set(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type, const char *value) { char *out; size_t len; if (!value) { return; } if (type >= MIX_META_LAST) { return; } len = SDL_strlen(value); out = (char *)SDL_malloc(sizeof(char) * len + 1); SDL_strlcpy(out, value, len +1); if (tags->tags[type]) { SDL_free(tags->tags[type]); } tags->tags[type] = out; } const char *meta_tags_get(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type) { switch (type) { case MIX_META_TITLE: case MIX_META_ARTIST: case MIX_META_ALBUM: case MIX_META_COPYRIGHT: return tags->tags[type] ? tags->tags[type] : ""; case MIX_META_LAST: default: break; } return ""; } /* for music->filename */ #if defined(_WIN32) static SDL_INLINE const char *get_last_dirsep (const char *p) { const char *p1 = SDL_strrchr(p, '/'); const char *p2 = SDL_strrchr(p, '\\'); if (!p1) return p2; if (!p2) return p1; return (p1 > p2)? p1 : p2; } #else /* unix */ static SDL_INLINE const char *get_last_dirsep (const char *p) { return SDL_strrchr(p, '/'); } #endif /* Interfaces for the various music interfaces, ordered by priority */ static Mix_MusicInterface *s_music_interfaces[] = { #ifdef MUSIC_WAV &Mix_MusicInterface_WAV, #endif #ifdef MUSIC_FLAC_DRFLAC &Mix_MusicInterface_DRFLAC, #endif #ifdef MUSIC_FLAC_LIBFLAC &Mix_MusicInterface_FLAC, #endif #ifdef MUSIC_WAVPACK &Mix_MusicInterface_WAVPACK, #endif #ifdef MUSIC_OGG &Mix_MusicInterface_OGG, #endif #ifdef MUSIC_OPUS &Mix_MusicInterface_Opus, #endif #ifdef MUSIC_MP3_DRMP3 &Mix_MusicInterface_DRMP3, #endif #ifdef MUSIC_MP3_MPG123 &Mix_MusicInterface_MPG123, #endif #ifdef MUSIC_MOD_XMP &Mix_MusicInterface_XMP, #endif #ifdef MUSIC_MID_FLUIDSYNTH &Mix_MusicInterface_FLUIDSYNTH, #endif #ifdef MUSIC_MID_TIMIDITY &Mix_MusicInterface_TIMIDITY, #endif #ifdef MUSIC_MID_NATIVE &Mix_MusicInterface_NATIVEMIDI, #endif #ifdef MUSIC_GME &Mix_MusicInterface_GME, #endif NULL }; int get_num_music_interfaces(void) { return SDL_arraysize(s_music_interfaces) - 1; } Mix_MusicInterface *get_music_interface(int index) { return s_music_interfaces[index]; } int Mix_GetNumMusicDecoders(void) { return num_decoders; } const char *Mix_GetMusicDecoder(int index) { if ((index < 0) || (index >= num_decoders)) { return NULL; } return music_decoders[index]; } bool Mix_HasMusicDecoder(const char *name) { int index; for (index = 0; index < num_decoders; ++index) { if (SDL_strcasecmp(name, music_decoders[index]) == 0) { return true; } } return false; } static void add_music_decoder(const char *decoder) { void *ptr; int i; /* Check to see if we already have this decoder */ for (i = 0; i < num_decoders; ++i) { if (SDL_strcmp(music_decoders[i], decoder) == 0) { return; } } ptr = SDL_realloc((void *)music_decoders, ((size_t)num_decoders + 1) * sizeof(const char *)); if (ptr == NULL) { return; /* oh well, go on without it. */ } music_decoders = (const char **) ptr; music_decoders[num_decoders++] = decoder; } /* Local low-level functions prototypes */ static void music_internal_initialize_volume(void); static void music_internal_volume(int volume); static int music_internal_play(Mix_Music *music, int play_count, double position); static int music_internal_position(double position); static bool music_internal_playing(void); static void music_internal_halt(void); /* Support for hooking when the music has finished */ static void (SDLCALL *music_finished_hook)(void) = NULL; void Mix_HookMusicFinished(void (SDLCALL *music_finished)(void)) { Mix_LockAudio(); music_finished_hook = music_finished; Mix_UnlockAudio(); } /* Convenience function to fill audio and mix at the specified volume This is called from many music player's GetAudio callback. */ int music_pcm_getaudio(void *context, void *data, int bytes, int volume, int (*GetSome)(void *context, void *data, int bytes, bool *done)) { Uint8 *snd = (Uint8 *)data; Uint8 *dst; int len = bytes; int zero_cycles = 0; const int MAX_ZERO_CYCLES = 10; /* just try to catch infinite loops */ bool done = false; if (volume == MIX_MAX_VOLUME) { dst = snd; } else { dst = SDL_stack_alloc(Uint8, (size_t)bytes); } while (len > 0 && !done) { int consumed = GetSome(context, dst, len, &done); if (consumed < 0) { break; } if (consumed == 0) { ++zero_cycles; if (zero_cycles > MAX_ZERO_CYCLES) { /* We went too many cycles with no data, we're done */ done = true; } continue; } zero_cycles = 0; if (volume == MIX_MAX_VOLUME) { dst += consumed; } else { SDL_MixAudio(snd, dst, music_spec.format, (Uint32)consumed, volume/(float)MIX_MAX_VOLUME); snd += consumed; } len -= consumed; } if (volume != MIX_MAX_VOLUME) { SDL_stack_free(dst); } return len; } /* Mixing function */ void SDLCALL music_mixer(void *udata, Uint8 *stream, int len) { bool done = false; (void)udata; while (music_playing && music_active && len > 0 && !done) { /* Handle fading */ if (music_playing->fading != MIX_NO_FADING) { if (music_playing->fade_step++ < music_playing->fade_steps) { int volume; int fade_step = music_playing->fade_step; int fade_steps = music_playing->fade_steps; if (music_playing->fading == MIX_FADING_OUT) { volume = (music_volume * (fade_steps-fade_step)) / fade_steps; } else { /* Fading in */ volume = (music_volume * fade_step) / fade_steps; } music_internal_volume(volume); } else { if (music_playing->fading == MIX_FADING_OUT) { music_internal_halt(); if (music_finished_hook) { music_finished_hook(); } return; } music_playing->fading = MIX_NO_FADING; } } if (music_playing->interface->GetAudio) { int left = music_playing->interface->GetAudio(music_playing->context, stream, len); if (left != 0) { /* Either an error or finished playing with data left */ music_playing->playing = false; done = true; } if (left > 0) { stream += (len - left); len = left; } else { len = 0; } } else { len = 0; } if (!music_internal_playing()) { music_internal_halt(); if (music_finished_hook) { music_finished_hook(); } } } } void pause_async_music(int pause_on) { if (!music_active || !music_playing || !music_playing->interface) { return; } if (pause_on) { if (music_playing->interface->Pause) { music_playing->interface->Pause(music_playing->context); } } else { if (music_playing->interface->Resume) { music_playing->interface->Resume(music_playing->context); } } } /* Load the music interface libraries for a given music type */ bool load_music_type(Mix_MusicType type) { int i; int loaded = 0; for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (interface->type != type) { continue; } if (!interface->loaded) { char hint[64]; SDL_snprintf(hint, sizeof(hint), "SDL_MIXER_DISABLE_%s", interface->tag); if (SDL_GetHintBoolean(hint, false)) { continue; } if (interface->Load && interface->Load() < 0) { if (SDL_GetHintBoolean(SDL_MIXER_HINT_DEBUG_MUSIC_INTERFACES, false)) { SDL_Log("Couldn't load %s: %s\n", interface->tag, SDL_GetError()); } continue; } interface->loaded = true; } ++loaded; } return (loaded > 0) ? true : false; } /* Open the music interfaces for a given music type */ bool open_music_type(Mix_MusicType type) { int i; int opened = 0; bool use_native_midi = false; if (!music_spec.format) { /* Music isn't opened yet */ return false; } #ifdef MUSIC_MID_NATIVE if (type == MUS_MID && SDL_GetHintBoolean("SDL_NATIVE_MUSIC", false) && native_midi_detect()) { use_native_midi = true; } #endif for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (!interface->loaded) { continue; } if (type != MUS_NONE && interface->type != type) { continue; } if (interface->type == MUS_MID && use_native_midi && interface->api != MIX_MUSIC_NATIVEMIDI) { continue; } if (!interface->opened) { if (interface->Open && interface->Open(&music_spec) < 0) { if (SDL_GetHintBoolean(SDL_MIXER_HINT_DEBUG_MUSIC_INTERFACES, false)) { SDL_Log("Couldn't open %s: %s\n", interface->tag, SDL_GetError()); } continue; } interface->opened = true; add_music_decoder(interface->tag); } ++opened; } if (has_music(MUS_MOD)) { add_music_decoder("MOD"); add_chunk_decoder("MOD"); } if (has_music(MUS_MID)) { add_music_decoder("MIDI"); add_chunk_decoder("MID"); } if (has_music(MUS_OGG)) { add_music_decoder("OGG"); add_chunk_decoder("OGG"); } if (has_music(MUS_OPUS)) { add_music_decoder("OPUS"); add_chunk_decoder("OPUS"); } if (has_music(MUS_MP3)) { add_music_decoder("MP3"); add_chunk_decoder("MP3"); } if (has_music(MUS_FLAC)) { add_music_decoder("FLAC"); add_chunk_decoder("FLAC"); } if (has_music(MUS_WAVPACK)) { add_music_decoder("WAVPACK"); add_chunk_decoder("WAVPACK"); } return (opened > 0) ? true : false; } /* Initialize the music interfaces with a certain desired audio format */ void open_music(const SDL_AudioSpec *spec) { #ifdef MIX_INIT_SOUNDFONT_PATHS if (!soundfont_paths) { soundfont_paths = SDL_strdup(MIX_INIT_SOUNDFONT_PATHS); } #endif /* Load the music interfaces that don't have explicit initialization */ load_music_type(MUS_WAV); /* Open all the interfaces that are loaded */ music_spec = *spec; open_music_type(MUS_NONE); Mix_VolumeMusic(MIX_MAX_VOLUME); /* Calculate the number of ms for each callback */ ms_per_step = (int) ((4096.0f * 1000.0f) / spec->freq); } /* Return true if the music type is available */ bool has_music(Mix_MusicType type) { int i; for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (interface->type != type) { continue; } if (interface->opened) { return true; } } return false; } Mix_MusicType detect_music_type(SDL_IOStream *src) { Uint8 magic[12]; if (SDL_ReadIO(src, magic, 12) != 12) { SDL_SetError("Couldn't read first 12 bytes of audio data"); return MUS_NONE; } SDL_SeekIO(src, -12, SDL_IO_SEEK_CUR); /* WAVE files have the magic four bytes "RIFF" AIFF files have the magic 12 bytes "FORM" XXXX "AIFF" */ if (((SDL_memcmp(magic, "RIFF", 4) == 0) && (SDL_memcmp((magic+8), "WAVE", 4) == 0)) || (SDL_memcmp(magic, "FORM", 4) == 0)) { return MUS_WAV; } /* Ogg Vorbis files have the magic four bytes "OggS" */ if (SDL_memcmp(magic, "OggS", 4) == 0) { SDL_SeekIO(src, 28, SDL_IO_SEEK_CUR); SDL_ReadIO(src, magic, 8); SDL_SeekIO(src,-36, SDL_IO_SEEK_CUR); if (SDL_memcmp(magic, "OpusHead", 8) == 0) { return MUS_OPUS; } if (magic[0] == 0x7F && SDL_memcmp(magic + 1, "FLAC", 4) == 0) { return MUS_FLAC; } return MUS_OGG; } /* FLAC files have the magic four bytes "fLaC" */ if (SDL_memcmp(magic, "fLaC", 4) == 0) { return MUS_FLAC; } /* WavPack files have the magic four bytes "wvpk" */ if (SDL_memcmp(magic, "wvpk", 4) == 0) { return MUS_WAVPACK; } /* MIDI files have the magic four bytes "MThd" */ if (SDL_memcmp(magic, "MThd", 4) == 0) { return MUS_MID; } /* RIFF MIDI files have the magic four bytes "RIFF" and then "RMID" */ if ((SDL_memcmp(magic, "RIFF", 4) == 0) && (SDL_memcmp(magic + 8, "RMID", 4) == 0)) { return MUS_MID; } if (SDL_memcmp(magic, "ID3", 3) == 0 || /* see: https://bugzilla.libsdl.org/show_bug.cgi?id=5322 */ (magic[0] == 0xFF && (magic[1] & 0xE6) == 0xE2)) { return MUS_MP3; } /* GME Specific files */ if (SDL_memcmp(magic, "ZXAY", 4) == 0 || SDL_memcmp(magic, "GBS\x01", 4) == 0 || SDL_memcmp(magic, "GYMX", 4) == 0 || SDL_memcmp(magic, "HESM", 4) == 0 || SDL_memcmp(magic, "KSCC", 4) == 0 || SDL_memcmp(magic, "KSSX", 4) == 0 || SDL_memcmp(magic, "NESM", 4) == 0 || SDL_memcmp(magic, "NSFE", 4) == 0 || SDL_memcmp(magic, "SAP\x0D", 4) == 0 || SDL_memcmp(magic, "SNES", 4) == 0 || SDL_memcmp(magic, "Vgm ", 4) == 0 || SDL_memcmp(magic, "\x1f\x8b", 2) == 0) { return MUS_GME; } /* Assume MOD format. * * Apparently there is no way to check if the file is really a MOD, * there are too many formats supported by libxmp. * The mod library does this check by itself. */ return MUS_MOD; } /* Load a music file */ Mix_Music *Mix_LoadMUS(const char *file) { int i; void *context; char *ext; Mix_MusicType type; SDL_IOStream *src; for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (!interface->opened || !interface->CreateFromFile) { continue; } context = interface->CreateFromFile(file); if (context) { const char *p; /* Allocate memory for the music structure */ Mix_Music *music = (Mix_Music *)SDL_calloc(1, sizeof(Mix_Music)); if (music == NULL) { return NULL; } music->interface = interface; music->context = context; p = get_last_dirsep(file); SDL_strlcpy(music->filename, (p != NULL)? p + 1 : file, 1024); return music; } } src = SDL_IOFromFile(file, "rb"); if (src == NULL) { SDL_SetError("Couldn't open '%s'", file); return NULL; } /* Use the extension as a first guess on the file type */ type = MUS_NONE; ext = SDL_strrchr(file, '.'); if (ext) { ++ext; /* skip the dot in the extension */ if (SDL_strcasecmp(ext, "WAV") == 0) { type = MUS_WAV; } else if (SDL_strcasecmp(ext, "MID") == 0 || SDL_strcasecmp(ext, "MIDI") == 0 || SDL_strcasecmp(ext, "KAR") == 0) { type = MUS_MID; } else if (SDL_strcasecmp(ext, "OGG") == 0) { type = MUS_OGG; } else if (SDL_strcasecmp(ext, "OPUS") == 0) { type = MUS_OPUS; } else if (SDL_strcasecmp(ext, "FLAC") == 0) { type = MUS_FLAC; } else if (SDL_strcasecmp(ext, "WV") == 0) { type = MUS_WAVPACK; } else if (SDL_strcasecmp(ext, "MPG") == 0 || SDL_strcasecmp(ext, "MPEG") == 0 || SDL_strcasecmp(ext, "MP3") == 0 || SDL_strcasecmp(ext, "MAD") == 0) { type = MUS_MP3; } else if (SDL_strcasecmp(ext, "669") == 0 || SDL_strcasecmp(ext, "AMF") == 0 || SDL_strcasecmp(ext, "AMS") == 0 || SDL_strcasecmp(ext, "DBM") == 0 || SDL_strcasecmp(ext, "DSM") == 0 || SDL_strcasecmp(ext, "FAR") == 0 || SDL_strcasecmp(ext, "GDM") == 0 || SDL_strcasecmp(ext, "IT") == 0 || SDL_strcasecmp(ext, "MED") == 0 || SDL_strcasecmp(ext, "MDL") == 0 || SDL_strcasecmp(ext, "MOD") == 0 || SDL_strcasecmp(ext, "MOL") == 0 || SDL_strcasecmp(ext, "MTM") == 0 || SDL_strcasecmp(ext, "NST") == 0 || SDL_strcasecmp(ext, "OKT") == 0 || SDL_strcasecmp(ext, "PTM") == 0 || SDL_strcasecmp(ext, "S3M") == 0 || SDL_strcasecmp(ext, "STM") == 0 || SDL_strcasecmp(ext, "ULT") == 0 || SDL_strcasecmp(ext, "UMX") == 0 || SDL_strcasecmp(ext, "WOW") == 0 || SDL_strcasecmp(ext, "XM") == 0) { type = MUS_MOD; } else if (SDL_strcasecmp(ext, "GBS") == 0 || SDL_strcasecmp(ext, "M3U") == 0 || SDL_strcasecmp(ext, "NSF") == 0 || SDL_strcasecmp(ext, "SPC") == 0 || SDL_strcasecmp(ext, "VGM") == 0) { type = MUS_GME; } } return Mix_LoadMUSType_IO(src, type, true); } Mix_Music *Mix_LoadMUS_IO(SDL_IOStream *src, bool closeio) { return Mix_LoadMUSType_IO(src, MUS_NONE, closeio); } Mix_Music *Mix_LoadMUSType_IO(SDL_IOStream *src, Mix_MusicType type, bool closeio) { int i; void *context; Sint64 start; if (!src) { SDL_SetError("src pointer is NULL"); return NULL; } start = SDL_TellIO(src); /* If the caller wants auto-detection, figure out what kind of file * this is. */ if (type == MUS_NONE) { if ((type = detect_music_type(src)) == MUS_NONE) { /* Don't call SDL_SetError() since detect_music_type() does that. */ if (closeio) { SDL_CloseIO(src); } return NULL; } } SDL_ClearError(); if (load_music_type(type) && open_music_type(type)) { for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (!interface->opened || type != interface->type || !interface->CreateFromIO) { continue; } context = interface->CreateFromIO(src, closeio); if (context) { /* Allocate memory for the music structure */ Mix_Music *music = (Mix_Music *)SDL_calloc(1, sizeof(Mix_Music)); if (music == NULL) { interface->Delete(context); return NULL; } music->interface = interface; music->context = context; if (SDL_GetHintBoolean(SDL_MIXER_HINT_DEBUG_MUSIC_INTERFACES, false)) { SDL_Log("Loaded music with %s\n", interface->tag); } return music; } /* Reset the stream for the next decoder */ SDL_SeekIO(src, start, SDL_IO_SEEK_SET); } } if (!*SDL_GetError()) { SDL_SetError("Unrecognized audio format"); } if (closeio) { SDL_CloseIO(src); } else { SDL_SeekIO(src, start, SDL_IO_SEEK_SET); } return NULL; } /* Free a music chunk previously loaded */ void Mix_FreeMusic(Mix_Music *music) { if (music) { /* Stop the music if it's currently playing */ Mix_LockAudio(); if (music == music_playing) { /* Wait for any fade out to finish */ while (music_active && music->fading == MIX_FADING_OUT) { Mix_UnlockAudio(); SDL_Delay(100); Mix_LockAudio(); } if (music == music_playing) { music_internal_halt(); } } Mix_UnlockAudio(); music->interface->Delete(music->context); SDL_free(music); } } /* Find out the music format of a mixer music, or the currently playing music, if 'music' is NULL. */ Mix_MusicType Mix_GetMusicType(const Mix_Music *music) { Mix_MusicType type = MUS_NONE; if (music) { type = music->interface->type; } else { Mix_LockAudio(); if (music_playing) { type = music_playing->interface->type; } Mix_UnlockAudio(); } return type; } static const char * get_music_tag_internal(const Mix_Music *music, Mix_MusicMetaTag tag_type) { const char *tag = ""; Mix_LockAudio(); if (music && music->interface->GetMetaTag) { tag = music->interface->GetMetaTag(music->context, tag_type); } else if (music_playing && music_playing->interface->GetMetaTag) { tag = music_playing->interface->GetMetaTag(music_playing->context, tag_type); } else { SDL_SetError("Music isn't playing"); } Mix_UnlockAudio(); return tag; } const char *Mix_GetMusicTitleTag(const Mix_Music *music) { return get_music_tag_internal(music, MIX_META_TITLE); } /* Get music title from meta-tag if possible */ const char *Mix_GetMusicTitle(const Mix_Music *music) { const char *tag = Mix_GetMusicTitleTag(music); if (SDL_strlen(tag) > 0) { return tag; } if (music) { return music->filename; } if (music_playing) { return music_playing->filename; } return ""; } const char *Mix_GetMusicArtistTag(const Mix_Music *music) { return get_music_tag_internal(music, MIX_META_ARTIST); } const char *Mix_GetMusicAlbumTag(const Mix_Music *music) { return get_music_tag_internal(music, MIX_META_ALBUM); } const char *Mix_GetMusicCopyrightTag(const Mix_Music *music) { return get_music_tag_internal(music, MIX_META_COPYRIGHT); } /* Play a music chunk. Returns 0, or -1 if there was an error. */ static int music_internal_play(Mix_Music *music, int play_count, double position) { int retval = 0; /* Note the music we're playing */ if (music_playing) { music_internal_halt(); } music_playing = music; music_playing->playing = true; /* Set the initial volume */ music_internal_initialize_volume(); /* Set up for playback */ retval = music->interface->Play(music->context, play_count); /* Set the playback position, note any errors if an offset is used */ if (retval == 0) { if (position > 0.0) { if (music_internal_position(position) < 0) { SDL_SetError("Position not implemented for music type"); retval = -1; } } else { music_internal_position(0.0); } } /* If the setup failed, we're not playing any music anymore */ if (retval < 0) { music->playing = false; music_playing = NULL; } return retval; } bool Mix_FadeInMusicPos(Mix_Music *music, int loops, int ms, double position) { bool retval; if (ms_per_step == 0) { return SDL_SetError("Audio device hasn't been opened"); } /* Don't play null pointers :-) */ if (music == NULL) { return SDL_SetError("music parameter was NULL"); } /* Setup the data */ if (ms) { music->fading = MIX_FADING_IN; } else { music->fading = MIX_NO_FADING; } music->fade_step = 0; music->fade_steps = (ms + ms_per_step - 1) / ms_per_step; /* Play the puppy */ Mix_LockAudio(); /* If the current music is fading out, wait for the fade to complete */ while (music_playing && (music_playing->fading == MIX_FADING_OUT)) { Mix_UnlockAudio(); SDL_Delay(100); Mix_LockAudio(); } if (loops == 0) { /* Loop is the number of times to play the audio */ loops = 1; } retval = (music_internal_play(music, loops, position) == 0); /* Set music as active */ music_active = retval; Mix_UnlockAudio(); return retval; } bool Mix_FadeInMusic(Mix_Music *music, int loops, int ms) { return Mix_FadeInMusicPos(music, loops, ms, 0.0); } bool Mix_PlayMusic(Mix_Music *music, int loops) { return Mix_FadeInMusicPos(music, loops, 0, 0.0); } /* Jump to a given order in mod music. */ bool Mix_ModMusicJumpToOrder(int order) { bool retval = false; Mix_LockAudio(); if (music_playing) { if (music_playing->interface->Jump) { retval = (music_playing->interface->Jump(music_playing->context, order) == 0); } else { SDL_SetError("Jump not implemented for music type"); } } else { SDL_SetError("Music isn't playing"); } Mix_UnlockAudio(); return retval; } /* Set the playing music position */ int music_internal_position(double position) { if (music_playing->interface->Seek) { return music_playing->interface->Seek(music_playing->context, position); } return -1; } bool Mix_SetMusicPosition(double position) { bool retval; Mix_LockAudio(); if (music_playing) { if (music_internal_position(position) == 0) { retval = true; } else { retval = SDL_SetError("Position not implemented for music type"); } } else { retval = SDL_SetError("Music isn't playing"); } Mix_UnlockAudio(); return retval; } /* Set the playing music position */ static double music_internal_position_get(Mix_Music *music) { if (music->interface->Tell) { return music->interface->Tell(music->context); } return -1; } double Mix_GetMusicPosition(Mix_Music *music) { double retval; Mix_LockAudio(); if (music) { retval = music_internal_position_get(music); } else if (music_playing) { retval = music_internal_position_get(music_playing); } else { SDL_SetError("Music isn't playing"); retval = -1.0; } Mix_UnlockAudio(); return retval; } static double music_internal_duration(Mix_Music *music) { if (music->interface->Duration) { return music->interface->Duration(music->context); } else { SDL_SetError("Duration not implemented for music type"); return -1; } } double Mix_MusicDuration(Mix_Music *music) { double retval; Mix_LockAudio(); if (music) { retval = music_internal_duration(music); } else if (music_playing) { retval = music_internal_duration(music_playing); } else { SDL_SetError("music is NULL and no playing music"); retval = -1.0; } Mix_UnlockAudio(); return retval; } /* Get Loop start position */ static double music_internal_loop_start(Mix_Music *music) { if (music->interface->LoopStart) { return music->interface->LoopStart(music->context); } return -1; } double Mix_GetMusicLoopStartTime(Mix_Music *music) { double retval; Mix_LockAudio(); if (music) { retval = music_internal_loop_start(music); } else if (music_playing) { retval = music_internal_loop_start(music_playing); } else { SDL_SetError("Music isn't playing"); retval = -1.0; } Mix_UnlockAudio(); return retval; } /* Get Loop end position */ static double music_internal_loop_end(Mix_Music *music) { if (music->interface->LoopEnd) { return music->interface->LoopEnd(music->context); } return -1; } double Mix_GetMusicLoopEndTime(Mix_Music *music) { double retval; Mix_LockAudio(); if (music) { retval = music_internal_loop_end(music); } else if (music_playing) { retval = music_internal_loop_end(music_playing); } else { SDL_SetError("Music isn't playing"); retval = -1.0; } Mix_UnlockAudio(); return retval; } /* Get Loop end position */ static double music_internal_loop_length(Mix_Music *music) { if (music->interface->LoopLength) { return music->interface->LoopLength(music->context); } return -1; } double Mix_GetMusicLoopLengthTime(Mix_Music *music) { double retval; Mix_LockAudio(); if (music) { retval = music_internal_loop_length(music); } else if (music_playing) { retval = music_internal_loop_length(music_playing); } else { SDL_SetError("Music isn't playing"); retval = -1.0; } Mix_UnlockAudio(); return retval; } /* Set the music's initial volume */ static void music_internal_initialize_volume(void) { if (music_playing->fading == MIX_FADING_IN) { music_internal_volume(0); } else { music_internal_volume(music_volume); } } /* Set the music volume */ static void music_internal_volume(int volume) { if (music_playing->interface->SetVolume) { music_playing->interface->SetVolume(music_playing->context, volume); } } int Mix_VolumeMusic(int volume) { int prev_volume; prev_volume = music_volume; if (volume < 0) { return prev_volume; } if (volume > MIX_MAX_VOLUME) { volume = MIX_MAX_VOLUME; } music_volume = volume; Mix_LockAudio(); if (music_playing) { music_internal_volume(music_volume); } Mix_UnlockAudio(); return prev_volume; } int Mix_GetMusicVolume(Mix_Music *music) { int prev_volume; if (music && music->interface->GetVolume) prev_volume = music->interface->GetVolume(music->context); else if (music_playing && music_playing->interface->GetVolume) { prev_volume = music_playing->interface->GetVolume(music_playing->context); } else { prev_volume = music_volume; } return prev_volume; } /* Halt playing of music */ static void music_internal_halt(void) { if (music_playing->interface->Stop) { music_playing->interface->Stop(music_playing->context); } music_playing->playing = false; music_playing->fading = MIX_NO_FADING; music_playing = NULL; } void Mix_HaltMusic(void) { Mix_LockAudio(); if (music_playing) { music_internal_halt(); if (music_finished_hook) { music_finished_hook(); } } Mix_UnlockAudio(); } /* Progressively stop the music */ bool Mix_FadeOutMusic(int ms) { bool retval = false; if (ms_per_step == 0) { return SDL_SetError("Audio device hasn't been opened"); } if (ms <= 0) { /* just halt immediately. */ Mix_HaltMusic(); return true; } Mix_LockAudio(); if (music_playing) { int fade_steps = (ms + ms_per_step - 1) / ms_per_step; if (music_playing->fading == MIX_NO_FADING) { music_playing->fade_step = 0; } else { int step; int old_fade_steps = music_playing->fade_steps; if (music_playing->fading == MIX_FADING_OUT) { step = music_playing->fade_step; } else { step = old_fade_steps - music_playing->fade_step + 1; } music_playing->fade_step = (step * fade_steps) / old_fade_steps; } music_playing->fading = MIX_FADING_OUT; music_playing->fade_steps = fade_steps; retval = true; } Mix_UnlockAudio(); return retval; } Mix_Fading Mix_FadingMusic(void) { Mix_Fading fading = MIX_NO_FADING; Mix_LockAudio(); if (music_playing) { fading = music_playing->fading; } Mix_UnlockAudio(); return fading; } /* Pause/Resume the music stream */ void Mix_PauseMusic(void) { Mix_LockAudio(); if (music_playing) { if (music_playing->interface->Pause) { music_playing->interface->Pause(music_playing->context); } } music_active = false; Mix_UnlockAudio(); } void Mix_ResumeMusic(void) { Mix_LockAudio(); if (music_playing) { if (music_playing->interface->Resume) { music_playing->interface->Resume(music_playing->context); } } music_active = true; Mix_UnlockAudio(); } void Mix_RewindMusic(void) { Mix_SetMusicPosition(0.0); } bool Mix_PausedMusic(void) { return !music_active; } bool Mix_StartTrack(Mix_Music *music, int track) { bool result; Mix_LockAudio(); if (music && music->interface->StartTrack) { if (music->interface->Pause) { music->interface->Pause(music->context); } result = (music->interface->StartTrack(music->context, track) == 0); } else { result = SDL_SetError("That operation is not supported"); } Mix_UnlockAudio(); return result; } int Mix_GetNumTracks(Mix_Music *music) { int result; Mix_LockAudio(); if (music && music->interface->GetNumTracks) { result = music->interface->GetNumTracks(music->context); } else { SDL_SetError("That operation is not supported"); result = -1; } Mix_UnlockAudio(); return result; } /* Check the status of the music */ static bool music_internal_playing(void) { if (!music_playing) { return false; } if (music_playing->interface->IsPlaying) { music_playing->playing = music_playing->interface->IsPlaying(music_playing->context); } return music_playing->playing; } bool Mix_PlayingMusic(void) { bool playing; Mix_LockAudio(); playing = music_internal_playing(); Mix_UnlockAudio(); return playing; } /* Uninitialize the music interfaces */ void close_music(void) { int i; Mix_HaltMusic(); for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (!interface || !interface->opened) { continue; } if (interface->Close) { interface->Close(); } interface->opened = false; } if (soundfont_paths) { SDL_free(soundfont_paths); soundfont_paths = NULL; } /* rcg06042009 report available decoders at runtime. */ if (music_decoders) { SDL_free((void *)music_decoders); music_decoders = NULL; } num_decoders = 0; ms_per_step = 0; } /* Unload the music interface libraries */ void unload_music(void) { int i; for (i = 0; i < get_num_music_interfaces(); ++i) { Mix_MusicInterface *interface = s_music_interfaces[i]; if (!interface || !interface->loaded) { continue; } if (interface->Unload) { interface->Unload(); } interface->loaded = false; } } bool Mix_SetTimidityCfg(const char *path) { if (timidity_cfg) { SDL_free(timidity_cfg); timidity_cfg = NULL; } if (path && *path) { if (!(timidity_cfg = SDL_strdup(path))) { return SDL_SetError("Insufficient memory to set Timidity cfg file"); } } return true; } const char* Mix_GetTimidityCfg(void) { return timidity_cfg; } bool Mix_SetSoundFonts(const char *paths) { if (soundfont_paths) { SDL_free(soundfont_paths); soundfont_paths = NULL; } if (paths) { if (!(soundfont_paths = SDL_strdup(paths))) { return SDL_SetError("Insufficient memory to set SoundFonts"); } } return true; } const char* Mix_GetSoundFonts(void) { const char *env_paths = SDL_getenv("SDL_SOUNDFONTS"); bool force_env_paths = SDL_GetHintBoolean("SDL_FORCE_SOUNDFONTS", false); if (force_env_paths && (!env_paths || !*env_paths)) { force_env_paths = false; } if (soundfont_paths && *soundfont_paths && !force_env_paths) { return soundfont_paths; } if (env_paths) { return env_paths; } /* We don't have any sound fonts set programmatically or in the environment Time to start guessing where they might be... */ { static char *s_soundfont_paths[] = { "/usr/share/sounds/sf2/FluidR3_GM.sf2" /* Remember to add ',' here */ }; unsigned i; for (i = 0; i < SDL_arraysize(s_soundfont_paths); ++i) { SDL_IOStream *io = SDL_IOFromFile(s_soundfont_paths[i], "rb"); if (io) { SDL_CloseIO(io); return s_soundfont_paths[i]; } } } return NULL; } bool Mix_EachSoundFont(Mix_EachSoundFontCallback function, void *data) { char *context, *path, *paths; const char* cpaths = Mix_GetSoundFonts(); int soundfonts_found = 0; if (!cpaths) { return SDL_SetError("No SoundFonts have been requested"); } if (!(paths = SDL_strdup(cpaths))) { return SDL_SetError("Insufficient memory to iterate over SoundFonts"); } #if defined(_WIN32) #define PATHSEP ";" #else #define PATHSEP ":;" #endif for (path = SDL_strtok_r(paths, PATHSEP, &context); path; path = SDL_strtok_r(NULL, PATHSEP, &context)) { if (!function(path, data)) { continue; } soundfonts_found++; } SDL_free(paths); return (soundfonts_found > 0); } libsdl3-mixer-3~git20250523~daf0503+ds/src/music.h000066400000000000000000000120261501405355700212720ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef MUSIC_H_ #define MUSIC_H_ #include /* Supported music APIs, in order of preference */ typedef enum { MIX_MUSIC_WAVE, MIX_MUSIC_FLUIDSYNTH, MIX_MUSIC_TIMIDITY, MIX_MUSIC_NATIVEMIDI, MIX_MUSIC_OGG, MIX_MUSIC_DRMP3, MIX_MUSIC_MPG123, MIX_MUSIC_DRFLAC, MIX_MUSIC_FLAC, MIX_MUSIC_OPUS, MIX_MUSIC_LIBXMP, MIX_MUSIC_WAVPACK, MIX_MUSIC_GME, MIX_MUSIC_LAST } Mix_MusicAPI; /* Supported meta-tags */ typedef enum { MIX_META_TITLE, MIX_META_ARTIST, MIX_META_ALBUM, MIX_META_COPYRIGHT, MIX_META_LAST } Mix_MusicMetaTag; /* MIXER-X: Meta-tags utility structure */ typedef struct { char *tags[4]; } Mix_MusicMetaTags; extern void meta_tags_init(Mix_MusicMetaTags *tags); extern void meta_tags_clear(Mix_MusicMetaTags *tags); extern void meta_tags_set(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type, const char *value); extern const char* meta_tags_get(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type); /* Music API implementation */ typedef struct { const char *tag; Mix_MusicAPI api; Mix_MusicType type; bool loaded; bool opened; /* Load the library */ int (*Load)(void); /* Initialize for the audio output */ int (*Open)(const SDL_AudioSpec *spec); /* Create a music object from an SDL_IOStream stream * If the function returns NULL, 'src' will be freed if needed by the caller. */ void *(*CreateFromIO)(SDL_IOStream *src, bool closeio); /* Create a music object from a file, if SDL_IOStream are not supported */ void *(*CreateFromFile)(const char *file); /* Set the volume */ void (*SetVolume)(void *music, int volume); /* Get the volume */ int (*GetVolume)(void *music); /* Start playing music from the beginning with an optional loop count */ int (*Play)(void *music, int play_count); /* Returns true if music is still playing */ bool (*IsPlaying)(void *music); /* Get music data, returns the number of bytes left */ int (*GetAudio)(void *music, void *data, int bytes); /* Jump to a given order in mod music */ int (*Jump)(void *music, int order); /* Seek to a play position (in seconds) */ int (*Seek)(void *music, double position); /* Tell a play position (in seconds) */ double (*Tell)(void *music); /* Get Music duration (in seconds) */ double (*Duration)(void *music); /* Tell a loop start position (in seconds) */ double (*LoopStart)(void *music); /* Tell a loop end position (in seconds) */ double (*LoopEnd)(void *music); /* Tell a loop length position (in seconds) */ double (*LoopLength)(void *music); /* Get a meta-tag string if available */ const char* (*GetMetaTag)(void *music, Mix_MusicMetaTag tag_type); /* Get number of tracks. Returns -1 if not applicable */ int (*GetNumTracks)(void *music); /* Start a specific track */ int (*StartTrack)(void *music, int track); /* Pause playing music */ void (*Pause)(void *music); /* Resume playing music */ void (*Resume)(void *music); /* Stop playing music */ void (*Stop)(void *music); /* Delete a music object */ void (*Delete)(void *music); /* Close the library and clean up */ void (*Close)(void); /* Unload the library */ void (*Unload)(void); } Mix_MusicInterface; extern int get_num_music_interfaces(void); extern Mix_MusicInterface *get_music_interface(int index); extern Mix_MusicType detect_music_type(SDL_IOStream *src); extern bool load_music_type(Mix_MusicType type); extern bool open_music_type(Mix_MusicType type); extern bool has_music(Mix_MusicType type); extern void open_music(const SDL_AudioSpec *spec); extern int music_pcm_getaudio(void *context, void *data, int bytes, int volume, int (*GetSome)(void *context, void *data, int bytes, bool *done)); extern void SDLCALL music_mixer(void *udata, Uint8 *stream, int len); extern void pause_async_music(int pause_on); extern void close_music(void); extern void unload_music(void); extern SDL_AudioSpec music_spec; #endif /* MUSIC_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/utils.c000066400000000000000000000042261501405355700213100ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /* misc helper routines */ #include "utils.h" #include /* Is given tag a loop tag? */ bool _Mix_IsLoopTag(const char *tag) { char buf[5]; SDL_strlcpy(buf, tag, 5); return SDL_strcasecmp(buf, "LOOP") == 0; } /* Parse time string of the form HH:MM:SS.mmm and return equivalent sample * position */ Sint64 _Mix_ParseTime(char *time, long samplerate_hz) { char *num_start, *p; Sint64 result; char c; int val; /* Time is directly expressed as a sample position */ if (SDL_strchr(time, ':') == NULL) { return SDL_strtoll(time, NULL, 10); } result = 0; num_start = time; for (p = time; *p != '\0'; ++p) { if (*p == '.' || *p == ':') { c = *p; *p = '\0'; if ((val = SDL_atoi(num_start)) < 0) return -1; result = result * 60 + val; num_start = p + 1; *p = c; } if (*p == '.') { double val_f = SDL_atof(p); if (val_f < 0) return -1; return result * samplerate_hz + (Sint64) (val_f * samplerate_hz); } } if ((val = SDL_atoi(num_start)) < 0) return -1; return (result * 60 + val) * samplerate_hz; } libsdl3-mixer-3~git20250523~daf0503+ds/src/utils.h000066400000000000000000000024121501405355700213100ustar00rootroot00000000000000/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef UTILS_H_ #define UTILS_H_ /* misc helper routines */ #include /* Parse time string of the form HH:MM:SS.mmm and return equivalent sample * position */ extern Sint64 _Mix_ParseTime(char *time, long samplerate_hz); extern bool _Mix_IsLoopTag(const char *tag); #endif /* UTILS_H_ */ libsdl3-mixer-3~git20250523~daf0503+ds/src/version.rc000066400000000000000000000017211501405355700220140ustar00rootroot00000000000000 #include "winresrc.h" LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 3,0,0,0 PRODUCTVERSION 3,0,0,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL_mixer\0" VALUE "FileVersion", "3, 0, 0, 0\0" VALUE "InternalName", "SDL_mixer\0" VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3_mixer.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" VALUE "ProductVersion", "3, 0, 0, 0\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END