Merge remote-tracking branch 'gzdoom/master' into big_beautiful_merge
This commit is contained in:
commit
3fdd22ef91
1433 changed files with 484787 additions and 9566 deletions
97
libraries/ZMusic/.github/workflows/continuous_integration.yml
vendored
Normal file
97
libraries/ZMusic/.github/workflows/continuous_integration.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
name: Continuous Integration
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.config.name }}
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- name: Visual Studio - Release
|
||||
os: windows-latest
|
||||
build_type: Release
|
||||
|
||||
- name: Visual Studio - Debug
|
||||
os: windows-latest
|
||||
build_type: Debug
|
||||
|
||||
- name: macOS Clang - Dynamic Deps
|
||||
os: macos-latest
|
||||
build_type: Release
|
||||
|
||||
- name: macOS Clang - Static Deps
|
||||
os: macos-latest
|
||||
build_type: Release
|
||||
cmake_options: -DDYN_FLUIDSYNTH=OFF -DDYN_MPG123=OFF -DDYN_SNDFILE=OFF
|
||||
deps_cmd: brew install libsndfile mpg123
|
||||
|
||||
- name: Linux GCC - Dynamic Deps
|
||||
os: ubuntu-latest
|
||||
build_type: Release
|
||||
deps_cmd: sudo apt update && sudo apt install libglib2.0-dev
|
||||
|
||||
- name: Linux GCC - Static Deps
|
||||
os: ubuntu-latest
|
||||
build_type: Release
|
||||
cmake_options: -DDYN_FLUIDSYNTH=OFF -DDYN_MPG123=OFF -DDYN_SNDFILE=OFF
|
||||
deps_cmd: sudo apt update && sudo apt install libasound2-dev libglib2.0-dev libmpg123-dev libsndfile1-dev
|
||||
|
||||
- name: Linux Clang - Dynamic Deps
|
||||
os: ubuntu-latest
|
||||
build_type: Release
|
||||
cmake_options: -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
|
||||
deps_cmd: sudo apt update && sudo apt install libglib2.0-dev
|
||||
|
||||
- name: Linux Clang - Static Deps
|
||||
os: ubuntu-latest
|
||||
build_type: Release
|
||||
cmake_options: -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DDYN_FLUIDSYNTH=OFF -DDYN_MPG123=OFF -DDYN_SNDFILE=OFF
|
||||
deps_cmd: sudo apt update && sudo apt install libasound2-dev libglib2.0-dev libmpg123-dev libsndfile1-dev
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
${{ matrix.config.deps_cmd }}
|
||||
|
||||
- name: Configure
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -DCMAKE_INSTALL_PREFIX=./build_install ${{ matrix.config.cmake_options }} .
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
export MAKEFLAGS=--keep-going
|
||||
cmake --build build --target install --config ${{ matrix.config.build_type }} --parallel 3
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
cd samples/list_midi_devices
|
||||
mkdir build
|
||||
cd build
|
||||
declare -x PREFIX=`pwd`/../../../build_install
|
||||
cmake -DCMAKE_PREFIX_PATH=${PREFIX} ${{ matrix.config.cmake_options }} ..
|
||||
cmake --build . --config ${{ matrix.config.build_type }}
|
||||
if [[ "${{ runner.os }}" == 'macOS' ]]; then
|
||||
declare -x DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${PREFIX}
|
||||
fi
|
||||
if [[ "${{ runner.os }}" == 'Windows' ]]; then
|
||||
cp ${PREFIX}/bin/zmusic.dll ${{ matrix.config.build_type }}
|
||||
${{ matrix.config.build_type }}/list_midi_devices.exe
|
||||
else
|
||||
./list_midi_devices
|
||||
fi
|
||||
|
||||
- name: Upload Install Directory
|
||||
if: false # Remove this line to upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.config.name }}
|
||||
path: build_install
|
||||
2
libraries/ZMusic/.gitignore
vendored
Normal file
2
libraries/ZMusic/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/build*
|
||||
*.user*
|
||||
212
libraries/ZMusic/CMakeLists.txt
Normal file
212
libraries/ZMusic/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
cmake_minimum_required(VERSION 3.13...3.19)
|
||||
|
||||
if (VCPKG_LIBSNDFILE)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "vcpkg-libsndfile")
|
||||
endif()
|
||||
|
||||
project(ZMusic
|
||||
VERSION 1.2.0
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
if (VCPKG_TOOLCHAIN)
|
||||
if(VCPKG_TARGET_TRIPLET MATCHES "-static$")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
|
||||
option(VCPKG_LIBSNDFILE "Import libsndfile from vcpkg" OFF)
|
||||
else()
|
||||
set(VCPKG_MANIFEST_FEATURES)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(GNUInstallDirs)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(ZUtility)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
# This project is being built standalone
|
||||
|
||||
# Give user option to build shared or static
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||
|
||||
# Enable install rules
|
||||
set(ZMUSIC_INSTALL ON)
|
||||
else()
|
||||
# This project is being vendored by another project, set option default if
|
||||
# the parent project doesn't provide them.
|
||||
|
||||
if(NOT DEFINED BUILD_SHARED_LIBS)
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
endif()
|
||||
|
||||
# Although install rules can be avoided with EXCLUDE_FROM_ALL on
|
||||
# add_subdirectory, the EXPORT rules may place certain usage requirements on
|
||||
# targets shared between the two projects.
|
||||
if(NOT DEFINED ZMUSIC_INSTALL)
|
||||
set(ZMUSIC_INSTALL OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING
|
||||
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel."
|
||||
FORCE)
|
||||
endif()
|
||||
|
||||
if(MSVC AND CMAKE_VERSION VERSION_LESS 3.15)
|
||||
message(WARNING "Some things may be misconfigured. Please update to CMake >= 3.15 with Visual Studio.")
|
||||
endif()
|
||||
if(NOT CMAKE_MSVC_RUNTIME_LIBRARY)
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Install path prefix" FORCE)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
# optionally generate assembly output for checking crash locations.
|
||||
option(ZMUSIC_GENERATE_ASM "Generate assembly output." OFF)
|
||||
if(ZMUSIC_GENERATE_ASM)
|
||||
add_compile_options("/FAcs")
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
"/GF" # String pooling
|
||||
"/Gy" # Function-level linking
|
||||
"/GR-" # Disable run-time type information
|
||||
"/permissive-"
|
||||
"/Oy" "/Oi" "/GS-"
|
||||
|
||||
# Disable warnings for unsecure CRT functions from VC8+
|
||||
"/wd4996"
|
||||
)
|
||||
|
||||
add_compile_definitions(
|
||||
"UNICODE"
|
||||
"_UNICODE"
|
||||
"_WIN32_WINNT=0x0600"
|
||||
# Debug allocations in debug builds
|
||||
$<$<CONFIG:DEBUG>:_CRTDBG_MAP_ALLOC>
|
||||
)
|
||||
|
||||
add_link_options(
|
||||
"/opt:ref" # Eliminate unreferenced functions and data
|
||||
"/opt:icf" # Perform identical COMDAT folding
|
||||
"/nodefaultlib:msvcrt"
|
||||
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:/TSAWARE>
|
||||
"/LARGEADDRESSAWARE"
|
||||
)
|
||||
|
||||
# RelWithDebInfo uses /Ob1 by default instead of Ob2 like Release
|
||||
string(REPLACE "/Ob1 " "/Ob2 " CMAKE_CXX_FLAGS_RELWITHDEBINFO ${CMAKE_CXX_FLAGS_RELWITHDEBINFO} )
|
||||
|
||||
# The CMake configurations set /GR by default, which conflict with our settings.
|
||||
# CMake 3.20 fixes the need to do this
|
||||
string(REPLACE " /GR" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} )
|
||||
|
||||
check_cxx_compiler_flag("/std:c++14" COMPILER_SUPPORTS_CXX14)
|
||||
if(COMPILER_SUPPORTS_CXX14)
|
||||
set(FLAG_CPP14 "/std:c++14")
|
||||
endif()
|
||||
if(MSVC_VERSION LESS 1910)
|
||||
unset(COMPILER_SUPPORTS_CXX14) # MSVC older than 2017 fails to build YMFM
|
||||
endif()
|
||||
else()
|
||||
add_compile_options("-ffp-contract=off")
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
# With standard Apple tools -stdlib=libc++ needs to be specified in order to get
|
||||
# C++11 support using SDKs 10.7 and 10.8.
|
||||
add_compile_options("-stdlib=libc++")
|
||||
add_link_options("-stdlib=libc++")
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
# If we're compiling with a custom GCC on the Mac (which we know since g++-4.2 doesn't support C++11) statically link libgcc.
|
||||
add_compile_options("-static-libgcc")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
check_cxx_compiler_flag("-std=c++14" COMPILER_SUPPORTS_CXX14)
|
||||
if(COMPILER_SUPPORTS_CXX14)
|
||||
set(FLAG_CPP14 "-std=c++14")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(COMPILER_SUPPORTS_CXX14)
|
||||
message("== Your C++ compiler supports C++14, YMFM emulators will be ENABLED")
|
||||
else()
|
||||
message("== Your C++ compiler does NOT supports C++14, YMFM emulators will be DISABLED")
|
||||
endif()
|
||||
|
||||
# Initialize our list of find_package dependencies for configure_package_config_file
|
||||
set(ZMUSIC_PACKAGE_DEPENDENCIES "" CACHE INTERNAL "")
|
||||
|
||||
if (WIN32 AND MINGW)
|
||||
add_compile_definitions(-D_UNICODE -DUNICODE)
|
||||
add_compile_definitions(-D__USE_MINGW_ANSI_STDIO=1)
|
||||
endif()
|
||||
|
||||
add_subdirectory(thirdparty)
|
||||
add_subdirectory(source)
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/ZMusicConfigVersion.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
configure_package_config_file(
|
||||
cmake/ZMusicConfig.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/ZMusicConfig.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake
|
||||
)
|
||||
if(ZMUSIC_INSTALL)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ZMusicConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/ZMusicConfigVersion.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ZMusic
|
||||
COMPONENT devel
|
||||
)
|
||||
endif()
|
||||
|
||||
if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(CPACK_PACKAGE_CONTACT "First Last <example@example.com>" CACHE STRING "Contact info for archive maintainer.")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "GZDoom's music system as a standalone library")
|
||||
string(TOLOWER "${PROJECT_NAME}" CPACK_PACKAGE_NAME)
|
||||
set(CPACK_PACKAGE_NAME "lib${CPACK_PACKAGE_NAME}")
|
||||
# Use same prefix for packaging for consistency
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
set(CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
|
||||
|
||||
set(CPACK_COMPONENT_DEVEL_DEPENDS Full Lite)
|
||||
|
||||
if(WIN32)
|
||||
set(CPACK_GENERATOR "ZIP")
|
||||
else()
|
||||
set(CPACK_GENERATOR "DEB")
|
||||
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON)
|
||||
set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
|
||||
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://zdoom.org")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "libs")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION ON)
|
||||
set(CPACK_DEBIAN_COMPRESSION_TYPE "xz")
|
||||
|
||||
set(CPACK_DEBIAN_FULL_PACKAGE_NAME "${CPACK_PACKAGE_NAME}${PROJECT_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_LITE_PACKAGE_NAME "${CPACK_PACKAGE_NAME}lite${PROJECT_VERSION_MAJOR}")
|
||||
|
||||
set(CPACK_DEBIAN_DEVEL_PACKAGE_NAME "${CPACK_PACKAGE_NAME}-dev")
|
||||
set(CPACK_DEBIAN_DEVEL_PACKAGE_SECTION "libdevel")
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
16
libraries/ZMusic/README.md
Normal file
16
libraries/ZMusic/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# ZMusic
|
||||
GZDoom's music system as a standalone library
|
||||
|
||||
Welcome! This repository is a library for use with the projects [GZDoom](https://github.com/coelckers/GZDoom), [Raze](https://github.com/coelckers/Raze), and the newer [PrBoom+](https://github.com/coelckers/prboom-plus).
|
||||
|
||||
Compile instructions are pretty simple for most systems.
|
||||
|
||||
```
|
||||
git clone https://github.com/coelckers/ZMusic.git
|
||||
mkdir ZMusic/build
|
||||
cd ZMusic/build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
On Unix/Linux you may also supply `sudo make install` in the build folder to push the compiled library directly into the file system so that it can be found by the previously mentioned projects.
|
||||
38
libraries/ZMusic/cmake/FindFluidSynth.cmake
Normal file
38
libraries/ZMusic/cmake/FindFluidSynth.cmake
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# - Find fluidsynth
|
||||
# Find the native fluidsynth includes and library
|
||||
#
|
||||
# FLUIDSYNTH_INCLUDE_DIR - where to find fluidsynth.h
|
||||
# FLUIDSYNTH_LIBRARY - Path to fluidsynth library.
|
||||
# FLUIDSYNTH_FOUND - True if fluidsynth found.
|
||||
|
||||
|
||||
if(FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARY)
|
||||
# Already in cache, be silent
|
||||
set(FluidSynth_FIND_QUIETLY TRUE)
|
||||
endif()
|
||||
|
||||
if(NOT FLUIDSYNTH_INCLUDE_DIR)
|
||||
find_path(FLUIDSYNTH_INCLUDE_DIR fluidsynth.h)
|
||||
endif()
|
||||
|
||||
if(NOT FLUIDSYNTH_LIBRARY)
|
||||
find_library(FLUIDSYNTH_LIBRARY NAMES fluidsynth)
|
||||
endif()
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set FLUIDSYNTH_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(FluidSynth DEFAULT_MSG FLUIDSYNTH_LIBRARY FLUIDSYNTH_INCLUDE_DIR)
|
||||
|
||||
if(FLUIDSYNTH_FOUND)
|
||||
add_library(libfluidsynth UNKNOWN IMPORTED)
|
||||
set_target_properties(libfluidsynth
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${FLUIDSYNTH_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${FLUIDSYNTH_INCLUDE_DIR}"
|
||||
)
|
||||
|
||||
# Legacy variables
|
||||
set(FLUIDSYNTH_INCLUDE_DIRS ${FLUIDSYNTH_INCLUDE_DIR})
|
||||
set(FLUIDSYNTH_LIBRARIES ${FLUIDSYNTH_LIBRARY})
|
||||
endif()
|
||||
37
libraries/ZMusic/cmake/FindGME.cmake
Normal file
37
libraries/ZMusic/cmake/FindGME.cmake
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# - Find Game Music Emulation
|
||||
#
|
||||
# GME_INCLUDE_DIR - where to find gme.h
|
||||
# GME_LIBRARY - Path to gme library.
|
||||
# GME_FOUND - True if gme found.
|
||||
|
||||
|
||||
if(GME_INCLUDE_DIR AND GME_LIBRARY)
|
||||
# Already in cache, be silent
|
||||
set(GME_FIND_QUIETLY TRUE)
|
||||
endif()
|
||||
|
||||
if(NOT GME_INCLUDE_DIR)
|
||||
find_path(GME_INCLUDE_DIR gme.h)
|
||||
endif()
|
||||
|
||||
if(NOT GME_LIBRARY)
|
||||
find_library(GME_LIBRARY NAMES gme)
|
||||
endif()
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set GME_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GME DEFAULT_MSG GME_LIBRARY GME_INCLUDE_DIR)
|
||||
|
||||
if(GME_FOUND)
|
||||
add_library(gme UNKNOWN IMPORTED)
|
||||
set_target_properties(gme
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${GME_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${GME_INCLUDE_DIR}"
|
||||
)
|
||||
|
||||
# Legacy variables
|
||||
set(GME_INCLUDE_DIRS ${GME_INCLUDE_DIR})
|
||||
set(GME_LIBRARIES ${GME_LIBRARY})
|
||||
endif()
|
||||
43
libraries/ZMusic/cmake/FindMPG123.cmake
Normal file
43
libraries/ZMusic/cmake/FindMPG123.cmake
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# - Find mpg123
|
||||
# Find the native mpg123 includes and library
|
||||
#
|
||||
# MPG123_INCLUDE_DIR - where to find mpg123.h
|
||||
# MPG123_LIBRARY - Path to mpg123 library.
|
||||
# MPG123_FOUND - True if mpg123 found.
|
||||
|
||||
if(MPG123_INCLUDE_DIR AND MPG123_LIBRARY)
|
||||
# Already in cache, be silent
|
||||
set(MPG123_FIND_QUIETLY TRUE)
|
||||
endif(MPG123_INCLUDE_DIR AND MPG123_LIBRARY)
|
||||
|
||||
if(NOT MPG123_INCLUDE_DIR)
|
||||
find_path(MPG123_INCLUDE_DIR mpg123.h
|
||||
PATHS "${MPG123_DIR}"
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT MPG123_LIBRARY)
|
||||
find_library(MPG123_LIBRARY NAMES mpg123 mpg123-0
|
||||
PATHS "${MPG123_DIR}"
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
endif()
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set MPG123_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(MPG123 DEFAULT_MSG MPG123_LIBRARY MPG123_INCLUDE_DIR)
|
||||
|
||||
if(MPG123_FOUND)
|
||||
add_library(mpg123 UNKNOWN IMPORTED)
|
||||
set_target_properties(mpg123
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${MPG123_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${MPG123_INCLUDE_DIR}"
|
||||
)
|
||||
|
||||
# Legacy variables
|
||||
set(MPG123_INCLUDE_DIRS ${MPG123_INCLUDE_DIR})
|
||||
set(MPG123_LIBRARIES ${MPG123_LIBRARY})
|
||||
endif()
|
||||
42
libraries/ZMusic/cmake/FindSndFile.cmake
Normal file
42
libraries/ZMusic/cmake/FindSndFile.cmake
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# - Try to find SndFile
|
||||
# Once done this will define
|
||||
#
|
||||
# SNDFILE_FOUND - system has SndFile
|
||||
# SNDFILE_INCLUDE_DIRS - the SndFile include directory
|
||||
# SNDFILE_LIBRARIES - Link these to use SndFile
|
||||
#
|
||||
# Copyright © 2006 Wengo
|
||||
# Copyright © 2009 Guillaume Martres
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the New
|
||||
# BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
#
|
||||
|
||||
if(NOT SNDFILE_INCLUDE_DIR)
|
||||
find_path(SNDFILE_INCLUDE_DIR NAMES sndfile.h)
|
||||
endif()
|
||||
|
||||
if(NOT SNDFILE_LIBRARY)
|
||||
find_library(SNDFILE_LIBRARY NAMES sndfile sndfile-1)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set SNDFILE_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR)
|
||||
|
||||
if(SNDFILE_FOUND)
|
||||
add_library(sndfile UNKNOWN IMPORTED GLOBAL)
|
||||
set_target_properties(sndfile
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${SNDFILE_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SNDFILE_INCLUDE_DIR}"
|
||||
)
|
||||
|
||||
add_library(SndFile::sndfile ALIAS sndfile)
|
||||
|
||||
# Legacy variables
|
||||
set(SNDFILE_INCLUDE_DIRS "${SNDFILE_INCLUDE_DIR}")
|
||||
set(SNDFILE_LIBRARIES "${SNDFILE_LIBRARY}")
|
||||
endif()
|
||||
19
libraries/ZMusic/cmake/ZMusicConfig.cmake.in
Normal file
19
libraries/ZMusic/cmake/ZMusicConfig.cmake.in
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
set(_supported_components "Full;Lite")
|
||||
|
||||
if(NOT ZMusic_FIND_COMPONENTS)
|
||||
set(ZMusic_FIND_COMPONENTS "${_supported_components}")
|
||||
endif()
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
foreach(_module @ZMUSIC_PACKAGE_DEPENDENCIES@)
|
||||
find_dependency(${_module})
|
||||
endforeach()
|
||||
|
||||
foreach(_comp ${ZMusic_FIND_COMPONENTS})
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/ZMusic${_comp}Targets.cmake")
|
||||
set(ZMusic_${_comp}_FOUND TRUE)
|
||||
endforeach()
|
||||
|
||||
check_required_components(ZMusic)
|
||||
137
libraries/ZMusic/cmake/ZUtility.cmake
Normal file
137
libraries/ZMusic/cmake/ZUtility.cmake
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
include_guard(DIRECTORY)
|
||||
|
||||
include(CheckFunctionExists)
|
||||
|
||||
# BEGIN: Variables
|
||||
|
||||
set(COMPILER_ID_GNU_COMPATIBLE "AppleClang;Clang;GNU")
|
||||
|
||||
# Replacement variables for a possible long list of C/C++ compilers compatible with GCC
|
||||
if(CMAKE_C_COMPILER_ID IN_LIST COMPILER_ID_GNU_COMPATIBLE)
|
||||
set(COMPILER_IS_GNUC_COMPATIBLE TRUE)
|
||||
else()
|
||||
set(COMPILER_IS_GNUC_COMPATIBLE FALSE)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID IN_LIST COMPILER_ID_GNU_COMPATIBLE)
|
||||
set(COMPILER_IS_GNUCXX_COMPATIBLE TRUE)
|
||||
else()
|
||||
set(COMPILER_IS_GNUCXX_COMPATIBLE FALSE)
|
||||
endif()
|
||||
|
||||
# BEGIN: Functions
|
||||
|
||||
function(determine_package_config_dependency)
|
||||
# Don't need to worry about this when building shared libraries
|
||||
if(BUILD_SHARED_LIBS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(Dest "${ARGV0}")
|
||||
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "TARGET;MODULE" "")
|
||||
|
||||
if(TARGET ${ARG_TARGET})
|
||||
get_property(TgtImported TARGET ${ARG_TARGET} PROPERTY IMPORTED)
|
||||
if(TgtImported)
|
||||
list(APPEND "${Dest}" "${ARG_MODULE}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set("${Dest}" "${${Dest}}" CACHE INTERNAL "")
|
||||
endfunction()
|
||||
|
||||
# Adds private links to a given target, but any OBJECT or INTERFACE library will
|
||||
# be hidden from install(EXPORT).
|
||||
function(target_link_libraries_hidden Tgt)
|
||||
set(Links ${ARGV})
|
||||
list(REMOVE_AT Links 0)
|
||||
|
||||
get_property(TgtType TARGET "${Tgt}" PROPERTY TYPE)
|
||||
|
||||
foreach(Link IN LISTS Links)
|
||||
if(TARGET "${Link}")
|
||||
get_property(LinkImported TARGET "${Link}" PROPERTY IMPORTED)
|
||||
get_property(LinkType TARGET "${Link}" PROPERTY TYPE)
|
||||
|
||||
if(NOT LinkImported AND (LinkType STREQUAL "OBJECT_LIBRARY" OR LinkType STREQUAL "INTERFACE_LIBRARY"))
|
||||
target_link_libraries("${Tgt}" PRIVATE "$<BUILD_INTERFACE:${Link}>")
|
||||
|
||||
# Since we're potentially hiding usage requirements from the
|
||||
# exported targets, we need to do this recursively.
|
||||
if(TgtType STREQUAL "STATIC_LIBRARY")
|
||||
get_property(TransitiveLinks TARGET "${Link}" PROPERTY INTERFACE_LINK_LIBRARIES)
|
||||
target_link_libraries_hidden("${Tgt}" ${TransitiveLinks})
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries("${Tgt}" PRIVATE "${Link}")
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries("${Tgt}" PRIVATE "${Link}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# For a given subdirectory, set all configs to release.
|
||||
function(make_release_only)
|
||||
if(CMAKE_CONFIGURATION_TYPES)
|
||||
set(ConfigTypes ${CMAKE_CONFIGURATION_TYPES})
|
||||
list(REMOVE_ITEM ConfigTypes "Release")
|
||||
else()
|
||||
set(ConfigTypes "Debug;MinSizeRel;RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
foreach(Config IN LISTS ConfigTypes)
|
||||
string(TOUPPER "${Config}" ConfigUpper)
|
||||
set("CMAKE_C_FLAGS_${ConfigUpper}" "${CMAKE_C_FLAGS_RELEASE}" PARENT_SCOPE)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# As documented, OBJECT libraries in INTERFACE_LINK_LIBRARIES are treated as if
|
||||
# they were INTERFACE libraries. So if we want to have an INTERFACE library
|
||||
# which links to OBJECT libraries we need to copy those objects into the
|
||||
# INTERFACE_SOURCES.
|
||||
#
|
||||
# This function should only be used on INTERFACE_LIBRARY targets.
|
||||
function(propagate_object_links Tgt)
|
||||
get_property(Links TARGET "${Tgt}" PROPERTY INTERFACE_LINK_LIBRARIES)
|
||||
|
||||
foreach(Link IN LISTS Links)
|
||||
if(TARGET "${Link}")
|
||||
get_property(LinkType TARGET "${Link}" PROPERTY TYPE)
|
||||
|
||||
if(LinkType STREQUAL "OBJECT_LIBRARY")
|
||||
target_sources("${Tgt}" INTERFACE $<TARGET_OBJECTS:${Link}>)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(require_stricmp Tgt Visibility)
|
||||
check_function_exists(stricmp STRICMP_EXISTS)
|
||||
if(NOT STRICMP_EXISTS)
|
||||
target_compile_definitions(${Tgt} ${Visibility} stricmp=strcasecmp)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(require_strnicmp Tgt Visibility)
|
||||
check_function_exists(strnicmp STRNICMP_EXISTS)
|
||||
if(NOT STRNICMP_EXISTS)
|
||||
target_compile_definitions(${Tgt} ${Visibility} strnicmp=strncasecmp)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(use_fast_math)
|
||||
foreach(Tgt IN LISTS ARGN)
|
||||
if(TARGET Tgt)
|
||||
set(TgtType TARGET)
|
||||
else()
|
||||
set(TgtType SOURCE)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
set_property("${TgtType}" "${Tgt}" APPEND PROPERTY COMPILE_OPTIONS "/fp:fast")
|
||||
elseif(COMPILER_IS_GNUCXX_COMPATIBLE)
|
||||
set_property("${TgtType}" "${Tgt}" APPEND PROPERTY COMPILE_OPTIONS "-ffast-math" "-ffp-contract=fast")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
448
libraries/ZMusic/include/zmusic.h
Normal file
448
libraries/ZMusic/include/zmusic.h
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
#ifndef __ZMUSIC_H_
|
||||
#define __ZMUSIC_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
struct SoundDecoder; // Anonymous to the client.
|
||||
|
||||
typedef unsigned char zmusic_bool;
|
||||
|
||||
// These constants must match the corresponding values of the Windows headers
|
||||
// to avoid readjustment in the native Windows device's playback functions
|
||||
// and should not be changed.
|
||||
typedef enum EMidiDeviceClass_
|
||||
{
|
||||
MIDIDEV_MIDIPORT = 1,
|
||||
MIDIDEV_SYNTH,
|
||||
MIDIDEV_SQSYNTH,
|
||||
MIDIDEV_FMSYNTH,
|
||||
MIDIDEV_MAPPER,
|
||||
MIDIDEV_WAVETABLE,
|
||||
MIDIDEV_SWSYNTH
|
||||
} EMidiDeviceClass;
|
||||
|
||||
typedef enum EMIDIType_
|
||||
{
|
||||
MIDI_NOTMIDI,
|
||||
MIDI_MIDI,
|
||||
MIDI_HMI,
|
||||
MIDI_XMI,
|
||||
MIDI_MUS,
|
||||
MIDI_MIDS
|
||||
} EMIDIType;
|
||||
|
||||
typedef enum EMidiDevice_
|
||||
{
|
||||
MDEV_DEFAULT = -1,
|
||||
MDEV_STANDARD = 0,
|
||||
MDEV_OPL = 1,
|
||||
MDEV_SNDSYS = 2,
|
||||
MDEV_TIMIDITY = 3,
|
||||
MDEV_FLUIDSYNTH = 4,
|
||||
MDEV_GUS = 5,
|
||||
MDEV_WILDMIDI = 6,
|
||||
MDEV_ADL = 7,
|
||||
MDEV_OPN = 8,
|
||||
|
||||
MDEV_COUNT
|
||||
} EMidiDevice;
|
||||
|
||||
typedef enum ESoundFontTypes_
|
||||
{
|
||||
SF_SF2 = 1,
|
||||
SF_GUS = 2,
|
||||
SF_WOPL = 4,
|
||||
SF_WOPN = 8
|
||||
} ESoundFontTypes;
|
||||
|
||||
typedef struct SoundStreamInfo_
|
||||
{
|
||||
int mBufferSize; // If mBufferSize is 0, the song doesn't use streaming but plays through a different interface.
|
||||
int mSampleRate;
|
||||
int mNumChannels; // If mNumChannels is negative, 16 bit integer format is used instead of floating point.
|
||||
} SoundStreamInfo;
|
||||
|
||||
typedef enum SampleType_
|
||||
{
|
||||
SampleType_UInt8,
|
||||
SampleType_Int16,
|
||||
SampleType_Float32
|
||||
} SampleType;
|
||||
|
||||
typedef enum ChannelConfig_
|
||||
{
|
||||
ChannelConfig_Mono,
|
||||
ChannelConfig_Stereo
|
||||
} ChannelConfig;
|
||||
|
||||
typedef struct SoundStreamInfoEx_
|
||||
{
|
||||
int mBufferSize; // If mBufferSize is 0, the song doesn't use streaming but plays through a different interface.
|
||||
int mSampleRate;
|
||||
SampleType mSampleType;
|
||||
ChannelConfig mChannelConfig;
|
||||
} SoundStreamInfoEx;
|
||||
|
||||
// NOTE: The following enums are part of the ABI! Do not insert new values in the middle - always append them at the end!
|
||||
typedef enum EIntConfigKey_
|
||||
{
|
||||
zmusic_adl_chips_count,
|
||||
zmusic_adl_emulator_id,
|
||||
zmusic_adl_run_at_pcm_rate,
|
||||
zmusic_adl_fullpan,
|
||||
zmusic_adl_bank,
|
||||
zmusic_adl_use_custom_bank,
|
||||
zmusic_adl_volume_model,
|
||||
|
||||
zmusic_fluid_reverb,
|
||||
zmusic_fluid_chorus,
|
||||
zmusic_fluid_voices,
|
||||
zmusic_fluid_interp,
|
||||
zmusic_fluid_samplerate,
|
||||
zmusic_fluid_threads,
|
||||
zmusic_fluid_chorus_voices,
|
||||
zmusic_fluid_chorus_type,
|
||||
|
||||
zmusic_opl_numchips,
|
||||
zmusic_opl_core,
|
||||
zmusic_opl_fullpan,
|
||||
|
||||
zmusic_opn_chips_count,
|
||||
zmusic_opn_emulator_id,
|
||||
zmusic_opn_run_at_pcm_rate,
|
||||
zmusic_opn_fullpan,
|
||||
zmusic_opn_use_custom_bank,
|
||||
|
||||
zmusic_gus_dmxgus,
|
||||
zmusic_gus_midi_voices,
|
||||
zmusic_gus_memsize,
|
||||
|
||||
zmusic_timidity_modulation_wheel,
|
||||
zmusic_timidity_portamento,
|
||||
zmusic_timidity_reverb,
|
||||
zmusic_timidity_reverb_level,
|
||||
zmusic_timidity_chorus,
|
||||
zmusic_timidity_surround_chorus,
|
||||
zmusic_timidity_channel_pressure,
|
||||
zmusic_timidity_lpf_def,
|
||||
zmusic_timidity_temper_control,
|
||||
zmusic_timidity_modulation_envelope,
|
||||
zmusic_timidity_overlap_voice_allow,
|
||||
zmusic_timidity_drum_effect,
|
||||
zmusic_timidity_pan_delay,
|
||||
zmusic_timidity_key_adjust,
|
||||
|
||||
zmusic_wildmidi_reverb,
|
||||
zmusic_wildmidi_enhanced_resampling,
|
||||
|
||||
zmusic_snd_midiprecache,
|
||||
|
||||
zmusic_mod_samplerate,
|
||||
zmusic_mod_volramp,
|
||||
zmusic_mod_interp,
|
||||
zmusic_mod_autochip,
|
||||
zmusic_mod_autochip_size_force,
|
||||
zmusic_mod_autochip_size_scan,
|
||||
zmusic_mod_autochip_scan_threshold,
|
||||
|
||||
zmusic_snd_streambuffersize,
|
||||
|
||||
zmusic_snd_mididevice,
|
||||
zmusic_snd_outputrate,
|
||||
zmusic_mod_preferredplayer,
|
||||
|
||||
// new constants since 1.2.0
|
||||
zmusic_adl_use_genmidi,
|
||||
zmusic_adl_chan_alloc,
|
||||
zmusic_adl_auto_arpeggio,
|
||||
zmusic_opn_volume_model,
|
||||
zmusic_opn_chan_alloc,
|
||||
zmusic_opn_auto_arpeggio,
|
||||
|
||||
NUM_ZMUSIC_INT_CONFIGS
|
||||
} EIntConfigKey;
|
||||
|
||||
typedef enum EFloatConfigKey_
|
||||
{
|
||||
zmusic_fluid_gain = 1000,
|
||||
zmusic_fluid_reverb_roomsize,
|
||||
zmusic_fluid_reverb_damping,
|
||||
zmusic_fluid_reverb_width,
|
||||
zmusic_fluid_reverb_level,
|
||||
zmusic_fluid_chorus_level,
|
||||
zmusic_fluid_chorus_speed,
|
||||
zmusic_fluid_chorus_depth,
|
||||
|
||||
zmusic_timidity_drum_power,
|
||||
zmusic_timidity_tempo_adjust,
|
||||
zmusic_timidity_min_sustain_time,
|
||||
|
||||
zmusic_gme_stereodepth,
|
||||
zmusic_mod_dumb_mastervolume,
|
||||
|
||||
zmusic_snd_musicvolume,
|
||||
zmusic_relative_volume,
|
||||
zmusic_snd_mastervolume,
|
||||
|
||||
// new constants since 1.2.0
|
||||
zmusic_opl_gain,
|
||||
zmusic_adl_gain,
|
||||
zmusic_opn_gain,
|
||||
|
||||
NUM_FLOAT_CONFIGS
|
||||
} EFloatConfigKey;
|
||||
|
||||
typedef enum EStringConfigKey_
|
||||
{
|
||||
zmusic_adl_custom_bank = 2000,
|
||||
zmusic_fluid_lib,
|
||||
zmusic_fluid_patchset,
|
||||
zmusic_opn_custom_bank,
|
||||
zmusic_gus_config,
|
||||
zmusic_gus_patchdir,
|
||||
zmusic_timidity_config,
|
||||
zmusic_wildmidi_config,
|
||||
|
||||
NUM_STRING_CONFIGS
|
||||
} EStringConfigKey;
|
||||
|
||||
|
||||
typedef struct ZMusicCustomReader_
|
||||
{
|
||||
void* handle;
|
||||
char* (*gets)(struct ZMusicCustomReader_* handle, char* buff, int n);
|
||||
long (*read)(struct ZMusicCustomReader_* handle, void* buff, int32_t size);
|
||||
long (*seek)(struct ZMusicCustomReader_* handle, long offset, int whence);
|
||||
long (*tell)(struct ZMusicCustomReader_* handle);
|
||||
void (*close)(struct ZMusicCustomReader_* handle);
|
||||
} ZMusicCustomReader;
|
||||
|
||||
typedef struct ZMusicMidiOutDevice_
|
||||
{
|
||||
char *Name;
|
||||
int ID;
|
||||
int Technology;
|
||||
} ZMusicMidiOutDevice;
|
||||
|
||||
typedef enum EZMusicMessageSeverity_
|
||||
{
|
||||
ZMUSIC_MSG_VERBOSE = 1,
|
||||
ZMUSIC_MSG_DEBUG = 5,
|
||||
ZMUSIC_MSG_NOTIFY = 10,
|
||||
ZMUSIC_MSG_WARNING = 50,
|
||||
ZMUSIC_MSG_ERROR = 100,
|
||||
ZMUSIC_MSG_FATAL = 666,
|
||||
} EZMusicMessageSeverity;
|
||||
|
||||
typedef struct ZMusicCallbacks_
|
||||
{
|
||||
// Callbacks the client can install to capture messages from the backends
|
||||
// or to provide sound font data.
|
||||
|
||||
void (*MessageFunc)(int severity, const char* msg);
|
||||
// The message callbacks are optional, without them the output goes to stdout.
|
||||
|
||||
// Retrieves the path to a soundfont identified by an identifier. Only needed if the client virtualizes the sound font names
|
||||
const char *(*PathForSoundfont)(const char *name, int type);
|
||||
|
||||
// The sound font callbacks are for allowing the client to customize sound font management and they are optional.
|
||||
// They only need to be defined if the client virtualizes the sound font management and doesn't pass real paths to the music code.
|
||||
// Without them only paths to real files can be used. If one of these gets set, all must be set.
|
||||
|
||||
// This opens a sound font. Must return a handle with which the sound font's content can be read.
|
||||
void *(*OpenSoundFont)(const char* name, int type);
|
||||
|
||||
// Opens a file in the sound font. For GUS patch sets this will try to open each patch with this function.
|
||||
// For other formats only the sound font's actual name can be requested.
|
||||
// When passed NULL this must open the Timidity config file, if this is requested for an SF2 sound font it should be synthesized.
|
||||
ZMusicCustomReader* (*SF_OpenFile)(void* handle, const char* fn);
|
||||
|
||||
//Adds a path to the list of directories in which files must be looked for.
|
||||
void (*SF_AddToSearchPath)(void* handle, const char* path);
|
||||
|
||||
// Closes the sound font reader.
|
||||
void (*SF_Close)(void* handle);
|
||||
|
||||
// Used to handle client-specific path macros. If not set, the path may not contain any special tokens that may need expansion.
|
||||
const char *(*NicePath)(const char* path);
|
||||
} ZMusicCallbacks;
|
||||
|
||||
typedef enum ZMusicVariableType_
|
||||
{
|
||||
ZMUSIC_VAR_INT,
|
||||
ZMUSIC_VAR_BOOL,
|
||||
ZMUSIC_VAR_FLOAT,
|
||||
ZMUSIC_VAR_STRING,
|
||||
} ZMusicVariableType;
|
||||
|
||||
typedef struct ZMusicConfigurationSetting_
|
||||
{
|
||||
const char* name;
|
||||
int identifier;
|
||||
ZMusicVariableType type;
|
||||
float defaultVal;
|
||||
const char* defaultString;
|
||||
} ZMusicConfigurationSetting;
|
||||
|
||||
|
||||
#ifndef ZMUSIC_INTERNAL
|
||||
#if defined(_MSC_VER) && !defined(ZMUSIC_STATIC)
|
||||
#define DLL_IMPORT _declspec(dllimport)
|
||||
#else
|
||||
#define DLL_IMPORT
|
||||
#endif
|
||||
// Note that the internal 'class' definitions are not C compatible!
|
||||
typedef struct _ZMusic_MidiSource_Struct { int zm1; } *ZMusic_MidiSource;
|
||||
typedef struct _ZMusic_MusicStream_Struct { int zm2; } *ZMusic_MusicStream;
|
||||
struct SoundDecoder;
|
||||
#endif
|
||||
|
||||
#ifndef ZMUSIC_NO_PROTOTYPES
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
DLL_IMPORT const char* ZMusic_GetLastError();
|
||||
|
||||
// Sets callbacks for functionality that the client needs to provide.
|
||||
DLL_IMPORT void ZMusic_SetCallbacks(const ZMusicCallbacks* callbacks);
|
||||
// Sets GenMidi data for OPL playback. If this isn't provided the OPL synth will not work.
|
||||
DLL_IMPORT void ZMusic_SetGenMidi(const uint8_t* data);
|
||||
// Set default bank for OPN. Without this OPN only works with custom banks.
|
||||
DLL_IMPORT void ZMusic_SetWgOpn(const void* data, unsigned len);
|
||||
// Set DMXGUS data for running the GUS synth in actual GUS mode.
|
||||
DLL_IMPORT void ZMusic_SetDmxGus(const void* data, unsigned len);
|
||||
|
||||
// Returns an array with all available configuration options - terminated with an empty entry where all elements are 0.
|
||||
DLL_IMPORT const ZMusicConfigurationSetting* ZMusic_GetConfiguration();
|
||||
|
||||
// These exports are needed by the MIDI dumpers which need to remain on the client side because the need access to the client's file system.
|
||||
DLL_IMPORT EMIDIType ZMusic_IdentifyMIDIType(uint32_t* id, int size);
|
||||
DLL_IMPORT ZMusic_MidiSource ZMusic_CreateMIDISource(const uint8_t* data, size_t length, EMIDIType miditype);
|
||||
DLL_IMPORT zmusic_bool ZMusic_MIDIDumpWave(ZMusic_MidiSource source, EMidiDevice devtype, const char* devarg, const char* outname, int subsong, int samplerate);
|
||||
|
||||
DLL_IMPORT ZMusic_MusicStream ZMusic_OpenSong(ZMusicCustomReader* reader, EMidiDevice device, const char* Args);
|
||||
DLL_IMPORT ZMusic_MusicStream ZMusic_OpenSongFile(const char *filename, EMidiDevice device, const char* Args);
|
||||
DLL_IMPORT ZMusic_MusicStream ZMusic_OpenSongMem(const void *mem, size_t size, EMidiDevice device, const char* Args);
|
||||
DLL_IMPORT ZMusic_MusicStream ZMusic_OpenCDSong(int track, int cdid);
|
||||
|
||||
DLL_IMPORT zmusic_bool ZMusic_FillStream(ZMusic_MusicStream stream, void* buff, int len);
|
||||
DLL_IMPORT zmusic_bool ZMusic_Start(ZMusic_MusicStream song, int subsong, zmusic_bool loop);
|
||||
DLL_IMPORT void ZMusic_Pause(ZMusic_MusicStream song);
|
||||
DLL_IMPORT void ZMusic_Resume(ZMusic_MusicStream song);
|
||||
DLL_IMPORT void ZMusic_Update(ZMusic_MusicStream song);
|
||||
DLL_IMPORT zmusic_bool ZMusic_IsPlaying(ZMusic_MusicStream song);
|
||||
DLL_IMPORT void ZMusic_Stop(ZMusic_MusicStream song);
|
||||
DLL_IMPORT void ZMusic_Close(ZMusic_MusicStream song);
|
||||
DLL_IMPORT zmusic_bool ZMusic_SetSubsong(ZMusic_MusicStream song, int subsong);
|
||||
DLL_IMPORT zmusic_bool ZMusic_IsLooping(ZMusic_MusicStream song);
|
||||
DLL_IMPORT int ZMusic_GetDeviceType(ZMusic_MusicStream song);
|
||||
DLL_IMPORT zmusic_bool ZMusic_IsMIDI(ZMusic_MusicStream song);
|
||||
DLL_IMPORT void ZMusic_VolumeChanged(ZMusic_MusicStream song);
|
||||
DLL_IMPORT zmusic_bool ZMusic_WriteSMF(ZMusic_MidiSource source, const char* fn, int looplimit);
|
||||
DLL_IMPORT void ZMusic_GetStreamInfo(ZMusic_MusicStream song, SoundStreamInfo *info);
|
||||
DLL_IMPORT void ZMusic_GetStreamInfoEx(ZMusic_MusicStream song, SoundStreamInfoEx *info);
|
||||
// Configuration interface. The return value specifies if a music restart is needed.
|
||||
// RealValue should be written back to the CVAR or whatever other method the client uses to store configuration state.
|
||||
DLL_IMPORT zmusic_bool ChangeMusicSettingInt(EIntConfigKey key, ZMusic_MusicStream song, int value, int* pRealValue);
|
||||
DLL_IMPORT zmusic_bool ChangeMusicSettingFloat(EFloatConfigKey key, ZMusic_MusicStream song, float value, float* pRealValue);
|
||||
DLL_IMPORT zmusic_bool ChangeMusicSettingString(EStringConfigKey key, ZMusic_MusicStream song, const char* value);
|
||||
DLL_IMPORT const char *ZMusic_GetStats(ZMusic_MusicStream song);
|
||||
|
||||
|
||||
DLL_IMPORT struct SoundDecoder* CreateDecoder(const uint8_t* data, size_t size, zmusic_bool isstatic);
|
||||
DLL_IMPORT void SoundDecoder_GetInfo(struct SoundDecoder* decoder, int* samplerate, ChannelConfig* chans, SampleType* type);
|
||||
DLL_IMPORT size_t SoundDecoder_Read(struct SoundDecoder* decoder, void* buffer, size_t length);
|
||||
DLL_IMPORT void SoundDecoder_Close(struct SoundDecoder* decoder);
|
||||
DLL_IMPORT void FindLoopTags(const uint8_t* data, size_t size, uint32_t* start, zmusic_bool* startass, uint32_t* end, zmusic_bool* endass);
|
||||
// The rest of the decoder interface is only useful for streaming music.
|
||||
|
||||
DLL_IMPORT const ZMusicMidiOutDevice *ZMusic_GetMidiDevices(int *pAmount);
|
||||
DLL_IMPORT int ZMusic_GetADLBanks(const char* const** pNames);
|
||||
|
||||
// Direct access to the CD drive.
|
||||
// Stops playing the CD
|
||||
DLL_IMPORT void CD_Stop();
|
||||
|
||||
// Pauses CD playing
|
||||
DLL_IMPORT void CD_Pause();
|
||||
|
||||
// Resumes CD playback after pausing
|
||||
DLL_IMPORT zmusic_bool CD_Resume();
|
||||
|
||||
// Eject the CD tray
|
||||
DLL_IMPORT void CD_Eject();
|
||||
|
||||
// Close the CD tray
|
||||
DLL_IMPORT zmusic_bool CD_UnEject();
|
||||
|
||||
// Closes a CD device previously opened with CD_Init
|
||||
DLL_IMPORT void CD_Close();
|
||||
|
||||
DLL_IMPORT zmusic_bool CD_Enable(const char* drive);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
inline bool ChangeMusicSetting(EIntConfigKey key, ZMusic_MusicStream song, int value, int* pRealValue = NULL)
|
||||
{
|
||||
return ChangeMusicSettingInt(key, song, value, pRealValue);
|
||||
}
|
||||
inline bool ChangeMusicSetting(EFloatConfigKey key, ZMusic_MusicStream song, float value, float* pRealValue = NULL)
|
||||
{
|
||||
return ChangeMusicSettingFloat(key, song, value, pRealValue);
|
||||
}
|
||||
inline bool ChangeMusicSetting(EStringConfigKey key, ZMusic_MusicStream song, const char* value)
|
||||
{
|
||||
return ChangeMusicSettingString(key, song, value);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Function typedefs for run-time linking
|
||||
typedef const char* (*pfn_ZMusic_GetLastError)();
|
||||
typedef void (*pfn_ZMusic_SetCallbacks)(const ZMusicCallbacks* callbacks);
|
||||
typedef void (*pfn_ZMusic_SetGenMidi)(const uint8_t* data);
|
||||
typedef void (*pfn_ZMusic_SetWgOpn)(const void* data, unsigned len);
|
||||
typedef void (*pfn_ZMusic_SetDmxGus)(const void* data, unsigned len);
|
||||
typedef const ZMusicConfigurationSetting* (*pfn_ZMusic_GetConfiguration)();
|
||||
typedef EMIDIType (*pfn_ZMusic_IdentifyMIDIType)(uint32_t* id, int size);
|
||||
typedef ZMusic_MidiSource (*pfn_ZMusic_CreateMIDISource)(const uint8_t* data, size_t length, EMIDIType miditype);
|
||||
typedef zmusic_bool (*pfn_ZMusic_MIDIDumpWave)(ZMusic_MidiSource source, EMidiDevice devtype, const char* devarg, const char* outname, int subsong, int samplerate);
|
||||
typedef ZMusic_MusicStream (*pfn_ZMusic_OpenSong)(ZMusicCustomReader* reader, EMidiDevice device, const char* Args);
|
||||
typedef ZMusic_MusicStream (*pfn_ZMusic_OpenSongFile)(const char *filename, EMidiDevice device, const char* Args);
|
||||
typedef ZMusic_MusicStream (*pfn_ZMusic_OpenSongMem)(const void *mem, size_t size, EMidiDevice device, const char* Args);
|
||||
typedef ZMusic_MusicStream (*pfn_ZMusic_OpenCDSong)(int track, int cdid);
|
||||
typedef zmusic_bool (*pfn_ZMusic_FillStream)(ZMusic_MusicStream stream, void* buff, int len);
|
||||
typedef zmusic_bool (*pfn_ZMusic_Start)(ZMusic_MusicStream song, int subsong, zmusic_bool loop);
|
||||
typedef void (*pfn_ZMusic_Pause)(ZMusic_MusicStream song);
|
||||
typedef void (*pfn_ZMusic_Resume)(ZMusic_MusicStream song);
|
||||
typedef void (*pfn_ZMusic_Update)(ZMusic_MusicStream song);
|
||||
typedef zmusic_bool (*pfn_ZMusic_IsPlaying)(ZMusic_MusicStream song);
|
||||
typedef void (*pfn_ZMusic_Stop)(ZMusic_MusicStream song);
|
||||
typedef void (*pfn_ZMusic_Close)(ZMusic_MusicStream song);
|
||||
typedef zmusic_bool (*pfn_ZMusic_SetSubsong)(ZMusic_MusicStream song, int subsong);
|
||||
typedef zmusic_bool (*pfn_ZMusic_IsLooping)(ZMusic_MusicStream song);
|
||||
typedef zmusic_bool (*pfn_ZMusic_IsMIDI)(ZMusic_MusicStream song);
|
||||
typedef void (*pfn_ZMusic_VolumeChanged)(ZMusic_MusicStream song);
|
||||
typedef zmusic_bool (*pfn_ZMusic_WriteSMF)(ZMusic_MidiSource source, const char* fn, int looplimit);
|
||||
typedef void (*pfn_ZMusic_GetStreamInfo)(ZMusic_MusicStream song, SoundStreamInfo *info);
|
||||
typedef void (*pfn_ZMusic_GetStreamInfoEx)(ZMusic_MusicStream song, SoundStreamInfoEx *info);
|
||||
typedef zmusic_bool (*pfn_ChangeMusicSettingInt)(EIntConfigKey key, ZMusic_MusicStream song, int value, int* pRealValue);
|
||||
typedef zmusic_bool (*pfn_ChangeMusicSettingFloat)(EFloatConfigKey key, ZMusic_MusicStream song, float value, float* pRealValue);
|
||||
typedef zmusic_bool (*pfn_ChangeMusicSettingString)(EStringConfigKey key, ZMusic_MusicStream song, const char* value);
|
||||
typedef const char *(*pfn_ZMusic_GetStats)(ZMusic_MusicStream song);
|
||||
typedef struct SoundDecoder* (*pfn_CreateDecoder)(const uint8_t* data, size_t size, zmusic_bool isstatic);
|
||||
typedef void (*pfn_SoundDecoder_GetInfo)(struct SoundDecoder* decoder, int* samplerate, ChannelConfig* chans, SampleType* type);
|
||||
typedef size_t (*pfn_SoundDecoder_Read)(struct SoundDecoder* decoder, void* buffer, size_t length);
|
||||
typedef void (*pfn_SoundDecoder_Close)(struct SoundDecoder* decoder);
|
||||
typedef void (*pfn_FindLoopTags)(const uint8_t* data, size_t size, uint32_t* start, zmusic_bool* startass, uint32_t* end, zmusic_bool* endass);
|
||||
typedef const ZMusicMidiOutDevice *(*pfn_ZMusic_GetMidiDevices)(int *pAmount);
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
27
libraries/ZMusic/licenses/bsd.txt
Normal file
27
libraries/ZMusic/licenses/bsd.txt
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2009 Randy Heit, Christoph Oelckers, et al.
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
87
libraries/ZMusic/licenses/dumb.txt
Normal file
87
libraries/ZMusic/licenses/dumb.txt
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/* _______ ____ __ ___ ___
|
||||
* \ _ \ \ / \ / \ \ / / ' ' '
|
||||
* | | \ \ | | || | \/ | . .
|
||||
* | | | | | | || ||\ /| |
|
||||
* | | | | | | || || \/ | | ' ' '
|
||||
* | | | | | | || || | | . .
|
||||
* | |_/ / \ \__// || | |
|
||||
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
|
||||
* / \
|
||||
* / . \
|
||||
* licence.txt - Conditions for use of DUMB. / / \ \
|
||||
* | < / \_
|
||||
* If you do not agree to these terms, please | \/ /\ /
|
||||
* do not use DUMB. \_ / > /
|
||||
* | \ / /
|
||||
* Information in [brackets] is provided to aid | ' /
|
||||
* interpretation of the licence. \__/
|
||||
*/
|
||||
|
||||
|
||||
Dynamic Universal Music Bibliotheque, Version 0.9.3
|
||||
|
||||
Copyright (C) 2001-2005 Ben Davis, Robert J Ohannessian and Julien Cugniere
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event shall 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, you are requested to acknowledge its use in the product
|
||||
documentation, along with details on where to get an unmodified version of
|
||||
this software, but this is not a strict requirement.
|
||||
|
||||
[Note that the above point asks for a link to DUMB, not just a mention.
|
||||
Googling for DUMB doesn't help much! The URL is "http://dumb.sf.net/".]
|
||||
|
||||
[The link was originally strictly required. This was changed for two
|
||||
reasons. Firstly, if many projects request an acknowledgement, the list of
|
||||
acknowledgements can become quite unmanageable. Secondly, DUMB was placing
|
||||
a restriction on the code using it, preventing people from using the GNU
|
||||
General Public Licence which disallows any such restrictions. See
|
||||
http://www.gnu.org/philosophy/bsd.html for more information on this
|
||||
subject. However, if DUMB plays a significant part in your project, we do
|
||||
urge you to acknowledge its use.]
|
||||
|
||||
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 from or altered in any source distribution.
|
||||
|
||||
4. If you are using the Program in someone else's bedroom on any Monday at
|
||||
3:05 pm, you are not allowed to modify the Program for ten minutes. [This
|
||||
clause provided by Inphernic; every licence should contain at least one
|
||||
clause, the reasoning behind which is far from obvious.]
|
||||
|
||||
5. Users who wish to use DUMB for the specific purpose of playing music are
|
||||
required to feed their dog on every full moon (if deemed appropriate).
|
||||
[This clause provided by Allefant, who couldn't remember what Inphernic's
|
||||
clause was.]
|
||||
|
||||
6. No clause in this licence shall prevent this software from being depended
|
||||
upon by a product licensed under the GNU General Public Licence. If such a
|
||||
clause is deemed to exist, Debian, then it shall be respected in spirit as
|
||||
far as possible and all other clauses shall continue to apply in full
|
||||
force.
|
||||
|
||||
8. Take the number stated as introducing this clause. Multiply it by two,
|
||||
then subtract four. Now insert a '+' between the two digits and evaluate
|
||||
the resulting sum. Call the result 'x'. If you have not yet concluded that
|
||||
every numbered clause in this licence whose ordinal number is strictly
|
||||
greater than 'x' (with the exception of the present clause) is null and
|
||||
void, Debian, then you are hereby informed that laughter is good for one's
|
||||
health and you are warmly suggested to do it. By the way, Clauses 4, 5 and
|
||||
6 are null and void. Incidentally, I like Kubuntu. The work you guys do is
|
||||
awesome. (Lawyers, on the other hand ...)
|
||||
|
||||
We regret that we cannot provide any warranty, not even the implied warranty
|
||||
of merchantability or fitness for a particular purpose.
|
||||
|
||||
Some files generated or copied by automake, autoconf and friends are
|
||||
available in an extra download. These fall under separate licences but are
|
||||
all free to distribute. Please check their licences as necessary.
|
||||
674
libraries/ZMusic/licenses/gplv3.txt
Normal file
674
libraries/ZMusic/licenses/gplv3.txt
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
25
libraries/ZMusic/licenses/legal.txt
Normal file
25
libraries/ZMusic/licenses/legal.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
ZMusic is licensed under the GPLv3.
|
||||
|
||||
The majority of original code uses a BSD-like lincese. See bsd.txt.
|
||||
|
||||
libADL and libOPN are licensed under the GPLv3.
|
||||
|
||||
WildMidi is licensed under the LGPLv3. See lgplv3.txt
|
||||
|
||||
This software uses the 'zlib' general purpose compression library by
|
||||
Jean-loup Gailly and Mark Adler.
|
||||
|
||||
This software uses the game_music_emu library, which is covered by the GNU Lesser
|
||||
General Public License. See lgplv21.txt.
|
||||
|
||||
This software uses the "Dynamic Universal Music Bibliotheque" library for
|
||||
MOD music playback. See dumb.txt for original license. The version used,
|
||||
however, has been heavily modified from its original form and is the same
|
||||
version used by the foobar2000 component foo_dumb as of mid-2008, found at
|
||||
http://kode54.foobar2000.org/.
|
||||
|
||||
|
||||
ZMusic Lite is a feature reduced version that omits all content that is not
|
||||
compatible with the LGPL v2.1. Use this if your project cannot comply with the GPL.
|
||||
This means that most of the available MIDI synths, except FluidSynth, have been removed
|
||||
and that the playback of native OPL file types is disabled.
|
||||
504
libraries/ZMusic/licenses/lgplv21.txt
Normal file
504
libraries/ZMusic/licenses/lgplv21.txt
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random
|
||||
Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
841
libraries/ZMusic/licenses/lgplv3.txt
Normal file
841
libraries/ZMusic/licenses/lgplv3.txt
Normal file
|
|
@ -0,0 +1,841 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
6
libraries/ZMusic/licenses/zmusic.txt
Normal file
6
libraries/ZMusic/licenses/zmusic.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
ZMusic comes in two flavors: full and lite.
|
||||
The difference between these two is that the lite version omits all playback code that is licensed under the GPL so that it can be used in projects incompatible with the GPL v3.
|
||||
Omitted features in the lite version are: All MIDI synths except for FluidSynth were removed. In addition raw OPL playback had to be removed.
|
||||
|
||||
The full version of zmusic can be used under the terms of the GPL v3. See gplv3.txt for details.
|
||||
The lite version can be used under the terms of the LGPL v2.1. See lgplv21.txt for details.
|
||||
1
libraries/ZMusic/samples/list_midi_devices/.gitignore
vendored
Normal file
1
libraries/ZMusic/samples/list_midi_devices/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build*
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
cmake_minimum_required(VERSION 2.8.7...3.19)
|
||||
project(list_midi_devices)
|
||||
|
||||
find_package(ZMusic REQUIRED)
|
||||
|
||||
add_executable(list_midi_devices list_midi_devices.cpp)
|
||||
target_link_libraries(list_midi_devices PRIVATE ZMusic::zmusic)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#include <stdio.h>
|
||||
#include <zmusic.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
int count = 0;
|
||||
const ZMusicMidiOutDevice *devices = ZMusic_GetMidiDevices(&count);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
const ZMusicMidiOutDevice &d = devices[i];
|
||||
const char *tech = "<Unknown>";
|
||||
switch (d.Technology)
|
||||
{
|
||||
case MIDIDEV_MIDIPORT: tech = "MIDIPORT"; break;
|
||||
case MIDIDEV_SYNTH: tech = "SYNTH"; break;
|
||||
case MIDIDEV_SQSYNTH: tech = "SQSYNTH"; break;
|
||||
case MIDIDEV_FMSYNTH: tech = "FMSYNTH"; break;
|
||||
case MIDIDEV_MAPPER: tech = "MAPPER"; break;
|
||||
case MIDIDEV_WAVETABLE: tech = "WAVETABLE"; break;
|
||||
case MIDIDEV_SWSYNTH: tech = "SWSYNTH"; break;
|
||||
}
|
||||
printf("[%i] %i. %s: %s\n", i, d.ID, d.Name, tech);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
224
libraries/ZMusic/source/CMakeLists.txt
Normal file
224
libraries/ZMusic/source/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# zmusic-obj doesn't actually build anything itself, but rather all sources are
|
||||
# added as interface sources. Thus whatever links to zmusic-obj will be in
|
||||
# charge of compiling. As a result any properties set on zmusic-obj should be
|
||||
# interface.
|
||||
add_library(zmusic-obj INTERFACE)
|
||||
target_sources(zmusic-obj
|
||||
INTERFACE
|
||||
loader/i_module.cpp
|
||||
mididevices/music_base_mididevice.cpp
|
||||
mididevices/music_adlmidi_mididevice.cpp
|
||||
mididevices/music_opl_mididevice.cpp
|
||||
mididevices/music_opnmidi_mididevice.cpp
|
||||
mididevices/music_timiditypp_mididevice.cpp
|
||||
mididevices/music_fluidsynth_mididevice.cpp
|
||||
mididevices/music_softsynth_mididevice.cpp
|
||||
mididevices/music_timidity_mididevice.cpp
|
||||
mididevices/music_wildmidi_mididevice.cpp
|
||||
mididevices/music_wavewriter_mididevice.cpp
|
||||
midisources/midisource.cpp
|
||||
midisources/midisource_mus.cpp
|
||||
midisources/midisource_smf.cpp
|
||||
midisources/midisource_hmi.cpp
|
||||
midisources/midisource_xmi.cpp
|
||||
midisources/midisource_mids.cpp
|
||||
streamsources/music_dumb.cpp
|
||||
streamsources/music_gme.cpp
|
||||
streamsources/music_libsndfile.cpp
|
||||
streamsources/music_libxmp.cpp
|
||||
streamsources/music_opl.cpp
|
||||
streamsources/music_xa.cpp
|
||||
musicformats/music_stream.cpp
|
||||
musicformats/music_midi.cpp
|
||||
musicformats/music_cd.cpp
|
||||
decoder/sounddecoder.cpp
|
||||
decoder/sndfile_decoder.cpp
|
||||
decoder/mpg123_decoder.cpp
|
||||
zmusic/configuration.cpp
|
||||
zmusic/zmusic.cpp
|
||||
zmusic/critsec.cpp
|
||||
loader/test.c
|
||||
)
|
||||
|
||||
file(GLOB HEADER_FILES
|
||||
zmusic/*.h
|
||||
loader/*.h
|
||||
mididevices/*.h
|
||||
midisources/*.h
|
||||
musicformats/*.h
|
||||
musicformats/win32/*.h
|
||||
decoder/*.h
|
||||
streamsources/*.h
|
||||
../thirdparty/*.h
|
||||
../include/*.h
|
||||
)
|
||||
target_sources(zmusic-obj INTERFACE ${HEADER_FILES})
|
||||
|
||||
target_compile_features(zmusic-obj INTERFACE cxx_std_11)
|
||||
#set_target_properties(zmusic-obj PROPERTIES LINKER_LANGUAGE CXX)
|
||||
|
||||
require_stricmp(zmusic-obj INTERFACE)
|
||||
require_strnicmp(zmusic-obj INTERFACE)
|
||||
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
find_package(Threads)
|
||||
target_link_libraries(zmusic-obj INTERFACE Threads::Threads)
|
||||
determine_package_config_dependency(ZMUSIC_PACKAGE_DEPENDENCIES TARGET Threads::Threads MODULE Threads)
|
||||
endif()
|
||||
|
||||
if ("vcpkg-libsndfile" IN_LIST VCPKG_MANIFEST_FEATURES)
|
||||
set(DYN_SNDFILE 0)
|
||||
else()
|
||||
option(DYN_SNDFILE "Dynamically load libsndfile" ON)
|
||||
endif()
|
||||
|
||||
if(DYN_SNDFILE)
|
||||
target_compile_definitions(zmusic-obj INTERFACE HAVE_SNDFILE DYN_SNDFILE)
|
||||
else()
|
||||
find_package(SndFile)
|
||||
|
||||
if(SNDFILE_FOUND)
|
||||
target_compile_definitions(zmusic-obj INTERFACE HAVE_SNDFILE)
|
||||
target_link_libraries(zmusic-obj INTERFACE SndFile::sndfile)
|
||||
determine_package_config_dependency(ZMUSIC_PACKAGE_DEPENDENCIES TARGET SndFile::sndfile MODULE SndFile)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if ("vcpkg-libsndfile" IN_LIST VCPKG_MANIFEST_FEATURES)
|
||||
set(DYN_MPG123 0)
|
||||
else()
|
||||
option(DYN_MPG123 "Dynamically load libmpg123" ON)
|
||||
endif()
|
||||
if(DYN_MPG123)
|
||||
target_compile_definitions(zmusic-obj INTERFACE HAVE_MPG123 DYN_MPG123)
|
||||
elseif(NOT ("vcpkg-libsndfile" IN_LIST VCPKG_MANIFEST_FEATURES))
|
||||
find_package(MPG123)
|
||||
|
||||
if(MPG123_FOUND)
|
||||
target_compile_definitions(zmusic-obj INTERFACE HAVE_MPG123)
|
||||
target_link_libraries(zmusic-obj INTERFACE mpg123)
|
||||
determine_package_config_dependency(ZMUSIC_PACKAGE_DEPENDENCIES TARGET mpg123 MODULE MPG123)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# System MIDI support
|
||||
if(WIN32)
|
||||
target_compile_definitions(zmusic-obj INTERFACE HAVE_SYSTEM_MIDI)
|
||||
target_link_libraries(zmusic-obj INTERFACE winmm)
|
||||
target_sources(zmusic-obj INTERFACE mididevices/music_win_mididevice.cpp)
|
||||
elseif(NOT APPLE)
|
||||
find_package(ALSA)
|
||||
if(ALSA_FOUND)
|
||||
target_compile_definitions(zmusic-obj INTERFACE HAVE_SYSTEM_MIDI)
|
||||
target_sources(zmusic-obj
|
||||
INTERFACE
|
||||
mididevices/music_alsa_mididevice.cpp
|
||||
mididevices/music_alsa_state.cpp
|
||||
)
|
||||
target_link_libraries(zmusic-obj INTERFACE ALSA::ALSA)
|
||||
determine_package_config_dependency(ZMUSIC_PACKAGE_DEPENDENCIES TARGET ALSA::ALSA MODULE ALSA)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_sources(zmusic-obj
|
||||
INTERFACE
|
||||
musicformats/win32/i_cd.cpp
|
||||
musicformats/win32/helperthread.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(zmusic-obj INTERFACE dumb gme libxmp miniz ${CMAKE_DL_LIBS})
|
||||
|
||||
target_include_directories(zmusic-obj
|
||||
INTERFACE
|
||||
../include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
zmusic
|
||||
)
|
||||
|
||||
propagate_object_links(zmusic-obj)
|
||||
|
||||
add_library(zmusic)
|
||||
add_library(ZMusic::zmusic ALIAS zmusic)
|
||||
add_library(zmusiclite)
|
||||
add_library(ZMusic::zmusiclite ALIAS zmusiclite)
|
||||
|
||||
use_fast_math(zmusic)
|
||||
use_fast_math(zmusiclite)
|
||||
|
||||
# Although zmusic-obj puts the public include directory in our private include
|
||||
# list, we need to add it to the interface include directories for consumers.
|
||||
target_include_directories(zmusic INTERFACE $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${ZMusic_SOURCE_DIR}/include>)
|
||||
target_include_directories(zmusiclite INTERFACE $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${ZMusic_SOURCE_DIR}/include>)
|
||||
|
||||
target_link_libraries_hidden(zmusic zmusic-obj adl oplsynth opn timidity timidityplus wildmidi fluidsynth)
|
||||
target_link_libraries_hidden(zmusiclite zmusic-obj fluidsynth)
|
||||
|
||||
target_compile_definitions(zmusic PUBLIC $<$<STREQUAL:$<TARGET_PROPERTY:zmusic,TYPE>,STATIC_LIBRARY>:ZMUSIC_STATIC>)
|
||||
target_compile_definitions(zmusiclite PRIVATE ZMUSIC_LITE=1 PUBLIC $<$<STREQUAL:$<TARGET_PROPERTY:zmusiclite,TYPE>,STATIC_LIBRARY>:ZMUSIC_STATIC>)
|
||||
|
||||
set_target_properties(zmusic zmusiclite
|
||||
PROPERTIES
|
||||
MACOSX_RPATH ON
|
||||
PUBLIC_HEADER ../include/zmusic.h
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
)
|
||||
|
||||
if (VCPKG_TOOLCHAIN)
|
||||
x_vcpkg_install_local_dependencies(TARGETS zmusic zmusiclite DESTINATION ".")
|
||||
endif()
|
||||
|
||||
if(ZMUSIC_INSTALL)
|
||||
install(TARGETS zmusic EXPORT ZMusicFullTargets
|
||||
PUBLIC_HEADER
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
COMPONENT devel
|
||||
LIBRARY
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
COMPONENT full
|
||||
NAMELINK_COMPONENT devel
|
||||
)
|
||||
|
||||
install(TARGETS zmusiclite EXPORT ZMusicLiteTargets
|
||||
PUBLIC_HEADER
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
COMPONENT devel
|
||||
LIBRARY
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
COMPONENT lite
|
||||
NAMELINK_COMPONENT devel
|
||||
)
|
||||
|
||||
install(EXPORT ZMusicFullTargets
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ZMusic"
|
||||
NAMESPACE ZMusic::
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
install(EXPORT ZMusicLiteTargets
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ZMusic"
|
||||
NAMESPACE ZMusic::
|
||||
COMPONENT devel
|
||||
)
|
||||
endif()
|
||||
|
||||
if( MSVC )
|
||||
option( ZMUSIC_GENERATE_MAPFILE "Generate .map file for debugging." OFF )
|
||||
|
||||
if( ZMUSIC_GENERATE_MAPFILE )
|
||||
target_link_options(zmusic PRIVATE "/MAP")
|
||||
target_link_options(zmusiclite PRIVATE "/MAP")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
source_group("MIDI Devices" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/mididevices/.+")
|
||||
source_group("MIDI Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/midisources/.+")
|
||||
source_group("Music Formats" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/musicformats/.+")
|
||||
source_group("Music Formats\\Win32" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/musicformats/win32/.+")
|
||||
source_group("ZMusic Core" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/zmusic/.+")
|
||||
source_group("Sound Decoding" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/decoder/.+")
|
||||
source_group("Stream Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/streamsources/.+")
|
||||
source_group("Third Party" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/.+")
|
||||
source_group("Public Interface" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/../include/.+")
|
||||
15527
libraries/ZMusic/source/data/xg.h
Normal file
15527
libraries/ZMusic/source/data/xg.h
Normal file
File diff suppressed because it is too large
Load diff
BIN
libraries/ZMusic/source/data/xg.wopn
Normal file
BIN
libraries/ZMusic/source/data/xg.wopn
Normal file
Binary file not shown.
238
libraries/ZMusic/source/decoder/mpg123_decoder.cpp
Normal file
238
libraries/ZMusic/source/decoder/mpg123_decoder.cpp
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
** mpg123_decoder.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Chris Robinson
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include "mpg123_decoder.h"
|
||||
#include "loader/i_module.h"
|
||||
|
||||
#ifdef HAVE_MPG123
|
||||
|
||||
|
||||
FModule MPG123Module{"MPG123"};
|
||||
|
||||
#include "mpgload.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MPG123LIB "libmpg123-0.dll"
|
||||
#elif defined(__APPLE__)
|
||||
#define MPG123LIB "libmpg123.0.dylib"
|
||||
#else
|
||||
#define MPG123LIB "libmpg123.so.0"
|
||||
#endif
|
||||
|
||||
bool IsMPG123Present()
|
||||
{
|
||||
#if !defined DYN_MPG123
|
||||
return true;
|
||||
#else
|
||||
static bool cached_result = false;
|
||||
static bool done = false;
|
||||
|
||||
if (!done)
|
||||
{
|
||||
done = true;
|
||||
auto abspath = FModule_GetProgDir() + "/" MPG123LIB;
|
||||
cached_result = MPG123Module.Load({abspath.c_str(), MPG123LIB});
|
||||
}
|
||||
return cached_result;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static bool inited = false;
|
||||
|
||||
|
||||
off_t MPG123Decoder::file_lseek(void *handle, off_t offset, int whence)
|
||||
{
|
||||
auto &reader = reinterpret_cast<MPG123Decoder*>(handle)->Reader;
|
||||
|
||||
if(whence == SEEK_CUR)
|
||||
{
|
||||
if(offset < 0 && reader->tell()+offset < 0)
|
||||
return -1;
|
||||
}
|
||||
else if(whence == SEEK_END)
|
||||
{
|
||||
if(offset < 0 && reader->filelength() + offset < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(reader->seek(offset, whence) != 0)
|
||||
return -1;
|
||||
return (off_t)reader->tell();
|
||||
}
|
||||
|
||||
ssize_t MPG123Decoder::file_read(void *handle, void *buffer, size_t bytes)
|
||||
{
|
||||
auto &reader = reinterpret_cast<MPG123Decoder*>(handle)->Reader;
|
||||
return (ssize_t)reader->read(buffer, (long)bytes);
|
||||
}
|
||||
|
||||
|
||||
MPG123Decoder::~MPG123Decoder()
|
||||
{
|
||||
if(MPG123)
|
||||
{
|
||||
mpg123_close(MPG123);
|
||||
mpg123_delete(MPG123);
|
||||
MPG123 = 0;
|
||||
}
|
||||
if (Reader) Reader->close();
|
||||
Reader = nullptr;
|
||||
}
|
||||
|
||||
bool MPG123Decoder::open(MusicIO::FileInterface *reader)
|
||||
{
|
||||
if(!inited)
|
||||
{
|
||||
if (!IsMPG123Present()) return false;
|
||||
if(mpg123_init() != MPG123_OK) return false;
|
||||
inited = true;
|
||||
}
|
||||
|
||||
Reader = reader;
|
||||
|
||||
{
|
||||
MPG123 = mpg123_new(NULL, NULL);
|
||||
if(mpg123_replace_reader_handle(MPG123, file_read, file_lseek, NULL) == MPG123_OK &&
|
||||
mpg123_open_handle(MPG123, this) == MPG123_OK)
|
||||
{
|
||||
int enc, channels;
|
||||
long srate;
|
||||
|
||||
if(mpg123_getformat(MPG123, &srate, &channels, &enc) == MPG123_OK)
|
||||
{
|
||||
if((channels == 1 || channels == 2) && srate > 0 &&
|
||||
mpg123_format_none(MPG123) == MPG123_OK &&
|
||||
mpg123_format(MPG123, srate, channels, MPG123_ENC_SIGNED_16) == MPG123_OK)
|
||||
{
|
||||
// All OK
|
||||
Done = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
mpg123_close(MPG123);
|
||||
}
|
||||
mpg123_delete(MPG123);
|
||||
MPG123 = 0;
|
||||
}
|
||||
|
||||
Reader = nullptr; // need to give it back.
|
||||
return false;
|
||||
}
|
||||
|
||||
void MPG123Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||
{
|
||||
int enc = 0, channels = 0;
|
||||
long srate = 0;
|
||||
|
||||
mpg123_getformat(MPG123, &srate, &channels, &enc);
|
||||
|
||||
*samplerate = srate;
|
||||
|
||||
if(channels == 2)
|
||||
*chans = ChannelConfig_Stereo;
|
||||
else
|
||||
*chans = ChannelConfig_Mono;
|
||||
|
||||
*type = SampleType_Int16;
|
||||
}
|
||||
|
||||
size_t MPG123Decoder::read(char *buffer, size_t bytes)
|
||||
{
|
||||
size_t amt = 0;
|
||||
while(!Done && bytes > 0)
|
||||
{
|
||||
size_t got = 0;
|
||||
int ret = mpg123_read(MPG123, (unsigned char*)buffer, bytes, &got);
|
||||
|
||||
bytes -= got;
|
||||
buffer += got;
|
||||
amt += got;
|
||||
|
||||
if(ret == MPG123_NEW_FORMAT || ret == MPG123_DONE || got == 0)
|
||||
{
|
||||
Done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return amt;
|
||||
}
|
||||
|
||||
bool MPG123Decoder::seek(size_t ms_offset, bool ms, bool mayrestart)
|
||||
{
|
||||
int enc, channels;
|
||||
long srate;
|
||||
|
||||
if (!mayrestart || ms_offset > 0)
|
||||
{
|
||||
if (mpg123_getformat(MPG123, &srate, &channels, &enc) == MPG123_OK)
|
||||
{
|
||||
size_t smp_offset = ms ? (size_t)((double)ms_offset / 1000. * srate) : ms_offset;
|
||||
if (mpg123_seek(MPG123, (off_t)smp_offset, SEEK_SET) >= 0)
|
||||
{
|
||||
Done = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restart the song instead of rewinding. A rewind seems to cause distortion when done repeatedly.
|
||||
// offset is intentionally ignored here.
|
||||
if (MPG123)
|
||||
{
|
||||
mpg123_close(MPG123);
|
||||
mpg123_delete(MPG123);
|
||||
MPG123 = 0;
|
||||
}
|
||||
Reader->seek(0, SEEK_SET);
|
||||
// Do not call open with our own reader variable, that would be catastrophic.
|
||||
auto reader = std::move(Reader);
|
||||
return open(reader);
|
||||
}
|
||||
}
|
||||
size_t MPG123Decoder::getSampleOffset()
|
||||
{
|
||||
return mpg123_tell(MPG123);
|
||||
}
|
||||
|
||||
size_t MPG123Decoder::getSampleLength()
|
||||
{
|
||||
off_t len = mpg123_length(MPG123);
|
||||
return (len > 0) ? len : 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
49
libraries/ZMusic/source/decoder/mpg123_decoder.h
Normal file
49
libraries/ZMusic/source/decoder/mpg123_decoder.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef MPG123_DECODER_H
|
||||
#define MPG123_DECODER_H
|
||||
|
||||
#include "zmusic/sounddecoder.h"
|
||||
|
||||
#ifdef HAVE_MPG123
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <stddef.h>
|
||||
typedef ptrdiff_t ssize_t;
|
||||
#endif
|
||||
|
||||
#ifndef DYN_MPG123
|
||||
#include "mpg123.h"
|
||||
#else
|
||||
#include "../thirdparty/mpg123.h"
|
||||
#endif
|
||||
|
||||
struct MPG123Decoder : public SoundDecoder
|
||||
{
|
||||
virtual void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) override;
|
||||
|
||||
virtual size_t read(char* buffer, size_t bytes) override;
|
||||
virtual bool seek(size_t ms_offset, bool ms, bool mayrestart) override;
|
||||
virtual size_t getSampleOffset() override;
|
||||
virtual size_t getSampleLength() override;
|
||||
|
||||
// Make non-copyable
|
||||
MPG123Decoder() = default;
|
||||
MPG123Decoder(const MPG123Decoder& rhs) = delete;
|
||||
MPG123Decoder& operator=(const MPG123Decoder& rhs) = delete;
|
||||
|
||||
virtual ~MPG123Decoder();
|
||||
|
||||
protected:
|
||||
virtual bool open(MusicIO::FileInterface *reader) override;
|
||||
|
||||
private:
|
||||
mpg123_handle *MPG123 = nullptr;
|
||||
bool Done = false;
|
||||
MusicIO::FileInterface* Reader = nullptr;
|
||||
|
||||
static off_t file_lseek(void *handle, off_t offset, int whence);
|
||||
static ssize_t file_read(void *handle, void *buffer, size_t bytes);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* MPG123_DECODER_H */
|
||||
40
libraries/ZMusic/source/decoder/mpgload.h
Normal file
40
libraries/ZMusic/source/decoder/mpgload.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef MPGDEF_H
|
||||
#define MPGDEF_H
|
||||
|
||||
#if defined HAVE_MPG123 && defined DYN_MPG123
|
||||
|
||||
#define DEFINE_ENTRY(type, name) static TReqProc<MPG123Module, type> p_##name{#name};
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh), mpg123_close)
|
||||
DEFINE_ENTRY(void (*)(mpg123_handle *mh), mpg123_delete)
|
||||
DEFINE_ENTRY(int (*)(void), mpg123_init)
|
||||
DEFINE_ENTRY(mpg123_handle* (*)(const char* decoder, int *error), mpg123_new)
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh, ssize_t (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*)), mpg123_replace_reader_handle)
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh, void *iohandle), mpg123_open_handle)
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh, long *rate, int *channels, int *encoding), mpg123_getformat)
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh), mpg123_format_none)
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done), mpg123_read)
|
||||
DEFINE_ENTRY(off_t (*)(mpg123_handle *mh, off_t sampleoff, int whence), mpg123_seek)
|
||||
DEFINE_ENTRY(int (*)(mpg123_handle *mh, long rate, int channels, int encodings), mpg123_format)
|
||||
DEFINE_ENTRY(off_t (*)(mpg123_handle *mh), mpg123_tell)
|
||||
DEFINE_ENTRY(off_t (*)(mpg123_handle *mh), mpg123_length)
|
||||
|
||||
#undef DEFINE_ENTRY
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define mpg123_close p_mpg123_close
|
||||
#define mpg123_delete p_mpg123_delete
|
||||
#define mpg123_init p_mpg123_init
|
||||
#define mpg123_new p_mpg123_new
|
||||
#define mpg123_replace_reader_handle p_mpg123_replace_reader_handle
|
||||
#define mpg123_open_handle p_mpg123_open_handle
|
||||
#define mpg123_getformat p_mpg123_getformat
|
||||
#define mpg123_format_none p_mpg123_format_none
|
||||
#define mpg123_read p_mpg123_read
|
||||
#define mpg123_seek p_mpg123_seek
|
||||
#define mpg123_tell p_mpg123_tell
|
||||
#define mpg123_format p_mpg123_format
|
||||
#define mpg123_length p_mpg123_length
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
268
libraries/ZMusic/source/decoder/sndfile_decoder.cpp
Normal file
268
libraries/ZMusic/source/decoder/sndfile_decoder.cpp
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
** sndfile_decoder.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Chris Robinson
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "sndfile_decoder.h"
|
||||
#include "loader/i_module.h"
|
||||
|
||||
#ifdef HAVE_SNDFILE
|
||||
|
||||
FModule SndFileModule{"SndFile"};
|
||||
|
||||
#include "sndload.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
static const char* libnames[] = { "sndfile.dll", "libsndfile-1.dll" };
|
||||
#elif defined(__APPLE__)
|
||||
static const char* libnames[] = { "libsndfile.1.dylib" };
|
||||
#else
|
||||
static const char* libnames[] = { "libsndfile.so.1" };
|
||||
#endif
|
||||
|
||||
extern "C" int IsSndFilePresent()
|
||||
{
|
||||
#if !defined DYN_SNDFILE
|
||||
return true;
|
||||
#else
|
||||
static bool cached_result = false;
|
||||
static bool done = false;
|
||||
|
||||
if (!done)
|
||||
{
|
||||
done = true;
|
||||
for (auto libname : libnames)
|
||||
{
|
||||
auto abspath = FModule_GetProgDir() + "/" + libname;
|
||||
cached_result = SndFileModule.Load({ abspath.c_str(), libname });
|
||||
if (cached_result) break;
|
||||
}
|
||||
}
|
||||
return cached_result;
|
||||
#endif
|
||||
}
|
||||
|
||||
sf_count_t SndFileDecoder::file_get_filelen(void *user_data)
|
||||
{
|
||||
auto &reader = reinterpret_cast<SndFileDecoder*>(user_data)->Reader;
|
||||
return reader->filelength();
|
||||
}
|
||||
|
||||
sf_count_t SndFileDecoder::file_seek(sf_count_t offset, int whence, void *user_data)
|
||||
{
|
||||
auto &reader = reinterpret_cast<SndFileDecoder*>(user_data)->Reader;
|
||||
|
||||
if(reader->seek((long)offset, whence) != 0)
|
||||
return -1;
|
||||
return reader->tell();
|
||||
}
|
||||
|
||||
sf_count_t SndFileDecoder::file_read(void *ptr, sf_count_t count, void *user_data)
|
||||
{
|
||||
auto &reader = reinterpret_cast<SndFileDecoder*>(user_data)->Reader;
|
||||
return reader->read(ptr, (long)count);
|
||||
}
|
||||
|
||||
sf_count_t SndFileDecoder::file_write(const void *ptr, sf_count_t count, void *user_data)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
sf_count_t SndFileDecoder::file_tell(void *user_data)
|
||||
{
|
||||
auto &reader = reinterpret_cast<SndFileDecoder*>(user_data)->Reader;
|
||||
return reader->tell();
|
||||
}
|
||||
|
||||
|
||||
SndFileDecoder::~SndFileDecoder()
|
||||
{
|
||||
if(SndFile)
|
||||
sf_close(SndFile);
|
||||
SndFile = 0;
|
||||
|
||||
if (Reader) Reader->close();
|
||||
Reader = nullptr;
|
||||
}
|
||||
|
||||
bool SndFileDecoder::open(MusicIO::FileInterface *reader)
|
||||
{
|
||||
if (!IsSndFilePresent()) return false;
|
||||
|
||||
SF_VIRTUAL_IO sfio = { file_get_filelen, file_seek, file_read, file_write, file_tell };
|
||||
|
||||
Reader = reader;
|
||||
SndInfo.format = 0;
|
||||
SndFile = sf_open_virtual(&sfio, SFM_READ, &SndInfo, this);
|
||||
if (SndFile)
|
||||
{
|
||||
if (SndInfo.channels == 1 || SndInfo.channels == 2)
|
||||
return true;
|
||||
|
||||
sf_close(SndFile);
|
||||
SndFile = 0;
|
||||
}
|
||||
Reader = nullptr; // need to give it back.
|
||||
return false;
|
||||
}
|
||||
|
||||
void SndFileDecoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||
{
|
||||
*samplerate = SndInfo.samplerate;
|
||||
|
||||
if(SndInfo.channels == 2)
|
||||
*chans = ChannelConfig_Stereo;
|
||||
else
|
||||
*chans = ChannelConfig_Mono;
|
||||
|
||||
*type = SampleType_Int16;
|
||||
}
|
||||
|
||||
size_t SndFileDecoder::read(char *buffer, size_t bytes)
|
||||
{
|
||||
short *out = (short*)buffer;
|
||||
size_t frames = bytes / SndInfo.channels / 2;
|
||||
size_t total = 0;
|
||||
|
||||
// It seems libsndfile has a bug with converting float samples from Vorbis
|
||||
// to the 16-bit shorts we use, which causes some PCM samples to overflow
|
||||
// and wrap, creating static. So instead, read the samples as floats and
|
||||
// convert to short ourselves.
|
||||
// Use a loop to convert a handful of samples at a time, avoiding a heap
|
||||
// allocation for temporary storage. 64 at a time works, though maybe it
|
||||
// could be more.
|
||||
while(total < frames)
|
||||
{
|
||||
size_t todo = std::min<size_t>(frames-total, 64/SndInfo.channels);
|
||||
float tmp[64];
|
||||
|
||||
size_t got = (size_t)sf_readf_float(SndFile, tmp, todo);
|
||||
if(got < todo) frames = total + got;
|
||||
|
||||
for(size_t i = 0;i < got*SndInfo.channels;i++)
|
||||
*out++ = (short)std::max(std::min(tmp[i] * 32767.f, 32767.f), -32768.f);
|
||||
total += got;
|
||||
}
|
||||
return total * SndInfo.channels * 2;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> SndFileDecoder::readAll()
|
||||
{
|
||||
if(SndInfo.frames <= 0)
|
||||
return SoundDecoder::readAll();
|
||||
|
||||
int framesize = 2 * SndInfo.channels;
|
||||
std::vector<uint8_t> output;
|
||||
|
||||
output.resize((unsigned)(SndInfo.frames * framesize));
|
||||
size_t got = read((char*)&output[0], output.size());
|
||||
output.resize((unsigned)got);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool SndFileDecoder::seek(size_t ms_offset, bool ms, bool /*mayrestart*/)
|
||||
{
|
||||
size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * SndInfo.samplerate) : ms_offset;
|
||||
if(sf_seek(SndFile, smp_offset, SEEK_SET) < 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t SndFileDecoder::getSampleOffset()
|
||||
{
|
||||
return (size_t)sf_seek(SndFile, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
size_t SndFileDecoder::getSampleLength()
|
||||
{
|
||||
return (size_t)((SndInfo.frames > 0) ? SndInfo.frames : 0);
|
||||
}
|
||||
|
||||
// band-aid for FluidSynth, which is C, not C++ and cannot use the module interface.
|
||||
#ifdef DYN_SNDFILE
|
||||
|
||||
#undef sf_open_virtual
|
||||
extern "C" SNDFILE * sf_open_virtual(SF_VIRTUAL_IO * sfvirtual, int mode, SF_INFO * sfinfo, void* user_data)
|
||||
{
|
||||
return p_sf_open_virtual(sfvirtual, mode, sfinfo, user_data);
|
||||
}
|
||||
|
||||
extern "C" const char* sf_strerror(SNDFILE * sndfile)
|
||||
{
|
||||
return p_sf_strerror(sndfile);
|
||||
}
|
||||
|
||||
extern "C" sf_count_t sf_readf_short(SNDFILE * sndfile, short* ptr, sf_count_t frames)
|
||||
{
|
||||
return p_sf_readf_short(sndfile, ptr, frames);
|
||||
}
|
||||
|
||||
#undef sf_close
|
||||
extern "C" int sf_close(SNDFILE * sndfile)
|
||||
{
|
||||
return p_sf_close(sndfile);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#else // in case someone decided to build without sndfile support
|
||||
|
||||
extern "C" int IsSndFilePresent()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" SNDFILE * sf_open_virtual(SF_VIRTUAL_IO * sfvirtual, int mode, SF_INFO * sfinfo, void* user_data)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern "C" const char* sf_strerror(SNDFILE * sndfile)
|
||||
{
|
||||
return "no sndfile support";
|
||||
}
|
||||
|
||||
extern "C" sf_count_t sf_readf_short(SNDFILE * sndfile, short* ptr, sf_count_t frames)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int sf_close(SNDFILE * sndfile)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
50
libraries/ZMusic/source/decoder/sndfile_decoder.h
Normal file
50
libraries/ZMusic/source/decoder/sndfile_decoder.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef SNDFILE_DECODER_H
|
||||
#define SNDFILE_DECODER_H
|
||||
|
||||
#include "zmusic/sounddecoder.h"
|
||||
|
||||
#ifdef HAVE_SNDFILE
|
||||
|
||||
#ifndef DYN_SNDFILE
|
||||
#include "sndfile.h"
|
||||
#else
|
||||
#include "../thirdparty/sndfile.h"
|
||||
#endif
|
||||
|
||||
struct SndFileDecoder : public SoundDecoder
|
||||
{
|
||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) override;
|
||||
|
||||
virtual size_t read(char *buffer, size_t bytes) override;
|
||||
virtual std::vector<uint8_t> readAll() override;
|
||||
virtual bool seek(size_t ms_offset, bool ms, bool mayrestart) override;
|
||||
virtual size_t getSampleOffset() override;
|
||||
virtual size_t getSampleLength() override;
|
||||
|
||||
SndFileDecoder() = default;
|
||||
// Make non-copyable
|
||||
SndFileDecoder(const SndFileDecoder& rhs) = delete;
|
||||
SndFileDecoder& operator=(const SndFileDecoder& rhs) = delete;
|
||||
|
||||
virtual ~SndFileDecoder();
|
||||
|
||||
protected:
|
||||
virtual bool open(MusicIO::FileInterface *reader) override;
|
||||
|
||||
private:
|
||||
SNDFILE *SndFile = nullptr;
|
||||
SF_INFO SndInfo;
|
||||
MusicIO::FileInterface* Reader = nullptr;
|
||||
|
||||
static sf_count_t file_get_filelen(void *user_data);
|
||||
static sf_count_t file_seek(sf_count_t offset, int whence, void *user_data);
|
||||
static sf_count_t file_read(void *ptr, sf_count_t count, void *user_data);
|
||||
static sf_count_t file_write(const void *ptr, sf_count_t count, void *user_data);
|
||||
static sf_count_t file_tell(void *user_data);
|
||||
};
|
||||
|
||||
#else
|
||||
#include "../thirdparty/sndfile.h"
|
||||
#endif
|
||||
|
||||
#endif /* SNDFILE_DECODER_H */
|
||||
27
libraries/ZMusic/source/decoder/sndload.h
Normal file
27
libraries/ZMusic/source/decoder/sndload.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef SNDDEF_H
|
||||
#define SNDDEF_H
|
||||
|
||||
|
||||
|
||||
#if defined HAVE_SNDFILE && defined DYN_SNDFILE
|
||||
|
||||
#define DEFINE_ENTRY(type, name) static TReqProc<SndFileModule, type> p_##name{#name};
|
||||
DEFINE_ENTRY(const char* (*)(SNDFILE* sndfile), sf_strerror)
|
||||
DEFINE_ENTRY(int (*)(SNDFILE *sndfile), sf_close)
|
||||
DEFINE_ENTRY(SNDFILE* (*)(SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data), sf_open_virtual)
|
||||
DEFINE_ENTRY(sf_count_t (*)(SNDFILE *sndfile, float *ptr, sf_count_t frames), sf_readf_float)
|
||||
DEFINE_ENTRY(sf_count_t(*)(SNDFILE* sndfile, short* ptr, sf_count_t frames), sf_readf_short)
|
||||
DEFINE_ENTRY(sf_count_t (*)(SNDFILE *sndfile, sf_count_t frames, int whence), sf_seek)
|
||||
#undef DEFINE_ENTRY
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define sf_close p_sf_close
|
||||
#define sf_open_virtual p_sf_open_virtual
|
||||
#define sf_readf_float p_sf_readf_float
|
||||
#define sf_seek p_sf_seek
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
184
libraries/ZMusic/source/decoder/sounddecoder.cpp
Normal file
184
libraries/ZMusic/source/decoder/sounddecoder.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
** sounddecoder.cpp
|
||||
** baseclass for sound format decoders
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2019 Chris Robinson
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "sndfile_decoder.h"
|
||||
#include "mpg123_decoder.h"
|
||||
|
||||
SoundDecoder *SoundDecoder::CreateDecoder(MusicIO::FileInterface *reader)
|
||||
{
|
||||
SoundDecoder *decoder = NULL;
|
||||
auto pos = reader->tell();
|
||||
|
||||
#ifdef HAVE_SNDFILE
|
||||
decoder = new SndFileDecoder;
|
||||
if (decoder->open(reader))
|
||||
return decoder;
|
||||
reader->seek(pos, SEEK_SET);
|
||||
|
||||
delete decoder;
|
||||
decoder = NULL;
|
||||
#endif
|
||||
#ifdef HAVE_MPG123
|
||||
decoder = new MPG123Decoder;
|
||||
if (decoder->open(reader))
|
||||
return decoder;
|
||||
reader->seek(pos, SEEK_SET);
|
||||
|
||||
delete decoder;
|
||||
decoder = NULL;
|
||||
#endif
|
||||
return decoder;
|
||||
}
|
||||
|
||||
|
||||
// Default readAll implementation, for decoders that can't do anything better
|
||||
std::vector<uint8_t> SoundDecoder::readAll()
|
||||
{
|
||||
std::vector<uint8_t> output;
|
||||
unsigned total = 0;
|
||||
unsigned got;
|
||||
|
||||
output.resize(total+32768);
|
||||
while((got=(unsigned)read((char*)&output[total], output.size()-total)) > 0)
|
||||
{
|
||||
total += got;
|
||||
output.resize(total*2);
|
||||
}
|
||||
output.resize(total);
|
||||
return output;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// other callbacks
|
||||
//
|
||||
//==========================================================================
|
||||
extern "C"
|
||||
short* dumb_decode_vorbis(int outlen, const void* oggstream, int sizebytes)
|
||||
{
|
||||
short* samples = (short*)calloc(1, outlen);
|
||||
ChannelConfig chans;
|
||||
SampleType type;
|
||||
int srate;
|
||||
|
||||
// The decoder will take ownership of the reader if it succeeds so this may not be a local variable.
|
||||
MusicIO::MemoryReader* reader = new MusicIO::MemoryReader((const uint8_t*)oggstream, sizebytes);
|
||||
|
||||
SoundDecoder* decoder = SoundDecoder::CreateDecoder(reader);
|
||||
if (!decoder)
|
||||
{
|
||||
reader->close();
|
||||
return samples;
|
||||
}
|
||||
|
||||
decoder->getInfo(&srate, &chans, &type);
|
||||
if (chans != ChannelConfig_Mono)
|
||||
{
|
||||
delete decoder;
|
||||
return samples;
|
||||
}
|
||||
|
||||
if(type == SampleType_Int16)
|
||||
decoder->read((char*)samples, outlen);
|
||||
else if(type == SampleType_Float32)
|
||||
{
|
||||
constexpr size_t tempsize = 1024;
|
||||
float temp[tempsize];
|
||||
size_t spos = 0;
|
||||
|
||||
outlen /= sizeof(short);
|
||||
int done = 0;
|
||||
while(done < outlen)
|
||||
{
|
||||
size_t got = decoder->read((char*)temp, tempsize * sizeof(float)) / sizeof(float);
|
||||
for(size_t i = 0;i < got;++i)
|
||||
{
|
||||
float s = temp[i] * 32768.0f;
|
||||
samples[spos++] = (s > 32767.0f) ? 32767 : (s < -32768.0f) ? -32768 : (short)s;
|
||||
}
|
||||
if(got < tempsize)
|
||||
break;
|
||||
done += got;
|
||||
}
|
||||
}
|
||||
else if(type == SampleType_UInt8)
|
||||
{
|
||||
constexpr size_t tempsize = 1024;
|
||||
uint8_t temp[tempsize];
|
||||
size_t spos = 0;
|
||||
|
||||
outlen /= sizeof(short);
|
||||
int done = 0;
|
||||
while(done < outlen)
|
||||
{
|
||||
size_t got = decoder->read((char*)temp, tempsize);
|
||||
for(size_t i = 0;i < got;++i)
|
||||
samples[spos++] = (short)((temp[i]-128) * 256);
|
||||
if(got < tempsize)
|
||||
break;
|
||||
done += got;
|
||||
}
|
||||
}
|
||||
delete decoder;
|
||||
return samples;
|
||||
}
|
||||
|
||||
DLL_EXPORT struct SoundDecoder* CreateDecoder(const uint8_t* data, size_t size, zmusic_bool isstatic)
|
||||
{
|
||||
MusicIO::FileInterface* reader;
|
||||
if (isstatic) reader = new MusicIO::MemoryReader(data, (long)size);
|
||||
else reader = new MusicIO::VectorReader(data, size);
|
||||
auto res = SoundDecoder::CreateDecoder(reader);
|
||||
if (!res) reader->close();
|
||||
return res;
|
||||
}
|
||||
|
||||
DLL_EXPORT void SoundDecoder_GetInfo(struct SoundDecoder* decoder, int* samplerate, ChannelConfig* chans, SampleType* type)
|
||||
{
|
||||
if (decoder) decoder->getInfo(samplerate, chans, type);
|
||||
else if (samplerate) *samplerate = 0;
|
||||
}
|
||||
|
||||
DLL_EXPORT size_t SoundDecoder_Read(struct SoundDecoder* decoder, void* buffer, size_t length)
|
||||
{
|
||||
if (decoder) return decoder->read((char*)buffer, length);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
DLL_EXPORT void SoundDecoder_Close(struct SoundDecoder* decoder)
|
||||
{
|
||||
if (decoder) delete decoder;
|
||||
}
|
||||
113
libraries/ZMusic/source/loader/i_module.cpp
Normal file
113
libraries/ZMusic/source/loader/i_module.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
** i_module.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2016 Braden Obrzut
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include "i_module.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
#define LoadLibraryA(x) dlopen((x), RTLD_LAZY)
|
||||
#define GetProcAddress(a,b) dlsym((a),(b))
|
||||
#define FreeLibrary(x) dlclose((x))
|
||||
using HMODULE = void*;
|
||||
#endif
|
||||
|
||||
bool FModule::Load(std::initializer_list<const char*> libnames)
|
||||
{
|
||||
for(auto lib : libnames)
|
||||
{
|
||||
if(!Open(lib))
|
||||
continue;
|
||||
|
||||
StaticProc *proc;
|
||||
for(proc = reqSymbols;proc;proc = proc->Next)
|
||||
{
|
||||
if(!(proc->Call = GetSym(proc->Name)) && !proc->Optional)
|
||||
{
|
||||
Unload();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(IsLoaded())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FModule::Unload()
|
||||
{
|
||||
if(handle)
|
||||
{
|
||||
FreeLibrary((HMODULE)handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool FModule::Open(const char* lib)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if((handle = GetModuleHandleA(lib)) != nullptr)
|
||||
return true;
|
||||
#else
|
||||
// Loading an empty string in Linux doesn't do what we expect it to.
|
||||
if(*lib == '\0')
|
||||
return false;
|
||||
#endif
|
||||
handle = LoadLibraryA(lib);
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
void *FModule::GetSym(const char* name)
|
||||
{
|
||||
return (void *)GetProcAddress((HMODULE)handle, name);
|
||||
}
|
||||
|
||||
static std::string module_progdir("."); // current program directory used to look up dynamic libraries. Default to something harmless in case the user didn't set it.
|
||||
|
||||
void FModule_SetProgDir(const char* progdir)
|
||||
{
|
||||
module_progdir = progdir;
|
||||
}
|
||||
|
||||
const std::string& FModule_GetProgDir()
|
||||
{
|
||||
return module_progdir;
|
||||
}
|
||||
233
libraries/ZMusic/source/loader/i_module.h
Normal file
233
libraries/ZMusic/source/loader/i_module.h
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
** i_module.h
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2016 Braden Obrzut
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
|
||||
/* FModule Run Time Library Loader
|
||||
*
|
||||
* This provides an interface for loading optional dependencies or detecting
|
||||
* version specific symbols at run time. These classes largely provide an
|
||||
* interface for statically declaring the symbols that are going to be used
|
||||
* ahead of time, thus should not be used on the stack or as part of a
|
||||
* dynamically allocated object. The procedure templates take the FModule
|
||||
* as a template argument largely to make such use of FModule awkward.
|
||||
*
|
||||
* Declared procedures register themselves with FModule and the module will not
|
||||
* be considered loaded unless all required procedures can be resolved. In
|
||||
* order to remove the need for boilerplate code, optional procedures do not
|
||||
* enforce the requirement that the value is null checked before use. As a
|
||||
* debugging aid debug builds will check that operator bool was called at some
|
||||
* point, but this is just a first order sanity check.
|
||||
*/
|
||||
|
||||
class FModule;
|
||||
class FStaticModule;
|
||||
|
||||
template<FModule &Module, typename Proto>
|
||||
class TOptProc;
|
||||
|
||||
template<FModule &Module, typename Proto>
|
||||
class TReqProc;
|
||||
|
||||
template<FStaticModule &Module, typename Proto, Proto Sym>
|
||||
class TStaticProc;
|
||||
|
||||
class FModule
|
||||
{
|
||||
template<FModule &Module, typename Proto>
|
||||
friend class TOptProc;
|
||||
template<FModule &Module, typename Proto>
|
||||
friend class TReqProc;
|
||||
|
||||
struct StaticProc
|
||||
{
|
||||
void *Call;
|
||||
const char* Name;
|
||||
StaticProc *Next;
|
||||
bool Optional;
|
||||
};
|
||||
|
||||
void RegisterStatic(StaticProc &proc)
|
||||
{
|
||||
proc.Next = reqSymbols;
|
||||
reqSymbols = &proc;
|
||||
}
|
||||
|
||||
void *handle = nullptr;
|
||||
|
||||
// Debugging aid
|
||||
const char *name;
|
||||
|
||||
// Since FModule is supposed to be statically allocated it is assumed that
|
||||
// reqSymbols will be initialized to nullptr avoiding initialization order
|
||||
// problems with declaring procedures.
|
||||
StaticProc *reqSymbols;
|
||||
|
||||
bool Open(const char* lib);
|
||||
void *GetSym(const char* name);
|
||||
|
||||
public:
|
||||
template<FModule &Module, typename Proto, Proto Sym>
|
||||
using Opt = TOptProc<Module, Proto>;
|
||||
template<FModule &Module, typename Proto, Proto Sym>
|
||||
using Req = TReqProc<Module, Proto>;
|
||||
|
||||
FModule(const char* name) : name(name) {};
|
||||
~FModule() { Unload(); }
|
||||
|
||||
// Load a shared library using the first library name which satisfies all
|
||||
// of the required symbols.
|
||||
bool Load(std::initializer_list<const char*> libnames);
|
||||
void Unload();
|
||||
|
||||
bool IsLoaded() const { return handle != nullptr; }
|
||||
};
|
||||
|
||||
// Null version of FModule which satisfies the API so the same code can be used
|
||||
// for run time and compile time linking.
|
||||
class FStaticModule
|
||||
{
|
||||
template<FStaticModule &Module, typename Proto, Proto Sym>
|
||||
friend class TStaticProc;
|
||||
|
||||
const char *name;
|
||||
public:
|
||||
template<FStaticModule &Module, typename Proto, Proto Sym>
|
||||
using Opt = TStaticProc<Module, Proto, Sym>;
|
||||
template<FStaticModule &Module, typename Proto, Proto Sym>
|
||||
using Req = TStaticProc<Module, Proto, Sym>;
|
||||
|
||||
FStaticModule(const char* name) : name(name) {};
|
||||
|
||||
bool Load(std::initializer_list<const char*> libnames) { return true; }
|
||||
void Unload() {}
|
||||
|
||||
bool IsLoaded() const { return true; }
|
||||
};
|
||||
|
||||
// Allow FModuleMaybe<DYN_XYZ> to switch based on preprocessor flag.
|
||||
// Use FModuleMaybe<DYN_XYZ>::Opt and FModuleMaybe<DYN_XYZ>::Req for procs.
|
||||
template<bool Dynamic>
|
||||
struct TModuleType { using Type = FModule; };
|
||||
template<>
|
||||
struct TModuleType<false> { using Type = FStaticModule; };
|
||||
|
||||
template<bool Dynamic>
|
||||
using FModuleMaybe = typename TModuleType<Dynamic>::Type;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
template<FModule &Module, typename Proto>
|
||||
class TOptProc
|
||||
{
|
||||
FModule::StaticProc proc;
|
||||
#ifndef NDEBUG
|
||||
mutable bool checked = false;
|
||||
#endif
|
||||
|
||||
// I am not a pointer
|
||||
bool operator==(void*) const;
|
||||
bool operator!=(void*) const;
|
||||
|
||||
public:
|
||||
TOptProc(const char* function)
|
||||
{
|
||||
proc.Name = function;
|
||||
proc.Optional = true;
|
||||
Module.RegisterStatic(proc);
|
||||
}
|
||||
|
||||
operator Proto() const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
assert(checked);
|
||||
#endif
|
||||
return (Proto)proc.Call;
|
||||
}
|
||||
explicit operator bool() const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
assert(Module.IsLoaded());
|
||||
checked = true;
|
||||
#endif
|
||||
return proc.Call != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<FModule &Module, typename Proto>
|
||||
class TReqProc
|
||||
{
|
||||
FModule::StaticProc proc;
|
||||
|
||||
// I am not a pointer
|
||||
bool operator==(void*) const;
|
||||
bool operator!=(void*) const;
|
||||
|
||||
public:
|
||||
TReqProc(const char* function)
|
||||
{
|
||||
proc.Name = function;
|
||||
proc.Optional = false;
|
||||
Module.RegisterStatic(proc);
|
||||
}
|
||||
|
||||
operator Proto() const
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
assert(Module.IsLoaded());
|
||||
#endif
|
||||
return (Proto)proc.Call;
|
||||
}
|
||||
explicit operator bool() const { return true; }
|
||||
};
|
||||
|
||||
template<FStaticModule &Module, typename Proto, Proto Sym>
|
||||
class TStaticProc
|
||||
{
|
||||
// I am not a pointer
|
||||
bool operator==(void*) const;
|
||||
bool operator!=(void*) const;
|
||||
|
||||
public:
|
||||
TStaticProc(const char* function) {}
|
||||
|
||||
operator Proto() const { return Sym; }
|
||||
explicit operator bool() const { return Sym != nullptr; }
|
||||
};
|
||||
|
||||
void FModule_SetProgDir(const char* progdir);
|
||||
const std::string& FModule_GetProgDir();
|
||||
2
libraries/ZMusic/source/loader/test.c
Normal file
2
libraries/ZMusic/source/loader/test.c
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// This file is only here to have one module that includes the header from a pure C source, so that it immediately errors out when something incompatible is found.
|
||||
#include "zmusic.h"
|
||||
783
libraries/ZMusic/source/mididevices/driver_adlib.cpp
Normal file
783
libraries/ZMusic/source/mididevices/driver_adlib.cpp
Normal file
|
|
@ -0,0 +1,783 @@
|
|||
/*
|
||||
Copyright (C) 1994-1995 Apogee Software, Ltd.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
/**********************************************************************
|
||||
module: AL_MIDI.C
|
||||
|
||||
author: James R. Dose
|
||||
date: April 1, 1994
|
||||
|
||||
Low level routines to support General MIDI music on AdLib compatible
|
||||
cards.
|
||||
|
||||
(c) Copyright 1994 James R. Dose. All Rights Reserved.
|
||||
**********************************************************************/
|
||||
|
||||
#include "driver_adlib.h"
|
||||
|
||||
#include "_al_midi.h"
|
||||
//#include "_multivc.h"
|
||||
#include "../adlmidi/chips/nuked_opl3.h"
|
||||
//#include "c_cvars.h"
|
||||
|
||||
/*
|
||||
CUSTOM_CVAR(Bool, mus_al_stereo, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||
{
|
||||
AL_Stereo = self;
|
||||
AL_SetStereo(AL_Stereo);
|
||||
}
|
||||
|
||||
CVAR(Bool, mus_al_additivemode, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
*/
|
||||
|
||||
enum
|
||||
{
|
||||
AdLibErr_Warning = -2,
|
||||
AdLibErr_Error = -1,
|
||||
AdLibErr_Ok = 0,
|
||||
};
|
||||
|
||||
static void AL_Shutdown(void);
|
||||
static int ErrorCode;
|
||||
|
||||
int AdLibDrv_GetError(void) { return ErrorCode; }
|
||||
|
||||
const char *AdLibDrv_ErrorString(int const ErrorNumber)
|
||||
{
|
||||
const char *ErrorString;
|
||||
|
||||
switch( ErrorNumber )
|
||||
{
|
||||
case AdLibErr_Warning :
|
||||
case AdLibErr_Error :
|
||||
ErrorString = AdLibDrv_ErrorString( ErrorCode );
|
||||
break;
|
||||
|
||||
case AdLibErr_Ok :
|
||||
ErrorString = "AdLib ok.";
|
||||
break;
|
||||
|
||||
default:
|
||||
ErrorString = "Unknown AdLib error.";
|
||||
break;
|
||||
}
|
||||
|
||||
return ErrorString;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int AdLibDrv_MIDI_Init(midifuncs * const funcs)
|
||||
{
|
||||
AdLibDrv_MIDI_Shutdown();
|
||||
Bmemset(funcs, 0, sizeof(midifuncs));
|
||||
|
||||
funcs->NoteOff = AL_NoteOff;
|
||||
funcs->NoteOn = AL_NoteOn;
|
||||
funcs->PolyAftertouch = nullptr;
|
||||
funcs->ControlChange = AL_ControlChange;
|
||||
funcs->ProgramChange = AL_ProgramChange;
|
||||
funcs->ChannelAftertouch = nullptr;
|
||||
funcs->PitchBend = AL_SetPitchBend;
|
||||
|
||||
return AdLibErr_Ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
//void AdLibDrv_MIDI_HaltPlayback(void) { MV_UnhookMusicRoutine(); }
|
||||
|
||||
void AdLibDrv_MIDI_Shutdown(void)
|
||||
{
|
||||
AdLibDrv_MIDI_HaltPlayback();
|
||||
AL_Shutdown();
|
||||
}
|
||||
|
||||
int AdLibDrv_MIDI_StartPlayback(void (*service)(void))
|
||||
{
|
||||
AdLibDrv_MIDI_HaltPlayback();
|
||||
|
||||
//AL_Init(MV_MixRate);
|
||||
//MV_HookMusicRoutine(service);
|
||||
|
||||
return 0;// MIDI_Ok;
|
||||
}
|
||||
|
||||
void AdLibDrv_MIDI_SetTempo(int const tempo, int const division)
|
||||
{
|
||||
//MV_MIDIRenderTempo = tempo * division / 60;
|
||||
//MV_MIDIRenderTimer = 0;
|
||||
}
|
||||
|
||||
static opl3_chip chip;
|
||||
|
||||
opl3_chip *AL_GetChip(void) { return &chip; }
|
||||
|
||||
static constexpr uint32_t OctavePitch[MAX_OCTAVE+1] = {
|
||||
OCTAVE_0, OCTAVE_1, OCTAVE_2, OCTAVE_3, OCTAVE_4, OCTAVE_5, OCTAVE_6, OCTAVE_7,
|
||||
};
|
||||
|
||||
static uint32_t NoteMod12[MAX_NOTE+1];
|
||||
static uint32_t NoteDiv12[MAX_NOTE+1];
|
||||
|
||||
// Pitch table
|
||||
|
||||
//static unsigned NotePitch[ FINETUNE_MAX+1 ][ 12 ] =
|
||||
// {
|
||||
// { C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B },
|
||||
// };
|
||||
|
||||
static constexpr uint32_t NotePitch[FINETUNE_MAX+1][12] = {
|
||||
{ 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287 },
|
||||
{ 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x220, 0x242, 0x264, 0x288 },
|
||||
{ 0x158, 0x16c, 0x182, 0x199, 0x1b1, 0x1cb, 0x1e6, 0x203, 0x221, 0x243, 0x265, 0x289 },
|
||||
{ 0x158, 0x16c, 0x183, 0x19a, 0x1b2, 0x1cc, 0x1e7, 0x204, 0x222, 0x244, 0x266, 0x28a },
|
||||
{ 0x159, 0x16d, 0x183, 0x19a, 0x1b3, 0x1cd, 0x1e8, 0x205, 0x223, 0x245, 0x267, 0x28b },
|
||||
{ 0x15a, 0x16e, 0x184, 0x19b, 0x1b3, 0x1ce, 0x1e9, 0x206, 0x224, 0x246, 0x268, 0x28c },
|
||||
{ 0x15a, 0x16e, 0x185, 0x19c, 0x1b4, 0x1ce, 0x1ea, 0x207, 0x225, 0x247, 0x269, 0x28e },
|
||||
{ 0x15b, 0x16f, 0x185, 0x19d, 0x1b5, 0x1cf, 0x1eb, 0x208, 0x226, 0x248, 0x26a, 0x28f },
|
||||
{ 0x15b, 0x170, 0x186, 0x19d, 0x1b6, 0x1d0, 0x1ec, 0x209, 0x227, 0x249, 0x26b, 0x290 },
|
||||
{ 0x15c, 0x170, 0x187, 0x19e, 0x1b7, 0x1d1, 0x1ec, 0x20a, 0x228, 0x24a, 0x26d, 0x291 },
|
||||
{ 0x15d, 0x171, 0x188, 0x19f, 0x1b7, 0x1d2, 0x1ed, 0x20b, 0x229, 0x24b, 0x26e, 0x292 },
|
||||
{ 0x15d, 0x172, 0x188, 0x1a0, 0x1b8, 0x1d3, 0x1ee, 0x20c, 0x22a, 0x24c, 0x26f, 0x293 },
|
||||
{ 0x15e, 0x172, 0x189, 0x1a0, 0x1b9, 0x1d4, 0x1ef, 0x20d, 0x22b, 0x24d, 0x270, 0x295 },
|
||||
{ 0x15f, 0x173, 0x18a, 0x1a1, 0x1ba, 0x1d4, 0x1f0, 0x20e, 0x22c, 0x24e, 0x271, 0x296 },
|
||||
{ 0x15f, 0x174, 0x18a, 0x1a2, 0x1bb, 0x1d5, 0x1f1, 0x20f, 0x22d, 0x24f, 0x272, 0x297 },
|
||||
{ 0x160, 0x174, 0x18b, 0x1a3, 0x1bb, 0x1d6, 0x1f2, 0x210, 0x22e, 0x250, 0x273, 0x298 },
|
||||
{ 0x161, 0x175, 0x18c, 0x1a3, 0x1bc, 0x1d7, 0x1f3, 0x211, 0x22f, 0x251, 0x274, 0x299 },
|
||||
{ 0x161, 0x176, 0x18c, 0x1a4, 0x1bd, 0x1d8, 0x1f4, 0x212, 0x230, 0x252, 0x276, 0x29b },
|
||||
{ 0x162, 0x176, 0x18d, 0x1a5, 0x1be, 0x1d9, 0x1f5, 0x212, 0x231, 0x254, 0x277, 0x29c },
|
||||
{ 0x162, 0x177, 0x18e, 0x1a6, 0x1bf, 0x1d9, 0x1f5, 0x213, 0x232, 0x255, 0x278, 0x29d },
|
||||
{ 0x163, 0x178, 0x18f, 0x1a6, 0x1bf, 0x1da, 0x1f6, 0x214, 0x233, 0x256, 0x279, 0x29e },
|
||||
{ 0x164, 0x179, 0x18f, 0x1a7, 0x1c0, 0x1db, 0x1f7, 0x215, 0x235, 0x257, 0x27a, 0x29f },
|
||||
{ 0x164, 0x179, 0x190, 0x1a8, 0x1c1, 0x1dc, 0x1f8, 0x216, 0x236, 0x258, 0x27b, 0x2a1 },
|
||||
{ 0x165, 0x17a, 0x191, 0x1a9, 0x1c2, 0x1dd, 0x1f9, 0x217, 0x237, 0x259, 0x27c, 0x2a2 },
|
||||
{ 0x166, 0x17b, 0x192, 0x1aa, 0x1c3, 0x1de, 0x1fa, 0x218, 0x238, 0x25a, 0x27e, 0x2a3 },
|
||||
{ 0x166, 0x17b, 0x192, 0x1aa, 0x1c3, 0x1df, 0x1fb, 0x219, 0x239, 0x25b, 0x27f, 0x2a4 },
|
||||
{ 0x167, 0x17c, 0x193, 0x1ab, 0x1c4, 0x1e0, 0x1fc, 0x21a, 0x23a, 0x25c, 0x280, 0x2a6 },
|
||||
{ 0x168, 0x17d, 0x194, 0x1ac, 0x1c5, 0x1e0, 0x1fd, 0x21b, 0x23b, 0x25d, 0x281, 0x2a7 },
|
||||
{ 0x168, 0x17d, 0x194, 0x1ad, 0x1c6, 0x1e1, 0x1fe, 0x21c, 0x23c, 0x25e, 0x282, 0x2a8 },
|
||||
{ 0x169, 0x17e, 0x195, 0x1ad, 0x1c7, 0x1e2, 0x1ff, 0x21d, 0x23d, 0x260, 0x283, 0x2a9 },
|
||||
{ 0x16a, 0x17f, 0x196, 0x1ae, 0x1c8, 0x1e3, 0x1ff, 0x21e, 0x23e, 0x261, 0x284, 0x2ab },
|
||||
{ 0x16a, 0x17f, 0x197, 0x1af, 0x1c8, 0x1e4, 0x200, 0x21f, 0x23f, 0x262, 0x286, 0x2ac }
|
||||
};
|
||||
|
||||
// Slot numbers as a function of the voice and the operator.
|
||||
// ( melodic only)
|
||||
|
||||
static constexpr int slotVoice[NUMADLIBVOICES][2] = {
|
||||
{ 0, 3 }, // voice 0
|
||||
{ 1, 4 }, // 1
|
||||
{ 2, 5 }, // 2
|
||||
{ 6, 9 }, // 3
|
||||
{ 7, 10 }, // 4
|
||||
{ 8, 11 }, // 5
|
||||
{ 12, 15 }, // 6
|
||||
{ 13, 16 }, // 7
|
||||
{ 14, 17 }, // 8
|
||||
};
|
||||
|
||||
static int VoiceLevel[AL_NumChipSlots][2];
|
||||
static int VoiceKsl[AL_NumChipSlots][2];
|
||||
|
||||
// This table gives the offset of each slot within the chip.
|
||||
// offset = fn( slot)
|
||||
|
||||
static constexpr int8_t offsetSlot[AL_NumChipSlots] = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21 };
|
||||
|
||||
static int VoiceReserved[NUMADLIBVOICES * 2];
|
||||
|
||||
static AdLibVoice Voice[NUMADLIBVOICES * 2];
|
||||
static AdLibVoiceList Voice_Pool;
|
||||
|
||||
static AdLibChannel Channel[NUMADLIBCHANNELS];
|
||||
|
||||
static int AL_LeftPort = ADLIB_PORT;
|
||||
static int AL_RightPort = ADLIB_PORT;
|
||||
int AL_Stereo = TRUE;
|
||||
static int AL_MaxMidiChannel = 16;
|
||||
|
||||
// TODO: clean up this shit...
|
||||
#define OFFSET(structure, offset) (*((char **)&(structure)[offset]))
|
||||
|
||||
#define LL_AddToTail(type, listhead, node) \
|
||||
LL_AddNode((char *)(node), (char **)&((listhead)->end), (char **)&((listhead)->start), (intptr_t) & ((type *)0)->prev, \
|
||||
(intptr_t) & ((type *)0)->next)
|
||||
|
||||
#define LL_Remove(type, listhead, node) \
|
||||
LL_RemoveNode((char *)(node), (char **)&((listhead)->start), (char **)&((listhead)->end), (intptr_t) & ((type *)0)->next, \
|
||||
(intptr_t) & ((type *)0)->prev)
|
||||
|
||||
static void LL_RemoveNode(char *item, char **head, char **tail, intptr_t next, intptr_t prev)
|
||||
{
|
||||
if (OFFSET(item, prev) == nullptr)
|
||||
*head = OFFSET(item, next);
|
||||
else
|
||||
OFFSET(OFFSET(item, prev), next) = OFFSET(item, next);
|
||||
|
||||
if (OFFSET(item, next) == nullptr)
|
||||
*tail = OFFSET(item, prev);
|
||||
else
|
||||
OFFSET(OFFSET(item, next), prev) = OFFSET(item, prev);
|
||||
|
||||
OFFSET(item, next) = nullptr;
|
||||
OFFSET(item, prev) = nullptr;
|
||||
}
|
||||
|
||||
static void LL_AddNode(char *item, char **head, char **tail, intptr_t next, intptr_t prev)
|
||||
{
|
||||
OFFSET(item, prev) = nullptr;
|
||||
OFFSET(item, next) = *head;
|
||||
|
||||
if (*head)
|
||||
OFFSET(*head, prev) = item;
|
||||
else
|
||||
*tail = item;
|
||||
|
||||
*head = item;
|
||||
}
|
||||
|
||||
|
||||
static void AL_SendOutputToPort(int const port, int const reg, int const data)
|
||||
{
|
||||
OPL3_WriteRegBuffered(&chip, (Bit16u)(reg + ((port & 2) << 7)), (Bit8u)data);
|
||||
}
|
||||
|
||||
|
||||
static void AL_SendOutput(int const voice, int const reg, int const data)
|
||||
{
|
||||
int port = (voice == 0) ? AL_RightPort : AL_LeftPort;
|
||||
AL_SendOutputToPort(port, reg, data);
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetVoiceTimbre(int const voice)
|
||||
{
|
||||
int const channel = Voice[voice].channel;
|
||||
int const patch = (channel == 9) ? Voice[voice].key + 128 : Channel[channel].Timbre;
|
||||
|
||||
if (Voice[voice].timbre == patch)
|
||||
return;
|
||||
|
||||
Voice[voice].timbre = patch;
|
||||
|
||||
auto const timbre = &ADLIB_TimbreBank[patch];
|
||||
|
||||
int const port = Voice[voice].port;
|
||||
int const voc = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
|
||||
int slot = slotVoice[voc][0];
|
||||
int off = offsetSlot[slot];
|
||||
|
||||
VoiceLevel[slot][port] = 63 - (timbre->Level[0] & 0x3f);
|
||||
VoiceKsl[slot][port] = timbre->Level[0] & 0xc0;
|
||||
|
||||
AL_SendOutput(port, 0xA0 + voc, 0);
|
||||
AL_SendOutput(port, 0xB0 + voc, 0);
|
||||
|
||||
// Let voice clear the release
|
||||
AL_SendOutput(port, 0x80 + off, 0xff);
|
||||
|
||||
AL_SendOutput(port, 0x60 + off, timbre->Env1[0]);
|
||||
AL_SendOutput(port, 0x80 + off, timbre->Env2[0]);
|
||||
AL_SendOutput(port, 0x20 + off, timbre->SAVEK[0]);
|
||||
AL_SendOutput(port, 0xE0 + off, timbre->Wave[0]);
|
||||
|
||||
AL_SendOutput(port, 0x40 + off, timbre->Level[0]);
|
||||
slot = slotVoice[voc][1];
|
||||
|
||||
AL_SendOutput(port, 0xC0 + voc, (timbre->Feedback & 0x0f) | 0x30);
|
||||
|
||||
off = offsetSlot[slot];
|
||||
|
||||
VoiceLevel[slot][port] = 63 - (timbre->Level[1] & 0x3f);
|
||||
VoiceKsl[slot][port] = timbre->Level[1] & 0xc0;
|
||||
|
||||
AL_SendOutput(port, 0x40 + off, 63);
|
||||
|
||||
// Let voice clear the release
|
||||
AL_SendOutput(port, 0x80 + off, 0xff);
|
||||
|
||||
AL_SendOutput(port, 0x60 + off, timbre->Env1[1]);
|
||||
AL_SendOutput(port, 0x80 + off, timbre->Env2[1]);
|
||||
AL_SendOutput(port, 0x20 + off, timbre->SAVEK[1]);
|
||||
AL_SendOutput(port, 0xE0 + off, timbre->Wave[1]);
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetVoiceVolume(int const voice)
|
||||
{
|
||||
int const channel = Voice[voice].channel;
|
||||
auto const timbre = &ADLIB_TimbreBank[Voice[voice].timbre];
|
||||
int const velocity = min<int>(Voice[voice].velocity + timbre->Velocity, MAX_VELOCITY);
|
||||
|
||||
int const voc = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
|
||||
int const slot = slotVoice[voc][1];
|
||||
int const port = Voice[voice].port;
|
||||
|
||||
// amplitude
|
||||
auto t1 = (uint32_t)VoiceLevel[slot][port] * (velocity + 0x80);
|
||||
t1 = (Channel[channel].Volume * t1) >> 15;
|
||||
|
||||
uint32_t volume = t1 ^ 63;
|
||||
volume |= (uint32_t)VoiceKsl[slot][port];
|
||||
|
||||
AL_SendOutput(port, 0x40 + offsetSlot[slot], volume);
|
||||
|
||||
// Check if this timbre is Additive
|
||||
if (timbre->Feedback & 0x01)
|
||||
{
|
||||
int const slot = slotVoice[voc][0];
|
||||
uint32_t t2;
|
||||
|
||||
// amplitude
|
||||
if (mus_al_additivemode)
|
||||
t1 = (uint32_t)VoiceLevel[slot][port] * (velocity + 0x80);
|
||||
|
||||
t2 = (Channel[channel].Volume * t1) >> 15;
|
||||
|
||||
volume = t2 ^ 63;
|
||||
volume |= (uint32_t)VoiceKsl[slot][port];
|
||||
|
||||
AL_SendOutput(port, 0x40 + offsetSlot[slot], volume);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int AL_AllocVoice(void)
|
||||
{
|
||||
if (Voice_Pool.start)
|
||||
{
|
||||
int const voice = Voice_Pool.start->num;
|
||||
LL_Remove(AdLibVoice, &Voice_Pool, &Voice[voice]);
|
||||
return voice;
|
||||
}
|
||||
|
||||
return AL_VoiceNotFound;
|
||||
}
|
||||
|
||||
|
||||
static int AL_GetVoice(int const channel, int const key)
|
||||
{
|
||||
auto const *voice = Channel[channel].Voices.start;
|
||||
|
||||
while (voice != nullptr)
|
||||
{
|
||||
if (voice->key == (uint32_t)key)
|
||||
return voice->num;
|
||||
voice = voice->next;
|
||||
}
|
||||
|
||||
return AL_VoiceNotFound;
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetVoicePitch(int const voice)
|
||||
{
|
||||
int const port = Voice[voice].port;
|
||||
int const voc = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
|
||||
int const channel = Voice[voice].channel;
|
||||
|
||||
int patch, note;
|
||||
|
||||
if (channel == 9)
|
||||
{
|
||||
patch = Voice[voice].key + 128;
|
||||
note = ADLIB_TimbreBank[patch].Transpose;
|
||||
}
|
||||
else
|
||||
{
|
||||
patch = Channel[channel].Timbre;
|
||||
note = Voice[voice].key + ADLIB_TimbreBank[patch].Transpose;
|
||||
}
|
||||
|
||||
note += Channel[channel].KeyOffset - 12;
|
||||
note = clamp(note, 0, MAX_NOTE);
|
||||
|
||||
int detune = Channel[channel].KeyDetune;
|
||||
|
||||
int ScaleNote = NoteMod12[note];
|
||||
int Octave = NoteDiv12[note];
|
||||
|
||||
int pitch = OctavePitch[Octave] | NotePitch[detune][ScaleNote];
|
||||
|
||||
Voice[voice].pitchleft = pitch;
|
||||
|
||||
pitch |= Voice[voice].status;
|
||||
|
||||
AL_SendOutput(port, 0xA0 + voc, pitch);
|
||||
AL_SendOutput(port, 0xB0 + voc, pitch >> 8);
|
||||
}
|
||||
|
||||
static void AL_SetVoicePan(int const voice)
|
||||
{
|
||||
int const port = Voice[voice].port;
|
||||
int const voc = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
|
||||
int const channel = Voice[voice].channel;
|
||||
|
||||
if (AL_Stereo)
|
||||
AL_SendOutput(port, 0xD0 + voc, Channel[channel].Pan << 1);
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetChannelVolume(int const channel, int const volume)
|
||||
{
|
||||
Channel[channel].Volume = clamp(volume, 0, AL_MaxVolume);
|
||||
|
||||
auto voice = Channel[channel].Voices.start;
|
||||
|
||||
while (voice != nullptr)
|
||||
{
|
||||
AL_SetVoiceVolume(voice->num);
|
||||
voice = voice->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetChannelPan(int const channel, int const pan)
|
||||
{
|
||||
// Don't pan drum sounds
|
||||
if (channel != 9)
|
||||
Channel[channel].Pan = pan;
|
||||
|
||||
auto voice = Channel[channel].Voices.start;
|
||||
while (voice != nullptr)
|
||||
{
|
||||
AL_SetVoicePan(voice->num);
|
||||
voice = voice->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetChannelDetune(int const channel, int const detune) { Channel[channel].Detune = detune; }
|
||||
|
||||
|
||||
static void AL_ResetVoices(void)
|
||||
{
|
||||
Voice_Pool.start = nullptr;
|
||||
Voice_Pool.end = nullptr;
|
||||
|
||||
int const numvoices = NUMADLIBVOICES * 2;
|
||||
|
||||
for (int index = 0; index < numvoices; index++)
|
||||
{
|
||||
if (VoiceReserved[index] == FALSE)
|
||||
{
|
||||
Voice[index].num = index;
|
||||
Voice[index].key = 0;
|
||||
Voice[index].velocity = 0;
|
||||
Voice[index].channel = -1;
|
||||
Voice[index].timbre = -1;
|
||||
Voice[index].port = (index < NUMADLIBVOICES) ? 0 : 1;
|
||||
Voice[index].status = NOTE_OFF;
|
||||
LL_AddToTail(AdLibVoice, &Voice_Pool, &Voice[index]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < NUMADLIBCHANNELS; index++)
|
||||
{
|
||||
Channel[index] = {};
|
||||
Channel[index].Volume = AL_DefaultChannelVolume;
|
||||
Channel[index].Pan = 64;
|
||||
Channel[index].PitchBendRange = AL_DefaultPitchBendRange;
|
||||
Channel[index].PitchBendSemiTones = AL_DefaultPitchBendRange / 100;
|
||||
Channel[index].PitchBendHundreds = AL_DefaultPitchBendRange % 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void AL_CalcPitchInfo(void)
|
||||
{
|
||||
// int finetune;
|
||||
// double detune;
|
||||
|
||||
for (int note = 0; note <= MAX_NOTE; note++)
|
||||
{
|
||||
NoteMod12[note] = note % 12;
|
||||
NoteDiv12[note] = note / 12;
|
||||
}
|
||||
|
||||
// for( finetune = 1; finetune <= FINETUNE_MAX; finetune++ )
|
||||
// {
|
||||
// detune = pow( 2, ( double )finetune / ( 12.0 * FINETUNE_RANGE ) );
|
||||
// for( note = 0; note < 12; note++ )
|
||||
// {
|
||||
// NotePitch[ finetune ][ note ] = ( ( double )NotePitch[ 0 ][ note ] * detune );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
static void AL_FlushCard(int const port)
|
||||
{
|
||||
for (int i = 0; i < NUMADLIBVOICES; i++)
|
||||
{
|
||||
if (VoiceReserved[i])
|
||||
continue;
|
||||
|
||||
auto slot1 = offsetSlot[slotVoice[i][0]];
|
||||
auto slot2 = offsetSlot[slotVoice[i][1]];
|
||||
|
||||
AL_SendOutputToPort(port, 0xA0 + i, 0);
|
||||
AL_SendOutputToPort(port, 0xB0 + i, 0);
|
||||
|
||||
AL_SendOutputToPort(port, 0xE0 + slot1, 0);
|
||||
AL_SendOutputToPort(port, 0xE0 + slot2, 0);
|
||||
|
||||
// Set the envelope to be fast and quiet
|
||||
AL_SendOutputToPort(port, 0x60 + slot1, 0xff);
|
||||
AL_SendOutputToPort(port, 0x60 + slot2, 0xff);
|
||||
AL_SendOutputToPort(port, 0x80 + slot1, 0xff);
|
||||
AL_SendOutputToPort(port, 0x80 + slot2, 0xff);
|
||||
|
||||
// Maximum attenuation
|
||||
AL_SendOutputToPort(port, 0x40 + slot1, 0xff);
|
||||
AL_SendOutputToPort(port, 0x40 + slot2, 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void AL_Reset(void)
|
||||
{
|
||||
AL_SendOutputToPort(ADLIB_PORT, 1, 0x20);
|
||||
AL_SendOutputToPort(ADLIB_PORT, 0x08, 0);
|
||||
|
||||
// Set the values: AM Depth, VIB depth & Rhythm
|
||||
AL_SendOutputToPort(ADLIB_PORT, 0xBD, 0);
|
||||
|
||||
AL_SetStereo(AL_Stereo);
|
||||
|
||||
AL_FlushCard(AL_LeftPort);
|
||||
AL_FlushCard(AL_RightPort);
|
||||
}
|
||||
|
||||
|
||||
void AL_SetStereo(int const stereo)
|
||||
{
|
||||
AL_SendOutputToPort(AL_RightPort, 0x5, (stereo<<1)+1);
|
||||
}
|
||||
|
||||
|
||||
static void AL_NoteOff(int const channel, int const key, int velocity)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(velocity);
|
||||
|
||||
// We only play channels 1 through 10
|
||||
if (channel > AL_MaxMidiChannel)
|
||||
return;
|
||||
|
||||
int voice = AL_GetVoice(channel, key);
|
||||
|
||||
if (voice == AL_VoiceNotFound)
|
||||
return;
|
||||
|
||||
Voice[voice].status = NOTE_OFF;
|
||||
|
||||
int port = Voice[voice].port;
|
||||
int voc = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
|
||||
|
||||
AL_SendOutput(port, 0xB0 + voc, hibyte(Voice[voice].pitchleft));
|
||||
|
||||
LL_Remove(AdLibVoice, &Channel[channel].Voices, &Voice[voice]);
|
||||
LL_AddToTail(AdLibVoice, &Voice_Pool, &Voice[voice]);
|
||||
}
|
||||
|
||||
|
||||
static void AL_NoteOn(int const channel, int const key, int const velocity)
|
||||
{
|
||||
// We only play channels 1 through 10
|
||||
if (channel > AL_MaxMidiChannel)
|
||||
return;
|
||||
|
||||
if (velocity == 0)
|
||||
{
|
||||
AL_NoteOff(channel, key, velocity);
|
||||
return;
|
||||
}
|
||||
|
||||
int voice = AL_AllocVoice();
|
||||
|
||||
if (voice == AL_VoiceNotFound)
|
||||
{
|
||||
if (Channel[9].Voices.start)
|
||||
{
|
||||
AL_NoteOff(9, Channel[9].Voices.start->key, 0);
|
||||
voice = AL_AllocVoice();
|
||||
}
|
||||
|
||||
if (voice == AL_VoiceNotFound)
|
||||
return;
|
||||
}
|
||||
|
||||
Voice[voice].key = key;
|
||||
Voice[voice].channel = channel;
|
||||
Voice[voice].velocity = velocity;
|
||||
Voice[voice].status = NOTE_ON;
|
||||
|
||||
LL_AddToTail(AdLibVoice, &Channel[channel].Voices, &Voice[voice]);
|
||||
|
||||
AL_SetVoiceTimbre(voice);
|
||||
AL_SetVoiceVolume(voice);
|
||||
AL_SetVoicePitch(voice);
|
||||
AL_SetVoicePan(voice);
|
||||
}
|
||||
|
||||
|
||||
static inline void AL_AllNotesOff(int const channel)
|
||||
{
|
||||
while (Channel[channel].Voices.start != nullptr)
|
||||
AL_NoteOff(channel, Channel[channel].Voices.start->key, 0);
|
||||
}
|
||||
|
||||
|
||||
static void AL_ControlChange(int const channel, int const type, int const data)
|
||||
{
|
||||
// We only play channels 1 through 10
|
||||
if (channel > AL_MaxMidiChannel)
|
||||
return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MIDI_VOLUME:
|
||||
AL_SetChannelVolume(channel, data);
|
||||
break;
|
||||
|
||||
case MIDI_PAN:
|
||||
AL_SetChannelPan(channel, data);
|
||||
break;
|
||||
|
||||
case MIDI_DETUNE:
|
||||
AL_SetChannelDetune(channel, data);
|
||||
break;
|
||||
|
||||
case MIDI_ALL_NOTES_OFF:
|
||||
AL_AllNotesOff(channel);
|
||||
break;
|
||||
|
||||
case MIDI_RESET_ALL_CONTROLLERS:
|
||||
AL_ResetVoices();
|
||||
AL_SetChannelVolume(channel, AL_DefaultChannelVolume);
|
||||
AL_SetChannelPan(channel, 64);
|
||||
AL_SetChannelDetune(channel, 0);
|
||||
break;
|
||||
|
||||
case MIDI_RPN_MSB:
|
||||
Channel[channel].RPN &= 0x00FF;
|
||||
Channel[channel].RPN |= (data & 0xFF) << 8;
|
||||
break;
|
||||
|
||||
case MIDI_RPN_LSB:
|
||||
Channel[channel].RPN &= 0xFF00;
|
||||
Channel[channel].RPN |= data & 0xFF;
|
||||
break;
|
||||
|
||||
case MIDI_DATAENTRY_MSB:
|
||||
if (Channel[channel].RPN == MIDI_PITCHBEND_RPN)
|
||||
{
|
||||
Channel[channel].PitchBendSemiTones = data;
|
||||
Channel[channel].PitchBendRange = Channel[channel].PitchBendSemiTones * 100 + Channel[channel].PitchBendHundreds;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIDI_DATAENTRY_LSB:
|
||||
if (Channel[channel].RPN == MIDI_PITCHBEND_RPN)
|
||||
{
|
||||
Channel[channel].PitchBendHundreds = data;
|
||||
Channel[channel].PitchBendRange = Channel[channel].PitchBendSemiTones * 100 + Channel[channel].PitchBendHundreds;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void AL_ProgramChange(int const channel, int const patch)
|
||||
{
|
||||
// We only play channels 1 through 10
|
||||
if (channel > AL_MaxMidiChannel)
|
||||
return;
|
||||
|
||||
Channel[channel].Timbre = patch;
|
||||
}
|
||||
|
||||
|
||||
static void AL_SetPitchBend(int const channel, int const lsb, int const msb)
|
||||
{
|
||||
// We only play channels 1 through 10
|
||||
if (channel > AL_MaxMidiChannel)
|
||||
return;
|
||||
|
||||
int const pitchbend = lsb + (msb << 8);
|
||||
|
||||
Channel[channel].Pitchbend = pitchbend;
|
||||
|
||||
int TotalBend = pitchbend * Channel[channel].PitchBendRange;
|
||||
TotalBend /= (PITCHBEND_CENTER / FINETUNE_RANGE);
|
||||
|
||||
Channel[channel].KeyOffset = (int)(TotalBend / FINETUNE_RANGE);
|
||||
Channel[channel].KeyOffset -= Channel[channel].PitchBendSemiTones;
|
||||
|
||||
Channel[channel].KeyDetune = (uint32_t)(TotalBend % FINETUNE_RANGE);
|
||||
|
||||
auto voice = Channel[channel].Voices.start;
|
||||
while (voice != nullptr)
|
||||
{
|
||||
AL_SetVoicePitch(voice->num);
|
||||
voice = voice->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void AL_Shutdown(void)
|
||||
{
|
||||
AL_ResetVoices();
|
||||
AL_Reset();
|
||||
}
|
||||
|
||||
|
||||
static int AL_Init(int const rate)
|
||||
{
|
||||
OPL3_Reset(&chip, rate);
|
||||
|
||||
AL_LeftPort = ADLIB_PORT;
|
||||
AL_RightPort = ADLIB_PORT + 2;
|
||||
|
||||
AL_CalcPitchInfo();
|
||||
AL_Reset();
|
||||
AL_ResetVoices();
|
||||
|
||||
return AdLibErr_Ok;
|
||||
}
|
||||
|
||||
|
||||
void AL_RegisterTimbreBank(uint8_t *timbres)
|
||||
{
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
ADLIB_TimbreBank[i].SAVEK[0] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].SAVEK[1] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Level[0] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Level[1] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Env1[0] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Env1[1] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Env2[0] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Env2[1] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Wave[0] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Wave[1] = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Feedback = *(timbres++);
|
||||
ADLIB_TimbreBank[i].Transpose = *(int8_t *)(timbres++);
|
||||
ADLIB_TimbreBank[i].Velocity = *(int8_t *)(timbres++);
|
||||
}
|
||||
}
|
||||
160
libraries/ZMusic/source/mididevices/mididevice.h
Normal file
160
libraries/ZMusic/source/mididevices/mididevice.h
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "zmusic/midiconfig.h"
|
||||
#include "zmusic/mididefs.h"
|
||||
|
||||
typedef void(*MidiCallback)(void *);
|
||||
|
||||
// A device that provides a WinMM-like MIDI streaming interface -------------
|
||||
|
||||
|
||||
struct MidiHeader
|
||||
{
|
||||
uint8_t *lpData;
|
||||
uint32_t dwBufferLength;
|
||||
uint32_t dwBytesRecorded;
|
||||
MidiHeader *lpNext;
|
||||
};
|
||||
|
||||
class MIDIDevice
|
||||
{
|
||||
public:
|
||||
MIDIDevice() = default;
|
||||
virtual ~MIDIDevice();
|
||||
|
||||
void SetCallback(MidiCallback callback, void* userdata)
|
||||
{
|
||||
Callback = callback;
|
||||
CallbackData = userdata;
|
||||
}
|
||||
virtual int Open() = 0;
|
||||
virtual void Close() = 0;
|
||||
virtual bool IsOpen() const = 0;
|
||||
virtual int GetTechnology() const = 0;
|
||||
virtual int SetTempo(int tempo) = 0;
|
||||
virtual int SetTimeDiv(int timediv) = 0;
|
||||
virtual int StreamOut(MidiHeader *data) = 0;
|
||||
virtual int StreamOutSync(MidiHeader *data) = 0;
|
||||
virtual int Resume() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual int PrepareHeader(MidiHeader *data);
|
||||
virtual int UnprepareHeader(MidiHeader *data);
|
||||
virtual bool FakeVolume();
|
||||
virtual bool Pause(bool paused) = 0;
|
||||
virtual void InitPlayback();
|
||||
virtual bool Update();
|
||||
virtual void PrecacheInstruments(const uint16_t *instruments, int count);
|
||||
virtual void ChangeSettingInt(const char *setting, int value);
|
||||
virtual void ChangeSettingNum(const char *setting, double value);
|
||||
virtual void ChangeSettingString(const char *setting, const char *value);
|
||||
virtual std::string GetStats();
|
||||
virtual int GetDeviceType() const { return MDEV_DEFAULT; }
|
||||
virtual bool CanHandleSysex() const { return true; }
|
||||
virtual SoundStreamInfoEx GetStreamInfoEx() const;
|
||||
|
||||
protected:
|
||||
MidiCallback Callback;
|
||||
void* CallbackData;
|
||||
};
|
||||
|
||||
// Base class for software synthesizer MIDI output devices ------------------
|
||||
|
||||
class SoftSynthMIDIDevice : public MIDIDevice
|
||||
{
|
||||
friend class MIDIWaveWriter;
|
||||
public:
|
||||
SoftSynthMIDIDevice(int samplerate, int minrate = 1, int maxrate = 1000000 /* something higher than any valid value */);
|
||||
~SoftSynthMIDIDevice();
|
||||
|
||||
void Close() override;
|
||||
bool IsOpen() const override;
|
||||
int GetTechnology() const override;
|
||||
int SetTempo(int tempo) override;
|
||||
int SetTimeDiv(int timediv) override;
|
||||
int StreamOut(MidiHeader *data) override;
|
||||
int StreamOutSync(MidiHeader *data) override;
|
||||
int Resume() override;
|
||||
void Stop() override;
|
||||
bool Pause(bool paused) override;
|
||||
|
||||
virtual int Open() override;
|
||||
virtual bool ServiceStream(void* buff, int numbytes);
|
||||
int GetSampleRate() const { return SampleRate; }
|
||||
SoundStreamInfoEx GetStreamInfoEx() const override;
|
||||
|
||||
protected:
|
||||
double Tempo;
|
||||
double Division;
|
||||
double SamplesPerTick;
|
||||
double NextTickIn;
|
||||
MidiHeader *Events;
|
||||
bool Started;
|
||||
bool isMono = false; // only relevant for OPL.
|
||||
bool isOpen = false;
|
||||
uint32_t Position;
|
||||
int SampleRate;
|
||||
int StreamBlockSize = 2;
|
||||
|
||||
virtual void CalcTickRate();
|
||||
int PlayTick();
|
||||
|
||||
virtual int OpenRenderer() = 0;
|
||||
virtual void HandleEvent(int status, int parm1, int parm2) = 0;
|
||||
virtual void HandleLongEvent(const uint8_t *data, int len) = 0;
|
||||
virtual void ComputeOutput(float *buffer, int len) = 0;
|
||||
};
|
||||
|
||||
|
||||
// Internal disk writing version of a MIDI device ------------------
|
||||
|
||||
class MIDIWaveWriter : public SoftSynthMIDIDevice
|
||||
{
|
||||
public:
|
||||
MIDIWaveWriter(const char *filename, SoftSynthMIDIDevice *devtouse);
|
||||
//~MIDIWaveWriter();
|
||||
bool CloseFile();
|
||||
int Resume() override;
|
||||
int Open() override
|
||||
{
|
||||
playDevice->SetCallback(Callback, CallbackData);
|
||||
return playDevice->Open();
|
||||
}
|
||||
int OpenRenderer() override { return playDevice->OpenRenderer(); }
|
||||
void Stop() override;
|
||||
void HandleEvent(int status, int parm1, int parm2) override { playDevice->HandleEvent(status, parm1, parm2); }
|
||||
void HandleLongEvent(const uint8_t *data, int len) override { playDevice->HandleLongEvent(data, len); }
|
||||
void ComputeOutput(float *buffer, int len) override { playDevice->ComputeOutput(buffer, len); }
|
||||
int StreamOutSync(MidiHeader *data) override { return playDevice->StreamOutSync(data); }
|
||||
int StreamOut(MidiHeader *data) override { return playDevice->StreamOut(data); }
|
||||
int GetDeviceType() const override { return playDevice->GetDeviceType(); }
|
||||
bool ServiceStream (void *buff, int numbytes) override { return playDevice->ServiceStream(buff, numbytes); }
|
||||
int GetTechnology() const override { return playDevice->GetTechnology(); }
|
||||
int SetTempo(int tempo) override { return playDevice->SetTempo(tempo); }
|
||||
int SetTimeDiv(int timediv) override { return playDevice->SetTimeDiv(timediv); }
|
||||
bool IsOpen() const override { return playDevice->IsOpen(); }
|
||||
void CalcTickRate() override { playDevice->CalcTickRate(); }
|
||||
|
||||
protected:
|
||||
FILE *File;
|
||||
SoftSynthMIDIDevice *playDevice;
|
||||
};
|
||||
|
||||
|
||||
// MIDI devices
|
||||
|
||||
MIDIDevice *CreateFluidSynthMIDIDevice(int samplerate, const char *Args);
|
||||
MIDIDevice *CreateADLMIDIDevice(const char* args);
|
||||
MIDIDevice *CreateOPNMIDIDevice(const char *args);
|
||||
MIDIDevice *CreateOplMIDIDevice(const char* Args);
|
||||
MIDIDevice *CreateTimidityMIDIDevice(const char* Args, int samplerate);
|
||||
MIDIDevice *CreateTimidityPPMIDIDevice(const char *Args, int samplerate);
|
||||
MIDIDevice *CreateWildMIDIDevice(const char *Args, int samplerate);
|
||||
|
||||
#ifdef _WIN32
|
||||
MIDIDevice* CreateWinMIDIDevice(int mididevice);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
MIDIDevice* CreateAlsaMIDIDevice(int mididevice);
|
||||
#endif
|
||||
661
libraries/ZMusic/source/mididevices/music_adlmidi_mididevice.cpp
Normal file
661
libraries/ZMusic/source/mididevices/music_adlmidi_mididevice.cpp
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
/*
|
||||
** music_timidity_mididevice.cpp
|
||||
** Provides access to TiMidity as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "mididevice.h"
|
||||
|
||||
#ifdef HAVE_ADL
|
||||
#include "adlmidi.h"
|
||||
#include "wopl/wopl_file.h"
|
||||
#include "oplsynth/genmidi.h"
|
||||
|
||||
ADLConfig adlConfig;
|
||||
|
||||
class ADLMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
struct ADL_MIDIPlayer *Renderer;
|
||||
float OutputGainFactor;
|
||||
float ConfigGainFactor;
|
||||
std::string custom_bank;
|
||||
std::vector<uint8_t> genmidi;
|
||||
bool use_custom_bank;
|
||||
bool use_genmidi;
|
||||
int last_bank;
|
||||
public:
|
||||
ADLMIDIDevice(const ADLConfig *config);
|
||||
~ADLMIDIDevice();
|
||||
|
||||
int OpenRenderer() override;
|
||||
int GetDeviceType() const override { return MDEV_ADL; }
|
||||
void ChangeSettingInt(const char *setting, int value) override;
|
||||
void ChangeSettingNum(const char *setting, double value) override;
|
||||
void ChangeSettingString(const char *setting, const char *value) override;
|
||||
|
||||
protected:
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
|
||||
private:
|
||||
void initGain();
|
||||
int LoadCustomBank(const ADLConfig *config);
|
||||
void OP2_To_WOPL(const ADLConfig *config);
|
||||
};
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
ME_NOTEOFF = 0x80,
|
||||
ME_NOTEON = 0x90,
|
||||
ME_KEYPRESSURE = 0xA0,
|
||||
ME_CONTROLCHANGE = 0xB0,
|
||||
ME_PROGRAM = 0xC0,
|
||||
ME_CHANNELPRESSURE = 0xD0,
|
||||
ME_PITCHWHEEL = 0xE0
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ADLMIDIDevice::ADLMIDIDevice(const ADLConfig *config)
|
||||
:SoftSynthMIDIDevice(44100)
|
||||
{
|
||||
Renderer = adl_init(44100); // todo: make it configurable
|
||||
OutputGainFactor = 3.5f;
|
||||
ConfigGainFactor = 1.0f;
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
adl_switchEmulator(Renderer, config->adl_emulator_id);
|
||||
adl_setRunAtPcmRate(Renderer, config->adl_run_at_pcm_rate);
|
||||
last_bank = config->adl_bank;
|
||||
use_genmidi = config->adl_use_genmidi;
|
||||
if (config->adl_genmidi_set)
|
||||
OP2_To_WOPL(config);
|
||||
if (!LoadCustomBank(config))
|
||||
adl_setBank(Renderer, config->adl_bank);
|
||||
adl_setNumChips(Renderer, config->adl_chips_count);
|
||||
adl_setVolumeRangeModel(Renderer, config->adl_volume_model);
|
||||
adl_setChannelAllocMode(Renderer, config->adl_chan_alloc);
|
||||
adl_setSoftPanEnabled(Renderer, config->adl_fullpan);
|
||||
adl_setAutoArpeggio(Renderer, (int)config->adl_auto_arpeggio);
|
||||
ConfigGainFactor = config->adl_gain;
|
||||
initGain();
|
||||
}
|
||||
else throw std::runtime_error("Failed to create ADL MIDI renderer.");
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ADLMIDIDevice::~ADLMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
adl_close(Renderer);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: LoadCustomBank
|
||||
//
|
||||
// Loads a custom WOPL bank for libADLMIDI. Returns 1 when bank has been
|
||||
// loaded, otherwise, returns 0 when custom banks are disabled or failed
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int ADLMIDIDevice::LoadCustomBank(const ADLConfig *config)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
custom_bank = config->adl_custom_bank;
|
||||
use_custom_bank = config->adl_use_custom_bank;
|
||||
}
|
||||
|
||||
const char *bankfile = custom_bank.c_str();
|
||||
if(!use_custom_bank)
|
||||
return 0;
|
||||
if (use_genmidi && genmidi.size()) // Try to set GENMIDI as a bank, otherwise, try regular custom bank
|
||||
{
|
||||
if(adl_openBankData(Renderer, genmidi.data(), (unsigned long)genmidi.size()) == 0)
|
||||
return true;
|
||||
}
|
||||
if(!*bankfile)
|
||||
return 0;
|
||||
return (adl_openBankFile(Renderer, bankfile) == 0);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// gen2ins
|
||||
//
|
||||
// Routin to convert a single GENMIDI instrument into WOPL format
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void gen2ins(const GenMidiInstrument *genmidi, WOPLInstrument *inst, bool isDrum)
|
||||
{
|
||||
uint8_t notenum = genmidi->fixed_note;
|
||||
int16_t noteOffset[2];
|
||||
|
||||
inst->inst_flags = 0;
|
||||
|
||||
if (genmidi->flags & 0x04)
|
||||
{
|
||||
inst->inst_flags |= WOPL_Ins_4op | WOPL_Ins_Pseudo4op;
|
||||
}
|
||||
|
||||
if (isDrum)
|
||||
{
|
||||
noteOffset[0] = 12;
|
||||
noteOffset[1] = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
noteOffset[0] = genmidi->voices[0].base_note_offset + 12;
|
||||
noteOffset[1] = genmidi->voices[1].base_note_offset + 12;
|
||||
}
|
||||
|
||||
if (isDrum)
|
||||
{
|
||||
notenum = (genmidi->flags & 0x01) ? genmidi->fixed_note : 60;
|
||||
}
|
||||
|
||||
while(notenum && notenum < 20)
|
||||
{
|
||||
notenum += 12;
|
||||
noteOffset[0] -= 12;
|
||||
noteOffset[1] -= 12;
|
||||
}
|
||||
|
||||
inst->note_offset1 = noteOffset[0];
|
||||
inst->note_offset2 = noteOffset[1];
|
||||
|
||||
inst->percussion_key_number = notenum;
|
||||
inst->second_voice_detune = static_cast<char>(static_cast<int>(genmidi->fine_tuning) - 128);
|
||||
|
||||
inst->operators[WOPL_OP_MODULATOR1].avekf_20 = genmidi->voices[0].modulator.tremolo;
|
||||
inst->operators[WOPL_OP_MODULATOR1].atdec_60 = genmidi->voices[0].modulator.attack;
|
||||
inst->operators[WOPL_OP_MODULATOR1].susrel_80 = genmidi->voices[0].modulator.sustain;
|
||||
inst->operators[WOPL_OP_MODULATOR1].waveform_E0 = genmidi->voices[0].modulator.waveform;
|
||||
inst->operators[WOPL_OP_MODULATOR1].ksl_l_40 = genmidi->voices[0].modulator.scale | genmidi->voices[0].modulator.level;
|
||||
|
||||
inst->operators[WOPL_OP_CARRIER1].avekf_20 = genmidi->voices[0].carrier.tremolo;
|
||||
inst->operators[WOPL_OP_CARRIER1].atdec_60 = genmidi->voices[0].carrier.attack;
|
||||
inst->operators[WOPL_OP_CARRIER1].susrel_80 = genmidi->voices[0].carrier.sustain;
|
||||
inst->operators[WOPL_OP_CARRIER1].waveform_E0 = genmidi->voices[0].carrier.waveform;
|
||||
inst->operators[WOPL_OP_CARRIER1].ksl_l_40 = genmidi->voices[0].carrier.scale | genmidi->voices[0].carrier.level;
|
||||
|
||||
inst->fb_conn1_C0 = genmidi->voices[0].feedback;
|
||||
|
||||
inst->operators[WOPL_OP_MODULATOR2].avekf_20 = genmidi->voices[1].modulator.tremolo;
|
||||
inst->operators[WOPL_OP_MODULATOR2].atdec_60 = genmidi->voices[1].modulator.attack;
|
||||
inst->operators[WOPL_OP_MODULATOR2].susrel_80 = genmidi->voices[1].modulator.sustain;
|
||||
inst->operators[WOPL_OP_MODULATOR2].waveform_E0 = genmidi->voices[1].modulator.waveform;
|
||||
inst->operators[WOPL_OP_MODULATOR2].ksl_l_40 = genmidi->voices[1].modulator.scale | genmidi->voices[1].modulator.level;
|
||||
|
||||
inst->operators[WOPL_OP_CARRIER2].avekf_20 = genmidi->voices[1].carrier.tremolo;
|
||||
inst->operators[WOPL_OP_CARRIER2].atdec_60 = genmidi->voices[1].carrier.attack;
|
||||
inst->operators[WOPL_OP_CARRIER2].susrel_80 = genmidi->voices[1].carrier.sustain;
|
||||
inst->operators[WOPL_OP_CARRIER2].waveform_E0 = genmidi->voices[1].carrier.waveform;
|
||||
inst->operators[WOPL_OP_CARRIER2].ksl_l_40 = genmidi->voices[1].carrier.scale | genmidi->voices[1].carrier.level;
|
||||
|
||||
// FIXME: Supposed to be computed from instrument data, set just constant for a test
|
||||
inst->delay_on_ms = 5000;
|
||||
inst->delay_off_ms = 5000;
|
||||
|
||||
inst->fb_conn2_C0 = genmidi->voices[1].feedback;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: OP2_To_WOPL
|
||||
//
|
||||
// Converts the WAD's GENMIDI bank into compatible WOPL format which can be
|
||||
// loaded
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::OP2_To_WOPL(const ADLConfig *config)
|
||||
{
|
||||
size_t bank_size;
|
||||
genmidi.clear();
|
||||
|
||||
if (!config->adl_genmidi_set)
|
||||
{
|
||||
return; // No GENMIDI bank presented
|
||||
}
|
||||
|
||||
const GenMidiInstrument *ginst = reinterpret_cast<const GenMidiInstrument *>(config->adl_genmidi_bank);
|
||||
WOPLFile *wopl_bank = WOPL_Init(1, 1);
|
||||
|
||||
wopl_bank->volume_model = (ADLMIDI_VolumeModel_DMX - 1);
|
||||
|
||||
for(size_t i = 0; i < 128; ++i)
|
||||
{
|
||||
wopl_bank->banks_melodic[0].ins[i].inst_flags = WOPL_Ins_IsBlank;
|
||||
wopl_bank->banks_percussive[0].ins[i].inst_flags = WOPL_Ins_IsBlank;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < GENMIDI_NUM_INSTRS; ++i, ++ginst)
|
||||
{
|
||||
gen2ins(ginst, &wopl_bank->banks_melodic->ins[i], false);
|
||||
}
|
||||
|
||||
for (size_t i = GENMIDI_FIST_PERCUSSION; i < GENMIDI_FIST_PERCUSSION + GENMIDI_NUM_PERCUSSION; ++i, ++ginst)
|
||||
{
|
||||
gen2ins(ginst, &wopl_bank->banks_percussive->ins[i], true);
|
||||
}
|
||||
|
||||
bank_size = WOPL_CalculateBankFileSize(wopl_bank, 0);
|
||||
|
||||
genmidi.resize(bank_size);
|
||||
int ret = WOPL_SaveBankToMem(wopl_bank, genmidi.data(), genmidi.size(), 0, 0);
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
genmidi.clear();
|
||||
|
||||
const char *err = "Unknown";
|
||||
switch (ret)
|
||||
{
|
||||
case WOPL_ERR_UNEXPECTED_ENDING:
|
||||
err = "Unexpected buffer ending";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Failed to convert GENMIDI bank to WOPL: %s.\n", err);
|
||||
}
|
||||
|
||||
WOPL_Free(wopl_bank);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int ADLMIDIDevice::OpenRenderer()
|
||||
{
|
||||
adl_rt_resetState(Renderer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: ChangeSettingInt
|
||||
//
|
||||
// Changes an integer setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::ChangeSettingInt(const char *setting, int value)
|
||||
{
|
||||
if (Renderer == nullptr || strncmp(setting, "libadl.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "volumemodel") == 0)
|
||||
{
|
||||
adl_setVolumeRangeModel(Renderer, value);
|
||||
initGain(); // Gain should be recomputed after changing this
|
||||
}
|
||||
else if (strcmp(setting, "chanalloc") == 0)
|
||||
{
|
||||
adl_setChannelAllocMode(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "emulator") == 0)
|
||||
{
|
||||
adl_switchEmulator(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "numchips") == 0)
|
||||
{
|
||||
adl_setNumChips(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "fullpan") == 0)
|
||||
{
|
||||
adl_setSoftPanEnabled(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "runatpcmrate") == 0)
|
||||
{
|
||||
adl_setRunAtPcmRate(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "autoarpeggio") == 0)
|
||||
{
|
||||
adl_setAutoArpeggio(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "usecustombank") == 0)
|
||||
{
|
||||
bool bvalue = (value != 0);
|
||||
bool update = (bvalue != use_custom_bank);
|
||||
use_custom_bank = bvalue;
|
||||
if (update)
|
||||
{
|
||||
if (!LoadCustomBank(nullptr))
|
||||
{
|
||||
adl_setBank(Renderer, last_bank);
|
||||
initGain();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(setting, "usegenmidi") == 0)
|
||||
{
|
||||
bool bvalue = (value != 0);
|
||||
bool update = (bvalue != use_genmidi);
|
||||
use_genmidi = bvalue;
|
||||
if (update && !genmidi.empty())
|
||||
{
|
||||
if (!LoadCustomBank(nullptr))
|
||||
{
|
||||
adl_setBank(Renderer, last_bank);
|
||||
initGain();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(setting, "banknum") == 0)
|
||||
{
|
||||
bool update = (value != last_bank);
|
||||
last_bank = value;
|
||||
if (update)
|
||||
{
|
||||
adl_setBank(Renderer, last_bank);
|
||||
initGain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: ChangeSettingNum
|
||||
//
|
||||
// Changes a numeric setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::ChangeSettingNum(const char *setting, double value)
|
||||
{
|
||||
if (Renderer == nullptr || strncmp(setting, "libadl.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "gain") == 0)
|
||||
{
|
||||
ConfigGainFactor = value;
|
||||
initGain();
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: ChangeSettingString
|
||||
//
|
||||
// Changes a string setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::ChangeSettingString(const char *setting, const char *value)
|
||||
{
|
||||
if (Renderer == nullptr || strncmp(setting, "libadl.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "custombank") == 0)
|
||||
{
|
||||
custom_bank = value;
|
||||
if (use_custom_bank)
|
||||
{
|
||||
if (!LoadCustomBank(nullptr))
|
||||
adl_setBank(Renderer, last_bank);
|
||||
initGain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int chan = status & 0x0F;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case ME_NOTEON:
|
||||
adl_rt_noteOn(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_NOTEOFF:
|
||||
adl_rt_noteOff(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_KEYPRESSURE:
|
||||
adl_rt_noteAfterTouch(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_CONTROLCHANGE:
|
||||
adl_rt_controllerChange(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_PROGRAM:
|
||||
adl_rt_patchChange(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_CHANNELPRESSURE:
|
||||
adl_rt_channelAfterTouch(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_PITCHWHEEL:
|
||||
adl_rt_pitchBendML(Renderer, chan, parm2, parm1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
adl_rt_systemExclusive(Renderer, data, len);
|
||||
}
|
||||
|
||||
static const ADLMIDI_AudioFormat audio_output_format =
|
||||
{
|
||||
ADLMIDI_SampleType_F32,
|
||||
sizeof(float),
|
||||
2 * sizeof(float)
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
ADL_UInt8* left = reinterpret_cast<ADL_UInt8*>(buffer);
|
||||
ADL_UInt8* right = reinterpret_cast<ADL_UInt8*>(buffer + 1);
|
||||
auto result = adl_generateFormat(Renderer, len * 2, left, right, &audio_output_format);
|
||||
for(int i=0; i < result; i++)
|
||||
{
|
||||
buffer[i] *= OutputGainFactor;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ADLMIDIDevice :: initGain
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ADLMIDIDevice::initGain()
|
||||
{
|
||||
if (Renderer == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OutputGainFactor = 3.5f;
|
||||
|
||||
// TODO: Please tune the factor for each volume model to avoid too loud or too silent sounding
|
||||
switch (adl_getVolumeRangeModel(Renderer))
|
||||
{
|
||||
// Louder models
|
||||
case ADLMIDI_VolumeModel_Generic:
|
||||
case ADLMIDI_VolumeModel_9X:
|
||||
case ADLMIDI_VolumeModel_9X_GENERIC_FM:
|
||||
OutputGainFactor = 2.0f;
|
||||
break;
|
||||
// Middle volume models
|
||||
case ADLMIDI_VolumeModel_HMI:
|
||||
case ADLMIDI_VolumeModel_HMI_OLD:
|
||||
OutputGainFactor = 2.5f;
|
||||
break;
|
||||
default:
|
||||
// Quite models
|
||||
case ADLMIDI_VolumeModel_DMX:
|
||||
case ADLMIDI_VolumeModel_DMX_Fixed:
|
||||
case ADLMIDI_VolumeModel_APOGEE:
|
||||
case ADLMIDI_VolumeModel_APOGEE_Fixed:
|
||||
case ADLMIDI_VolumeModel_AIL:
|
||||
OutputGainFactor = 3.5f;
|
||||
break;
|
||||
// Quiter models
|
||||
case ADLMIDI_VolumeModel_NativeOPL3:
|
||||
OutputGainFactor = 3.8f;
|
||||
break;
|
||||
}
|
||||
|
||||
OutputGainFactor *= ConfigGainFactor;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
extern ADLConfig adlConfig;
|
||||
|
||||
MIDIDevice *CreateADLMIDIDevice(const char *Args)
|
||||
{
|
||||
ADLConfig config = adlConfig;
|
||||
|
||||
const char* bank = Args && *Args ? Args : adlConfig.adl_use_custom_bank ? adlConfig.adl_custom_bank.c_str() : nullptr;
|
||||
if (bank && *bank)
|
||||
{
|
||||
if (*bank >= '0' && *bank <= '9')
|
||||
{
|
||||
// Args specify a bank by index.
|
||||
config.adl_bank = (int)strtoll(bank, nullptr, 10);
|
||||
config.adl_use_custom_bank = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* info;
|
||||
if (musicCallbacks.PathForSoundfont)
|
||||
{
|
||||
info = musicCallbacks.PathForSoundfont(bank, SF_WOPL);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = bank;
|
||||
}
|
||||
if (info == nullptr)
|
||||
{
|
||||
config.adl_custom_bank = "";
|
||||
config.adl_use_custom_bank = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.adl_custom_bank = info;
|
||||
config.adl_use_custom_bank = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ADLMIDIDevice(&config);
|
||||
}
|
||||
|
||||
DLL_EXPORT int ZMusic_GetADLBanks(const char* const** pNames)
|
||||
{
|
||||
if (pNames) *pNames = adl_getBankNames();
|
||||
return adl_getBanksCount();
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateADLMIDIDevice(const char* Args)
|
||||
{
|
||||
throw std::runtime_error("ADL device not supported in this configuration");
|
||||
}
|
||||
|
||||
DLL_EXPORT int ZMusic_GetADLBanks(const char* const** pNames)
|
||||
{
|
||||
// The export needs to be there even if non-functional.
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
497
libraries/ZMusic/source/mididevices/music_alsa_mididevice.cpp
Normal file
497
libraries/ZMusic/source/mididevices/music_alsa_mididevice.cpp
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
** Provides an ALSA implementation of a MIDI output device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Randy Heit
|
||||
** Copyright 2020 Petr Mrazek
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#if defined __linux__ && defined HAVE_SYSTEM_MIDI
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
#include "zmusic_internal.h"
|
||||
|
||||
#include "music_alsa_state.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class EventType {
|
||||
Null,
|
||||
Action
|
||||
};
|
||||
|
||||
struct EventState {
|
||||
int ticks = 0;
|
||||
snd_seq_event_t data;
|
||||
int size_of = 0;
|
||||
|
||||
void Clear() {
|
||||
ticks = 0;
|
||||
snd_seq_ev_clear(&data);
|
||||
size_of = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class AlsaMIDIDevice : public MIDIDevice
|
||||
{
|
||||
public:
|
||||
AlsaMIDIDevice(int dev_id);
|
||||
~AlsaMIDIDevice();
|
||||
int Open() override;
|
||||
void Close() override;
|
||||
bool IsOpen() const override;
|
||||
int GetTechnology() const override;
|
||||
int SetTempo(int tempo) override;
|
||||
int SetTimeDiv(int timediv) override;
|
||||
int StreamOut(MidiHeader *data) override;
|
||||
int StreamOutSync(MidiHeader *data) override;
|
||||
int Resume() override;
|
||||
void Stop() override;
|
||||
|
||||
bool FakeVolume() override {
|
||||
// Not sure if we even can control the volume this way with Alsa, so make it fake.
|
||||
return true;
|
||||
};
|
||||
|
||||
bool Pause(bool paused) override;
|
||||
void InitPlayback() override;
|
||||
bool Update() override;
|
||||
void PrecacheInstruments(const uint16_t *instruments, int count) override {}
|
||||
|
||||
bool CanHandleSysex() const override
|
||||
{
|
||||
// Assume we can, let Alsa sort it out. We do not truly have full control.
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendStopEvents();
|
||||
void SetExit(bool exit);
|
||||
bool WaitForExit(std::chrono::microseconds usec, snd_seq_queue_status_t * status);
|
||||
EventType PullEvent(EventState & state);
|
||||
void PumpEvents();
|
||||
|
||||
|
||||
protected:
|
||||
AlsaSequencer &sequencer;
|
||||
|
||||
MidiHeader *Events = nullptr;
|
||||
bool Started = false;
|
||||
uint32_t Position = 0;
|
||||
|
||||
const static int IntendedPortId = 0;
|
||||
bool Connected = false;
|
||||
int PortId = -1;
|
||||
int QueueId = -1;
|
||||
|
||||
int DestinationClientId;
|
||||
int DestinationPortId;
|
||||
int Technology;
|
||||
|
||||
int Tempo = 480000;
|
||||
int TimeDiv = 480;
|
||||
|
||||
std::thread PlayerThread;
|
||||
bool Exit = false;
|
||||
std::mutex ExitLock;
|
||||
std::condition_variable ExitCond;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
AlsaMIDIDevice::AlsaMIDIDevice(int dev_id) : sequencer(AlsaSequencer::Get())
|
||||
{
|
||||
auto & internalDevices = sequencer.GetInternalDevices();
|
||||
auto & device = internalDevices.at(dev_id);
|
||||
DestinationClientId = device.ClientID;
|
||||
DestinationPortId = device.PortNumber;
|
||||
Technology = device.GetDeviceClass();
|
||||
}
|
||||
|
||||
AlsaMIDIDevice::~AlsaMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::Open()
|
||||
{
|
||||
if (!sequencer.IsOpen()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(PortId < 0)
|
||||
{
|
||||
snd_seq_port_info_t *pinfo;
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
snd_seq_port_info_set_port(pinfo, IntendedPortId);
|
||||
snd_seq_port_info_set_port_specified(pinfo, 1);
|
||||
|
||||
snd_seq_port_info_set_name(pinfo, "ZMusic Program Music");
|
||||
|
||||
snd_seq_port_info_set_capability(pinfo, 0);
|
||||
snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
int err = 0;
|
||||
err = snd_seq_create_port(sequencer.handle, pinfo);
|
||||
PortId = IntendedPortId;
|
||||
}
|
||||
|
||||
if (QueueId < 0)
|
||||
{
|
||||
QueueId = snd_seq_alloc_named_queue(sequencer.handle, "ZMusic Program Queue");
|
||||
}
|
||||
|
||||
if (!Connected) {
|
||||
Connected = (snd_seq_connect_to(sequencer.handle, PortId, DestinationClientId, DestinationPortId) == 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::Close()
|
||||
{
|
||||
if(Connected) {
|
||||
snd_seq_disconnect_to(sequencer.handle, PortId, DestinationClientId, DestinationPortId);
|
||||
Connected = false;
|
||||
}
|
||||
if(QueueId >= 0) {
|
||||
snd_seq_free_queue(sequencer.handle, QueueId);
|
||||
QueueId = -1;
|
||||
}
|
||||
if(PortId >= 0) {
|
||||
snd_seq_delete_port(sequencer.handle, PortId);
|
||||
PortId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::IsOpen() const
|
||||
{
|
||||
return Connected;
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::GetTechnology() const
|
||||
{
|
||||
return Technology;
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::SetTempo(int tempo)
|
||||
{
|
||||
Tempo = tempo;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AlsaMIDIDevice::SetTimeDiv(int timediv)
|
||||
{
|
||||
TimeDiv = timediv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EventType AlsaMIDIDevice::PullEvent(EventState & state) {
|
||||
state.Clear();
|
||||
|
||||
if(!Events) {
|
||||
Callback(CallbackData);
|
||||
if(!Events) {
|
||||
return EventType::Null;
|
||||
}
|
||||
}
|
||||
if (Position >= Events->dwBytesRecorded)
|
||||
{
|
||||
Events = Events->lpNext;
|
||||
Position = 0;
|
||||
|
||||
if (Callback != NULL)
|
||||
{
|
||||
Callback(CallbackData);
|
||||
}
|
||||
if(!Events) {
|
||||
return EventType::Null;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t *event = (uint32_t *)(Events->lpData + Position);
|
||||
state.ticks = event[0];
|
||||
|
||||
// Advance to next event.
|
||||
if (event[2] < 0x80000000)
|
||||
{ // Short message
|
||||
state.size_of = 12;
|
||||
}
|
||||
else
|
||||
{ // Long message
|
||||
state.size_of = 12 + ((MEVENT_EVENTPARM(event[2]) + 3) & ~3);
|
||||
}
|
||||
|
||||
if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO) {
|
||||
int tempo = MEVENT_EVENTPARM(event[2]);
|
||||
if(Tempo != tempo) {
|
||||
Tempo = tempo;
|
||||
snd_seq_change_queue_tempo(sequencer.handle, QueueId, Tempo, &state.data);
|
||||
return EventType::Action;
|
||||
}
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG) {
|
||||
// SysEx messages...
|
||||
uint8_t * data = (uint8_t *)&event[3];
|
||||
int len = MEVENT_EVENTPARM(event[2]);
|
||||
if (len > 1 && (data[0] == 0xF0 || data[0] == 0xF7))
|
||||
{
|
||||
snd_seq_ev_set_sysex(&state.data, len, (void *)data);
|
||||
return EventType::Action;
|
||||
}
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == 0) {
|
||||
// Short MIDI event
|
||||
int command = event[2] & 0xF0;
|
||||
int channel = event[2] & 0x0F;
|
||||
int parm1 = (event[2] >> 8) & 0x7f;
|
||||
int parm2 = (event[2] >> 16) & 0x7f;
|
||||
switch (command)
|
||||
{
|
||||
case MIDI_NOTEOFF:
|
||||
snd_seq_ev_set_noteoff(&state.data, channel, parm1, parm2);
|
||||
return EventType::Action;
|
||||
|
||||
case MIDI_NOTEON:
|
||||
snd_seq_ev_set_noteon(&state.data, channel, parm1, parm2);
|
||||
return EventType::Action;
|
||||
|
||||
case MIDI_POLYPRESS:
|
||||
snd_seq_ev_set_keypress(&state.data, channel, parm1, parm2);
|
||||
return EventType::Action;
|
||||
|
||||
case MIDI_CTRLCHANGE:
|
||||
snd_seq_ev_set_controller(&state.data, channel, parm1, parm2);
|
||||
return EventType::Action;
|
||||
|
||||
case MIDI_PRGMCHANGE:
|
||||
snd_seq_ev_set_pgmchange(&state.data, channel, parm1);
|
||||
return EventType::Action;
|
||||
|
||||
case MIDI_CHANPRESS:
|
||||
snd_seq_ev_set_chanpress(&state.data, channel, parm1);
|
||||
return EventType::Action;
|
||||
|
||||
case MIDI_PITCHBEND: {
|
||||
long bend = ((long)parm1 + (long)(parm2 << 7)) - 0x2000;
|
||||
snd_seq_ev_set_pitchbend(&state.data, channel, bend);
|
||||
return EventType::Action;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We didn't really recognize the event, treat it as a NOP
|
||||
state.data.type = SND_SEQ_EVENT_NONE;
|
||||
snd_seq_ev_set_fixed(&state.data);
|
||||
return EventType::Action;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::SetExit(bool exit) {
|
||||
std::unique_lock<std::mutex> lock(ExitLock);
|
||||
if(exit != Exit) {
|
||||
Exit = exit;
|
||||
ExitCond.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::WaitForExit(std::chrono::microseconds usec, snd_seq_queue_status_t * status) {
|
||||
std::unique_lock<std::mutex> lock(ExitLock);
|
||||
if(Exit) {
|
||||
return true;
|
||||
}
|
||||
ExitCond.wait_for(lock, usec);
|
||||
if(Exit) {
|
||||
return true;
|
||||
}
|
||||
snd_seq_get_queue_status(sequencer.handle, QueueId, status);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pumps events from the input to the output in a worker thread.
|
||||
* It tries to keep the amount of events (time-wise) in the ALSA sequencer queue to be between 40 and 80ms by sleeping where necessary.
|
||||
* This means Alsa can play them safely without running out of things to do, and we have good control over the events themselves (volume, pause, etc.).
|
||||
*/
|
||||
void AlsaMIDIDevice::PumpEvents() {
|
||||
const std::chrono::microseconds pump_step(40000);
|
||||
|
||||
// TODO: fill in error handling throughout this.
|
||||
snd_seq_queue_tempo_t *tempo;
|
||||
snd_seq_queue_tempo_alloca(&tempo);
|
||||
snd_seq_queue_tempo_set_tempo(tempo, Tempo);
|
||||
snd_seq_queue_tempo_set_ppq(tempo, TimeDiv);
|
||||
snd_seq_set_queue_tempo(sequencer.handle, QueueId, tempo);
|
||||
|
||||
snd_seq_start_queue(sequencer.handle, QueueId, NULL);
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
|
||||
int buffer_ticks = 0;
|
||||
EventState event;
|
||||
|
||||
snd_seq_queue_status_t *status;
|
||||
snd_seq_queue_status_malloc(&status);
|
||||
|
||||
while (true) {
|
||||
auto type = PullEvent(event);
|
||||
// if we reach the end of events, await our doom at a steady rate while looking for more events
|
||||
if(type == EventType::Null) {
|
||||
if(WaitForExit(pump_step, status)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Figure out if we should sleep (the event is too far in the future for us to care), and for how long
|
||||
int next_event_tick = buffer_ticks + event.ticks;
|
||||
int queue_tick = snd_seq_queue_status_get_tick_time(status);
|
||||
int tick_delta = next_event_tick - queue_tick;
|
||||
auto usecs = std::chrono::microseconds(tick_delta * Tempo / TimeDiv);
|
||||
auto schedule_time = std::max(std::chrono::microseconds(0), usecs - pump_step);
|
||||
if(schedule_time >= pump_step) {
|
||||
if(WaitForExit(schedule_time, status)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (tick_delta < 0) {
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Alsa sequencer underrun: %d ticks!\n", tick_delta);
|
||||
}
|
||||
|
||||
// We found an event worthy of sending to the sequencer
|
||||
snd_seq_ev_set_source(&event.data, PortId);
|
||||
snd_seq_ev_set_subs(&event.data);
|
||||
snd_seq_ev_schedule_tick(&event.data, QueueId, false, buffer_ticks + event.ticks);
|
||||
int result = snd_seq_event_output(sequencer.handle, &event.data);
|
||||
if(result < 0) {
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Alsa sequencer did not accept event: error %d!\n", result);
|
||||
if(WaitForExit(pump_step, status)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
buffer_ticks += event.ticks;
|
||||
Position += event.size_of;
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
}
|
||||
|
||||
snd_seq_queue_status_free(status);
|
||||
snd_seq_drop_output(sequencer.handle);
|
||||
// FIXME: the event source should give use these, but it doesn't.
|
||||
{
|
||||
for (int channel = 0; channel < 16; ++channel)
|
||||
{
|
||||
snd_seq_event_t ev;
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, PortId);
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
snd_seq_ev_schedule_tick(&ev, QueueId, true, 0);
|
||||
snd_seq_ev_set_controller(&ev, channel, MIDI_CTL_ALL_NOTES_OFF, 0);
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
snd_seq_ev_set_controller(&ev, channel, MIDI_CTL_RESET_CONTROLLERS, 0);
|
||||
snd_seq_event_output(sequencer.handle, &ev);
|
||||
}
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
snd_seq_sync_output_queue(sequencer.handle);
|
||||
}
|
||||
snd_seq_stop_queue(sequencer.handle, QueueId, NULL);
|
||||
snd_seq_drain_output(sequencer.handle);
|
||||
}
|
||||
|
||||
|
||||
int AlsaMIDIDevice::Resume()
|
||||
{
|
||||
if(!Connected) {
|
||||
return 1;
|
||||
}
|
||||
SetExit(false);
|
||||
PlayerThread = std::thread(&AlsaMIDIDevice::PumpEvents, this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::InitPlayback()
|
||||
{
|
||||
SetExit(false);
|
||||
}
|
||||
|
||||
void AlsaMIDIDevice::Stop()
|
||||
{
|
||||
SetExit(true);
|
||||
PlayerThread.join();
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::Pause(bool paused)
|
||||
{
|
||||
// TODO: implement
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int AlsaMIDIDevice::StreamOut(MidiHeader *header)
|
||||
{
|
||||
header->lpNext = NULL;
|
||||
if (Events == NULL)
|
||||
{
|
||||
Events = header;
|
||||
Position = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
MidiHeader **p;
|
||||
|
||||
for (p = &Events; *p != NULL; p = &(*p)->lpNext)
|
||||
{ }
|
||||
*p = header;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int AlsaMIDIDevice::StreamOutSync(MidiHeader *header)
|
||||
{
|
||||
return StreamOut(header);
|
||||
}
|
||||
|
||||
bool AlsaMIDIDevice::Update()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
MIDIDevice *CreateAlsaMIDIDevice(int mididevice)
|
||||
{
|
||||
return new AlsaMIDIDevice(mididevice);
|
||||
}
|
||||
#endif
|
||||
169
libraries/ZMusic/source/mididevices/music_alsa_state.cpp
Normal file
169
libraries/ZMusic/source/mididevices/music_alsa_state.cpp
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
** Provides an implementation of an ALSA sequencer wrapper
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2020 Petr Mrazek
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
#include "music_alsa_state.h"
|
||||
|
||||
#if defined __linux__ && defined HAVE_SYSTEM_MIDI
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <sstream>
|
||||
|
||||
EMidiDeviceClass MidiOutDeviceInternal::GetDeviceClass() const
|
||||
{
|
||||
if (type & SND_SEQ_PORT_TYPE_SYNTH)
|
||||
return MIDIDEV_FMSYNTH;
|
||||
if (type & (SND_SEQ_PORT_TYPE_DIRECT_SAMPLE|SND_SEQ_PORT_TYPE_SAMPLE))
|
||||
return MIDIDEV_SYNTH;
|
||||
if (type & (SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION))
|
||||
return MIDIDEV_MIDIPORT;
|
||||
// assume FM synth otherwise
|
||||
return MIDIDEV_FMSYNTH;
|
||||
}
|
||||
|
||||
AlsaSequencer & AlsaSequencer::Get() {
|
||||
static AlsaSequencer sequencer;
|
||||
return sequencer;
|
||||
}
|
||||
|
||||
AlsaSequencer::AlsaSequencer() {
|
||||
Open();
|
||||
}
|
||||
|
||||
AlsaSequencer::~AlsaSequencer() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool AlsaSequencer::Open() {
|
||||
error = snd_seq_open(&handle, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK);
|
||||
if(error) {
|
||||
return false;
|
||||
}
|
||||
error = snd_seq_set_client_name(handle, "ZMusic Program");
|
||||
if(error) {
|
||||
snd_seq_close(handle);
|
||||
handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
OurId = snd_seq_client_id(handle);
|
||||
if (OurId < 0) {
|
||||
error = OurId;
|
||||
OurId = -1;
|
||||
|
||||
snd_seq_close(handle);
|
||||
handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void AlsaSequencer::Close() {
|
||||
if(!handle) {
|
||||
return;
|
||||
}
|
||||
snd_seq_close(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool filter(snd_seq_port_info_t *pinfo)
|
||||
{
|
||||
int capability = snd_seq_port_info_get_capability(pinfo);
|
||||
if(capability & SND_SEQ_PORT_CAP_NO_EXPORT) {
|
||||
return false;
|
||||
}
|
||||
const int writable = (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE);
|
||||
if((capability & writable) != writable) {
|
||||
return false;
|
||||
}
|
||||
// TODO: filter based on type here? maybe?
|
||||
// int type = snd_seq_port_info_get_type(pinfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int AlsaSequencer::EnumerateDevices() {
|
||||
if(!handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
snd_seq_client_info_t *cinfo;
|
||||
snd_seq_port_info_t *pinfo;
|
||||
|
||||
snd_seq_client_info_alloca(&cinfo);
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
int index = 0;
|
||||
|
||||
// enumerate clients
|
||||
snd_seq_client_info_set_client(cinfo, -1);
|
||||
while (snd_seq_query_next_client(handle, cinfo) >= 0) {
|
||||
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
||||
|
||||
int clientID = snd_seq_client_info_get_client(cinfo);
|
||||
|
||||
snd_seq_port_info_set_port(pinfo, -1);
|
||||
// enumerate ports
|
||||
while (snd_seq_query_next_port(handle, pinfo) >= 0) {
|
||||
if (!filter(pinfo)) {
|
||||
continue;
|
||||
}
|
||||
internalDevices.emplace_back();
|
||||
|
||||
auto & itemInternal = internalDevices.back();
|
||||
itemInternal.ID = index++;
|
||||
const char *name = snd_seq_port_info_get_name(pinfo);
|
||||
int portNumber = snd_seq_port_info_get_port(pinfo);
|
||||
if(!name) {
|
||||
std::ostringstream out;
|
||||
out << "MIDI Port " << clientID << ":" << portNumber;
|
||||
itemInternal.Name = out.str();
|
||||
}
|
||||
else {
|
||||
itemInternal.Name = name;
|
||||
}
|
||||
itemInternal.ClientID = clientID;
|
||||
itemInternal.PortNumber = portNumber;
|
||||
itemInternal.type = snd_seq_port_info_get_type(pinfo);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
const std::vector<MidiOutDeviceInternal> & AlsaSequencer::GetInternalDevices()
|
||||
{
|
||||
return internalDevices;
|
||||
}
|
||||
|
||||
#endif
|
||||
80
libraries/ZMusic/source/mididevices/music_alsa_state.h
Normal file
80
libraries/ZMusic/source/mididevices/music_alsa_state.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
** Provides an implementation of an ALSA sequencer wrapper
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Randy Heit
|
||||
** Copyright 2020 Petr Mrazek
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined __linux__ && defined HAVE_SYSTEM_MIDI
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
typedef struct _snd_seq snd_seq_t;
|
||||
|
||||
// FIXME: make not visible from outside
|
||||
struct MidiOutDeviceInternal {
|
||||
std::string Name;
|
||||
int ID = -1;
|
||||
int ClientID = -1;
|
||||
int PortNumber = -1;
|
||||
unsigned int type = 0;
|
||||
EMidiDeviceClass GetDeviceClass() const;
|
||||
};
|
||||
|
||||
// NOTE: the sequencer state is shared between actually playing MIDI music and device enumeration, therefore we keep it around.
|
||||
class AlsaSequencer {
|
||||
private:
|
||||
AlsaSequencer();
|
||||
~AlsaSequencer();
|
||||
|
||||
public:
|
||||
static AlsaSequencer &Get();
|
||||
bool Open();
|
||||
void Close();
|
||||
bool IsOpen() const {
|
||||
return nullptr != handle;
|
||||
}
|
||||
|
||||
int EnumerateDevices();
|
||||
const std::vector<MidiOutDeviceInternal> &GetInternalDevices();
|
||||
|
||||
snd_seq_t *handle = nullptr;
|
||||
|
||||
int OurId = -1;
|
||||
int error = -1;
|
||||
|
||||
private:
|
||||
std::vector<MidiOutDeviceInternal> internalDevices;
|
||||
};
|
||||
|
||||
#endif
|
||||
182
libraries/ZMusic/source/mididevices/music_base_mididevice.cpp
Normal file
182
libraries/ZMusic/source/mididevices/music_base_mididevice.cpp
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
** music_midistream.cpp
|
||||
** Implements base class for MIDI and MUS streaming.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
|
||||
#include "mididevice.h"
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice stubs.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDIDevice::~MIDIDevice()
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: PrecacheInstruments
|
||||
//
|
||||
// The MIDIStreamer calls this method between device open and the first
|
||||
// buffered stream with a list of instruments known to be used by the song.
|
||||
// If the device can benefit from preloading the instruments, it can do so
|
||||
// now.
|
||||
//
|
||||
// Each entry is packed as follows:
|
||||
// Bits 0- 6: Instrument number
|
||||
// Bits 7-13: Bank number
|
||||
// Bit 14: Select drum set if 1, tone bank if 0
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: PrepareHeader
|
||||
//
|
||||
// Wrapper for MCI's midiOutPrepareHeader.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MIDIDevice::PrepareHeader(MidiHeader *header)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: UnprepareHeader
|
||||
//
|
||||
// Wrapper for MCI's midiOutUnprepareHeader.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MIDIDevice::UnprepareHeader(MidiHeader *header)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: FakeVolume
|
||||
//
|
||||
// Since most implementations render as a normal stream, their volume is
|
||||
// controlled through the GSnd interface, not here.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MIDIDevice::FakeVolume()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIDevice::InitPlayback()
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MIDIDevice::Update()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: ChangeSettingInt
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIDevice::ChangeSettingInt(const char *setting, int value)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: ChangeSettingNum
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIDevice::ChangeSettingNum(const char *setting, double value)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: ChangeSettingString
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIDevice::ChangeSettingString(const char *setting, const char *value)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string MIDIDevice::GetStats()
|
||||
{
|
||||
return "This MIDI device does not have any stats.";
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIDevice :: GetStreamInfoEx
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SoundStreamInfoEx MIDIDevice::GetStreamInfoEx() const
|
||||
{
|
||||
return {}; // i.e. do not use streaming.
|
||||
}
|
||||
|
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
** music_fluidsynth_mididevice.cpp
|
||||
** Provides access to FluidSynth as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2010 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
#include "loader/i_module.h"
|
||||
|
||||
// FluidSynth implementation of a MIDI device -------------------------------
|
||||
|
||||
FluidConfig fluidConfig;
|
||||
|
||||
#include "../thirdparty/fluidsynth/include/fluidsynth.h"
|
||||
|
||||
class FluidSynthMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
public:
|
||||
FluidSynthMIDIDevice(int samplerate, std::vector<std::string> &config);
|
||||
~FluidSynthMIDIDevice();
|
||||
|
||||
int OpenRenderer() override;
|
||||
std::string GetStats() override;
|
||||
void ChangeSettingInt(const char *setting, int value) override;
|
||||
void ChangeSettingNum(const char *setting, double value) override;
|
||||
void ChangeSettingString(const char *setting, const char *value) override;
|
||||
int GetDeviceType() const override { return MDEV_FLUIDSYNTH; }
|
||||
|
||||
protected:
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
int LoadPatchSets(const std::vector<std::string>& config);
|
||||
|
||||
fluid_settings_t *FluidSettings;
|
||||
fluid_synth_t *FluidSynth;
|
||||
|
||||
// Possible results returned by fluid_settings_...() functions
|
||||
// Initial values are for FluidSynth 2.x
|
||||
int FluidSettingsResultOk = FLUID_OK;
|
||||
int FluidSettingsResultFailed = FLUID_FAILED;
|
||||
|
||||
};
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
const char *BaseFileSearch(const char *file, const char *ext, bool lookfirstinprogdir = false);
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FluidSynthMIDIDevice::FluidSynthMIDIDevice(int samplerate, std::vector<std::string> &config)
|
||||
: SoftSynthMIDIDevice(samplerate <= 0? fluidConfig.fluid_samplerate : samplerate, 22050, 96000)
|
||||
{
|
||||
StreamBlockSize = 4;
|
||||
|
||||
FluidSynth = NULL;
|
||||
FluidSettings = NULL;
|
||||
|
||||
FluidSettings = new_fluid_settings();
|
||||
if (FluidSettings == NULL)
|
||||
{
|
||||
throw std::runtime_error("Failed to create FluidSettings.\n");
|
||||
}
|
||||
fluid_settings_setint(FluidSettings, "synth.dynamic-sample-loading", 1);
|
||||
fluid_settings_setnum(FluidSettings, "synth.sample-rate", SampleRate);
|
||||
fluid_settings_setnum(FluidSettings, "synth.gain", fluidConfig.fluid_gain);
|
||||
fluid_settings_setint(FluidSettings, "synth.reverb.active", fluidConfig.fluid_reverb);
|
||||
fluid_settings_setint(FluidSettings, "synth.chorus.active", fluidConfig.fluid_chorus);
|
||||
fluid_settings_setint(FluidSettings, "synth.polyphony", fluidConfig.fluid_voices);
|
||||
fluid_settings_setint(FluidSettings, "synth.cpu-cores", fluidConfig.fluid_threads);
|
||||
FluidSynth = new_fluid_synth(FluidSettings);
|
||||
if (FluidSynth == NULL)
|
||||
{
|
||||
delete_fluid_settings(FluidSettings);
|
||||
throw std::runtime_error("Failed to create FluidSynth.\n");
|
||||
}
|
||||
fluid_synth_set_interp_method(FluidSynth, -1, fluidConfig.fluid_interp);
|
||||
fluid_synth_set_reverb(FluidSynth, fluidConfig.fluid_reverb_roomsize, fluidConfig.fluid_reverb_damping,
|
||||
fluidConfig.fluid_reverb_width, fluidConfig.fluid_reverb_level);
|
||||
fluid_synth_set_chorus(FluidSynth, fluidConfig.fluid_chorus_voices, fluidConfig.fluid_chorus_level,
|
||||
fluidConfig.fluid_chorus_speed, fluidConfig.fluid_chorus_depth, fluidConfig.fluid_chorus_type);
|
||||
|
||||
// try loading a patch set that got specified with $mididevice.
|
||||
|
||||
if (LoadPatchSets(config))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
delete_fluid_synth(FluidSynth);
|
||||
delete_fluid_settings(FluidSettings);
|
||||
FluidSynth = nullptr;
|
||||
FluidSettings = nullptr;
|
||||
throw std::runtime_error("Failed to load any MIDI patches.\n");
|
||||
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FluidSynthMIDIDevice::~FluidSynthMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (FluidSynth != NULL)
|
||||
{
|
||||
delete_fluid_synth(FluidSynth);
|
||||
}
|
||||
if (FluidSettings != NULL)
|
||||
{
|
||||
delete_fluid_settings(FluidSettings);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int FluidSynthMIDIDevice::OpenRenderer()
|
||||
{
|
||||
// Send MIDI system reset command (big red 'panic' button), turns off notes, resets controllers and restores initial basic channel configuration.
|
||||
//fluid_synth_system_reset(FluidSynth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: HandleEvent
|
||||
//
|
||||
// Translates a MIDI event into FluidSynth calls.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FluidSynthMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int channel = status & 0x0F;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case MIDI_NOTEOFF:
|
||||
fluid_synth_noteoff(FluidSynth, channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_NOTEON:
|
||||
fluid_synth_noteon(FluidSynth, channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_POLYPRESS:
|
||||
break;
|
||||
|
||||
case MIDI_CTRLCHANGE:
|
||||
fluid_synth_cc(FluidSynth, channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_PRGMCHANGE:
|
||||
fluid_synth_program_change(FluidSynth, channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_CHANPRESS:
|
||||
fluid_synth_channel_pressure(FluidSynth, channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_PITCHBEND:
|
||||
fluid_synth_pitch_bend(FluidSynth, channel, (parm1 & 0x7f) | ((parm2 & 0x7f) << 7));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
// Handle SysEx messages.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FluidSynthMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
constexpr int excludedByteCount = 2; // 0xF0 (first byte) and 0xF7 (last byte) are not given to FluidSynth.
|
||||
if (len > excludedByteCount && data[0] == 0xF0 && data[len - 1] == 0xF7)
|
||||
{
|
||||
fluid_synth_sysex(FluidSynth, (const char *)data + 1, len - excludedByteCount, NULL, NULL, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FluidSynthMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
fluid_synth_write_float(FluidSynth, len,
|
||||
buffer, 0, 2,
|
||||
buffer, 1, 2);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: LoadPatchSets
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int FluidSynthMIDIDevice::LoadPatchSets(const std::vector<std::string> &config)
|
||||
{
|
||||
int count = 0;
|
||||
for (auto& file : config)
|
||||
{
|
||||
if (FLUID_FAILED != fluid_synth_sfload(FluidSynth, file.c_str(), count == 0))
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_DEBUG, "Loaded patch set %s.\n", file.c_str());
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Failed to load patch set %s.\n", file.c_str());
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: ChangeSettingInt
|
||||
//
|
||||
// Changes an integer setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FluidSynthMIDIDevice::ChangeSettingInt(const char *setting, int value)
|
||||
{
|
||||
if (FluidSynth == nullptr || FluidSettings == nullptr || strncmp(setting, "fluidsynth.", 11))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 11;
|
||||
|
||||
if (strcmp(setting, "synth.interpolation") == 0)
|
||||
{
|
||||
if (FLUID_OK != fluid_synth_set_interp_method(FluidSynth, -1, value))
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Setting interpolation method %d failed.\n", value);
|
||||
}
|
||||
}
|
||||
else if (strcmp(setting, "synth.polyphony") == 0)
|
||||
{
|
||||
if (FLUID_OK != fluid_synth_set_polyphony(FluidSynth, value))
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Setting polyphony to %d failed.\n", value);
|
||||
}
|
||||
}
|
||||
else if (FluidSettingsResultFailed == fluid_settings_setint(FluidSettings, setting, value))
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Failed to set %s to %d.\n", setting, value);
|
||||
}
|
||||
// fluid_settings_setint succeeded; update these settings in the running synth, too
|
||||
else if (strcmp(setting, "synth.reverb.active") == 0)
|
||||
{
|
||||
fluid_synth_set_reverb_on(FluidSynth, value);
|
||||
}
|
||||
else if (strcmp(setting, "synth.chorus.active") == 0)
|
||||
{
|
||||
fluid_synth_set_chorus_on(FluidSynth, value);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: ChangeSettingNum
|
||||
//
|
||||
// Changes a numeric setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FluidSynthMIDIDevice::ChangeSettingNum(const char *setting, double value)
|
||||
{
|
||||
if (FluidSynth == nullptr || FluidSettings == nullptr || strncmp(setting, "fluidsynth.", 11))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 11;
|
||||
|
||||
if (strcmp(setting, "z.reverb") == 0)
|
||||
{
|
||||
fluid_synth_set_reverb(FluidSynth, fluidConfig.fluid_reverb_roomsize, fluidConfig.fluid_reverb_damping, fluidConfig.fluid_reverb_width, fluidConfig.fluid_reverb_level);
|
||||
}
|
||||
else if (strcmp(setting, "z.chorus") == 0)
|
||||
{
|
||||
fluid_synth_set_chorus(FluidSynth, fluidConfig.fluid_chorus_voices, fluidConfig.fluid_chorus_level, fluidConfig.fluid_chorus_speed, fluidConfig.fluid_chorus_depth, fluidConfig.fluid_chorus_type);
|
||||
}
|
||||
else if (FluidSettingsResultFailed == fluid_settings_setnum(FluidSettings, setting, value))
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Failed to set %s to %g.\n", setting, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: ChangeSettingString
|
||||
//
|
||||
// Changes a string setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FluidSynthMIDIDevice::ChangeSettingString(const char *setting, const char *value)
|
||||
{
|
||||
if (FluidSynth == nullptr || FluidSettings == nullptr || strncmp(setting, "fluidsynth.", 11))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 11;
|
||||
|
||||
if (FluidSettingsResultFailed == fluid_settings_setstr(FluidSettings, setting, value))
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Failed to set %s to %s.\n", setting, value);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FluidSynthMIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string FluidSynthMIDIDevice::GetStats()
|
||||
{
|
||||
if (FluidSynth == NULL || FluidSettings == NULL)
|
||||
{
|
||||
return "FluidSynth is invalid";
|
||||
}
|
||||
|
||||
int polyphony = fluid_synth_get_polyphony(FluidSynth);
|
||||
int voices = fluid_synth_get_active_voice_count(FluidSynth);
|
||||
double load = fluid_synth_get_cpu_load(FluidSynth);
|
||||
int chorus, reverb, maxpoly;
|
||||
fluid_settings_getint(FluidSettings, "synth.chorus.active", &chorus);
|
||||
fluid_settings_getint(FluidSettings, "synth.reverb.active", &reverb);
|
||||
fluid_settings_getint(FluidSettings, "synth.polyphony", &maxpoly);
|
||||
|
||||
char out[100];
|
||||
snprintf(out, 100,"Voices: %3d/%3d(%3d) %6.2f%% CPU Reverb: %3s Chorus: %3s",
|
||||
voices, polyphony, maxpoly, load, reverb ? "yes" : "no", chorus ? "yes" : "no");
|
||||
return out;
|
||||
}
|
||||
|
||||
//
|
||||
// sndfile
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
// do this without including windows.h for this one single prototype
|
||||
extern "C" unsigned __stdcall GetSystemDirectoryA(char* lpBuffer, unsigned uSize);
|
||||
#endif // _WIN32
|
||||
|
||||
void Fluid_SetupConfig(const char* patches, std::vector<std::string> &patch_paths, bool systemfallback)
|
||||
{
|
||||
if (*patches == 0) patches = fluidConfig.fluid_patchset.c_str();
|
||||
|
||||
//Resolve the paths here, the renderer will only get a final list of file names.
|
||||
|
||||
if (musicCallbacks.PathForSoundfont)
|
||||
{
|
||||
auto info = musicCallbacks.PathForSoundfont(patches, SF_SF2);
|
||||
if (info) patches = info;
|
||||
}
|
||||
|
||||
int count;
|
||||
char* wpatches = strdup(patches);
|
||||
char* tok;
|
||||
#ifdef _WIN32
|
||||
const char* const delim = ";";
|
||||
#else
|
||||
const char* const delim = ":";
|
||||
#endif
|
||||
|
||||
if (wpatches != NULL)
|
||||
{
|
||||
tok = strtok(wpatches, delim);
|
||||
count = 0;
|
||||
while (tok != NULL)
|
||||
{
|
||||
std::string path;
|
||||
#ifdef _WIN32
|
||||
// If the path does not contain any path separators, automatically
|
||||
// prepend $PROGDIR to the path.
|
||||
if (strcspn(tok, ":/\\") == strlen(tok))
|
||||
{
|
||||
path = FModule_GetProgDir() + "/" + tok;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
path = tok;
|
||||
}
|
||||
if (musicCallbacks.NicePath)
|
||||
path = musicCallbacks.NicePath(path.c_str());
|
||||
|
||||
if (MusicIO::fileExists(path.c_str()))
|
||||
{
|
||||
patch_paths.push_back(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
ZMusic_Printf(ZMUSIC_MSG_ERROR, "Could not find patch set %s.\n", tok);
|
||||
}
|
||||
tok = strtok(NULL, delim);
|
||||
}
|
||||
free(wpatches);
|
||||
if (patch_paths.size() > 0) return;
|
||||
}
|
||||
|
||||
if (systemfallback)
|
||||
{
|
||||
// The following will only be used if no soundfont at all is provided, i.e. even the standard one coming with GZDoom is missing.
|
||||
#ifdef __unix__
|
||||
// This is the standard location on Ubuntu.
|
||||
Fluid_SetupConfig("/usr/share/sounds/sf2/FluidR3_GS.sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2", patch_paths, false);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
// On Windows, look for the 4 megabyte patch set installed by Creative's drivers as a default.
|
||||
char sysdir[260 + sizeof("\\CT4MGM.SF2")];
|
||||
uint32_t filepart;
|
||||
if (0 != (filepart = GetSystemDirectoryA(sysdir, 260)))
|
||||
{
|
||||
strcat(sysdir, "\\CT4MGM.SF2");
|
||||
if (MusicIO::fileExists(sysdir))
|
||||
{
|
||||
patch_paths.push_back(sysdir);
|
||||
return;
|
||||
}
|
||||
// Try again with CT2MGM.SF2
|
||||
sysdir[filepart + 3] = '2';
|
||||
if (MusicIO::fileExists(sysdir))
|
||||
{
|
||||
patch_paths.push_back(sysdir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDIDevice *CreateFluidSynthMIDIDevice(int samplerate, const char *Args)
|
||||
{
|
||||
std::vector<std::string> fluid_patchset;
|
||||
|
||||
Fluid_SetupConfig(Args, fluid_patchset, true);
|
||||
return new FluidSynthMIDIDevice(samplerate, fluid_patchset);
|
||||
}
|
||||
369
libraries/ZMusic/source/mididevices/music_opl_mididevice.cpp
Normal file
369
libraries/ZMusic/source/mididevices/music_opl_mididevice.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
** music_opl_mididevice.cpp
|
||||
** Provides an emulated OPL implementation of a MIDI output device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <stdexcept>
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
|
||||
#ifdef HAVE_OPL
|
||||
#include "oplsynth/opl.h"
|
||||
#include "oplsynth/opl_mus_player.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
OPLConfig oplConfig;
|
||||
|
||||
// OPL implementation of a MIDI output device -------------------------------
|
||||
|
||||
class OPLMIDIDevice : public SoftSynthMIDIDevice, protected OPLmusicBlock
|
||||
{
|
||||
float OutputGainFactor;
|
||||
public:
|
||||
OPLMIDIDevice(int core);
|
||||
int OpenRenderer() override;
|
||||
void Close() override;
|
||||
int GetTechnology() const override;
|
||||
std::string GetStats() override;
|
||||
|
||||
protected:
|
||||
void CalcTickRate() override;
|
||||
int PlayTick() override;
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
bool ServiceStream(void *buff, int numbytes) override;
|
||||
int GetDeviceType() const override { return MDEV_OPL; }
|
||||
void ChangeSettingNum(const char *setting, double value) override;
|
||||
};
|
||||
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice Contructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPLMIDIDevice::OPLMIDIDevice(int core)
|
||||
: SoftSynthMIDIDevice((int)OPL_SAMPLE_RATE), OPLmusicBlock(core, oplConfig.numchips)
|
||||
{
|
||||
FullPan = oplConfig.fullpan;
|
||||
memcpy(OPLinstruments, oplConfig.OPLinstruments, sizeof(OPLinstruments));
|
||||
OutputGainFactor = oplConfig.gain;
|
||||
StreamBlockSize = 14;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPLMIDIDevice::OpenRenderer()
|
||||
{
|
||||
if (io == NULL || 0 == (NumChips = io->Init(currentCore, NumChips, FullPan, true)))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
isMono = !FullPan && !io->IsOPL3;
|
||||
stopAllVoices();
|
||||
resetAllControllers(100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: Close
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::Close()
|
||||
{
|
||||
SoftSynthMIDIDevice::Close();
|
||||
io->Reset();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: GetTechnology
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPLMIDIDevice::GetTechnology() const
|
||||
{
|
||||
return MIDIDEV_FMSYNTH;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: CalcTickRate
|
||||
//
|
||||
// Tempo is the number of microseconds per quarter note.
|
||||
// Division is the number of ticks per quarter note.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::CalcTickRate()
|
||||
{
|
||||
SoftSynthMIDIDevice::CalcTickRate();
|
||||
io->SetClockRate(OPLmusicBlock::SamplesPerTick = SoftSynthMIDIDevice::SamplesPerTick);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: PlayTick
|
||||
//
|
||||
// We derive from two base classes that both define PlayTick(), so we need
|
||||
// to be unambiguous about which one to use.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPLMIDIDevice::PlayTick()
|
||||
{
|
||||
return SoftSynthMIDIDevice::PlayTick();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: HandleEvent
|
||||
//
|
||||
// Processes a normal MIDI event.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int channel = status & 0x0F;
|
||||
|
||||
// Swap voices 9 and 15, because their roles are reversed
|
||||
// in MUS and MIDI formats.
|
||||
if (channel == 9)
|
||||
{
|
||||
channel = 15;
|
||||
}
|
||||
else if (channel == 15)
|
||||
{
|
||||
channel = 9;
|
||||
}
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case MIDI_NOTEOFF:
|
||||
playingcount--;
|
||||
noteOff(channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_NOTEON:
|
||||
playingcount++;
|
||||
noteOn(channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_POLYPRESS:
|
||||
//DEBUGOUT("Unhandled note aftertouch: Channel %d, note %d, value %d\n", channel, parm1, parm2);
|
||||
break;
|
||||
|
||||
case MIDI_CTRLCHANGE:
|
||||
switch (parm1)
|
||||
{
|
||||
// some controllers here get passed on but are not handled by the player.
|
||||
//case 0: changeBank(channel, parm2); break;
|
||||
case 1: changeModulation(channel, parm2); break;
|
||||
case 6: changeExtended(channel, ctrlDataEntryHi, parm2); break;
|
||||
case 7: changeVolume(channel, parm2, false); break;
|
||||
case 10: changePanning(channel, parm2); break;
|
||||
case 11: changeVolume(channel, parm2, true); break;
|
||||
case 38: changeExtended(channel, ctrlDataEntryLo, parm2); break;
|
||||
case 64: changeSustain(channel, parm2); break;
|
||||
//case 67: changeSoftPedal(channel, parm2); break;
|
||||
//case 91: changeReverb(channel, parm2); break;
|
||||
//case 93: changeChorus(channel, parm2); break;
|
||||
case 98: changeExtended(channel, ctrlNRPNLo, parm2); break;
|
||||
case 99: changeExtended(channel, ctrlNRPNHi, parm2); break;
|
||||
case 100: changeExtended(channel, ctrlRPNLo, parm2); break;
|
||||
case 101: changeExtended(channel, ctrlRPNHi, parm2); break;
|
||||
case 120: allNotesOff(channel, parm2); break;
|
||||
case 121: resetControllers(channel, 100); break;
|
||||
case 123: notesOff(channel, parm2); break;
|
||||
//case 126: changeMono(channel, parm2); break;
|
||||
//case 127: changePoly(channel, parm2); break;
|
||||
default:
|
||||
//DEBUGOUT("Unhandled controller: Channel %d, controller %d, value %d\n", channel, parm1, parm2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIDI_PRGMCHANGE:
|
||||
programChange(channel, parm1);
|
||||
break;
|
||||
|
||||
case MIDI_CHANPRESS:
|
||||
//DEBUGOUT("Unhandled channel aftertouch: Channel %d, value %d\n", channel, parm1, 0);
|
||||
break;
|
||||
|
||||
case MIDI_PITCHBEND:
|
||||
changePitch(channel, parm1, parm2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: ComputeOutput
|
||||
//
|
||||
// We override ServiceStream, so this function is never actually called.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: ServiceStream
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool OPLMIDIDevice::ServiceStream(void *buff, int numbytes)
|
||||
{
|
||||
float *samples = (float *)buff;
|
||||
bool ret = OPLmusicBlock::ServiceStream(buff, numbytes);
|
||||
int numsamples = numbytes / sizeof(float);
|
||||
for(int i=0; i < numsamples; i++)
|
||||
{
|
||||
samples[i] *= OutputGainFactor;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: ChangeSettingNum
|
||||
//
|
||||
// Changes a numeric setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMIDIDevice::ChangeSettingNum(const char *setting, double value)
|
||||
{
|
||||
if (strncmp(setting, "oplemu.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "gain") == 0)
|
||||
{
|
||||
OutputGainFactor = value;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPLMIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string OPLMIDIDevice::GetStats()
|
||||
{
|
||||
std::string out;
|
||||
for (uint32_t i = 0; i < io->NumChannels; ++i)
|
||||
{
|
||||
char star = '*';
|
||||
if (voices[i].index == ~0u)
|
||||
{
|
||||
star = '/';
|
||||
}
|
||||
else if (voices[i].sustained)
|
||||
{
|
||||
star = '-';
|
||||
}
|
||||
else if (voices[i].current_instr_voice == &voices[i].current_instr->voices[1])
|
||||
{
|
||||
star = '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
star = '+';
|
||||
}
|
||||
out += star;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
MIDIDevice* CreateOplMIDIDevice(const char *Args)
|
||||
{
|
||||
if (!oplConfig.genmidiset) throw std::runtime_error("Cannot play OPL without GENMIDI data");
|
||||
int core = oplConfig.core;
|
||||
if (Args != NULL && *Args >= '0' && *Args < '4') core = *Args - '0';
|
||||
return new OPLMIDIDevice(core);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateOplMIDIDevice(const char* Args)
|
||||
{
|
||||
throw std::runtime_error("OPL device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
427
libraries/ZMusic/source/mididevices/music_opnmidi_mididevice.cpp
Normal file
427
libraries/ZMusic/source/mididevices/music_opnmidi_mididevice.cpp
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
** music_opnmidi_mididevice.cpp
|
||||
** Provides access to libOPNMIDI as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <stdexcept>
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_OPN
|
||||
#include "opnmidi.h"
|
||||
|
||||
OpnConfig opnConfig;
|
||||
|
||||
class OPNMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
struct OPN2_MIDIPlayer *Renderer;
|
||||
float OutputGainFactor;
|
||||
std::vector<uint8_t> default_bank;
|
||||
std::string custom_bank;
|
||||
bool use_custom_bank;
|
||||
|
||||
public:
|
||||
OPNMIDIDevice(const OpnConfig *config);
|
||||
~OPNMIDIDevice();
|
||||
|
||||
int OpenRenderer() override;
|
||||
int GetDeviceType() const override { return MDEV_OPN; }
|
||||
void ChangeSettingInt(const char *setting, int value) override;
|
||||
void ChangeSettingNum(const char *setting, double value) override;
|
||||
void ChangeSettingString(const char *setting, const char *value) override;
|
||||
|
||||
protected:
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
|
||||
private:
|
||||
int LoadCustomBank(const OpnConfig *config);
|
||||
void LoadDefaultBank();
|
||||
};
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
ME_NOTEOFF = 0x80,
|
||||
ME_NOTEON = 0x90,
|
||||
ME_KEYPRESSURE = 0xA0,
|
||||
ME_CONTROLCHANGE = 0xB0,
|
||||
ME_PROGRAM = 0xC0,
|
||||
ME_CHANNELPRESSURE = 0xD0,
|
||||
ME_PITCHWHEEL = 0xE0
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
#include "data/xg.h"
|
||||
|
||||
OPNMIDIDevice::OPNMIDIDevice(const OpnConfig *config)
|
||||
:SoftSynthMIDIDevice(44100)
|
||||
{
|
||||
Renderer = opn2_init(44100); // todo: make it configurable
|
||||
OutputGainFactor = 4.0f;
|
||||
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
default_bank = config->default_bank;
|
||||
|
||||
if (!LoadCustomBank(config))
|
||||
LoadDefaultBank();
|
||||
|
||||
OutputGainFactor *= config->opn_gain;
|
||||
|
||||
opn2_switchEmulator(Renderer, (int)config->opn_emulator_id);
|
||||
opn2_setRunAtPcmRate(Renderer, (int)config->opn_run_at_pcm_rate);
|
||||
opn2_setNumChips(Renderer, config->opn_chips_count);
|
||||
opn2_setVolumeRangeModel(Renderer, config->opn_volume_model);
|
||||
opn2_setChannelAllocMode(Renderer, config->opn_chan_alloc);
|
||||
opn2_setSoftPanEnabled(Renderer, (int)config->opn_fullpan);
|
||||
opn2_setAutoArpeggio(Renderer, (int)config->opn_auto_arpeggio);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Unable to create OPN renderer.");
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPNMIDIDevice::~OPNMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
opn2_close(Renderer);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: LoadCustomBank
|
||||
//
|
||||
// Loads a custom WOPN bank for libOPNMIDI. Returns 1 when bank has been
|
||||
// loaded, otherwise, returns 0 when custom banks are disabled or failed
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
||||
int OPNMIDIDevice::LoadCustomBank(const OpnConfig *config)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
custom_bank = config->opn_custom_bank;
|
||||
use_custom_bank = config->opn_use_custom_bank;
|
||||
}
|
||||
|
||||
const char *bankfile = custom_bank.c_str();
|
||||
if(!use_custom_bank)
|
||||
return 0;
|
||||
if(!*bankfile)
|
||||
return 0;
|
||||
return (opn2_openBankFile(Renderer, bankfile) == 0);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: LoadDefaultBank
|
||||
//
|
||||
// Loads default bank for libOPNMIDI
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::LoadDefaultBank()
|
||||
{
|
||||
if (Renderer == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(default_bank.size() == 0)
|
||||
{
|
||||
opn2_openBankData(Renderer, xg_default, sizeof(xg_default));
|
||||
}
|
||||
else opn2_openBankData(Renderer, default_bank.data(), (long)default_bank.size());
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int OPNMIDIDevice::OpenRenderer()
|
||||
{
|
||||
opn2_rt_resetState(Renderer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: ChangeSettingInt
|
||||
//
|
||||
// Changes an integer setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::ChangeSettingInt(const char *setting, int value)
|
||||
{
|
||||
if (Renderer == nullptr || strncmp(setting, "libopn.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "volumemodel") == 0)
|
||||
{
|
||||
opn2_setVolumeRangeModel(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "chanalloc") == 0)
|
||||
{
|
||||
opn2_setChannelAllocMode(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "emulator") == 0)
|
||||
{
|
||||
opn2_switchEmulator(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "numchips") == 0)
|
||||
{
|
||||
opn2_setNumChips(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "fullpan") == 0)
|
||||
{
|
||||
opn2_setSoftPanEnabled(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "runatpcmrate") == 0)
|
||||
{
|
||||
opn2_setRunAtPcmRate(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "autoarpeggio") == 0)
|
||||
{
|
||||
opn2_setAutoArpeggio(Renderer, value);
|
||||
}
|
||||
else if (strcmp(setting, "usecustombank") == 0)
|
||||
{
|
||||
bool bvalue = (value != 0);
|
||||
bool update = (bvalue != use_custom_bank);
|
||||
use_custom_bank = bvalue;
|
||||
if (update)
|
||||
{
|
||||
if (!LoadCustomBank(nullptr))
|
||||
LoadDefaultBank();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: ChangeSettingNum
|
||||
//
|
||||
// Changes a numeric setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::ChangeSettingNum(const char *setting, double value)
|
||||
{
|
||||
if (Renderer == nullptr || strncmp(setting, "libopn.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "gain") == 0)
|
||||
{
|
||||
OutputGainFactor = 4.0f * value;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: ChangeSettingString
|
||||
//
|
||||
// Changes a string setting.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::ChangeSettingString(const char *setting, const char *value)
|
||||
{
|
||||
if (Renderer == nullptr || strncmp(setting, "libopn.", 7))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setting += 7;
|
||||
|
||||
if (strcmp(setting, "custombank") == 0)
|
||||
{
|
||||
custom_bank = value;
|
||||
if (use_custom_bank)
|
||||
{
|
||||
if (!LoadCustomBank(nullptr))
|
||||
LoadDefaultBank();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
int command = status & 0xF0;
|
||||
int chan = status & 0x0F;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case ME_NOTEON:
|
||||
opn2_rt_noteOn(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_NOTEOFF:
|
||||
opn2_rt_noteOff(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_KEYPRESSURE:
|
||||
opn2_rt_noteAfterTouch(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_CONTROLCHANGE:
|
||||
opn2_rt_controllerChange(Renderer, chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_PROGRAM:
|
||||
opn2_rt_patchChange(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_CHANNELPRESSURE:
|
||||
opn2_rt_channelAfterTouch(Renderer, chan, parm1);
|
||||
break;
|
||||
|
||||
case ME_PITCHWHEEL:
|
||||
opn2_rt_pitchBendML(Renderer, chan, parm2, parm1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
opn2_rt_systemExclusive(Renderer, data, len);
|
||||
}
|
||||
|
||||
static const OPNMIDI_AudioFormat audio_output_format =
|
||||
{
|
||||
OPNMIDI_SampleType_F32,
|
||||
sizeof(float),
|
||||
2 * sizeof(float)
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPNMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPNMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
OPN2_UInt8* left = reinterpret_cast<OPN2_UInt8*>(buffer);
|
||||
OPN2_UInt8* right = reinterpret_cast<OPN2_UInt8*>(buffer + 1);
|
||||
auto result = opn2_generateFormat(Renderer, len * 2, left, right, &audio_output_format);
|
||||
for(int i=0; i < result; i++)
|
||||
{
|
||||
buffer[i] *= OutputGainFactor;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDIDevice *CreateOPNMIDIDevice(const char *Args)
|
||||
{
|
||||
OpnConfig config = opnConfig;
|
||||
|
||||
const char* bank = Args && *Args ? Args : opnConfig.opn_use_custom_bank ? opnConfig.opn_custom_bank.c_str() : nullptr;
|
||||
if (bank && *bank)
|
||||
{
|
||||
const char* info;
|
||||
if (musicCallbacks.PathForSoundfont)
|
||||
{
|
||||
info = musicCallbacks.PathForSoundfont(bank, SF_WOPN);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = bank;
|
||||
}
|
||||
|
||||
if(info == nullptr)
|
||||
{
|
||||
config.opn_custom_bank = "";
|
||||
config.opn_use_custom_bank = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.opn_custom_bank = info;
|
||||
config.opn_use_custom_bank = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new OPNMIDIDevice(&config);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateOPNMIDIDevice(const char* Args)
|
||||
{
|
||||
throw std::runtime_error("OPN device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
** music_softsynth_mididevice.cpp
|
||||
** Common base class for software synthesis MIDI devices.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2010 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include "mididevice.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
//CVAR(Bool, synth_watch, false, 0)
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SoftSynthMIDIDevice::SoftSynthMIDIDevice(int samplerate, int minrate, int maxrate)
|
||||
{
|
||||
Tempo = 0;
|
||||
Division = 0;
|
||||
Events = NULL;
|
||||
Started = false;
|
||||
SampleRate = samplerate;
|
||||
if (SampleRate < minrate || SampleRate > maxrate) SampleRate = 44100;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SoftSynthMIDIDevice::~SoftSynthMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: GetStreamInfoEx
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SoundStreamInfoEx SoftSynthMIDIDevice::GetStreamInfoEx() const
|
||||
{
|
||||
int chunksize = (SampleRate / StreamBlockSize) * 4;
|
||||
if (!isMono)
|
||||
{
|
||||
chunksize *= 2;
|
||||
}
|
||||
return { chunksize, SampleRate, SampleType_Float32,
|
||||
isMono ? ChannelConfig_Mono : ChannelConfig_Stereo };
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: Open
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::Open()
|
||||
{
|
||||
Tempo = 500000;
|
||||
Division = 100;
|
||||
CalcTickRate();
|
||||
isOpen = true;
|
||||
return OpenRenderer();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: Close
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void SoftSynthMIDIDevice::Close()
|
||||
{
|
||||
Started = false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: IsOpen
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool SoftSynthMIDIDevice::IsOpen() const
|
||||
{
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: GetTechnology
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::GetTechnology() const
|
||||
{
|
||||
return MIDIDEV_SWSYNTH;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: SetTempo
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::SetTempo(int tempo)
|
||||
{
|
||||
Tempo = tempo;
|
||||
CalcTickRate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: SetTimeDiv
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::SetTimeDiv(int timediv)
|
||||
{
|
||||
Division = timediv;
|
||||
CalcTickRate();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: CalcTickRate
|
||||
//
|
||||
// Tempo is the number of microseconds per quarter note.
|
||||
// Division is the number of ticks per quarter note.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void SoftSynthMIDIDevice::CalcTickRate()
|
||||
{
|
||||
SamplesPerTick = SampleRate / (1000000.0 / Tempo) / Division;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: Resume
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::Resume()
|
||||
{
|
||||
Started = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: Stop
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void SoftSynthMIDIDevice::Stop()
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: StreamOutSync
|
||||
//
|
||||
// This version is called from the main game thread and needs to
|
||||
// synchronize with the player thread.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::StreamOutSync(MidiHeader *header)
|
||||
{
|
||||
StreamOut(header);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: StreamOut
|
||||
//
|
||||
// This version is called from the player thread so does not need to
|
||||
// arbitrate for access to the Events pointer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::StreamOut(MidiHeader *header)
|
||||
{
|
||||
header->lpNext = NULL;
|
||||
if (Events == NULL)
|
||||
{
|
||||
Events = header;
|
||||
NextTickIn = SamplesPerTick * *(uint32_t *)header->lpData;
|
||||
Position = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
MidiHeader **p;
|
||||
|
||||
for (p = &Events; *p != NULL; p = &(*p)->lpNext)
|
||||
{ }
|
||||
*p = header;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: Pause
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool SoftSynthMIDIDevice::Pause(bool paused)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: PlayTick
|
||||
//
|
||||
// event[0] = delta time
|
||||
// event[1] = unused
|
||||
// event[2] = event
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int SoftSynthMIDIDevice::PlayTick()
|
||||
{
|
||||
uint32_t delay = 0;
|
||||
|
||||
while (delay == 0 && Events != NULL)
|
||||
{
|
||||
uint32_t *event = (uint32_t *)(Events->lpData + Position);
|
||||
if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO)
|
||||
{
|
||||
SetTempo(MEVENT_EVENTPARM(event[2]));
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG)
|
||||
{
|
||||
HandleLongEvent((uint8_t *)&event[3], MEVENT_EVENTPARM(event[2]));
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == 0)
|
||||
{ // Short MIDI event
|
||||
int status = event[2] & 0xff;
|
||||
int parm1 = (event[2] >> 8) & 0x7f;
|
||||
int parm2 = (event[2] >> 16) & 0x7f;
|
||||
HandleEvent(status, parm1, parm2);
|
||||
|
||||
#if 0
|
||||
if (synth_watch)
|
||||
{
|
||||
static const char *const commands[8] =
|
||||
{
|
||||
"Note off",
|
||||
"Note on",
|
||||
"Poly press",
|
||||
"Ctrl change",
|
||||
"Prgm change",
|
||||
"Chan press",
|
||||
"Pitch bend",
|
||||
"SysEx"
|
||||
};
|
||||
char buffer[128];
|
||||
mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2);
|
||||
#ifdef _WIN32
|
||||
I_DebugPrint(buffer);
|
||||
#else
|
||||
fputs(buffer, stderr);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Advance to next event.
|
||||
if (event[2] < 0x80000000)
|
||||
{ // Short message
|
||||
Position += 12;
|
||||
}
|
||||
else
|
||||
{ // Long message
|
||||
Position += 12 + ((MEVENT_EVENTPARM(event[2]) + 3) & ~3);
|
||||
}
|
||||
|
||||
// Did we use up this buffer?
|
||||
if (Position >= Events->dwBytesRecorded)
|
||||
{
|
||||
Events = Events->lpNext;
|
||||
Position = 0;
|
||||
|
||||
if (Callback != NULL)
|
||||
{
|
||||
Callback(CallbackData);
|
||||
}
|
||||
}
|
||||
|
||||
if (Events == NULL)
|
||||
{ // No more events. Just return something to keep the song playing
|
||||
// while we wait for more to be submitted.
|
||||
return int(Division);
|
||||
}
|
||||
|
||||
delay = *(uint32_t *)(Events->lpData + Position);
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SoftSynthMIDIDevice :: ServiceStream
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool SoftSynthMIDIDevice::ServiceStream (void *buff, int numbytes)
|
||||
{
|
||||
float *samples = (float *)buff;
|
||||
float *samples1;
|
||||
int numsamples = numbytes / sizeof(float) / 2;
|
||||
bool res = true;
|
||||
|
||||
samples1 = samples;
|
||||
memset(buff, 0, numbytes);
|
||||
|
||||
while (Events != NULL && numsamples > 0)
|
||||
{
|
||||
double ticky = NextTickIn;
|
||||
int tick_in = int(NextTickIn);
|
||||
int samplesleft = std::min(numsamples, tick_in);
|
||||
|
||||
if (samplesleft > 0)
|
||||
{
|
||||
ComputeOutput(samples1, samplesleft);
|
||||
assert(NextTickIn == ticky);
|
||||
NextTickIn -= samplesleft;
|
||||
assert(NextTickIn >= 0);
|
||||
numsamples -= samplesleft;
|
||||
samples1 += samplesleft * 2;
|
||||
}
|
||||
|
||||
if (NextTickIn < 1)
|
||||
{
|
||||
int next = PlayTick();
|
||||
assert(next >= 0);
|
||||
if (next == 0)
|
||||
{ // end of song
|
||||
if (numsamples > 0)
|
||||
{
|
||||
ComputeOutput(samples1, numsamples);
|
||||
}
|
||||
res = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
NextTickIn += SamplesPerTick * next;
|
||||
assert(NextTickIn >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Events == NULL)
|
||||
{
|
||||
res = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
** music_timidity_mididevice.cpp
|
||||
** Provides access to TiMidity as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_GUS
|
||||
|
||||
#include "timidity/timidity.h"
|
||||
#include "timidity/playmidi.h"
|
||||
#include "timidity/instrum.h"
|
||||
#include "fileio.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
GUSConfig gusConfig;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// The actual device.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
namespace Timidity { struct Renderer; }
|
||||
|
||||
class TimidityMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
void LoadInstruments();
|
||||
public:
|
||||
TimidityMIDIDevice(int samplerate);
|
||||
~TimidityMIDIDevice();
|
||||
|
||||
int OpenRenderer() override;
|
||||
void PrecacheInstruments(const uint16_t *instruments, int count) override;
|
||||
int GetDeviceType() const override { return MDEV_GUS; }
|
||||
|
||||
protected:
|
||||
Timidity::Renderer *Renderer;
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
};
|
||||
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
|
||||
void TimidityMIDIDevice::LoadInstruments()
|
||||
{
|
||||
if (gusConfig.reader)
|
||||
{
|
||||
// Check if we got some GUS data before using it.
|
||||
std::string ultradir;
|
||||
const char *ret = getenv("ULTRADIR");
|
||||
if (ret) ultradir = std::string(ret);
|
||||
// The GUS put its patches in %ULTRADIR%/MIDI so we can try that
|
||||
if (ultradir.length())
|
||||
{
|
||||
ultradir += "/midi";
|
||||
gusConfig.reader->add_search_path(ultradir.c_str());
|
||||
}
|
||||
// Load DMXGUS lump and patches from gus_patchdir
|
||||
if (gusConfig.gus_patchdir.length() != 0) gusConfig.reader->add_search_path(gusConfig.gus_patchdir.c_str());
|
||||
|
||||
gusConfig.instruments.reset(new Timidity::Instruments(gusConfig.reader));
|
||||
gusConfig.loadedConfig = gusConfig.readerName;
|
||||
}
|
||||
|
||||
if (gusConfig.instruments == nullptr)
|
||||
{
|
||||
throw std::runtime_error("No instruments set for GUS device");
|
||||
}
|
||||
|
||||
if (gusConfig.gus_dmxgus && gusConfig.dmxgus.size())
|
||||
{
|
||||
bool success = gusConfig.instruments->LoadDMXGUS(gusConfig.gus_memsize, (const char*)gusConfig.dmxgus.data(), gusConfig.dmxgus.size()) >= 0;
|
||||
gusConfig.reader = nullptr;
|
||||
|
||||
if (!success)
|
||||
{
|
||||
gusConfig.instruments.reset();
|
||||
gusConfig.loadedConfig = "";
|
||||
throw std::runtime_error("Unable to initialize DMXGUS for GUS MIDI device");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool err = gusConfig.instruments->LoadConfig() < 0;
|
||||
gusConfig.reader = nullptr;
|
||||
|
||||
if (err)
|
||||
{
|
||||
gusConfig.instruments.reset();
|
||||
gusConfig.loadedConfig = "";
|
||||
throw std::runtime_error("Unable to initialize instruments for GUS MIDI device");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
TimidityMIDIDevice::TimidityMIDIDevice(int samplerate)
|
||||
: SoftSynthMIDIDevice(samplerate, 11025, 65535)
|
||||
{
|
||||
LoadInstruments();
|
||||
Renderer = new Timidity::Renderer((float)SampleRate, gusConfig.midi_voices, gusConfig.instruments.get());
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
TimidityMIDIDevice::~TimidityMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
delete Renderer;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int TimidityMIDIDevice::OpenRenderer()
|
||||
{
|
||||
Renderer->Reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice :: PrecacheInstruments
|
||||
//
|
||||
// Each entry is packed as follows:
|
||||
// Bits 0- 6: Instrument number
|
||||
// Bits 7-13: Bank number
|
||||
// Bit 14: Select drum set if 1, tone bank if 0
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Renderer->MarkInstrument((instruments[i] >> 7) & 127, instruments[i] >> 14, instruments[i] & 127);
|
||||
}
|
||||
Renderer->load_missing_instruments();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
Renderer->HandleEvent(status, parm1, parm2);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
Renderer->HandleLongMessage(data, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
Renderer->ComputeOutput(buffer, len);
|
||||
for (int i = 0; i < len * 2; i++) buffer[i] *= 0.7f;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Sets up the date to load the instruments for the GUS device.
|
||||
// The actual instrument loader is part of the device.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool GUS_SetupConfig(const char* args)
|
||||
{
|
||||
if (*args == 0) args = gusConfig.gus_config.c_str();
|
||||
if (gusConfig.gus_dmxgus && *args == 0) args = "DMXGUS";
|
||||
//if (stricmp(gusConfig.loadedConfig.c_str(), args) == 0) return false; // aleady loaded
|
||||
|
||||
MusicIO::SoundFontReaderInterface* reader = MusicIO::ClientOpenSoundFont(args, SF_GUS);
|
||||
if (!reader && MusicIO::fileExists(args))
|
||||
{
|
||||
auto f = MusicIO::utf8_fopen(args, "rb");
|
||||
if (f)
|
||||
{
|
||||
char test[12] = {};
|
||||
fread(test, 1, 12, f);
|
||||
fclose(f);
|
||||
// If the passed file is an SF2 sound font we need to use the special reader that fakes a config for it.
|
||||
if (memcmp(test, "RIFF", 4) == 0 && memcmp(test + 8, "sfbk", 4) == 0)
|
||||
reader = new MusicIO::SF2Reader(args);
|
||||
}
|
||||
if (!reader) reader = new MusicIO::FileSystemSoundFontReader(args, true);
|
||||
}
|
||||
|
||||
if (!reader && gusConfig.gus_dmxgus)
|
||||
{
|
||||
reader = new MusicIO::FileSystemSoundFontReader(args, true);
|
||||
}
|
||||
|
||||
if (reader == nullptr)
|
||||
{
|
||||
char error[80];
|
||||
snprintf(error, 80, "GUS: %s: Unable to load sound font\n", args);
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
gusConfig.reader = reader;
|
||||
gusConfig.readerName = args;
|
||||
return true;
|
||||
}
|
||||
|
||||
#
|
||||
MIDIDevice* CreateTimidityMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
GUS_SetupConfig(Args);
|
||||
return new TimidityMIDIDevice(samplerate);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateTimidityMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
throw std::runtime_error("GUS device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
** music_timiditypp_mididevice.cpp
|
||||
** Provides access to timidity.exe
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2001-2017 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_TIMIDITY
|
||||
|
||||
#include "timiditypp/timidity.h"
|
||||
#include "timiditypp/instrum.h"
|
||||
#include "timiditypp/playmidi.h"
|
||||
|
||||
|
||||
TimidityConfig timidityConfig;
|
||||
|
||||
class TimidityPPMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
std::shared_ptr<TimidityPlus::Instruments> instruments;
|
||||
public:
|
||||
TimidityPPMIDIDevice(int samplerate);
|
||||
~TimidityPPMIDIDevice();
|
||||
|
||||
int OpenRenderer() override;
|
||||
void PrecacheInstruments(const uint16_t *instruments, int count) override;
|
||||
//std::string GetStats();
|
||||
int GetDeviceType() const override { return MDEV_TIMIDITY; }
|
||||
|
||||
double test[3] = { 0, 0, 0 };
|
||||
|
||||
protected:
|
||||
TimidityPlus::Player *Renderer;
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
void LoadInstruments();
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityPPMIDIDevice::LoadInstruments()
|
||||
{
|
||||
if (timidityConfig.reader)
|
||||
{
|
||||
timidityConfig.loadedConfig = timidityConfig.readerName;
|
||||
timidityConfig.instruments.reset(new TimidityPlus::Instruments());
|
||||
bool success = timidityConfig.instruments->load(timidityConfig.reader);
|
||||
timidityConfig.reader = nullptr;
|
||||
|
||||
if (!success)
|
||||
{
|
||||
timidityConfig.instruments.reset();
|
||||
timidityConfig.loadedConfig = "";
|
||||
throw std::runtime_error("Unable to initialize instruments for Timidity++ MIDI device");
|
||||
}
|
||||
}
|
||||
else if (timidityConfig.instruments == nullptr)
|
||||
{
|
||||
throw std::runtime_error("No instruments set for Timidity++ device");
|
||||
}
|
||||
instruments = timidityConfig.instruments;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
TimidityPPMIDIDevice::TimidityPPMIDIDevice(int samplerate)
|
||||
:SoftSynthMIDIDevice(samplerate, 4000, 65000)
|
||||
{
|
||||
TimidityPlus::set_playback_rate(SampleRate);
|
||||
LoadInstruments();
|
||||
Renderer = new TimidityPlus::Player(instruments.get());
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
TimidityPPMIDIDevice::~TimidityPPMIDIDevice ()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != nullptr)
|
||||
{
|
||||
delete Renderer;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice :: Open
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int TimidityPPMIDIDevice::OpenRenderer()
|
||||
{
|
||||
Renderer->playmidi_stream_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice :: PrecacheInstruments
|
||||
//
|
||||
// Each entry is packed as follows:
|
||||
// Bits 0- 6: Instrument number
|
||||
// Bits 7-13: Bank number
|
||||
// Bit 14: Select drum set if 1, tone bank if 0
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityPPMIDIDevice::PrecacheInstruments(const uint16_t *instrumentlist, int count)
|
||||
{
|
||||
if (instruments != nullptr)
|
||||
instruments->PrecacheInstruments(instrumentlist, count);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityPPMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
if (Renderer != nullptr)
|
||||
Renderer->send_event(status, parm1, parm2);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityPPMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
if (Renderer != nullptr)
|
||||
Renderer->send_long_event(data, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// TimidityPPMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void TimidityPPMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
if (Renderer != nullptr)
|
||||
Renderer->compute_data(buffer, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool Timidity_SetupConfig(const char* args)
|
||||
{
|
||||
if (*args == 0) args = timidityConfig.timidity_config.c_str();
|
||||
if (stricmp(timidityConfig.loadedConfig.c_str(), args) == 0) return false; // aleady loaded
|
||||
|
||||
MusicIO::SoundFontReaderInterface* reader = MusicIO::ClientOpenSoundFont(args, SF_GUS | SF_SF2);
|
||||
if (!reader && MusicIO::fileExists(args))
|
||||
{
|
||||
auto f = MusicIO::utf8_fopen(args, "rb");
|
||||
if (f)
|
||||
{
|
||||
char test[12] = {};
|
||||
fread(test, 1, 12, f);
|
||||
fclose(f);
|
||||
// If the passed file is an SF2 sound font we need to use the special reader that fakes a config for it.
|
||||
if (memcmp(test, "RIFF", 4) == 0 && memcmp(test + 8, "sfbk", 4) == 0)
|
||||
reader = new MusicIO::SF2Reader(args);
|
||||
}
|
||||
if (!reader) reader = new MusicIO::FileSystemSoundFontReader(args, true);
|
||||
}
|
||||
|
||||
if (reader == nullptr)
|
||||
{
|
||||
char error[80];
|
||||
snprintf(error, 80, "Timidity++: %s: Unable to load sound font\n", args);
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
timidityConfig.reader = reader;
|
||||
timidityConfig.readerName = args;
|
||||
return true;
|
||||
}
|
||||
|
||||
MIDIDevice *CreateTimidityPPMIDIDevice(const char *Args, int samplerate)
|
||||
{
|
||||
Timidity_SetupConfig(Args);
|
||||
return new TimidityPPMIDIDevice(samplerate);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateTimidityPPMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
throw std::runtime_error("Timidity++ device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
** music_wavewriter_mididevice.cpp
|
||||
** Dumps a MIDI to a wave file by using one of the other software synths.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** Copyright 2018 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
#include "fileio.h"
|
||||
#include <stdexcept>
|
||||
#include <errno.h>
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
struct FmtChunk
|
||||
{
|
||||
//uint32_t ChunkID;
|
||||
uint32_t ChunkLen;
|
||||
uint16_t FormatTag;
|
||||
uint16_t Channels;
|
||||
uint32_t SamplesPerSec;
|
||||
uint32_t AvgBytesPerSec;
|
||||
uint16_t BlockAlign;
|
||||
uint16_t BitsPerSample;
|
||||
uint16_t ExtensionSize;
|
||||
uint16_t ValidBitsPerSample;
|
||||
uint32_t ChannelMask;
|
||||
uint32_t SubFormatA;
|
||||
uint16_t SubFormatB;
|
||||
uint16_t SubFormatC;
|
||||
uint8_t SubFormatD[8];
|
||||
};
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIWaveWriter Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDIWaveWriter::MIDIWaveWriter(const char *filename, SoftSynthMIDIDevice *playdevice)
|
||||
: SoftSynthMIDIDevice(playdevice->GetSampleRate())
|
||||
{
|
||||
File = MusicIO::utf8_fopen(filename, "wb");
|
||||
playDevice = playdevice;
|
||||
if (File != nullptr)
|
||||
{ // Write wave header
|
||||
FmtChunk fmt;
|
||||
|
||||
if (fwrite("RIFF\0\0\0\0WAVEfmt ", 1, 16, File) != 16) goto fail;
|
||||
|
||||
playDevice->CalcTickRate();
|
||||
fmt.ChunkLen = LittleLong(uint32_t(sizeof(fmt) - 4));
|
||||
fmt.FormatTag = LittleShort((uint16_t)0xFFFE); // WAVE_FORMAT_EXTENSIBLE
|
||||
fmt.Channels = LittleShort((uint16_t)2);
|
||||
fmt.SamplesPerSec = LittleLong(SampleRate);
|
||||
fmt.AvgBytesPerSec = LittleLong(SampleRate * 8);
|
||||
fmt.BlockAlign = LittleShort((uint16_t)8);
|
||||
fmt.BitsPerSample = LittleShort((uint16_t)32);
|
||||
fmt.ExtensionSize = LittleShort((uint16_t)(2 + 4 + 16));
|
||||
fmt.ValidBitsPerSample = LittleShort((uint16_t)32);
|
||||
fmt.ChannelMask = LittleLong(3);
|
||||
fmt.SubFormatA = LittleLong(0x00000003); // Set subformat to KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
|
||||
fmt.SubFormatB = 0x0000;
|
||||
fmt.SubFormatC = LittleShort((uint16_t)0x0010);
|
||||
fmt.SubFormatD[0] = 0x80;
|
||||
fmt.SubFormatD[1] = 0x00;
|
||||
fmt.SubFormatD[2] = 0x00;
|
||||
fmt.SubFormatD[3] = 0xaa;
|
||||
fmt.SubFormatD[4] = 0x00;
|
||||
fmt.SubFormatD[5] = 0x38;
|
||||
fmt.SubFormatD[6] = 0x9b;
|
||||
fmt.SubFormatD[7] = 0x71;
|
||||
if (sizeof(fmt) != fwrite(&fmt, 1, sizeof(fmt), File)) goto fail;
|
||||
|
||||
if (fwrite("data\0\0\0\0", 1, 8, File) != 8) goto fail;
|
||||
|
||||
return;
|
||||
fail:
|
||||
char buffer[80];
|
||||
fclose(File);
|
||||
File = nullptr;
|
||||
snprintf(buffer, 80, "Failed to write %s: %s\n", filename, strerror(errno));
|
||||
throw std::runtime_error(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIWaveWriter Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MIDIWaveWriter::CloseFile()
|
||||
{
|
||||
if (File != nullptr)
|
||||
{
|
||||
auto pos = ftell(File);
|
||||
uint32_t size;
|
||||
|
||||
// data chunk size
|
||||
size = LittleLong(uint32_t(pos - 8));
|
||||
if (0 == fseek(File, 4, SEEK_SET))
|
||||
{
|
||||
if (4 == fwrite(&size, 1, 4, File))
|
||||
{
|
||||
size = LittleLong(uint32_t(pos - 12 - sizeof(FmtChunk) - 8));
|
||||
if (0 == fseek(File, 4 + sizeof(FmtChunk) + 8, SEEK_CUR))
|
||||
{
|
||||
if (4 == fwrite(&size, 1, 4, File))
|
||||
{
|
||||
fclose(File);
|
||||
File = nullptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(File);
|
||||
File = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIWaveWriter :: Resume
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MIDIWaveWriter::Resume()
|
||||
{
|
||||
float writebuffer[4096];
|
||||
|
||||
while (ServiceStream(writebuffer, sizeof(writebuffer)))
|
||||
{
|
||||
if (fwrite(writebuffer, 1, sizeof(writebuffer), File) != sizeof(writebuffer))
|
||||
{
|
||||
fclose(File);
|
||||
File = nullptr;
|
||||
char buffer[80];
|
||||
snprintf(buffer, 80, "Could not write entire wave file: %s\n", strerror(errno));
|
||||
throw std::runtime_error(buffer);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIWaveWriter Stop
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDIWaveWriter::Stop()
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
** music_wildmidi_mididevice.cpp
|
||||
** Provides access to WildMidi as a generic MIDI device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2015 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <stdexcept>
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_WILDMIDI
|
||||
|
||||
#include "wildmidi/wildmidi_lib.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
WildMidiConfig wildMidiConfig;
|
||||
|
||||
// WildMidi implementation of a MIDI device ---------------------------------
|
||||
|
||||
class WildMIDIDevice : public SoftSynthMIDIDevice
|
||||
{
|
||||
public:
|
||||
WildMIDIDevice(int samplerate);
|
||||
~WildMIDIDevice();
|
||||
|
||||
int OpenRenderer() override;
|
||||
void PrecacheInstruments(const uint16_t *instruments, int count) override;
|
||||
std::string GetStats() override;
|
||||
int GetDeviceType() const override { return MDEV_WILDMIDI; }
|
||||
|
||||
protected:
|
||||
WildMidi::Renderer *Renderer;
|
||||
std::shared_ptr<WildMidi::Instruments> instruments;
|
||||
|
||||
void HandleEvent(int status, int parm1, int parm2) override;
|
||||
void HandleLongEvent(const uint8_t *data, int len) override;
|
||||
void ComputeOutput(float *buffer, int len) override;
|
||||
void ChangeSettingInt(const char *opt, int set) override;
|
||||
void LoadInstruments();
|
||||
|
||||
};
|
||||
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::LoadInstruments()
|
||||
{
|
||||
if (wildMidiConfig.reader)
|
||||
{
|
||||
wildMidiConfig.loadedConfig = wildMidiConfig.readerName;
|
||||
wildMidiConfig.instruments.reset(new WildMidi::Instruments(wildMidiConfig.reader, SampleRate));
|
||||
wildMidiConfig.reader = nullptr;
|
||||
}
|
||||
else if (wildMidiConfig.instruments == nullptr)
|
||||
{
|
||||
throw std::runtime_error("No instruments set for WildMidi device");
|
||||
}
|
||||
instruments = wildMidiConfig.instruments;
|
||||
if (instruments->LoadConfig(nullptr) < 0)
|
||||
{
|
||||
wildMidiConfig.instruments.reset();
|
||||
wildMidiConfig.loadedConfig = "";
|
||||
throw std::runtime_error("Unable to initialize instruments for WildMidi device");
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
WildMIDIDevice::WildMIDIDevice(int samplerate)
|
||||
:SoftSynthMIDIDevice(samplerate, 11025, 65535)
|
||||
{
|
||||
Renderer = NULL;
|
||||
LoadInstruments();
|
||||
|
||||
Renderer = new WildMidi::Renderer(instruments.get());
|
||||
int flags = 0;
|
||||
if (wildMidiConfig.enhanced_resampling) flags |= WildMidi::WM_MO_ENHANCED_RESAMPLING;
|
||||
if (wildMidiConfig.reverb) flags |= WildMidi::WM_MO_REVERB;
|
||||
Renderer->SetOption(WildMidi::WM_MO_ENHANCED_RESAMPLING | WildMidi::WM_MO_REVERB, flags);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
WildMIDIDevice::~WildMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
if (Renderer != NULL)
|
||||
{
|
||||
delete Renderer;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: Open
|
||||
//
|
||||
// Returns 0 on success.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WildMIDIDevice::OpenRenderer()
|
||||
{
|
||||
return 0; // This one's a no-op
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: PrecacheInstruments
|
||||
//
|
||||
// Each entry is packed as follows:
|
||||
// Bits 0- 6: Instrument number
|
||||
// Bits 7-13: Bank number
|
||||
// Bit 14: Select drum set if 1, tone bank if 0
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Renderer->LoadInstrument((instruments[i] >> 7) & 127, instruments[i] >> 14, instruments[i] & 127);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: HandleEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::HandleEvent(int status, int parm1, int parm2)
|
||||
{
|
||||
Renderer->ShortEvent(status, parm1, parm2);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: HandleLongEvent
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::HandleLongEvent(const uint8_t *data, int len)
|
||||
{
|
||||
Renderer->LongEvent(data, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: ComputeOutput
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::ComputeOutput(float *buffer, int len)
|
||||
{
|
||||
Renderer->ComputeOutput(buffer, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string WildMIDIDevice::GetStats()
|
||||
{
|
||||
char out[20];
|
||||
snprintf(out, 20, "%3d voices", Renderer->GetVoiceCount());
|
||||
return out;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WildMIDIDevice :: ChangeSettingInt
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WildMIDIDevice::ChangeSettingInt(const char *opt, int set)
|
||||
{
|
||||
int option;
|
||||
if (!stricmp(opt, "wildmidi.reverb")) option = WildMidi::WM_MO_REVERB;
|
||||
else if (!stricmp(opt, "wildmidi.resampling")) option = WildMidi::WM_MO_ENHANCED_RESAMPLING;
|
||||
else return;
|
||||
int setit = option * int(set);
|
||||
Renderer->SetOption(option, setit);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool WildMidi_SetupConfig(const char* args)
|
||||
{
|
||||
if (*args == 0) args = wildMidiConfig.config.c_str();
|
||||
if (stricmp(wildMidiConfig.loadedConfig.c_str(), args) == 0) return false; // aleady loaded
|
||||
|
||||
MusicIO::SoundFontReaderInterface* reader = MusicIO::ClientOpenSoundFont(args, SF_GUS);
|
||||
if (!reader && MusicIO::fileExists(args))
|
||||
{
|
||||
reader = new MusicIO::FileSystemSoundFontReader(args, true);
|
||||
}
|
||||
|
||||
if (reader == nullptr)
|
||||
{
|
||||
char error[80];
|
||||
snprintf(error, 80, "WildMidi: %s: Unable to load sound font\n", args);
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
wildMidiConfig.reader = reader;
|
||||
wildMidiConfig.readerName = args;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDIDevice *CreateWildMIDIDevice(const char *Args, int samplerate)
|
||||
{
|
||||
WildMidi_SetupConfig(Args);
|
||||
return new WildMIDIDevice(samplerate);
|
||||
}
|
||||
|
||||
#else
|
||||
MIDIDevice* CreateWildMIDIDevice(const char* Args, int samplerate)
|
||||
{
|
||||
throw std::runtime_error("WildMidi device not supported in this configuration");
|
||||
}
|
||||
#endif
|
||||
696
libraries/ZMusic/source/mididevices/music_win_mididevice.cpp
Normal file
696
libraries/ZMusic/source/mididevices/music_win_mididevice.cpp
Normal file
|
|
@ -0,0 +1,696 @@
|
|||
/*
|
||||
** music_win_mididevice.cpp
|
||||
** Provides a WinMM implementation of a MIDI output device.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "mididevice.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
#include "zmusic/mus2midi.h"
|
||||
|
||||
#ifndef __GNUC__
|
||||
#include <mmdeviceapi.h>
|
||||
#endif
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
static bool IgnoreMIDIVolume(UINT id);
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
// WinMM implementation of a MIDI output device -----------------------------
|
||||
|
||||
class WinMIDIDevice : public MIDIDevice
|
||||
{
|
||||
public:
|
||||
WinMIDIDevice(int dev_id, bool precache);
|
||||
~WinMIDIDevice();
|
||||
int Open();
|
||||
void Close();
|
||||
bool IsOpen() const;
|
||||
int GetTechnology() const;
|
||||
int SetTempo(int tempo);
|
||||
int SetTimeDiv(int timediv);
|
||||
int StreamOut(MidiHeader *data);
|
||||
int StreamOutSync(MidiHeader *data);
|
||||
int Resume();
|
||||
void Stop();
|
||||
int PrepareHeader(MidiHeader *data);
|
||||
int UnprepareHeader(MidiHeader *data);
|
||||
bool FakeVolume();
|
||||
bool Pause(bool paused);
|
||||
void InitPlayback() override;
|
||||
bool Update() override;
|
||||
void PrecacheInstruments(const uint16_t *instruments, int count);
|
||||
DWORD PlayerLoop();
|
||||
bool CanHandleSysex() const override
|
||||
{
|
||||
// No Sysex for GS synth.
|
||||
return VolumeWorks;
|
||||
}
|
||||
|
||||
|
||||
//protected:
|
||||
static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD);
|
||||
|
||||
HMIDISTRM MidiOut;
|
||||
UINT DeviceID;
|
||||
DWORD SavedVolume;
|
||||
MIDIHDR WinMidiHeaders[2];
|
||||
int HeaderIndex;
|
||||
bool VolumeWorks;
|
||||
bool Precache;
|
||||
|
||||
HANDLE BufferDoneEvent;
|
||||
HANDLE ExitEvent;
|
||||
HANDLE PlayerThread;
|
||||
|
||||
|
||||
};
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice Contructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
WinMIDIDevice::WinMIDIDevice(int dev_id, bool precache)
|
||||
{
|
||||
DeviceID = std::max<DWORD>(dev_id, 0);
|
||||
MidiOut = 0;
|
||||
HeaderIndex = 0;
|
||||
Precache = precache;
|
||||
memset(WinMidiHeaders, 0, sizeof(WinMidiHeaders));
|
||||
|
||||
BufferDoneEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
if (BufferDoneEvent == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Could not create buffer done event for MIDI playback");
|
||||
}
|
||||
ExitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
if (ExitEvent == nullptr)
|
||||
{
|
||||
CloseHandle(BufferDoneEvent);
|
||||
BufferDoneEvent = nullptr;
|
||||
throw std::runtime_error("Could not create exit event for MIDI playback");
|
||||
}
|
||||
PlayerThread = nullptr;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
WinMIDIDevice::~WinMIDIDevice()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (ExitEvent != nullptr)
|
||||
{
|
||||
CloseHandle(ExitEvent);
|
||||
}
|
||||
if (BufferDoneEvent != nullptr)
|
||||
{
|
||||
CloseHandle(BufferDoneEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: Open
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::Open()
|
||||
{
|
||||
MMRESULT err;
|
||||
|
||||
if (MidiOut == nullptr)
|
||||
{
|
||||
err = midiStreamOpen(&MidiOut, &DeviceID, 1, (DWORD_PTR)CallbackFunc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
||||
|
||||
if (err == MMSYSERR_NOERROR)
|
||||
{
|
||||
if (IgnoreMIDIVolume(DeviceID))
|
||||
{
|
||||
VolumeWorks = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set master volume to full, if the device allows it on this interface.
|
||||
VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume((HMIDIOUT)MidiOut, &SavedVolume));
|
||||
if (VolumeWorks)
|
||||
{
|
||||
VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume((HMIDIOUT)MidiOut, 0xffffffff));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: Close
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WinMIDIDevice::Close()
|
||||
{
|
||||
if (MidiOut != nullptr)
|
||||
{
|
||||
midiStreamClose(MidiOut);
|
||||
MidiOut = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: IsOpen
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool WinMIDIDevice::IsOpen() const
|
||||
{
|
||||
return MidiOut != nullptr;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: GetTechnology
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::GetTechnology() const
|
||||
{
|
||||
MIDIOUTCAPS caps;
|
||||
|
||||
if (MMSYSERR_NOERROR == midiOutGetDevCaps(DeviceID, &caps, sizeof(caps)))
|
||||
{
|
||||
return caps.wTechnology;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: SetTempo
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::SetTempo(int tempo)
|
||||
{
|
||||
MIDIPROPTEMPO data = { sizeof(MIDIPROPTEMPO), (DWORD)tempo };
|
||||
return midiStreamProperty(MidiOut, (LPBYTE)&data, MIDIPROP_SET | MIDIPROP_TEMPO);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: SetTimeDiv
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::SetTimeDiv(int timediv)
|
||||
{
|
||||
MIDIPROPTIMEDIV data = { sizeof(MIDIPROPTIMEDIV), (DWORD)timediv };
|
||||
return midiStreamProperty(MidiOut, (LPBYTE)&data, MIDIPROP_SET | MIDIPROP_TIMEDIV);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIStreamer :: PlayerProc Static
|
||||
//
|
||||
// Entry point for the player thread.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD WINAPI PlayerProc(LPVOID lpParameter)
|
||||
{
|
||||
return ((WinMIDIDevice *)lpParameter)->PlayerLoop();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: Resume
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::Resume()
|
||||
{
|
||||
DWORD tid;
|
||||
int ret = midiStreamRestart(MidiOut);
|
||||
if (ret == 0)
|
||||
{
|
||||
PlayerThread = CreateThread(nullptr, 0, PlayerProc, this, 0, &tid);
|
||||
if (PlayerThread == nullptr)
|
||||
{
|
||||
Stop();
|
||||
throw std::runtime_error("Creating MIDI thread failed\n");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: InitPlayback
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WinMIDIDevice::InitPlayback()
|
||||
{
|
||||
ResetEvent(ExitEvent);
|
||||
ResetEvent(BufferDoneEvent);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: Stop
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WinMIDIDevice::Stop()
|
||||
{
|
||||
if (PlayerThread != nullptr)
|
||||
{
|
||||
SetEvent(ExitEvent);
|
||||
WaitForSingleObject(PlayerThread, INFINITE);
|
||||
CloseHandle(PlayerThread);
|
||||
PlayerThread = nullptr;
|
||||
}
|
||||
|
||||
midiStreamStop(MidiOut);
|
||||
midiOutReset((HMIDIOUT)MidiOut);
|
||||
if (VolumeWorks)
|
||||
{
|
||||
midiOutSetVolume((HMIDIOUT)MidiOut, SavedVolume);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIStreamer :: PlayerLoop
|
||||
//
|
||||
// Services MIDI playback events.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD WinMIDIDevice::PlayerLoop()
|
||||
{
|
||||
HANDLE events[2] = { BufferDoneEvent, ExitEvent };
|
||||
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
switch (WaitForMultipleObjects(2, events, FALSE, INFINITE))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
if (Callback != nullptr) Callback(CallbackData);
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
// Should not happen.
|
||||
return MMSYSERR_ERROR;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: PrecacheInstruments
|
||||
//
|
||||
// Each entry is packed as follows:
|
||||
// Bits 0- 6: Instrument number
|
||||
// Bits 7-13: Bank number
|
||||
// Bit 14: Select drum set if 1, tone bank if 0
|
||||
//
|
||||
// My old GUS PnP needed the instruments to be preloaded, or it would miss
|
||||
// some notes the first time through a song. I doubt any modern
|
||||
// hardware has this problem, but since I'd already written the code for
|
||||
// ZDoom 1.22 and below, I'm resurrecting it now for completeness, since I'm
|
||||
// using preloading for the internal Timidity.
|
||||
//
|
||||
// NOTETOSELF: Why did I never notice the midiOutCache(Drum)Patches calls
|
||||
// before now? Should I switch to them? This code worked on my GUS, but
|
||||
// using the APIs intended for caching might be better.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void WinMIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
|
||||
{
|
||||
// Setting snd_midiprecache to false disables this precaching, since it
|
||||
// does involve sleeping for more than a miniscule amount of time.
|
||||
if (!Precache)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint8_t bank[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
int i, chan;
|
||||
|
||||
for (i = 0, chan = 0; i < count; ++i)
|
||||
{
|
||||
int instr = instruments[i] & 127;
|
||||
int banknum = (instruments[i] >> 7) & 127;
|
||||
int percussion = instruments[i] >> 14;
|
||||
|
||||
if (percussion)
|
||||
{
|
||||
if (bank[9] != banknum)
|
||||
{
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_CTRLCHANGE | 9 | (0 << 8) | (banknum << 16));
|
||||
bank[9] = banknum;
|
||||
}
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_NOTEON | 9 | ((instruments[i] & 0x7f) << 8) | (1 << 16));
|
||||
}
|
||||
else
|
||||
{ // Melodic
|
||||
if (bank[chan] != banknum)
|
||||
{
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_CTRLCHANGE | 9 | (0 << 8) | (banknum << 16));
|
||||
bank[chan] = banknum;
|
||||
}
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_PRGMCHANGE | chan | (instruments[i] << 8));
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_NOTEON | chan | (60 << 8) | (1 << 16));
|
||||
if (++chan == 9)
|
||||
{ // Skip the percussion channel
|
||||
chan = 10;
|
||||
}
|
||||
}
|
||||
// Once we've got an instrument playing on each melodic channel, sleep to give
|
||||
// the driver time to load the instruments. Also do this for the final batch
|
||||
// of instruments.
|
||||
if (chan == 16 || i == count - 1)
|
||||
{
|
||||
Sleep(250);
|
||||
for (chan = 15; chan-- != 0; )
|
||||
{
|
||||
// Turn all notes off
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_CTRLCHANGE | chan | (123 << 8));
|
||||
}
|
||||
// And now chan is back at 0, ready to start the cycle over.
|
||||
}
|
||||
}
|
||||
// Make sure all channels are set back to bank 0.
|
||||
for (i = 0; i < 16; ++i)
|
||||
{
|
||||
if (bank[i] != 0)
|
||||
{
|
||||
midiOutShortMsg((HMIDIOUT)MidiOut, MIDI_CTRLCHANGE | 9 | (0 << 8) | (0 << 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: Pause
|
||||
//
|
||||
// Some docs claim pause is unreliable and can cause the stream to stop
|
||||
// functioning entirely. Truth or fiction?
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool WinMIDIDevice::Pause(bool paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: StreamOut
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::StreamOut(MidiHeader *header)
|
||||
{
|
||||
auto syshdr = (MIDIHDR*)header->lpNext;
|
||||
assert(syshdr == &WinMidiHeaders[0] || syshdr == &WinMidiHeaders[1]);
|
||||
return midiStreamOut(MidiOut, syshdr, sizeof(MIDIHDR));
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: StreamOutSync
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::StreamOutSync(MidiHeader *header)
|
||||
{
|
||||
return StreamOut(header);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: PrepareHeader
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::PrepareHeader(MidiHeader *header)
|
||||
{
|
||||
// This code depends on the driving implementation only having two buffers that get passed alternatingly.
|
||||
// If there were more buffers this would require more intelligent handling.
|
||||
assert(header->lpNext == nullptr);
|
||||
MIDIHDR *syshdr = &WinMidiHeaders[HeaderIndex ^= 1];
|
||||
memset(syshdr, 0, sizeof(MIDIHDR));
|
||||
syshdr->lpData = (LPSTR)header->lpData;
|
||||
syshdr->dwBufferLength = header->dwBufferLength;
|
||||
syshdr->dwBytesRecorded = header->dwBytesRecorded;
|
||||
// this device does not use the lpNext pointer to link MIDI events so use it to point to the system data structure.
|
||||
header->lpNext = (MidiHeader*)syshdr;
|
||||
return midiOutPrepareHeader((HMIDIOUT)MidiOut, syshdr, sizeof(MIDIHDR));
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: UnprepareHeader
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int WinMIDIDevice::UnprepareHeader(MidiHeader *header)
|
||||
{
|
||||
auto syshdr = (MIDIHDR*)header->lpNext;
|
||||
if (syshdr != nullptr)
|
||||
{
|
||||
assert(syshdr == &WinMidiHeaders[0] || syshdr == &WinMidiHeaders[1]);
|
||||
header->lpNext = nullptr;
|
||||
return midiOutUnprepareHeader((HMIDIOUT)MidiOut, syshdr, sizeof(MIDIHDR));
|
||||
}
|
||||
else
|
||||
{
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: FakeVolume
|
||||
//
|
||||
// Because there are too many MIDI devices out there that don't support
|
||||
// global volume changes, fake the volume for all of them.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool WinMIDIDevice::FakeVolume()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: Update
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool WinMIDIDevice::Update()
|
||||
{
|
||||
// If the PlayerThread is signalled, then it's dead.
|
||||
if (PlayerThread != nullptr &&
|
||||
WaitForSingleObject(PlayerThread, 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
static const char *const MMErrorCodes[] =
|
||||
{
|
||||
"No error",
|
||||
"Unspecified error",
|
||||
"Device ID out of range",
|
||||
"Driver failed enable",
|
||||
"Device already allocated",
|
||||
"Device handle is invalid",
|
||||
"No device driver present",
|
||||
"Memory allocation error",
|
||||
"Function isn't supported",
|
||||
"Error value out of range",
|
||||
"Invalid flag passed",
|
||||
"Invalid parameter passed",
|
||||
"Handle being used simultaneously on another thread",
|
||||
"Specified alias not found",
|
||||
"Bad registry database",
|
||||
"Registry key not found",
|
||||
"Registry read error",
|
||||
"Registry write error",
|
||||
"Registry delete error",
|
||||
"Registry value not found",
|
||||
"Driver does not call DriverCallback",
|
||||
"More data to be returned",
|
||||
};
|
||||
static const char *const MidiErrorCodes[] =
|
||||
{
|
||||
"MIDI header not prepared",
|
||||
"MIDI still playing something",
|
||||
"MIDI no configured instruments",
|
||||
"MIDI hardware is still busy",
|
||||
"MIDI port no longer connected",
|
||||
"MIDI invalid MIF",
|
||||
"MIDI operation unsupported with open mode",
|
||||
"MIDI through device 'eating' a message",
|
||||
};
|
||||
DWORD code = 0xABADCAFE;
|
||||
GetExitCodeThread(PlayerThread, &code);
|
||||
CloseHandle(PlayerThread);
|
||||
PlayerThread = nullptr;
|
||||
char errmsg[100];
|
||||
const char *m = "MIDI playback failure: ";
|
||||
if (code < 8)
|
||||
{
|
||||
snprintf(errmsg, 100, "%s%s", m, MMErrorCodes[code]);
|
||||
}
|
||||
else if (code >= MIDIERR_BASE && code < MIDIERR_BASE + 8)
|
||||
{
|
||||
snprintf(errmsg, 100, "%s%s", m, MMErrorCodes[code - MIDIERR_BASE]);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(errmsg, 100, "%s%08x", m, code);
|
||||
}
|
||||
throw std::runtime_error(errmsg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WinMIDIDevice :: CallbackFunc static
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void CALLBACK WinMIDIDevice::CallbackFunc(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
|
||||
{
|
||||
WinMIDIDevice *self = (WinMIDIDevice *)dwInstance;
|
||||
if (uMsg == MOM_DONE)
|
||||
{
|
||||
SetEvent(self->BufferDoneEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// IgnoreMIDIVolume
|
||||
//
|
||||
// Should we ignore this MIDI device's volume control even if it works?
|
||||
//
|
||||
// Under Windows Vista and up, when using the standard "Microsoft GS
|
||||
// Wavetable Synth", midiOutSetVolume() will affect the application's audio
|
||||
// session volume rather than the volume for just the MIDI stream. At first,
|
||||
// I thought I could get around this by enumerating the streams in the
|
||||
// audio session to find the MIDI device's stream to set its volume
|
||||
// manually, but there doesn't appear to be any way to enumerate the
|
||||
// individual streams in a session. Consequently, we'll just assume the MIDI
|
||||
// device gets created at full volume like we want. (Actual volume changes
|
||||
// are done by sending MIDI channel volume messages to the stream, not
|
||||
// through midiOutSetVolume().)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static bool IgnoreMIDIVolume(UINT id)
|
||||
{
|
||||
MIDIOUTCAPSA caps;
|
||||
|
||||
if (MMSYSERR_NOERROR == midiOutGetDevCapsA(id, &caps, sizeof(caps)))
|
||||
{
|
||||
if (caps.wTechnology == MIDIDEV_MAPPER)
|
||||
{
|
||||
// We cannot determine what this is so we have to assume the worst, as the default
|
||||
// devive's volume control is irreparably broken.
|
||||
return true;
|
||||
}
|
||||
// The Microsoft GS Wavetable Synth advertises itself as MIDIDEV_SWSYNTH with a VOLUME control.
|
||||
// If the one we're using doesn't match that, we don't need to bother checking the name.
|
||||
if (caps.wTechnology == MIDIDEV_SWSYNTH && (caps.dwSupport & MIDICAPS_VOLUME))
|
||||
{
|
||||
if (strncmp(caps.szPname, "Microsoft GS", 12) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MIDIDevice *CreateWinMIDIDevice(int mididevice)
|
||||
{
|
||||
return new WinMIDIDevice(mididevice, miscConfig.snd_midiprecache);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
516
libraries/ZMusic/source/midisources/midisource.cpp
Normal file
516
libraries/ZMusic/source/midisources/midisource.cpp
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
/*
|
||||
** midisource.cpp
|
||||
** Implements base class for the different MIDI formats
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008-2016 Randy Heit
|
||||
** Copyright 2017-2018 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include "zmusic_internal.h"
|
||||
#include "midisource.h"
|
||||
|
||||
|
||||
char MIDI_EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 };
|
||||
char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISource :: SetTempo
|
||||
//
|
||||
// Sets the tempo from a track's initial meta events. Later tempo changes
|
||||
// create MEVENT_TEMPO events instead.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISource::SetTempo(int new_tempo)
|
||||
{
|
||||
InitialTempo = new_tempo;
|
||||
// This intentionally uses a callback to avoid any dependencies on the class that is playing the song.
|
||||
// This should probably be done differently, but right now that's not yet possible.
|
||||
if (TempoCallback(new_tempo))
|
||||
{
|
||||
Tempo = new_tempo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISource :: ClampLoopCount
|
||||
//
|
||||
// We use the XMIDI interpretation of loop count here, where 1 means it
|
||||
// plays that section once (in other words, no loop) rather than the EMIDI
|
||||
// interpretation where 1 means to loop it once.
|
||||
//
|
||||
// If LoopLimit is 1, we limit all loops, since this pass over the song is
|
||||
// used to determine instruments for precaching.
|
||||
//
|
||||
// If LoopLimit is higher, we only limit infinite loops, since this song is
|
||||
// being exported.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MIDISource::ClampLoopCount(int loopcount)
|
||||
{
|
||||
if (LoopLimit == 0)
|
||||
{
|
||||
return loopcount;
|
||||
}
|
||||
if (LoopLimit == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (loopcount == 0)
|
||||
{
|
||||
return LoopLimit;
|
||||
}
|
||||
return loopcount;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISource :: VolumeControllerChange
|
||||
//
|
||||
// Some devices don't support master volume
|
||||
// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
|
||||
// so assume none of them do and scale channel volumes manually.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MIDISource::VolumeControllerChange(int channel, int volume)
|
||||
{
|
||||
ChannelVolumes[channel] = volume;
|
||||
// When exporting this MIDI file,
|
||||
// we should not adjust the volume level.
|
||||
return Exporting? volume : ((volume + 1) * Volume) >> 16;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISource :: Precache
|
||||
//
|
||||
// Generates a list of instruments this song uses and passes them to the
|
||||
// MIDI device for precaching. The default implementation here pretends to
|
||||
// play the song and watches for program change events on normal channels
|
||||
// and note on events on channel 10.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::vector<uint16_t> MIDISource::PrecacheData()
|
||||
{
|
||||
uint32_t Events[2][MAX_MIDI_EVENTS*3];
|
||||
uint8_t found_instruments[256] = { 0, };
|
||||
uint8_t found_banks[256] = { 0, };
|
||||
bool multiple_banks = false;
|
||||
|
||||
LoopLimit = 1;
|
||||
DoRestart();
|
||||
found_banks[0] = true; // Bank 0 is always used.
|
||||
found_banks[128] = true;
|
||||
|
||||
// Simulate playback to pick out used instruments.
|
||||
while (!CheckDone())
|
||||
{
|
||||
uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_MIDI_EVENTS*3], 1000000*600);
|
||||
for (uint32_t *event = Events[0]; event < event_end; )
|
||||
{
|
||||
if (MEVENT_EVENTTYPE(event[2]) == 0)
|
||||
{
|
||||
int command = (event[2] & 0x70);
|
||||
int channel = (event[2] & 0x0f);
|
||||
int data1 = (event[2] >> 8) & 0x7f;
|
||||
int data2 = (event[2] >> 16) & 0x7f;
|
||||
|
||||
if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70))
|
||||
{
|
||||
found_instruments[data1] = true;
|
||||
}
|
||||
else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0)
|
||||
{ // On a percussion channel, program change also serves as bank select.
|
||||
multiple_banks = true;
|
||||
found_banks[data1 | 128] = true;
|
||||
}
|
||||
else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0)
|
||||
{
|
||||
found_instruments[data1 | 128] = true;
|
||||
}
|
||||
else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0)
|
||||
{
|
||||
multiple_banks = true;
|
||||
if (channel == 9)
|
||||
{
|
||||
found_banks[data2 | 128] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
found_banks[data2] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Advance to next event
|
||||
if (event[2] < 0x80000000)
|
||||
{ // short message
|
||||
event += 3;
|
||||
}
|
||||
else
|
||||
{ // long message
|
||||
event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
DoRestart();
|
||||
|
||||
// Now pack everything into a contiguous region for the PrecacheInstruments call().
|
||||
std::vector<uint16_t> packed;
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
if (found_instruments[i])
|
||||
{
|
||||
uint16_t packnum = (i & 127) | ((i & 128) << 7);
|
||||
if (!multiple_banks)
|
||||
{
|
||||
packed.push_back(packnum);
|
||||
}
|
||||
else
|
||||
{ // In order to avoid having to multiplex tracks in a type 1 file,
|
||||
// precache every used instrument in every used bank, even if not
|
||||
// all combinations are actually used.
|
||||
for (int j = 0; j < 128; ++j)
|
||||
{
|
||||
if (found_banks[j + (i & 128)])
|
||||
{
|
||||
packed.push_back(packnum | (j << 7));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return packed;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISource :: CheckCaps
|
||||
//
|
||||
// Called immediately after the device is opened in case a source should
|
||||
// want to alter its behavior depending on which device it got.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISource::CheckCaps(int tech)
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISource :: SetMIDISubsong
|
||||
//
|
||||
// Selects which subsong to play. This is private.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MIDISource::SetMIDISubsong(int subsong)
|
||||
{
|
||||
return subsong == 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// WriteVarLen
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void WriteVarLen (std::vector<uint8_t> &file, uint32_t value)
|
||||
{
|
||||
uint32_t buffer = value & 0x7F;
|
||||
|
||||
while ( (value >>= 7) )
|
||||
{
|
||||
buffer <<= 8;
|
||||
buffer |= (value & 0x7F) | 0x80;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
file.push_back(uint8_t(buffer));
|
||||
if (buffer & 0x80)
|
||||
{
|
||||
buffer >>= 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDIStreamer :: CreateSMF
|
||||
//
|
||||
// Simulates playback to create a Standard MIDI File.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISource::CreateSMF(std::vector<uint8_t> &file, int looplimit)
|
||||
{
|
||||
const int EXPORT_LOOP_LIMIT = 30; // Maximum number of times to loop when exporting a MIDI file.
|
||||
// (for songs with loop controller events)
|
||||
|
||||
static const uint8_t StaticMIDIhead[] =
|
||||
{
|
||||
'M','T','h','d', 0, 0, 0, 6,
|
||||
0, 0, // format 0: only one track
|
||||
0, 1, // yes, there is really only one track
|
||||
0, 0, // divisions (filled in)
|
||||
'M','T','r','k', 0, 0, 0, 0,
|
||||
// The first event sets the tempo (filled in)
|
||||
0, 255, 81, 3, 0, 0, 0
|
||||
};
|
||||
|
||||
uint32_t Events[2][MAX_MIDI_EVENTS*3];
|
||||
uint32_t delay = 0;
|
||||
uint8_t running_status = 255;
|
||||
|
||||
// Always create songs aimed at GM devices.
|
||||
CheckCaps(MIDIDEV_MIDIPORT);
|
||||
LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit;
|
||||
DoRestart();
|
||||
StartPlayback(false, LoopLimit);
|
||||
|
||||
file.resize(sizeof(StaticMIDIhead));
|
||||
memcpy(file.data(), StaticMIDIhead, sizeof(StaticMIDIhead));
|
||||
file[12] = Division >> 8;
|
||||
file[13] = Division & 0xFF;
|
||||
file[26] = InitialTempo >> 16;
|
||||
file[27] = InitialTempo >> 8;
|
||||
file[28] = InitialTempo;
|
||||
|
||||
while (!CheckDone())
|
||||
{
|
||||
uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_MIDI_EVENTS*3], 1000000*600);
|
||||
for (uint32_t *event = Events[0]; event < event_end; )
|
||||
{
|
||||
delay += event[0];
|
||||
if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO)
|
||||
{
|
||||
WriteVarLen(file, delay);
|
||||
delay = 0;
|
||||
uint32_t tempo = MEVENT_EVENTPARM(event[2]);
|
||||
file.push_back(MIDI_META);
|
||||
file.push_back(MIDI_META_TEMPO);
|
||||
file.push_back(3);
|
||||
file.push_back(uint8_t(tempo >> 16));
|
||||
file.push_back(uint8_t(tempo >> 8));
|
||||
file.push_back(uint8_t(tempo));
|
||||
running_status = 255;
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG)
|
||||
{
|
||||
WriteVarLen(file, delay);
|
||||
delay = 0;
|
||||
uint32_t len = MEVENT_EVENTPARM(event[2]);
|
||||
uint8_t *bytes = (uint8_t *)&event[3];
|
||||
if (bytes[0] == MIDI_SYSEX)
|
||||
{
|
||||
len--;
|
||||
file.push_back(MIDI_SYSEX);
|
||||
WriteVarLen(file, len);
|
||||
auto p = file.size();
|
||||
file.resize(p + len);
|
||||
memcpy(&file[p], bytes + 1, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
file.push_back(MIDI_SYSEXEND);
|
||||
WriteVarLen(file, len);
|
||||
auto p = file.size();
|
||||
file.resize(p + len);
|
||||
memcpy(&file[p], bytes, len);
|
||||
}
|
||||
running_status = 255;
|
||||
}
|
||||
else if (MEVENT_EVENTTYPE(event[2]) == 0)
|
||||
{
|
||||
WriteVarLen(file, delay);
|
||||
delay = 0;
|
||||
uint8_t status = uint8_t(event[2]);
|
||||
if (status != running_status)
|
||||
{
|
||||
running_status = status;
|
||||
file.push_back(status);
|
||||
}
|
||||
file.push_back(uint8_t((event[2] >> 8) & 0x7F));
|
||||
if (MIDI_EventLengths[(status >> 4) & 7] == 2)
|
||||
{
|
||||
file.push_back(uint8_t((event[2] >> 16) & 0x7F));
|
||||
}
|
||||
}
|
||||
// Advance to next event
|
||||
if (event[2] < 0x80000000)
|
||||
{ // short message
|
||||
event += 3;
|
||||
}
|
||||
else
|
||||
{ // long message
|
||||
event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End track
|
||||
WriteVarLen(file, delay);
|
||||
file.push_back(MIDI_META);
|
||||
file.push_back(MIDI_META_EOT);
|
||||
file.push_back(0);
|
||||
|
||||
// Fill in track length
|
||||
uint32_t len = (uint32_t)file.size() - 22;
|
||||
file[18] = uint8_t(len >> 24);
|
||||
file[19] = uint8_t(len >> 16);
|
||||
file[20] = uint8_t(len >> 8);
|
||||
file[21] = uint8_t(len & 255);
|
||||
|
||||
LoopLimit = 0;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Global interface (identification / creation of MIDI sources)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
extern int MUSHeaderSearch(const uint8_t *head, int len);
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// identify MIDI file type
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT EMIDIType ZMusic_IdentifyMIDIType(uint32_t *id, int size)
|
||||
{
|
||||
// Check for MUS format
|
||||
// Tolerate sloppy wads by searching up to 32 bytes for the header
|
||||
if (MUSHeaderSearch((uint8_t*)id, size) >= 0)
|
||||
{
|
||||
return MIDI_MUS;
|
||||
}
|
||||
// Check for HMI format
|
||||
else
|
||||
if (id[0] == MAKE_ID('H','M','I','-') &&
|
||||
id[1] == MAKE_ID('M','I','D','I') &&
|
||||
id[2] == MAKE_ID('S','O','N','G'))
|
||||
{
|
||||
return MIDI_HMI;
|
||||
}
|
||||
// Check for HMP format
|
||||
else
|
||||
if (id[0] == MAKE_ID('H','M','I','M') &&
|
||||
id[1] == MAKE_ID('I','D','I','P'))
|
||||
{
|
||||
return MIDI_HMI;
|
||||
}
|
||||
// Check for XMI format
|
||||
else
|
||||
if ((id[0] == MAKE_ID('F','O','R','M') &&
|
||||
id[2] == MAKE_ID('X','D','I','R')) ||
|
||||
((id[0] == MAKE_ID('C','A','T',' ') || id[0] == MAKE_ID('F','O','R','M')) &&
|
||||
id[2] == MAKE_ID('X','M','I','D')))
|
||||
{
|
||||
return MIDI_XMI;
|
||||
}
|
||||
// Check for MIDS format
|
||||
else
|
||||
if (id[0] == MAKE_ID('R','I','F','F') &&
|
||||
id[2] == MAKE_ID('M','I','D','S'))
|
||||
{
|
||||
return MIDI_MIDS;
|
||||
}
|
||||
// Check for MIDI format
|
||||
else if (id[0] == MAKE_ID('M','T','h','d'))
|
||||
{
|
||||
return MIDI_MIDI;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MIDI_NOTMIDI;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// create a source based on MIDI file type
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT ZMusic_MidiSource ZMusic_CreateMIDISource(const uint8_t *data, size_t length, EMIDIType miditype)
|
||||
{
|
||||
try
|
||||
{
|
||||
MIDISource* source;
|
||||
switch (miditype)
|
||||
{
|
||||
case MIDI_MUS:
|
||||
source = new MUSSong2(data, length);
|
||||
break;
|
||||
|
||||
case MIDI_MIDI:
|
||||
source = new MIDISong2(data, length);
|
||||
break;
|
||||
|
||||
case MIDI_HMI:
|
||||
source = new HMISong(data, length);
|
||||
break;
|
||||
|
||||
case MIDI_XMI:
|
||||
source = new XMISong(data, length);
|
||||
break;
|
||||
|
||||
case MIDI_MIDS:
|
||||
source = new MIDSSong(data, length);
|
||||
break;
|
||||
|
||||
default:
|
||||
SetError("Unable to identify MIDI data");
|
||||
source = nullptr;
|
||||
break;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
catch (const std::exception & ex)
|
||||
{
|
||||
SetError(ex.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
250
libraries/ZMusic/source/midisources/midisource.h
Normal file
250
libraries/ZMusic/source/midisources/midisource.h
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
//
|
||||
// midisources.h
|
||||
// GZDoom
|
||||
//
|
||||
// Created by Christoph Oelckers on 23.02.18.
|
||||
//
|
||||
|
||||
#ifndef midisources_h
|
||||
#define midisources_h
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "zmusic/mus2midi.h"
|
||||
#include "zmusic/mididefs.h"
|
||||
|
||||
extern char MIDI_EventLengths[7];
|
||||
extern char MIDI_CommonLengths[15];
|
||||
|
||||
|
||||
// base class for the different MIDI sources --------------------------------------
|
||||
|
||||
class MIDISource
|
||||
{
|
||||
int Volume = 0xffff;
|
||||
int LoopLimit = 0;
|
||||
std::function<bool(int)> TempoCallback = [](int t) { return false; };
|
||||
|
||||
protected:
|
||||
|
||||
bool isLooping = false;
|
||||
bool skipSysex = false;
|
||||
int Division = 0;
|
||||
int Tempo = 500000;
|
||||
int InitialTempo = 500000;
|
||||
uint8_t ChannelVolumes[16];
|
||||
|
||||
int VolumeControllerChange(int channel, int volume);
|
||||
void SetTempo(int new_tempo);
|
||||
int ClampLoopCount(int loopcount);
|
||||
|
||||
|
||||
public:
|
||||
bool Exporting = false;
|
||||
|
||||
// Virtuals for subclasses to override
|
||||
virtual ~MIDISource() {}
|
||||
virtual void CheckCaps(int tech);
|
||||
virtual void DoInitialSetup() = 0;
|
||||
virtual void DoRestart() = 0;
|
||||
virtual bool CheckDone() = 0;
|
||||
virtual std::vector<uint16_t> PrecacheData();
|
||||
virtual bool SetMIDISubsong(int subsong);
|
||||
virtual uint32_t *MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time) = 0;
|
||||
|
||||
void StartPlayback(bool looped = true, int looplimit = 0)
|
||||
{
|
||||
Tempo = InitialTempo;
|
||||
LoopLimit = looplimit;
|
||||
isLooping = looped;
|
||||
}
|
||||
|
||||
void SkipSysex() { skipSysex = true; }
|
||||
|
||||
bool isValid() const { return Division > 0; }
|
||||
int getDivision() const { return Division; }
|
||||
int getInitialTempo() const { return InitialTempo; }
|
||||
int getTempo() const { return Tempo; }
|
||||
int getChannelVolume(int ch) const { return ChannelVolumes[ch]; }
|
||||
void setVolume(int vol) { Volume = vol; }
|
||||
void setLoopLimit(int lim) { LoopLimit = lim; }
|
||||
void setTempoCallback(std::function<bool(int)> cb)
|
||||
{
|
||||
TempoCallback = cb;
|
||||
}
|
||||
|
||||
void CreateSMF(std::vector<uint8_t> &file, int looplimit);
|
||||
|
||||
};
|
||||
|
||||
// MUS file played with a MIDI stream ---------------------------------------
|
||||
|
||||
class MUSSong2 : public MIDISource
|
||||
{
|
||||
public:
|
||||
MUSSong2(const uint8_t *data, size_t len);
|
||||
|
||||
protected:
|
||||
void DoInitialSetup() override;
|
||||
void DoRestart() override;
|
||||
bool CheckDone() override;
|
||||
std::vector<uint16_t> PrecacheData() override;
|
||||
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> MusData;
|
||||
uint8_t* MusBuffer;
|
||||
uint8_t LastVelocity[16];
|
||||
size_t MusP, MaxMusP;
|
||||
};
|
||||
|
||||
|
||||
// MIDI file played with a MIDI stream --------------------------------------
|
||||
|
||||
class MIDISong2 : public MIDISource
|
||||
{
|
||||
public:
|
||||
MIDISong2(const uint8_t* data, size_t len);
|
||||
|
||||
protected:
|
||||
void CheckCaps(int tech) override;
|
||||
void DoInitialSetup() override;
|
||||
void DoRestart() override;
|
||||
bool CheckDone() override;
|
||||
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
|
||||
|
||||
private:
|
||||
void AdvanceTracks(uint32_t time);
|
||||
|
||||
struct TrackInfo;
|
||||
|
||||
void ProcessInitialMetaEvents ();
|
||||
uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
|
||||
TrackInfo *FindNextDue ();
|
||||
|
||||
std::vector<uint8_t> MusHeader;
|
||||
std::vector<TrackInfo> Tracks;
|
||||
TrackInfo *TrackDue;
|
||||
int NumTracks;
|
||||
int Format;
|
||||
uint16_t DesignationMask;
|
||||
};
|
||||
|
||||
// HMI file played with a MIDI stream ---------------------------------------
|
||||
|
||||
struct AutoNoteOff
|
||||
{
|
||||
uint32_t Delay;
|
||||
uint8_t Channel, Key;
|
||||
};
|
||||
// Sorry, std::priority_queue, but I want to be able to modify the contents of the heap.
|
||||
class NoteOffQueue : public std::vector<AutoNoteOff>
|
||||
{
|
||||
public:
|
||||
void AddNoteOff(uint32_t delay, uint8_t channel, uint8_t key);
|
||||
void AdvanceTime(uint32_t time);
|
||||
bool Pop(AutoNoteOff &item);
|
||||
|
||||
protected:
|
||||
void Heapify();
|
||||
|
||||
unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; }
|
||||
unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; }
|
||||
unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; }
|
||||
};
|
||||
|
||||
class HMISong : public MIDISource
|
||||
{
|
||||
public:
|
||||
HMISong(const uint8_t* data, size_t len);
|
||||
|
||||
protected:
|
||||
|
||||
void DoInitialSetup() override;
|
||||
void DoRestart() override;
|
||||
bool CheckDone() override;
|
||||
void CheckCaps(int tech) override;
|
||||
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
|
||||
|
||||
private:
|
||||
void SetupForHMI(int len);
|
||||
void SetupForHMP(int len);
|
||||
void AdvanceTracks(uint32_t time);
|
||||
|
||||
struct TrackInfo;
|
||||
|
||||
void ProcessInitialMetaEvents ();
|
||||
uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
|
||||
TrackInfo *FindNextDue ();
|
||||
|
||||
static uint32_t ReadVarLenHMI(TrackInfo *);
|
||||
static uint32_t ReadVarLenHMP(TrackInfo *);
|
||||
|
||||
std::vector<uint8_t> MusHeader;
|
||||
int NumTracks;
|
||||
std::vector<TrackInfo> Tracks;
|
||||
TrackInfo *TrackDue;
|
||||
TrackInfo *FakeTrack;
|
||||
uint32_t (*ReadVarLen)(TrackInfo *);
|
||||
NoteOffQueue NoteOffs;
|
||||
};
|
||||
|
||||
// XMI file played with a MIDI stream ---------------------------------------
|
||||
|
||||
class XMISong : public MIDISource
|
||||
{
|
||||
public:
|
||||
XMISong(const uint8_t* data, size_t len);
|
||||
|
||||
protected:
|
||||
bool SetMIDISubsong(int subsong) override;
|
||||
void DoInitialSetup() override;
|
||||
void DoRestart() override;
|
||||
bool CheckDone() override;
|
||||
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
|
||||
|
||||
private:
|
||||
struct TrackInfo;
|
||||
enum EventSource { EVENT_None, EVENT_Real, EVENT_Fake };
|
||||
|
||||
int FindXMIDforms(const uint8_t *chunk, int len, TrackInfo *songs) const;
|
||||
void FoundXMID(const uint8_t *chunk, int len, TrackInfo *song) const;
|
||||
void AdvanceSong(uint32_t time);
|
||||
|
||||
void ProcessInitialMetaEvents();
|
||||
uint32_t *SendCommand (uint32_t *event, EventSource track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
|
||||
EventSource FindNextDue();
|
||||
|
||||
std::vector<uint8_t> MusHeader;
|
||||
int NumSongs;
|
||||
std::vector<TrackInfo> Songs;
|
||||
TrackInfo *CurrSong;
|
||||
NoteOffQueue NoteOffs;
|
||||
EventSource EventDue;
|
||||
};
|
||||
|
||||
// MIDS file played with a MIDI Stream
|
||||
|
||||
class MIDSSong : public MIDISource
|
||||
{
|
||||
public:
|
||||
MIDSSong(const uint8_t* data, size_t len);
|
||||
|
||||
protected:
|
||||
void DoInitialSetup() override;
|
||||
void DoRestart() override;
|
||||
bool CheckDone() override;
|
||||
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
|
||||
|
||||
private:
|
||||
std::vector<uint32_t> MidsBuffer;
|
||||
size_t MidsP, MaxMidsP;
|
||||
int FormatFlags;
|
||||
|
||||
void ProcessInitialTempoEvents();
|
||||
};
|
||||
|
||||
#endif /* midisources_h */
|
||||
996
libraries/ZMusic/source/midisources/midisource_hmi.cpp
Normal file
996
libraries/ZMusic/source/midisources/midisource_hmi.cpp
Normal file
|
|
@ -0,0 +1,996 @@
|
|||
/*
|
||||
** music_hmi_midiout.cpp
|
||||
** Code to let ZDoom play HMI MIDI music through the MIDI streaming API.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2010 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include "midisource.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
#define HMP_NEW_DATE "013195"
|
||||
#define HMI_SONG_MAGIC "HMI-MIDISONG061595"
|
||||
#define TRACK_MAGIC "HMI-MIDITRACK"
|
||||
|
||||
// Used by SendCommand to check for unexpected end-of-track conditions.
|
||||
#define CHECK_FINISHED \
|
||||
if (track->TrackP >= track->MaxTrackP) \
|
||||
{ \
|
||||
track->Finished = true; \
|
||||
return events; \
|
||||
}
|
||||
|
||||
// In song header
|
||||
#define HMI_DIVISION_OFFSET 0xD4
|
||||
#define HMI_TRACK_COUNT_OFFSET 0xE4
|
||||
#define HMI_TRACK_DIR_PTR_OFFSET 0xE8
|
||||
|
||||
#define HMP_DIVISION_OFFSET 0x38
|
||||
#define HMP_TRACK_COUNT_OFFSET 0x30
|
||||
#define HMP_DESIGNATIONS_OFFSET 0x94
|
||||
#define HMP_TRACK_OFFSET_0 0x308 // original HMP
|
||||
#define HMP_TRACK_OFFSET_1 0x388 // newer HMP
|
||||
|
||||
// In track header
|
||||
#define HMITRACK_DATA_PTR_OFFSET 0x57
|
||||
#define HMITRACK_DESIGNATION_OFFSET 0x99
|
||||
|
||||
#define HMPTRACK_LEN_OFFSET 4
|
||||
#define HMPTRACK_DESIGNATION_OFFSET 8
|
||||
#define HMPTRACK_MIDI_DATA_OFFSET 12
|
||||
|
||||
#define NUM_HMP_DESIGNATIONS 5
|
||||
#define NUM_HMI_DESIGNATIONS 8
|
||||
|
||||
// MIDI device types for designation
|
||||
#define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device)
|
||||
#define HMI_DEV_MPU401 0xA001 // MPU-401, Roland Sound Canvas, Ensoniq SoundScape, Rolad RAP-10
|
||||
#define HMI_DEV_OPL2 0xA002 // SoundBlaster (Pro), ESS AudioDrive
|
||||
#define HMI_DEV_MT32 0xA004 // MT-32
|
||||
#define HMI_DEV_SBAWE32 0xA008 // SoundBlaster AWE32
|
||||
#define HMI_DEV_OPL3 0xA009 // SoundBlaster 16, Microsoft Sound System, Pro Audio Spectrum 16
|
||||
#define HMI_DEV_GUS 0xA00A // Gravis UltraSound, Gravis UltraSound Max/Ace
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
struct HMISong::TrackInfo
|
||||
{
|
||||
const uint8_t *TrackBegin;
|
||||
size_t TrackP;
|
||||
size_t MaxTrackP;
|
||||
uint32_t Delay;
|
||||
uint32_t PlayedTime;
|
||||
uint16_t Designation[NUM_HMI_DESIGNATIONS];
|
||||
bool Enabled;
|
||||
bool Finished;
|
||||
uint8_t RunningStatus;
|
||||
|
||||
uint32_t ReadVarLenHMI();
|
||||
uint32_t ReadVarLenHMP();
|
||||
};
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong Constructor
|
||||
//
|
||||
// Buffers the file and does some validation of the HMI header.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
HMISong::HMISong (const uint8_t *data, size_t len)
|
||||
{
|
||||
if (len < 0x100)
|
||||
{ // Way too small to be HMI.
|
||||
return;
|
||||
}
|
||||
MusHeader.resize(len);
|
||||
memcpy(MusHeader.data(), data, len);
|
||||
NumTracks = 0;
|
||||
|
||||
// Do some validation of the MIDI file
|
||||
if (memcmp(&MusHeader[0], HMI_SONG_MAGIC, sizeof(HMI_SONG_MAGIC)) == 0)
|
||||
{
|
||||
SetupForHMI((int)len);
|
||||
}
|
||||
else if (memcmp(&MusHeader[0], "HMIMIDIP", 8) == 0)
|
||||
{
|
||||
SetupForHMP((int)len);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: SetupForHMI
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong::SetupForHMI(int len)
|
||||
{
|
||||
int i, p;
|
||||
|
||||
auto MusPtr = &MusHeader[0];
|
||||
|
||||
ReadVarLen = ReadVarLenHMI;
|
||||
NumTracks = GetShort(MusPtr + HMI_TRACK_COUNT_OFFSET);
|
||||
|
||||
if (NumTracks <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The division is the number of pulses per quarter note (PPQN).
|
||||
// HMI files have two values here, a full value and a quarter value. Some games,
|
||||
// notably Quarantines, have identical values for some reason, so it's safer to
|
||||
// use the quarter value and multiply it by four than to trust the full value.
|
||||
Division = GetShort(MusPtr + HMI_DIVISION_OFFSET) << 2;
|
||||
Tempo = InitialTempo = 4000000;
|
||||
|
||||
Tracks.resize(NumTracks + 1);
|
||||
int track_dir = GetInt(MusPtr + HMI_TRACK_DIR_PTR_OFFSET);
|
||||
|
||||
// Gather information about each track
|
||||
for (i = 0, p = 0; i < NumTracks; ++i)
|
||||
{
|
||||
int start = GetInt(MusPtr + track_dir + i*4);
|
||||
int tracklen, datastart;
|
||||
|
||||
if (start > len - HMITRACK_DESIGNATION_OFFSET - 4)
|
||||
{ // Track is incomplete.
|
||||
continue;
|
||||
}
|
||||
|
||||
// BTW, HMI does not actually check the track header.
|
||||
if (memcmp(MusPtr + start, TRACK_MAGIC, 13) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The track ends where the next one begins. If this is the
|
||||
// last track, then it ends at the end of the file.
|
||||
if (i == NumTracks - 1)
|
||||
{
|
||||
tracklen = len - start;
|
||||
}
|
||||
else
|
||||
{
|
||||
tracklen = GetInt(MusPtr + track_dir + i*4 + 4) - start;
|
||||
}
|
||||
// Clamp incomplete tracks to the end of the file.
|
||||
tracklen = std::min(tracklen, len - start);
|
||||
if (tracklen <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Offset to actual MIDI events.
|
||||
datastart = GetInt(MusPtr + start + HMITRACK_DATA_PTR_OFFSET);
|
||||
tracklen -= datastart;
|
||||
if (tracklen <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store track information
|
||||
Tracks[p].TrackBegin = MusPtr + start + datastart;
|
||||
Tracks[p].TrackP = 0;
|
||||
Tracks[p].MaxTrackP = tracklen;
|
||||
|
||||
// Retrieve track designations. We can't check them yet, since we have not yet
|
||||
// connected to the MIDI device.
|
||||
for (int ii = 0; ii < NUM_HMI_DESIGNATIONS; ++ii)
|
||||
{
|
||||
Tracks[p].Designation[ii] = GetShort(MusPtr + start + HMITRACK_DESIGNATION_OFFSET + ii*2);
|
||||
}
|
||||
|
||||
p++;
|
||||
}
|
||||
|
||||
// In case there were fewer actual chunks in the file than the
|
||||
// header specified, update NumTracks with the current value of p.
|
||||
NumTracks = p;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: SetupForHMP
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong::SetupForHMP(int len)
|
||||
{
|
||||
int track_data;
|
||||
int i, p;
|
||||
|
||||
auto MusPtr = &MusHeader[0];
|
||||
|
||||
ReadVarLen = ReadVarLenHMP;
|
||||
if (MusPtr[8] == 0)
|
||||
{
|
||||
track_data = HMP_TRACK_OFFSET_0;
|
||||
}
|
||||
else if (memcmp(MusPtr + 8, HMP_NEW_DATE, sizeof(HMP_NEW_DATE)) == 0)
|
||||
{
|
||||
track_data = HMP_TRACK_OFFSET_1;
|
||||
}
|
||||
else
|
||||
{ // unknown HMIMIDIP version
|
||||
return;
|
||||
}
|
||||
|
||||
NumTracks = GetInt(MusPtr + HMP_TRACK_COUNT_OFFSET);
|
||||
|
||||
if (NumTracks <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The division is the number of pulses per quarter note (PPQN).
|
||||
Division = GetInt(MusPtr + HMP_DIVISION_OFFSET);
|
||||
Tempo = InitialTempo = 1000000;
|
||||
|
||||
Tracks.resize(NumTracks + 1);
|
||||
|
||||
// Gather information about each track
|
||||
for (i = 0, p = 0; i < NumTracks; ++i)
|
||||
{
|
||||
int start = track_data;
|
||||
int tracklen;
|
||||
|
||||
if (start > len - HMPTRACK_MIDI_DATA_OFFSET)
|
||||
{ // Track is incomplete.
|
||||
break;
|
||||
}
|
||||
|
||||
tracklen = GetInt(MusPtr + start + HMPTRACK_LEN_OFFSET);
|
||||
track_data += tracklen;
|
||||
|
||||
// Clamp incomplete tracks to the end of the file.
|
||||
tracklen = std::min(tracklen, len - start);
|
||||
if (tracklen <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Subtract track header size.
|
||||
tracklen -= HMPTRACK_MIDI_DATA_OFFSET;
|
||||
if (tracklen <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store track information
|
||||
Tracks[p].TrackBegin = MusPtr + start + HMPTRACK_MIDI_DATA_OFFSET;
|
||||
Tracks[p].TrackP = 0;
|
||||
Tracks[p].MaxTrackP = tracklen;
|
||||
|
||||
// Retrieve track designations. We can't check them yet, since we have not yet
|
||||
// connected to the MIDI device.
|
||||
#if 0
|
||||
// This is completely a guess based on knowledge of how designations work with
|
||||
// HMI files. Some songs contain nothing but zeroes for this data, so I'd rather
|
||||
// not go around using it without confirmation.
|
||||
|
||||
Printf("Track %d: %d %08x %d: \034I", i, GetInt(MusPtr + start),
|
||||
GetInt(MusPtr + start + 4), GetInt(MusPtr + start + 8));
|
||||
|
||||
int designations = HMP_DESIGNATIONS_OFFSET +
|
||||
GetInt(MusPtr + start + HMPTRACK_DESIGNATION_OFFSET) * 4 * NUM_HMP_DESIGNATIONS;
|
||||
for (int ii = 0; ii < NUM_HMP_DESIGNATIONS; ++ii)
|
||||
{
|
||||
Printf(" %04x", GetInt(MusPtr + designations + ii*4));
|
||||
}
|
||||
Printf("\n");
|
||||
#endif
|
||||
Tracks[p].Designation[0] = HMI_DEV_GM;
|
||||
Tracks[p].Designation[1] = HMI_DEV_GUS;
|
||||
Tracks[p].Designation[2] = HMI_DEV_OPL2;
|
||||
Tracks[p].Designation[3] = 0;
|
||||
|
||||
p++;
|
||||
}
|
||||
|
||||
// In case there were fewer actual chunks in the file than the
|
||||
// header specified, update NumTracks with the current value of p.
|
||||
NumTracks = p;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: CheckCaps
|
||||
//
|
||||
// Check track designations and disable tracks that have not been
|
||||
// designated for the device we will be playing on.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong::CheckCaps(int tech)
|
||||
{
|
||||
// What's the equivalent HMI device for our technology?
|
||||
if (tech == MIDIDEV_FMSYNTH)
|
||||
{
|
||||
tech = HMI_DEV_OPL3;
|
||||
}
|
||||
else if (tech == MIDIDEV_MIDIPORT)
|
||||
{
|
||||
tech = HMI_DEV_MPU401;
|
||||
}
|
||||
else
|
||||
{ // Good enough? Or should we just say we're GM.
|
||||
tech = HMI_DEV_SBAWE32;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
Tracks[i].Enabled = false;
|
||||
// Track designations are stored in a 0-terminated array.
|
||||
for (unsigned int j = 0; j < NUM_HMI_DESIGNATIONS && Tracks[i].Designation[j] != 0; ++j)
|
||||
{
|
||||
if (Tracks[i].Designation[j] == tech)
|
||||
{
|
||||
Tracks[i].Enabled = true;
|
||||
}
|
||||
// If a track is designated for device 0xA000, it will be played by a MIDI
|
||||
// driver for device types 0xA000, 0xA001, and 0xA008. Why this does not
|
||||
// include the GUS, I do not know.
|
||||
else if (Tracks[i].Designation[j] == HMI_DEV_GM)
|
||||
{
|
||||
Tracks[i].Enabled = (tech == HMI_DEV_MPU401 || tech == HMI_DEV_SBAWE32);
|
||||
}
|
||||
// If a track is designated for device 0xA002, it will be played by a MIDI
|
||||
// driver for device types 0xA002 or 0xA009.
|
||||
else if (Tracks[i].Designation[j] == HMI_DEV_OPL2)
|
||||
{
|
||||
Tracks[i].Enabled = (tech == HMI_DEV_OPL3);
|
||||
}
|
||||
// Any other designation must match the specific MIDI driver device number.
|
||||
// (Which we handled first above.)
|
||||
|
||||
if (Tracks[i].Enabled)
|
||||
{ // This track's been enabled, so we can stop checking other designations.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: DoInitialSetup
|
||||
//
|
||||
// Sets the starting channel volumes.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong :: DoInitialSetup()
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
ChannelVolumes[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: DoRestart
|
||||
//
|
||||
// Rewinds every track.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong :: DoRestart()
|
||||
{
|
||||
int i;
|
||||
|
||||
// Set initial state.
|
||||
FakeTrack = &Tracks[NumTracks];
|
||||
NoteOffs.clear();
|
||||
for (i = 0; i <= NumTracks; ++i)
|
||||
{
|
||||
Tracks[i].TrackP = 0;
|
||||
Tracks[i].Finished = false;
|
||||
Tracks[i].RunningStatus = 0;
|
||||
Tracks[i].PlayedTime = 0;
|
||||
}
|
||||
ProcessInitialMetaEvents ();
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
Tracks[i].Delay = ReadVarLen(&Tracks[i]);
|
||||
}
|
||||
Tracks[i].Delay = 0; // for the FakeTrack
|
||||
Tracks[i].Enabled = true;
|
||||
TrackDue = Tracks.data();
|
||||
TrackDue = FindNextDue();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: CheckDone
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool HMISong::CheckDone()
|
||||
{
|
||||
return TrackDue == nullptr;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: MakeEvents
|
||||
//
|
||||
// Copies MIDI events from the file and puts them into a MIDI stream
|
||||
// buffer. Returns the new position in the buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *HMISong::MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time)
|
||||
{
|
||||
uint32_t *start_events;
|
||||
uint32_t tot_time = 0;
|
||||
uint32_t time = 0;
|
||||
uint32_t delay;
|
||||
|
||||
start_events = events;
|
||||
while (TrackDue && events < max_event_p && tot_time <= max_time)
|
||||
{
|
||||
// It's possible that this tick may be nothing but meta-events and
|
||||
// not generate any real events. Repeat this until we actually
|
||||
// get some output so we don't send an empty buffer to the MIDI
|
||||
// device.
|
||||
do
|
||||
{
|
||||
delay = TrackDue->Delay;
|
||||
time += delay;
|
||||
// Advance time for all tracks by the amount needed for the one up next.
|
||||
tot_time += delay * Tempo / Division;
|
||||
AdvanceTracks(delay);
|
||||
// Play all events for this tick.
|
||||
do
|
||||
{
|
||||
bool sysex_noroom = false;
|
||||
uint32_t *new_events = SendCommand(events, TrackDue, time, max_event_p - events, sysex_noroom);
|
||||
if (sysex_noroom)
|
||||
{
|
||||
return events;
|
||||
}
|
||||
TrackDue = FindNextDue();
|
||||
if (new_events != events)
|
||||
{
|
||||
time = 0;
|
||||
}
|
||||
events = new_events;
|
||||
}
|
||||
while (TrackDue && TrackDue->Delay == 0 && events < max_event_p);
|
||||
}
|
||||
while (start_events == events && TrackDue);
|
||||
time = 0;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: AdvanceTracks
|
||||
//
|
||||
// Advances time for all tracks by the specified amount.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong::AdvanceTracks(uint32_t time)
|
||||
{
|
||||
for (int i = 0; i <= NumTracks; ++i)
|
||||
{
|
||||
if (Tracks[i].Enabled && !Tracks[i].Finished)
|
||||
{
|
||||
Tracks[i].Delay -= time;
|
||||
Tracks[i].PlayedTime += time;
|
||||
}
|
||||
}
|
||||
NoteOffs.AdvanceTime(time);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: SendCommand
|
||||
//
|
||||
// Places a single MIDIEVENT in the event buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *HMISong::SendCommand (uint32_t *events, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom)
|
||||
{
|
||||
uint32_t len;
|
||||
uint8_t event, data1 = 0, data2 = 0;
|
||||
|
||||
// If the next event comes from the fake track, pop an entry off the note-off queue.
|
||||
if (track == FakeTrack)
|
||||
{
|
||||
AutoNoteOff off;
|
||||
NoteOffs.Pop(off);
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = MIDI_NOTEON | off.Channel | (off.Key << 8);
|
||||
return events + 3;
|
||||
}
|
||||
|
||||
sysex_noroom = false;
|
||||
size_t start_p = track->TrackP;
|
||||
|
||||
CHECK_FINISHED
|
||||
event = track->TrackBegin[track->TrackP++];
|
||||
CHECK_FINISHED
|
||||
|
||||
// The actual event type will be filled in below. If it's not a NOP,
|
||||
// the events pointer will be advanced once the actual event is written.
|
||||
// Otherwise, we do it at the end of the function.
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = MEVENT_NOP << 24;
|
||||
|
||||
if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe)
|
||||
{
|
||||
// Normal short message
|
||||
if ((event & 0xF0) == 0xF0)
|
||||
{
|
||||
if (MIDI_CommonLengths[event & 15] > 0)
|
||||
{
|
||||
data1 = track->TrackBegin[track->TrackP++];
|
||||
if (MIDI_CommonLengths[event & 15] > 1)
|
||||
{
|
||||
data2 = track->TrackBegin[track->TrackP++];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((event & 0x80) == 0)
|
||||
{
|
||||
data1 = event;
|
||||
event = track->RunningStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
track->RunningStatus = event;
|
||||
data1 = track->TrackBegin[track->TrackP++];
|
||||
}
|
||||
|
||||
CHECK_FINISHED
|
||||
|
||||
if (MIDI_EventLengths[(event&0x70)>>4] == 2)
|
||||
{
|
||||
data2 = track->TrackBegin[track->TrackP++];
|
||||
}
|
||||
|
||||
// Monitor channel volume controller changes.
|
||||
if ((event & 0x70) == (MIDI_CTRLCHANGE & 0x70) && data1 == 7)
|
||||
{
|
||||
data2 = VolumeControllerChange(event & 15, data2);
|
||||
}
|
||||
|
||||
if (event != MIDI_META)
|
||||
{
|
||||
events[2] = event | (data1<<8) | (data2<<16);
|
||||
}
|
||||
|
||||
if (ReadVarLen == ReadVarLenHMI && (event & 0x70) == (MIDI_NOTEON & 0x70))
|
||||
{ // HMI note on events include the time until an implied note off event.
|
||||
NoteOffs.AddNoteOff(track->ReadVarLenHMI(), event & 0x0F, data1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// SysEx events could potentially not have enough room in the buffer...
|
||||
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
|
||||
{
|
||||
len = ReadVarLen(track);
|
||||
if (len >= (MAX_MIDI_EVENTS-1)*3*4 || skipSysex)
|
||||
{ // This message will never fit. Throw it away.
|
||||
track->TrackP += len;
|
||||
}
|
||||
else if (len + 12 >= (size_t)room * 4)
|
||||
{ // Not enough room left in this buffer. Backup and wait for the next one.
|
||||
track->TrackP = start_p;
|
||||
sysex_noroom = true;
|
||||
return events;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t *msg = (uint8_t *)&events[3];
|
||||
if (event == MIDI_SYSEX)
|
||||
{ // Need to add the SysEx marker to the message.
|
||||
events[2] = (MEVENT_LONGMSG << 24) | (len + 1);
|
||||
*msg++ = MIDI_SYSEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
events[2] = (MEVENT_LONGMSG << 24) | len;
|
||||
}
|
||||
memcpy(msg, &track->TrackBegin[track->TrackP], len);
|
||||
msg += len;
|
||||
// Must pad with 0
|
||||
while ((size_t)msg & 3)
|
||||
{
|
||||
*msg++ = 0;
|
||||
}
|
||||
track->TrackP += len;
|
||||
}
|
||||
}
|
||||
else if (event == MIDI_META)
|
||||
{
|
||||
// It's a meta-event
|
||||
event = track->TrackBegin[track->TrackP++];
|
||||
CHECK_FINISHED
|
||||
len = ReadVarLen(track);
|
||||
CHECK_FINISHED
|
||||
|
||||
if (track->TrackP + len <= track->MaxTrackP)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case MIDI_META_EOT:
|
||||
track->Finished = true;
|
||||
break;
|
||||
|
||||
case MIDI_META_TEMPO:
|
||||
Tempo =
|
||||
(track->TrackBegin[track->TrackP+0]<<16) |
|
||||
(track->TrackBegin[track->TrackP+1]<<8) |
|
||||
(track->TrackBegin[track->TrackP+2]);
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = (MEVENT_TEMPO << 24) | Tempo;
|
||||
break;
|
||||
}
|
||||
track->TrackP += len;
|
||||
if (track->TrackP == track->MaxTrackP)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
else if (event == 0xFE)
|
||||
{ // Skip unknown HMI events.
|
||||
event = track->TrackBegin[track->TrackP++];
|
||||
CHECK_FINISHED
|
||||
if (event == 0x13 || event == 0x15)
|
||||
{
|
||||
track->TrackP += 6;
|
||||
}
|
||||
else if (event == 0x12 || event == 0x14)
|
||||
{
|
||||
track->TrackP += 2;
|
||||
}
|
||||
else if (event == 0x10)
|
||||
{
|
||||
track->TrackP += 2;
|
||||
CHECK_FINISHED
|
||||
track->TrackP += track->TrackBegin[track->TrackP] + 5;
|
||||
CHECK_FINISHED
|
||||
}
|
||||
else
|
||||
{ // No idea.
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!track->Finished)
|
||||
{
|
||||
track->Delay = ReadVarLen(track);
|
||||
}
|
||||
// Advance events pointer unless this is a non-delaying NOP.
|
||||
if (events[0] != 0 || MEVENT_EVENTTYPE(events[2]) != MEVENT_NOP)
|
||||
{
|
||||
if (MEVENT_EVENTTYPE(events[2]) == MEVENT_LONGMSG)
|
||||
{
|
||||
events += 3 + ((MEVENT_EVENTPARM(events[2]) + 3) >> 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
events += 3;
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: ProcessInitialMetaEvents
|
||||
//
|
||||
// Handle all the meta events at the start of each track.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void HMISong::ProcessInitialMetaEvents ()
|
||||
{
|
||||
TrackInfo *track;
|
||||
int i;
|
||||
uint8_t event;
|
||||
uint32_t len;
|
||||
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
track = &Tracks[i];
|
||||
while (!track->Finished &&
|
||||
track->TrackP < track->MaxTrackP - 4 &&
|
||||
track->TrackBegin[track->TrackP] == 0 &&
|
||||
track->TrackBegin[track->TrackP+1] == 0xFF)
|
||||
{
|
||||
event = track->TrackBegin[track->TrackP+2];
|
||||
track->TrackP += 3;
|
||||
len = ReadVarLen(track);
|
||||
if (track->TrackP + len <= track->MaxTrackP)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case MIDI_META_EOT:
|
||||
track->Finished = true;
|
||||
break;
|
||||
|
||||
case MIDI_META_TEMPO:
|
||||
SetTempo(
|
||||
(track->TrackBegin[track->TrackP+0]<<16) |
|
||||
(track->TrackBegin[track->TrackP+1]<<8) |
|
||||
(track->TrackBegin[track->TrackP+2])
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
track->TrackP += len;
|
||||
}
|
||||
if (track->TrackP >= track->MaxTrackP - 4)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: ReadVarLenHMI static
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t HMISong::ReadVarLenHMI(TrackInfo *track)
|
||||
{
|
||||
return track->ReadVarLenHMI();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: ReadVarLenHMP static
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t HMISong::ReadVarLenHMP(TrackInfo *track)
|
||||
{
|
||||
return track->ReadVarLenHMP();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: TrackInfo :: ReadVarLenHMI
|
||||
//
|
||||
// Reads a variable-length SMF number.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t HMISong::TrackInfo::ReadVarLenHMI()
|
||||
{
|
||||
uint32_t time = 0, t = 0x80;
|
||||
|
||||
while ((t & 0x80) && TrackP < MaxTrackP)
|
||||
{
|
||||
t = TrackBegin[TrackP++];
|
||||
time = (time << 7) | (t & 127);
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: TrackInfo :: ReadVarLenHMP
|
||||
//
|
||||
// Reads a variable-length HMP number. This is similar to the standard SMF
|
||||
// variable length number, except it's stored little-endian, and the high
|
||||
// bit set means the number is done.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t HMISong::TrackInfo::ReadVarLenHMP()
|
||||
{
|
||||
uint32_t time = 0;
|
||||
uint8_t t = 0;
|
||||
int off = 0;
|
||||
|
||||
while (!(t & 0x80) && TrackP < MaxTrackP)
|
||||
{
|
||||
t = TrackBegin[TrackP++];
|
||||
time |= (t & 127) << off;
|
||||
off += 7;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// NoteOffQueue :: AddNoteOff
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void NoteOffQueue::AddNoteOff(uint32_t delay, uint8_t channel, uint8_t key)
|
||||
{
|
||||
uint32_t i = (uint32_t)size();
|
||||
resize(i + 1);
|
||||
while (i > 0 && (*this)[Parent(i)].Delay > delay)
|
||||
{
|
||||
(*this)[i] = (*this)[Parent(i)];
|
||||
i = Parent(i);
|
||||
}
|
||||
(*this)[i].Delay = delay;
|
||||
(*this)[i].Channel = channel;
|
||||
(*this)[i].Key = key;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// NoteOffQueue :: Pop
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool NoteOffQueue::Pop(AutoNoteOff &item)
|
||||
{
|
||||
if (size() > 0)
|
||||
{
|
||||
item = front();
|
||||
front() = back();
|
||||
pop_back();
|
||||
Heapify();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// NoteOffQueue :: AdvanceTime
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void NoteOffQueue::AdvanceTime(uint32_t time)
|
||||
{
|
||||
// Because the time is decreasing by the same amount for every entry,
|
||||
// the heap property is maintained.
|
||||
for (auto &item : *this)
|
||||
{
|
||||
assert(item.Delay >= time);
|
||||
item.Delay -= time;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// NoteOffQueue :: Heapify
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void NoteOffQueue::Heapify()
|
||||
{
|
||||
unsigned int i = 0;
|
||||
for (;;)
|
||||
{
|
||||
unsigned int l = Left(i);
|
||||
unsigned int r = Right(i);
|
||||
unsigned int smallest = i;
|
||||
if (l < (unsigned)size() && (*this)[l].Delay < (*this)[i].Delay)
|
||||
{
|
||||
smallest = l;
|
||||
}
|
||||
if (r < (unsigned)size() && (*this)[r].Delay < (*this)[smallest].Delay)
|
||||
{
|
||||
smallest = r;
|
||||
}
|
||||
if (smallest == i)
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::swap((*this)[i], (*this)[smallest]);
|
||||
i = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HMISong :: FindNextDue
|
||||
//
|
||||
// Scans every track for the next event to play. Returns nullptr if all events
|
||||
// have been consumed.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
HMISong::TrackInfo *HMISong::FindNextDue ()
|
||||
{
|
||||
TrackInfo *track;
|
||||
uint32_t best;
|
||||
int i;
|
||||
|
||||
// Give precedence to whichever track last had events taken from it.
|
||||
if (TrackDue != FakeTrack && !TrackDue->Finished && TrackDue->Delay == 0)
|
||||
{
|
||||
return TrackDue;
|
||||
}
|
||||
if (TrackDue == FakeTrack && NoteOffs.size() != 0 && NoteOffs[0].Delay == 0)
|
||||
{
|
||||
FakeTrack->Delay = 0;
|
||||
return FakeTrack;
|
||||
}
|
||||
|
||||
// Check regular tracks.
|
||||
track = nullptr;
|
||||
best = 0xFFFFFFFF;
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
if (Tracks[i].Enabled && !Tracks[i].Finished && Tracks[i].Delay < best)
|
||||
{
|
||||
best = Tracks[i].Delay;
|
||||
track = &Tracks[i];
|
||||
}
|
||||
}
|
||||
// Check automatic note-offs.
|
||||
if (NoteOffs.size() != 0 && NoteOffs[0].Delay <= best)
|
||||
{
|
||||
FakeTrack->Delay = NoteOffs[0].Delay;
|
||||
return FakeTrack;
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
192
libraries/ZMusic/source/midisources/midisource_mids.cpp
Normal file
192
libraries/ZMusic/source/midisources/midisource_mids.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
** midisource_mids.cpp
|
||||
** Code to let ZMusic play MIDS MIDI music through the MIDI streaming API.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2020 Cacodemon345
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <algorithm>
|
||||
#include "midisource.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDSSong constructor
|
||||
//
|
||||
// Reads the buffers from the file and validates the MIDS header
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDSSong::MIDSSong(const uint8_t* data, size_t len)
|
||||
{
|
||||
if (len <= 52)
|
||||
return;
|
||||
|
||||
if ((len % 4) != 0)
|
||||
return;
|
||||
|
||||
// Validate the header first.
|
||||
if (data[12] != 'f' || data[13] != 'm' || data[14] != 't' || data[15] != ' ')
|
||||
{
|
||||
return;
|
||||
}
|
||||
int headerSize = LittleLong(GetInt(&data[16]));
|
||||
if (headerSize != 12) return;
|
||||
Division = LittleLong(GetInt(&data[20]));
|
||||
FormatFlags = LittleLong(GetInt(&data[28]));
|
||||
// Validate the data chunk.
|
||||
if (data[32] != 'd' || data[33] != 'a' || data[34] != 't' || data[35] != 'a')
|
||||
{
|
||||
return;
|
||||
}
|
||||
int NumBlocks = LittleLong(GetInt(&data[40]));
|
||||
const uint32_t* midiData = (const uint32_t*)&data[44];
|
||||
uint32_t tkStart = 0;
|
||||
uint32_t cbBuffer = 0;
|
||||
while (NumBlocks-- > 0)
|
||||
{
|
||||
tkStart = LittleLong(*midiData);
|
||||
cbBuffer = LittleLong(*(midiData + 1));
|
||||
midiData += 2;
|
||||
if ((cbBuffer % (FormatFlags ? 8 : 12)) != 0) return;
|
||||
MidsBuffer.insert(MidsBuffer.end(), midiData, midiData + (cbBuffer / 4));
|
||||
midiData += cbBuffer / 4;
|
||||
}
|
||||
MidsP = 0;
|
||||
MaxMidsP = MidsBuffer.size() - 1;
|
||||
for (auto& curMidiData : MidsBuffer)
|
||||
{
|
||||
curMidiData = LittleLong(curMidiData);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDSSong :: DoInitialSetup
|
||||
//
|
||||
// Sets the starting channel volumes.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDSSong::DoInitialSetup()
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
ChannelVolumes[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDSSong :: CheckDone
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MIDSSong::CheckDone()
|
||||
{
|
||||
return MidsP >= MaxMidsP;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDSSong :: DoRestart()
|
||||
//
|
||||
// Rewinds the song
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDSSong::DoRestart()
|
||||
{
|
||||
MidsP = 0;
|
||||
ProcessInitialTempoEvents();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDSSong :: ProcessInitialTempoEvents()
|
||||
//
|
||||
// Process initial tempo events at the start of the song.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDSSong::ProcessInitialTempoEvents()
|
||||
{
|
||||
if (MEVENT_EVENTTYPE(MidsBuffer[FormatFlags ? 1 : 2]) == MEVENT_TEMPO)
|
||||
{
|
||||
SetTempo(MEVENT_EVENTPARM(MidsBuffer[FormatFlags ? 1 : 2]));
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 :: MakeEvents
|
||||
//
|
||||
// Puts MIDS events into a MIDI stream
|
||||
// buffer. Returns the new position in the buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t* MIDSSong::MakeEvents(uint32_t* events, uint32_t* max_event_p, uint32_t max_time)
|
||||
{
|
||||
uint32_t time = 0;
|
||||
uint32_t tot_time = 0;
|
||||
|
||||
max_time = max_time * Division / Tempo;
|
||||
while (events < max_event_p && tot_time <= max_time)
|
||||
{
|
||||
events[0] = time = MidsBuffer[MidsP++];
|
||||
events[1] = FormatFlags ? 0 : MidsBuffer[MidsP++];
|
||||
events[2] = MidsBuffer[MidsP++];
|
||||
events += 3;
|
||||
tot_time += time;
|
||||
if (MidsP >= MaxMidsP) break;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
365
libraries/ZMusic/source/midisources/midisource_mus.cpp
Normal file
365
libraries/ZMusic/source/midisources/midisource_mus.cpp
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
** music_mus_midiout.cpp
|
||||
** Code to let ZDoom play MUS music through the MIDI streaming API.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <algorithm>
|
||||
#include "midisource.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
int MUSHeaderSearch(const uint8_t *head, int len);
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
static const uint8_t CtrlTranslate[15] =
|
||||
{
|
||||
0, // program change
|
||||
0, // bank select
|
||||
1, // modulation pot
|
||||
7, // volume
|
||||
10, // pan pot
|
||||
11, // expression pot
|
||||
91, // reverb depth
|
||||
93, // chorus depth
|
||||
64, // sustain pedal
|
||||
67, // soft pedal
|
||||
120, // all sounds off
|
||||
123, // all notes off
|
||||
126, // mono
|
||||
127, // poly
|
||||
121, // reset all controllers
|
||||
};
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 Constructor
|
||||
//
|
||||
// Performs some validity checks on the MUS file, buffers it, and creates
|
||||
// the playback thread control events.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MUSSong2::MUSSong2 (const uint8_t *data, size_t len)
|
||||
{
|
||||
int start;
|
||||
|
||||
// To tolerate sloppy wads (diescum.wad, I'm looking at you), we search
|
||||
// the first 32 bytes of the file for a signature. My guess is that DMX
|
||||
// does no validation whatsoever and just assumes it was passed a valid
|
||||
// MUS file, since where the header is offset affects how it plays.
|
||||
start = MUSHeaderSearch(data, 32);
|
||||
if (start < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
data += start;
|
||||
len -= start;
|
||||
|
||||
// Read the remainder of the song.
|
||||
if (len < sizeof(MUSHeader))
|
||||
{ // It's too short.
|
||||
return;
|
||||
}
|
||||
MusData.resize(len);
|
||||
memcpy(MusData.data(), data, len);
|
||||
auto MusHeader = (MUSHeader*)MusData.data();
|
||||
|
||||
// Do some validation of the MUS file.
|
||||
if (LittleShort(MusHeader->NumChans) > 15)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MusBuffer = MusData.data() + LittleShort(MusHeader->SongStart);
|
||||
MaxMusP = std::min<int>(LittleShort(MusHeader->SongLen), int(len) - LittleShort(MusHeader->SongStart));
|
||||
Division = 140;
|
||||
Tempo = InitialTempo = 1000000;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 :: DoInitialSetup
|
||||
//
|
||||
// Sets up initial velocities and channel volumes.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MUSSong2::DoInitialSetup()
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
LastVelocity[i] = 127;
|
||||
ChannelVolumes[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 :: DoRestart
|
||||
//
|
||||
// Rewinds the song.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MUSSong2::DoRestart()
|
||||
{
|
||||
MusP = 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 :: CheckDone
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MUSSong2::CheckDone()
|
||||
{
|
||||
return MusP >= MaxMusP;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 :: Precache
|
||||
//
|
||||
// MUS songs contain information in their header for exactly this purpose.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::vector<uint16_t> MUSSong2::PrecacheData()
|
||||
{
|
||||
auto MusHeader = (MUSHeader*)MusData.data();
|
||||
std::vector<uint16_t> work;
|
||||
const uint8_t *used = MusData.data() + sizeof(MUSHeader) / sizeof(uint8_t);
|
||||
int i, k;
|
||||
|
||||
int numinstr = LittleShort(MusHeader->NumInstruments);
|
||||
work.reserve(LittleShort(MusHeader->NumInstruments));
|
||||
for (i = k = 0; i < numinstr; ++i)
|
||||
{
|
||||
uint8_t instr = used[k++];
|
||||
uint16_t val;
|
||||
if (instr < 128)
|
||||
{
|
||||
val = instr;
|
||||
}
|
||||
else if (instr >= 135 && instr <= 188)
|
||||
{ // Percussions are 100-based, not 128-based, eh?
|
||||
val = instr - 100 + (1 << 14);
|
||||
}
|
||||
else
|
||||
{
|
||||
// skip it.
|
||||
val = used[k++];
|
||||
k += val;
|
||||
continue;
|
||||
}
|
||||
|
||||
int numbanks = used[k++];
|
||||
if (numbanks > 0)
|
||||
{
|
||||
for (int b = 0; b < numbanks; b++)
|
||||
{
|
||||
work.push_back(val | (used[k++] << 7));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
work.push_back(val);
|
||||
}
|
||||
}
|
||||
return work;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSSong2 :: MakeEvents
|
||||
//
|
||||
// Translates MUS events into MIDI events and puts them into a MIDI stream
|
||||
// buffer. Returns the new position in the buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *MUSSong2::MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time)
|
||||
{
|
||||
uint32_t tot_time = 0;
|
||||
uint32_t time = 0;
|
||||
auto MusHeader = (MUSHeader*)MusData.data();
|
||||
|
||||
max_time = max_time * Division / Tempo;
|
||||
|
||||
while (events < max_event_p && tot_time <= max_time)
|
||||
{
|
||||
uint8_t mid1, mid2;
|
||||
uint8_t channel;
|
||||
uint8_t t = 0, status;
|
||||
uint8_t event = MusBuffer[MusP++];
|
||||
|
||||
if ((event & 0x70) != MUS_SCOREEND)
|
||||
{
|
||||
t = MusBuffer[MusP++];
|
||||
}
|
||||
channel = event & 15;
|
||||
|
||||
// Map MUS channels to MIDI channels
|
||||
if (channel == 15)
|
||||
{
|
||||
channel = 9;
|
||||
}
|
||||
else if (channel >= 9)
|
||||
{
|
||||
channel = channel + 1;
|
||||
}
|
||||
|
||||
status = channel;
|
||||
|
||||
switch (event & 0x70)
|
||||
{
|
||||
case MUS_NOTEOFF:
|
||||
status |= MIDI_NOTEON;
|
||||
mid1 = t;
|
||||
mid2 = 0;
|
||||
break;
|
||||
|
||||
case MUS_NOTEON:
|
||||
status |= MIDI_NOTEON;
|
||||
mid1 = t & 127;
|
||||
if (t & 128)
|
||||
{
|
||||
LastVelocity[channel] = MusBuffer[MusP++];
|
||||
}
|
||||
mid2 = LastVelocity[channel];
|
||||
break;
|
||||
|
||||
case MUS_PITCHBEND:
|
||||
status |= MIDI_PITCHBEND;
|
||||
mid1 = (t & 1) << 6;
|
||||
mid2 = (t >> 1) & 127;
|
||||
break;
|
||||
|
||||
case MUS_SYSEVENT:
|
||||
status |= MIDI_CTRLCHANGE;
|
||||
mid1 = CtrlTranslate[t];
|
||||
mid2 = t == 12 ? LittleShort(MusHeader->NumChans) : 0;
|
||||
break;
|
||||
|
||||
case MUS_CTRLCHANGE:
|
||||
if (t == 0)
|
||||
{ // program change
|
||||
status |= MIDI_PRGMCHANGE;
|
||||
mid1 = MusBuffer[MusP++];
|
||||
mid2 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
status |= MIDI_CTRLCHANGE;
|
||||
mid1 = CtrlTranslate[t];
|
||||
mid2 = MusBuffer[MusP++];
|
||||
if (mid1 == 7)
|
||||
{ // Clamp volume to 127, since DMX apparently allows 8-bit volumes.
|
||||
// Fix courtesy of Gez, courtesy of Ben Ryves.
|
||||
mid2 = VolumeControllerChange(channel, std::min<int>(mid2, 0x7F));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MUS_SCOREEND:
|
||||
default:
|
||||
MusP = MaxMusP;
|
||||
goto end;
|
||||
}
|
||||
|
||||
events[0] = time; // dwDeltaTime
|
||||
events[1] = 0; // dwStreamID
|
||||
events[2] = status | (mid1 << 8) | (mid2 << 16);
|
||||
events += 3;
|
||||
|
||||
time = 0;
|
||||
if (event & 128)
|
||||
{
|
||||
do
|
||||
{
|
||||
t = MusBuffer[MusP++];
|
||||
time = (time << 7) | (t & 127);
|
||||
}
|
||||
while (t & 128);
|
||||
}
|
||||
tot_time += time;
|
||||
}
|
||||
end:
|
||||
if (time != 0)
|
||||
{
|
||||
events[0] = time; // dwDeltaTime
|
||||
events[1] = 0; // dwStreamID
|
||||
events[2] = MEVENT_NOP << 24; // dwEvent
|
||||
events += 3;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MUSHeaderSearch
|
||||
//
|
||||
// Searches for the MUS header within the given memory block, returning
|
||||
// the offset it was found at, or -1 if not present.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MUSHeaderSearch(const uint8_t *head, int len)
|
||||
{
|
||||
len -= 4;
|
||||
for (int i = 0; i <= len; ++i)
|
||||
{
|
||||
if (head[i+0] == 'M' && head[i+1] == 'U' && head[i+2] == 'S' && head[i+3] == 0x1A)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
801
libraries/ZMusic/source/midisources/midisource_smf.cpp
Normal file
801
libraries/ZMusic/source/midisources/midisource_smf.cpp
Normal file
|
|
@ -0,0 +1,801 @@
|
|||
/*
|
||||
** music_midi_midiout.cpp
|
||||
** Code to let ZDoom play SMF MIDI music through the MIDI streaming API.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
** This file also supports the Apogee Sound System's EMIDI files. That
|
||||
** basically means you can play the Duke3D songs without any editing and
|
||||
** have them sound right.
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "midisource.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// Used by SendCommand to check for unexpected end-of-track conditions.
|
||||
#define CHECK_FINISHED \
|
||||
if (track->TrackP >= track->MaxTrackP) \
|
||||
{ \
|
||||
track->Finished = true; \
|
||||
return events; \
|
||||
}
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
struct MIDISong2::TrackInfo
|
||||
{
|
||||
const uint8_t *TrackBegin;
|
||||
size_t TrackP;
|
||||
size_t MaxTrackP;
|
||||
uint32_t Delay;
|
||||
uint32_t PlayedTime;
|
||||
bool Finished;
|
||||
uint8_t RunningStatus;
|
||||
bool Designated;
|
||||
bool EProgramChange;
|
||||
bool EVolume;
|
||||
uint16_t Designation;
|
||||
|
||||
size_t LoopBegin;
|
||||
uint32_t LoopDelay;
|
||||
int LoopCount;
|
||||
bool LoopFinished;
|
||||
|
||||
uint32_t ReadVarLen ();
|
||||
};
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 Constructor
|
||||
//
|
||||
// Buffers the file and does some validation of the SMF header.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDISong2::MIDISong2 (const uint8_t* data, size_t len)
|
||||
: MusHeader(0), Tracks(0)
|
||||
{
|
||||
unsigned p;
|
||||
int i;
|
||||
|
||||
MusHeader.resize(len);
|
||||
memcpy(MusHeader.data(), data, len);
|
||||
|
||||
// Do some validation of the MIDI file
|
||||
if (MusHeader[4] != 0 || MusHeader[5] != 0 || MusHeader[6] != 0 || MusHeader[7] != 6)
|
||||
return;
|
||||
|
||||
if (MusHeader[8] != 0 || MusHeader[9] > 2)
|
||||
return;
|
||||
|
||||
Format = MusHeader[9];
|
||||
|
||||
if (Format == 0)
|
||||
{
|
||||
NumTracks = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
NumTracks = MusHeader[10] * 256 + MusHeader[11];
|
||||
}
|
||||
|
||||
// The division is the number of pulses per quarter note (PPQN).
|
||||
Division = MusHeader[12] * 256 + MusHeader[13];
|
||||
if (Division == 0)
|
||||
{ // PPQN is zero? Then the song cannot play because it never pulses.
|
||||
return;
|
||||
}
|
||||
|
||||
Tracks.resize(NumTracks);
|
||||
|
||||
// Gather information about each track
|
||||
for (i = 0, p = 14; i < NumTracks && p < MusHeader.size() + 8; ++i)
|
||||
{
|
||||
uint32_t chunkLen =
|
||||
(MusHeader[p+4]<<24) |
|
||||
(MusHeader[p+5]<<16) |
|
||||
(MusHeader[p+6]<<8) |
|
||||
(MusHeader[p+7]);
|
||||
|
||||
if (chunkLen + p + 8 > MusHeader.size())
|
||||
{ // Track too long, so truncate it
|
||||
chunkLen = (uint32_t)MusHeader.size() - p - 8;
|
||||
}
|
||||
|
||||
if (MusHeader[p+0] == 'M' &&
|
||||
MusHeader[p+1] == 'T' &&
|
||||
MusHeader[p+2] == 'r' &&
|
||||
MusHeader[p+3] == 'k')
|
||||
{
|
||||
Tracks[i].TrackBegin = &MusHeader[p + 8];
|
||||
Tracks[i].TrackP = 0;
|
||||
Tracks[i].MaxTrackP = chunkLen;
|
||||
}
|
||||
|
||||
p += chunkLen + 8;
|
||||
}
|
||||
|
||||
// In case there were fewer actual chunks in the file than the
|
||||
// header specified, update NumTracks with the current value of i
|
||||
NumTracks = i;
|
||||
|
||||
if (NumTracks == 0)
|
||||
{ // No tracks, so nothing to play
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: CheckCaps
|
||||
//
|
||||
// Find out if this is an FM synth or not for EMIDI's benefit.
|
||||
// (Do any released EMIDIs use track designations?)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
enum
|
||||
{
|
||||
EMIDI_GeneralMIDI = 0,
|
||||
EMIDI_SoundCanvas = 1,
|
||||
EMIDI_AWE32 = 2,
|
||||
EMIDI_WaveBlaster = 3,
|
||||
EMIDI_SoundBlaster = 4,
|
||||
EMIDI_ProAudio = 5,
|
||||
EMIDI_SoundMan16 = 6,
|
||||
EMIDI_Adlib = 7,
|
||||
EMIDI_Soundscape = 8,
|
||||
EMIDI_Ultrasound = 9,
|
||||
};
|
||||
|
||||
void MIDISong2::CheckCaps(int tech)
|
||||
{
|
||||
if (tech == MIDIDEV_FMSYNTH)
|
||||
{
|
||||
DesignationMask = 1 << EMIDI_Adlib;
|
||||
}
|
||||
else
|
||||
{
|
||||
DesignationMask = 1 << EMIDI_GeneralMIDI;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: DoInitialSetup
|
||||
//
|
||||
// Sets the starting channel volumes.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISong2 :: DoInitialSetup()
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
// The ASS uses a default volume of 90, but all the other
|
||||
// sources I can find say it's 100. Ideally, any song that
|
||||
// cares about its volume is going to initialize it to
|
||||
// whatever it wants and override this default.
|
||||
ChannelVolumes[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: DoRestart
|
||||
//
|
||||
// Rewinds every track.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISong2 :: DoRestart()
|
||||
{
|
||||
int i;
|
||||
|
||||
// Set initial state.
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
Tracks[i].TrackP = 0;
|
||||
Tracks[i].Finished = false;
|
||||
Tracks[i].RunningStatus = 0;
|
||||
Tracks[i].Designated = false;
|
||||
Tracks[i].Designation = 0;
|
||||
Tracks[i].LoopCount = -1;
|
||||
Tracks[i].EProgramChange = false;
|
||||
Tracks[i].EVolume = false;
|
||||
Tracks[i].PlayedTime = 0;
|
||||
}
|
||||
ProcessInitialMetaEvents ();
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
Tracks[i].Delay = Tracks[i].ReadVarLen();
|
||||
}
|
||||
TrackDue = Tracks.data();
|
||||
TrackDue = FindNextDue();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: CheckDone
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool MIDISong2::CheckDone()
|
||||
{
|
||||
return TrackDue == nullptr;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: MakeEvents
|
||||
//
|
||||
// Copies MIDI events from the SMF and puts them into a MIDI stream
|
||||
// buffer. Returns the new position in the buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *MIDISong2::MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time)
|
||||
{
|
||||
uint32_t *start_events;
|
||||
uint32_t tot_time = 0;
|
||||
uint32_t time = 0;
|
||||
uint32_t delay;
|
||||
|
||||
start_events = events;
|
||||
while (TrackDue && events < max_event_p && tot_time <= max_time)
|
||||
{
|
||||
// It's possible that this tick may be nothing but meta-events and
|
||||
// not generate any real events. Repeat this until we actually
|
||||
// get some output so we don't send an empty buffer to the MIDI
|
||||
// device.
|
||||
do
|
||||
{
|
||||
delay = TrackDue->Delay;
|
||||
time += delay;
|
||||
// Advance time for all tracks by the amount needed for the one up next.
|
||||
tot_time += delay * Tempo / Division;
|
||||
AdvanceTracks(delay);
|
||||
// Play all events for this tick.
|
||||
do
|
||||
{
|
||||
bool sysex_noroom = false;
|
||||
uint32_t *new_events = SendCommand(events, TrackDue, time, max_event_p - events, sysex_noroom);
|
||||
if (sysex_noroom)
|
||||
{
|
||||
return events;
|
||||
}
|
||||
TrackDue = FindNextDue();
|
||||
if (new_events != events)
|
||||
{
|
||||
time = 0;
|
||||
}
|
||||
events = new_events;
|
||||
}
|
||||
while (TrackDue && TrackDue->Delay == 0 && events < max_event_p);
|
||||
}
|
||||
while (start_events == events && TrackDue);
|
||||
time = 0;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: AdvanceTracks
|
||||
//
|
||||
// Advances time for all tracks by the specified amount.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISong2::AdvanceTracks(uint32_t time)
|
||||
{
|
||||
for (int i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
if (!Tracks[i].Finished)
|
||||
{
|
||||
Tracks[i].Delay -= time;
|
||||
Tracks[i].PlayedTime += time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: SendCommand
|
||||
//
|
||||
// Places a single MIDIEVENT in the event buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom)
|
||||
{
|
||||
uint32_t len;
|
||||
uint8_t event, data1 = 0, data2 = 0;
|
||||
int i;
|
||||
|
||||
sysex_noroom = false;
|
||||
size_t start_p = track->TrackP;
|
||||
|
||||
CHECK_FINISHED
|
||||
event = track->TrackBegin[track->TrackP++];
|
||||
CHECK_FINISHED
|
||||
|
||||
// The actual event type will be filled in below.
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = MEVENT_NOP << 24;
|
||||
|
||||
if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND)
|
||||
{
|
||||
// Normal short message
|
||||
if ((event & 0xF0) == 0xF0)
|
||||
{
|
||||
if (MIDI_CommonLengths[event & 15] > 0)
|
||||
{
|
||||
data1 = track->TrackBegin[track->TrackP++];
|
||||
if (MIDI_CommonLengths[event & 15] > 1)
|
||||
{
|
||||
data2 = track->TrackBegin[track->TrackP++];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((event & 0x80) == 0)
|
||||
{
|
||||
data1 = event;
|
||||
event = track->RunningStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
track->RunningStatus = event;
|
||||
data1 = track->TrackBegin[track->TrackP++];
|
||||
}
|
||||
|
||||
CHECK_FINISHED
|
||||
|
||||
if (MIDI_EventLengths[(event&0x70)>>4] == 2)
|
||||
{
|
||||
data2 = track->TrackBegin[track->TrackP++];
|
||||
}
|
||||
|
||||
switch (event & 0x70)
|
||||
{
|
||||
case MIDI_PRGMCHANGE & 0x70:
|
||||
if (track->EProgramChange)
|
||||
{
|
||||
event = MIDI_META;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIDI_CTRLCHANGE & 0x70:
|
||||
switch (data1)
|
||||
{
|
||||
case 7: // Channel volume
|
||||
if (track->EVolume)
|
||||
{ // Tracks that use EMIDI volume ignore normal volume changes.
|
||||
event = MIDI_META;
|
||||
}
|
||||
else
|
||||
{
|
||||
data2 = VolumeControllerChange(event & 15, data2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 7+32: // Channel volume (LSB)
|
||||
if (track->EVolume)
|
||||
{
|
||||
event = MIDI_META;
|
||||
}
|
||||
// It should be safe to pass this straight through to the
|
||||
// MIDI device, since it's a very fine amount.
|
||||
break;
|
||||
|
||||
case 110: // EMIDI Track Designation - InitBeat only
|
||||
// Instruments 4, 5, 6, and 7 are all FM synth.
|
||||
// The rest are all wavetable.
|
||||
if (track->PlayedTime < (uint32_t)Division)
|
||||
{
|
||||
if (data2 == 127)
|
||||
{
|
||||
track->Designation = ~0;
|
||||
track->Designated = true;
|
||||
}
|
||||
else if (data2 <= 9)
|
||||
{
|
||||
track->Designation |= 1 << data2;
|
||||
track->Designated = true;
|
||||
}
|
||||
event = MIDI_META;
|
||||
}
|
||||
break;
|
||||
|
||||
case 111: // EMIDI Track Exclusion - InitBeat only
|
||||
if (track->PlayedTime < (uint32_t)Division)
|
||||
{
|
||||
if (!track->Designated)
|
||||
{
|
||||
track->Designation = ~0;
|
||||
track->Designated = true;
|
||||
}
|
||||
if (data2 <= 9)
|
||||
{
|
||||
track->Designation &= ~(1 << data2);
|
||||
}
|
||||
event = MIDI_META;
|
||||
}
|
||||
break;
|
||||
|
||||
case 112: // EMIDI Program Change
|
||||
// Ignored unless it also appears in the InitBeat
|
||||
if (track->PlayedTime < (uint32_t)Division || track->EProgramChange)
|
||||
{
|
||||
track->EProgramChange = true;
|
||||
event = 0xC0 | (event & 0x0F);
|
||||
data1 = data2;
|
||||
data2 = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 113: // EMIDI Volume
|
||||
// Ignored unless it also appears in the InitBeat
|
||||
if (track->PlayedTime < (uint32_t)Division || track->EVolume)
|
||||
{
|
||||
track->EVolume = true;
|
||||
data1 = 7;
|
||||
data2 = VolumeControllerChange(event & 15, data2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 116: // EMIDI Loop Begin
|
||||
{
|
||||
// We convert the loop count to XMIDI conventions before clamping.
|
||||
// Then we convert it back to EMIDI conventions after clamping.
|
||||
// (XMIDI can create "loops" that don't loop. EMIDI cannot.)
|
||||
int loopcount = ClampLoopCount(data2 == 0 ? 0 : data2 + 1);
|
||||
if (loopcount != 1)
|
||||
{
|
||||
track->LoopBegin = track->TrackP;
|
||||
track->LoopDelay = 0;
|
||||
track->LoopCount = loopcount == 0 ? 0 : loopcount - 1;
|
||||
track->LoopFinished = track->Finished;
|
||||
}
|
||||
}
|
||||
event = MIDI_META;
|
||||
break;
|
||||
|
||||
case 117: // EMIDI Loop End
|
||||
if (track->LoopCount >= 0 && data2 == 127)
|
||||
{
|
||||
if (track->LoopCount == 0 && !isLooping)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (track->LoopCount > 0 && --track->LoopCount == 0)
|
||||
{
|
||||
track->LoopCount = -1;
|
||||
}
|
||||
track->TrackP = track->LoopBegin;
|
||||
track->Delay = track->LoopDelay;
|
||||
track->Finished = track->LoopFinished;
|
||||
}
|
||||
}
|
||||
event = MIDI_META;
|
||||
break;
|
||||
|
||||
case 118: // EMIDI Global Loop Begin
|
||||
{
|
||||
int loopcount = ClampLoopCount(data2 == 0 ? 0 : data2 + 1);
|
||||
if (loopcount != 1)
|
||||
{
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
Tracks[i].LoopBegin = Tracks[i].TrackP;
|
||||
Tracks[i].LoopDelay = Tracks[i].Delay;
|
||||
Tracks[i].LoopCount = loopcount == 0 ? 0 : loopcount - 1;
|
||||
Tracks[i].LoopFinished = Tracks[i].Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
event = MIDI_META;
|
||||
break;
|
||||
|
||||
case 119: // EMIDI Global Loop End
|
||||
if (data2 == 127)
|
||||
{
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
if (Tracks[i].LoopCount >= 0)
|
||||
{
|
||||
if (Tracks[i].LoopCount == 0 && !isLooping)
|
||||
{
|
||||
Tracks[i].Finished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Tracks[i].LoopCount > 0 && --Tracks[i].LoopCount == 0)
|
||||
{
|
||||
Tracks[i].LoopCount = -1;
|
||||
}
|
||||
Tracks[i].TrackP = Tracks[i].LoopBegin;
|
||||
Tracks[i].Delay = Tracks[i].LoopDelay;
|
||||
Tracks[i].Finished = Tracks[i].LoopFinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
event = MIDI_META;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (event != MIDI_META && (!track->Designated || (track->Designation & DesignationMask)))
|
||||
{
|
||||
events[2] = event | (data1<<8) | (data2<<16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// SysEx events could potentially not have enough room in the buffer...
|
||||
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
|
||||
{
|
||||
len = track->ReadVarLen();
|
||||
if (len >= (MAX_MIDI_EVENTS-1)*3*4 || skipSysex)
|
||||
{ // This message will never fit. Throw it away.
|
||||
track->TrackP += len;
|
||||
}
|
||||
else if (len + 12 >= (size_t)room * 4)
|
||||
{ // Not enough room left in this buffer. Backup and wait for the next one.
|
||||
track->TrackP = start_p;
|
||||
sysex_noroom = true;
|
||||
return events;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t *msg = (uint8_t *)&events[3];
|
||||
if (event == MIDI_SYSEX)
|
||||
{ // Need to add the SysEx marker to the message.
|
||||
events[2] = (MEVENT_LONGMSG << 24) | (len + 1);
|
||||
*msg++ = MIDI_SYSEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
events[2] = (MEVENT_LONGMSG << 24) | len;
|
||||
}
|
||||
memcpy(msg, &track->TrackBegin[track->TrackP], len);
|
||||
msg += len;
|
||||
// Must pad with 0
|
||||
while ((size_t)msg & 3)
|
||||
{
|
||||
*msg++ = 0;
|
||||
}
|
||||
track->TrackP += len;
|
||||
}
|
||||
}
|
||||
else if (event == MIDI_META)
|
||||
{
|
||||
// It's a meta-event
|
||||
event = track->TrackBegin[track->TrackP++];
|
||||
CHECK_FINISHED
|
||||
len = track->ReadVarLen ();
|
||||
CHECK_FINISHED
|
||||
|
||||
if (track->TrackP + len <= track->MaxTrackP)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case MIDI_META_EOT:
|
||||
track->Finished = true;
|
||||
break;
|
||||
|
||||
case MIDI_META_TEMPO:
|
||||
Tempo =
|
||||
(track->TrackBegin[track->TrackP+0]<<16) |
|
||||
(track->TrackBegin[track->TrackP+1]<<8) |
|
||||
(track->TrackBegin[track->TrackP+2]);
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = (MEVENT_TEMPO << 24) | Tempo;
|
||||
break;
|
||||
}
|
||||
track->TrackP += len;
|
||||
if (track->TrackP == track->MaxTrackP)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!track->Finished)
|
||||
{
|
||||
track->Delay = track->ReadVarLen();
|
||||
}
|
||||
// Advance events pointer unless this is a non-delaying NOP.
|
||||
if (events[0] != 0 || MEVENT_EVENTTYPE(events[2]) != MEVENT_NOP)
|
||||
{
|
||||
if (MEVENT_EVENTTYPE(events[2]) == MEVENT_LONGMSG)
|
||||
{
|
||||
events += 3 + ((MEVENT_EVENTPARM(events[2]) + 3) >> 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
events += 3;
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: ProcessInitialMetaEvents
|
||||
//
|
||||
// Handle all the meta events at the start of each track.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void MIDISong2::ProcessInitialMetaEvents ()
|
||||
{
|
||||
TrackInfo *track;
|
||||
int i;
|
||||
uint8_t event;
|
||||
uint32_t len;
|
||||
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
track = &Tracks[i];
|
||||
while (!track->Finished &&
|
||||
track->TrackP < track->MaxTrackP - 4 &&
|
||||
track->TrackBegin[track->TrackP] == 0 &&
|
||||
track->TrackBegin[track->TrackP+1] == 0xFF)
|
||||
{
|
||||
event = track->TrackBegin[track->TrackP+2];
|
||||
track->TrackP += 3;
|
||||
len = track->ReadVarLen ();
|
||||
if (track->TrackP + len <= track->MaxTrackP)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case MIDI_META_EOT:
|
||||
track->Finished = true;
|
||||
break;
|
||||
|
||||
case MIDI_META_TEMPO:
|
||||
SetTempo(
|
||||
(track->TrackBegin[track->TrackP+0]<<16) |
|
||||
(track->TrackBegin[track->TrackP+1]<<8) |
|
||||
(track->TrackBegin[track->TrackP+2])
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
track->TrackP += len;
|
||||
}
|
||||
if (track->TrackP >= track->MaxTrackP - 4)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: TrackInfo :: ReadVarLen
|
||||
//
|
||||
// Reads a variable-length SMF number.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t MIDISong2::TrackInfo::ReadVarLen ()
|
||||
{
|
||||
uint32_t time = 0, t = 0x80;
|
||||
|
||||
while ((t & 0x80) && TrackP < MaxTrackP)
|
||||
{
|
||||
t = TrackBegin[TrackP++];
|
||||
time = (time << 7) | (t & 127);
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// MIDISong2 :: FindNextDue
|
||||
//
|
||||
// Scans every track for the next event to play. Returns nullptr if all events
|
||||
// have been consumed.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
MIDISong2::TrackInfo *MIDISong2::FindNextDue ()
|
||||
{
|
||||
TrackInfo *track;
|
||||
uint32_t best;
|
||||
int i;
|
||||
|
||||
// Give precedence to whichever track last had events taken from it.
|
||||
if (!TrackDue->Finished && TrackDue->Delay == 0)
|
||||
{
|
||||
return TrackDue;
|
||||
}
|
||||
|
||||
switch (Format)
|
||||
{
|
||||
case 0:
|
||||
return Tracks[0].Finished ? nullptr : Tracks.data();
|
||||
|
||||
case 1:
|
||||
track = nullptr;
|
||||
best = 0xFFFFFFFF;
|
||||
for (i = 0; i < NumTracks; ++i)
|
||||
{
|
||||
if (!Tracks[i].Finished)
|
||||
{
|
||||
if (Tracks[i].Delay < best)
|
||||
{
|
||||
best = Tracks[i].Delay;
|
||||
track = &Tracks[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return track;
|
||||
|
||||
case 2:
|
||||
track = TrackDue;
|
||||
if (track->Finished)
|
||||
{
|
||||
track++;
|
||||
}
|
||||
return track < &Tracks[NumTracks] ? track : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
674
libraries/ZMusic/source/midisources/midisource_xmi.cpp
Normal file
674
libraries/ZMusic/source/midisources/midisource_xmi.cpp
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
/*
|
||||
** music_xmi_midiout.cpp
|
||||
** Code to let ZDoom play XMIDI music through the MIDI streaming API.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2010 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "midisource.h"
|
||||
#include "zmusic/mididefs.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
#define MAX_FOR_DEPTH 4
|
||||
|
||||
#define GET_DELAY (EventDue == EVENT_Real ? CurrSong->Delay : NoteOffs[0].Delay)
|
||||
|
||||
// Used by SendCommand to check for unexpected end-of-track conditions.
|
||||
#define CHECK_FINISHED \
|
||||
if (track->EventP >= track->EventLen) \
|
||||
{ \
|
||||
track->Finished = true; \
|
||||
return events; \
|
||||
}
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
struct LoopInfo
|
||||
{
|
||||
size_t LoopBegin;
|
||||
int LoopCount;
|
||||
bool LoopFinished;
|
||||
};
|
||||
|
||||
struct XMISong::TrackInfo
|
||||
{
|
||||
const uint8_t *EventChunk;
|
||||
size_t EventLen;
|
||||
size_t EventP;
|
||||
|
||||
const uint8_t *TimbreChunk;
|
||||
size_t TimbreLen;
|
||||
|
||||
uint32_t Delay;
|
||||
uint32_t PlayedTime;
|
||||
bool Finished;
|
||||
|
||||
LoopInfo ForLoops[MAX_FOR_DEPTH];
|
||||
int ForDepth;
|
||||
|
||||
uint32_t ReadVarLen();
|
||||
uint32_t ReadDelay();
|
||||
};
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong Constructor
|
||||
//
|
||||
// Buffers the file and does some validation of the SMF header.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
XMISong::XMISong (const uint8_t* data, size_t len)
|
||||
: MusHeader(0), Songs(0)
|
||||
{
|
||||
MusHeader.resize(len);
|
||||
memcpy(MusHeader.data(), data, len);
|
||||
|
||||
// Find all the songs in this file.
|
||||
NumSongs = FindXMIDforms(&MusHeader[0], (int)MusHeader.size(), nullptr);
|
||||
if (NumSongs == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// XMIDI files are played with a constant 120 Hz clock rate. While the
|
||||
// song may contain tempo events, these are vestigial remnants from the
|
||||
// original MIDI file that were not removed by the converter and should
|
||||
// be ignored.
|
||||
//
|
||||
// We can use any combination of Division and Tempo values that work out
|
||||
// to be 120 Hz.
|
||||
Division = 60;
|
||||
Tempo = InitialTempo = 500000;
|
||||
|
||||
Songs.resize(NumSongs);
|
||||
memset(Songs.data(), 0, sizeof(Songs[0]) * NumSongs);
|
||||
FindXMIDforms(&MusHeader[0], (int)MusHeader.size(), Songs.data());
|
||||
CurrSong = Songs.data();
|
||||
//DPrintf(DMSG_SPAMMY, "XMI song count: %d\n", NumSongs);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: FindXMIDforms
|
||||
//
|
||||
// Find all FORM XMID chunks in this chunk.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int XMISong::FindXMIDforms(const uint8_t *chunk, int len, TrackInfo *songs) const
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int p = 0; p <= len - 12; )
|
||||
{
|
||||
int chunktype = GetNativeInt(chunk + p);
|
||||
int chunklen = GetBigInt(chunk + p + 4);
|
||||
|
||||
if (chunktype == MAKE_ID('F','O','R','M'))
|
||||
{
|
||||
if (GetNativeInt(chunk + p + 8) == MAKE_ID('X','M','I','D'))
|
||||
{
|
||||
if (songs != nullptr)
|
||||
{
|
||||
FoundXMID(chunk + p + 12, chunklen - 4, songs + count);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else if (chunktype == MAKE_ID('C','A','T',' '))
|
||||
{
|
||||
// Recurse to handle CAT chunks.
|
||||
count += FindXMIDforms(chunk + p + 12, chunklen - 4, songs + count);
|
||||
}
|
||||
// IFF chunks are padded to even byte boundaries to avoid
|
||||
// unaligned reads on 68k processors.
|
||||
p += 8 + chunklen + (chunklen & 1);
|
||||
// Avoid crashes from corrupt chunks which indicate a negative size.
|
||||
if (chunklen < 0) p = len;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: FoundXMID
|
||||
//
|
||||
// Records information about this XMID song.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void XMISong::FoundXMID(const uint8_t *chunk, int len, TrackInfo *song) const
|
||||
{
|
||||
for (int p = 0; p <= len - 8; )
|
||||
{
|
||||
int chunktype = GetNativeInt(chunk + p);
|
||||
int chunklen = GetBigInt(chunk + p + 4);
|
||||
|
||||
if (chunktype == MAKE_ID('T','I','M','B'))
|
||||
{
|
||||
song->TimbreChunk = chunk + p + 8;
|
||||
song->TimbreLen = chunklen;
|
||||
}
|
||||
else if (chunktype == MAKE_ID('E','V','N','T'))
|
||||
{
|
||||
song->EventChunk = chunk + p + 8;
|
||||
song->EventLen = chunklen;
|
||||
// EVNT must be the final chunk in the FORM.
|
||||
break;
|
||||
}
|
||||
p += 8 + chunklen + (chunklen & 1);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: SetMIDISubsong
|
||||
//
|
||||
// Selects which song in this file to play.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool XMISong::SetMIDISubsong(int subsong)
|
||||
{
|
||||
if ((unsigned)subsong >= (unsigned)NumSongs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CurrSong = &Songs[subsong];
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: DoInitialSetup
|
||||
//
|
||||
// Sets the starting channel volumes.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void XMISong::DoInitialSetup()
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
ChannelVolumes[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: DoRestart
|
||||
//
|
||||
// Rewinds the current song.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void XMISong::DoRestart()
|
||||
{
|
||||
CurrSong->EventP = 0;
|
||||
CurrSong->Finished = false;
|
||||
CurrSong->PlayedTime = 0;
|
||||
CurrSong->ForDepth = 0;
|
||||
NoteOffs.clear();
|
||||
|
||||
ProcessInitialMetaEvents ();
|
||||
|
||||
CurrSong->Delay = CurrSong->ReadDelay();
|
||||
EventDue = FindNextDue();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: CheckDone
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool XMISong::CheckDone()
|
||||
{
|
||||
return EventDue == EVENT_None;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: MakeEvents
|
||||
//
|
||||
// Copies MIDI events from the XMI and puts them into a MIDI stream
|
||||
// buffer. Returns the new position in the buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *XMISong::MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time)
|
||||
{
|
||||
uint32_t *start_events;
|
||||
uint32_t tot_time = 0;
|
||||
uint32_t time = 0;
|
||||
uint32_t delay;
|
||||
|
||||
start_events = events;
|
||||
while (EventDue != EVENT_None && events < max_event_p && tot_time <= max_time)
|
||||
{
|
||||
// It's possible that this tick may be nothing but meta-events and
|
||||
// not generate any real events. Repeat this until we actually
|
||||
// get some output so we don't send an empty buffer to the MIDI
|
||||
// device.
|
||||
do
|
||||
{
|
||||
delay = GET_DELAY;
|
||||
time += delay;
|
||||
// Advance time for all tracks by the amount needed for the one up next.
|
||||
tot_time += delay * Tempo / Division;
|
||||
AdvanceSong(delay);
|
||||
// Play all events for this tick.
|
||||
do
|
||||
{
|
||||
bool sysex_noroom = false;
|
||||
uint32_t *new_events = SendCommand(events, EventDue, time, max_event_p - events, sysex_noroom);
|
||||
if (sysex_noroom)
|
||||
{
|
||||
return events;
|
||||
}
|
||||
EventDue = FindNextDue();
|
||||
if (new_events != events)
|
||||
{
|
||||
time = 0;
|
||||
}
|
||||
events = new_events;
|
||||
}
|
||||
while (EventDue != EVENT_None && GET_DELAY == 0 && events < max_event_p);
|
||||
}
|
||||
while (start_events == events && EventDue != EVENT_None);
|
||||
time = 0;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: AdvanceSong
|
||||
//
|
||||
// Advances time for the current song by the specified amount.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void XMISong::AdvanceSong(uint32_t time)
|
||||
{
|
||||
if (time != 0)
|
||||
{
|
||||
if (!CurrSong->Finished)
|
||||
{
|
||||
CurrSong->Delay -= time;
|
||||
CurrSong->PlayedTime += time;
|
||||
}
|
||||
NoteOffs.AdvanceTime(time);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: SendCommand
|
||||
//
|
||||
// Places a single MIDIEVENT in the event buffer.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t delay, ptrdiff_t room, bool &sysex_noroom)
|
||||
{
|
||||
uint32_t len;
|
||||
uint8_t event, data1 = 0, data2 = 0;
|
||||
|
||||
if (due == EVENT_Fake)
|
||||
{
|
||||
AutoNoteOff off;
|
||||
NoteOffs.Pop(off);
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = MIDI_NOTEON | off.Channel | (off.Key << 8);
|
||||
return events + 3;
|
||||
}
|
||||
|
||||
TrackInfo *track = CurrSong;
|
||||
|
||||
sysex_noroom = false;
|
||||
size_t start_p = track->EventP;
|
||||
|
||||
CHECK_FINISHED
|
||||
event = track->EventChunk[track->EventP++];
|
||||
CHECK_FINISHED
|
||||
|
||||
// The actual event type will be filled in below. If it's not a NOP,
|
||||
// the events pointer will be advanced once the actual event is written.
|
||||
// Otherwise, we do it at the end of the function.
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
events[2] = MEVENT_NOP << 24;
|
||||
|
||||
if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND)
|
||||
{
|
||||
// Normal short message
|
||||
if ((event & 0xF0) == 0xF0)
|
||||
{
|
||||
if (MIDI_CommonLengths[event & 15] > 0)
|
||||
{
|
||||
data1 = track->EventChunk[track->EventP++];
|
||||
if (MIDI_CommonLengths[event & 15] > 1)
|
||||
{
|
||||
data2 = track->EventChunk[track->EventP++];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
data1 = track->EventChunk[track->EventP++];
|
||||
}
|
||||
|
||||
CHECK_FINISHED
|
||||
|
||||
if (MIDI_EventLengths[(event&0x70)>>4] == 2)
|
||||
{
|
||||
data2 = track->EventChunk[track->EventP++];
|
||||
}
|
||||
|
||||
if ((event & 0x70) == (MIDI_CTRLCHANGE & 0x70))
|
||||
{
|
||||
switch (data1)
|
||||
{
|
||||
case 7: // Channel volume
|
||||
data2 = VolumeControllerChange(event & 15, data2);
|
||||
break;
|
||||
|
||||
case 110: // XMI channel lock
|
||||
case 111: // XMI channel lock protect
|
||||
case 112: // XMI voice protect
|
||||
case 113: // XMI timbre protect
|
||||
case 115: // XMI indirect controller prefix
|
||||
case 118: // XMI clear beat/bar count
|
||||
case 119: // XMI callback trigger
|
||||
case 120:
|
||||
event = MIDI_META; // none of these are relevant to us.
|
||||
break;
|
||||
|
||||
case 114: // XMI patch bank select
|
||||
data1 = 0; // Turn this into a standard MIDI bank select controller.
|
||||
break;
|
||||
|
||||
case 116: // XMI for loop controller
|
||||
if (track->ForDepth < MAX_FOR_DEPTH)
|
||||
{
|
||||
track->ForLoops[track->ForDepth].LoopBegin = track->EventP;
|
||||
track->ForLoops[track->ForDepth].LoopCount = ClampLoopCount(data2);
|
||||
track->ForLoops[track->ForDepth].LoopFinished = track->Finished;
|
||||
}
|
||||
track->ForDepth++;
|
||||
event = MIDI_META;
|
||||
break;
|
||||
|
||||
case 117: // XMI next loop controller
|
||||
if (track->ForDepth > 0)
|
||||
{
|
||||
int depth = track->ForDepth - 1;
|
||||
if (depth < MAX_FOR_DEPTH)
|
||||
{
|
||||
if (data2 < 64 || (track->ForLoops[depth].LoopCount == 0 && !isLooping))
|
||||
{ // throw away this loop.
|
||||
track->ForLoops[depth].LoopCount = 1;
|
||||
}
|
||||
// A loop count of 0 loops forever.
|
||||
if (track->ForLoops[depth].LoopCount == 0 || --track->ForLoops[depth].LoopCount > 0)
|
||||
{
|
||||
track->EventP = track->ForLoops[depth].LoopBegin;
|
||||
track->Finished = track->ForLoops[depth].LoopFinished;
|
||||
}
|
||||
else
|
||||
{ // done with this loop
|
||||
track->ForDepth = depth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // ignore any loops deeper than the max depth
|
||||
track->ForDepth = depth;
|
||||
}
|
||||
}
|
||||
event = MIDI_META;
|
||||
break;
|
||||
}
|
||||
}
|
||||
events[0] = delay;
|
||||
events[1] = 0;
|
||||
if (event != MIDI_META)
|
||||
{
|
||||
events[2] = event | (data1<<8) | (data2<<16);
|
||||
}
|
||||
|
||||
|
||||
if ((event & 0x70) == (MIDI_NOTEON & 0x70))
|
||||
{ // XMI note on events include the time until an implied note off event.
|
||||
NoteOffs.AddNoteOff(track->ReadVarLen(), event & 0x0F, data1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// SysEx events could potentially not have enough room in the buffer...
|
||||
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
|
||||
{
|
||||
len = track->ReadVarLen();
|
||||
if (len >= (MAX_MIDI_EVENTS-1)*3*4 || skipSysex)
|
||||
{ // This message will never fit. Throw it away.
|
||||
track->EventP += len;
|
||||
}
|
||||
else if (len + 12 >= (size_t)room * 4)
|
||||
{ // Not enough room left in this buffer. Backup and wait for the next one.
|
||||
track->EventP = start_p;
|
||||
sysex_noroom = true;
|
||||
return events;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t *msg = (uint8_t *)&events[3];
|
||||
if (event == MIDI_SYSEX)
|
||||
{ // Need to add the SysEx marker to the message.
|
||||
events[2] = (MEVENT_LONGMSG << 24) | (len + 1);
|
||||
*msg++ = MIDI_SYSEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
events[2] = (MEVENT_LONGMSG << 24) | len;
|
||||
}
|
||||
memcpy(msg, &track->EventChunk[track->EventP++], len);
|
||||
msg += len;
|
||||
// Must pad with 0
|
||||
while ((size_t)msg & 3)
|
||||
{
|
||||
*msg++ = 0;
|
||||
}
|
||||
track->EventP += len;
|
||||
}
|
||||
}
|
||||
else if (event == MIDI_META)
|
||||
{
|
||||
// It's a meta-event
|
||||
event = track->EventChunk[track->EventP++];
|
||||
CHECK_FINISHED
|
||||
len = track->ReadVarLen ();
|
||||
CHECK_FINISHED
|
||||
|
||||
if (track->EventP + len <= track->EventLen)
|
||||
{
|
||||
if (event == MIDI_META_EOT)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
track->EventP += len;
|
||||
if (track->EventP == track->EventLen)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!track->Finished)
|
||||
{
|
||||
track->Delay = track->ReadDelay();
|
||||
}
|
||||
// Advance events pointer unless this is a non-delaying NOP.
|
||||
if (events[0] != 0 || MEVENT_EVENTTYPE(events[2]) != MEVENT_NOP)
|
||||
{
|
||||
if (MEVENT_EVENTTYPE(events[2]) == MEVENT_LONGMSG)
|
||||
{
|
||||
events += 3 + ((MEVENT_EVENTPARM(events[2]) + 3) >> 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
events += 3;
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: ProcessInitialMetaEvents
|
||||
//
|
||||
// Handle all the meta events at the start of the current song.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void XMISong::ProcessInitialMetaEvents ()
|
||||
{
|
||||
TrackInfo *track = CurrSong;
|
||||
uint8_t event;
|
||||
uint32_t len;
|
||||
|
||||
while (!track->Finished &&
|
||||
track->EventP < track->EventLen - 3 &&
|
||||
track->EventChunk[track->EventP] == MIDI_META)
|
||||
{
|
||||
event = track->EventChunk[track->EventP+1];
|
||||
track->EventP += 2;
|
||||
len = track->ReadVarLen();
|
||||
if (track->EventP + len <= track->EventLen && event == MIDI_META_EOT)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
track->EventP += len;
|
||||
}
|
||||
if (track->EventP >= track->EventLen - 1)
|
||||
{
|
||||
track->Finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: TrackInfo :: ReadVarLen
|
||||
//
|
||||
// Reads a variable length SMF number.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t XMISong::TrackInfo::ReadVarLen()
|
||||
{
|
||||
uint32_t time = 0, t = 0x80;
|
||||
|
||||
while ((t & 0x80) && EventP < EventLen)
|
||||
{
|
||||
t = EventChunk[EventP++];
|
||||
time = (time << 7) | (t & 127);
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: TrackInfo :: ReadDelay
|
||||
//
|
||||
// XMI does not use variable length numbers for delays. Instead, it uses
|
||||
// runs of bytes with the high bit clear.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
uint32_t XMISong::TrackInfo::ReadDelay()
|
||||
{
|
||||
uint32_t time = 0, t;
|
||||
|
||||
while (EventP < EventLen && !((t = EventChunk[EventP]) & 0x80))
|
||||
{
|
||||
time += t;
|
||||
EventP++;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XMISong :: FindNextDue
|
||||
//
|
||||
// Decides whether the next event should come from the actual stong or
|
||||
// from the auto note offs.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
XMISong::EventSource XMISong::FindNextDue()
|
||||
{
|
||||
// Are there still events available?
|
||||
if (CurrSong->Finished && NoteOffs.size() == 0)
|
||||
{
|
||||
return EVENT_None;
|
||||
}
|
||||
|
||||
// Which is due sooner? The current song or the note-offs?
|
||||
uint32_t real_delay = CurrSong->Finished ? 0xFFFFFFFF : CurrSong->Delay;
|
||||
uint32_t fake_delay = NoteOffs.size() == 0 ? 0xFFFFFFFF : NoteOffs[0].Delay;
|
||||
|
||||
return (fake_delay <= real_delay) ? EVENT_Fake : EVENT_Real;
|
||||
}
|
||||
|
||||
|
||||
211
libraries/ZMusic/source/musicformats/music_cd.cpp
Normal file
211
libraries/ZMusic/source/musicformats/music_cd.cpp
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
** music_cd.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1999-2003 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "zmusic/musinfo.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "zmusic/m_swap.h"
|
||||
#include "win32/i_cd.h"
|
||||
|
||||
|
||||
// CD track/disk played through the multimedia system -----------------------
|
||||
|
||||
class CDSong : public MusInfo
|
||||
{
|
||||
public:
|
||||
CDSong(int track, int id);
|
||||
~CDSong();
|
||||
void Play(bool looping, int subsong);
|
||||
void Pause();
|
||||
void Resume();
|
||||
void Stop();
|
||||
bool IsPlaying();
|
||||
SoundStreamInfoEx GetStreamInfoEx() const override { return {}; }
|
||||
bool IsValid() const { return m_Inited; }
|
||||
|
||||
protected:
|
||||
CDSong() : m_Inited(false) {}
|
||||
|
||||
int m_Track;
|
||||
bool m_Inited;
|
||||
};
|
||||
|
||||
// CD track on a specific disk played through the multimedia system ---------
|
||||
|
||||
class CDDAFile : public CDSong
|
||||
{
|
||||
public:
|
||||
CDDAFile(MusicIO::FileInterface* reader);
|
||||
};
|
||||
|
||||
|
||||
|
||||
void CDSong::Play (bool looping, int subsong)
|
||||
{
|
||||
m_Status = STATE_Stopped;
|
||||
m_Looping = looping;
|
||||
if (m_Track != 0 ? CD_Play (m_Track, looping) : CD_PlayCD (looping))
|
||||
{
|
||||
m_Status = STATE_Playing;
|
||||
}
|
||||
}
|
||||
|
||||
void CDSong::Pause ()
|
||||
{
|
||||
if (m_Status == STATE_Playing)
|
||||
{
|
||||
CD_Pause ();
|
||||
m_Status = STATE_Paused;
|
||||
}
|
||||
}
|
||||
|
||||
void CDSong::Resume ()
|
||||
{
|
||||
if (m_Status == STATE_Paused)
|
||||
{
|
||||
if (CD_Resume ())
|
||||
m_Status = STATE_Playing;
|
||||
}
|
||||
}
|
||||
|
||||
void CDSong::Stop ()
|
||||
{
|
||||
if (m_Status != STATE_Stopped)
|
||||
{
|
||||
m_Status = STATE_Stopped;
|
||||
CD_Stop ();
|
||||
}
|
||||
}
|
||||
|
||||
CDSong::~CDSong ()
|
||||
{
|
||||
Stop ();
|
||||
m_Inited = false;
|
||||
}
|
||||
|
||||
CDSong::CDSong (int track, int id)
|
||||
{
|
||||
bool success;
|
||||
|
||||
m_Inited = false;
|
||||
|
||||
if (id != 0)
|
||||
{
|
||||
success = CD_InitID (id);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = CD_Init (-1);
|
||||
}
|
||||
|
||||
if (success && (track == 0 || CD_CheckTrack (track)))
|
||||
{
|
||||
m_Inited = true;
|
||||
m_Track = track;
|
||||
}
|
||||
}
|
||||
|
||||
bool CDSong::IsPlaying ()
|
||||
{
|
||||
if (m_Status == STATE_Playing)
|
||||
{
|
||||
if (CD_GetMode () != CDMode_Play)
|
||||
{
|
||||
Stop ();
|
||||
}
|
||||
}
|
||||
return m_Status != STATE_Stopped;
|
||||
}
|
||||
|
||||
CDDAFile::CDDAFile (MusicIO::FileInterface* reader)
|
||||
: CDSong ()
|
||||
{
|
||||
uint32_t chunk;
|
||||
uint16_t track;
|
||||
uint32_t discid;
|
||||
auto endpos = reader->tell() + reader->filelength() - 8;
|
||||
|
||||
// ZMusic_OpenSong already identified this as a CDDA file, so we
|
||||
// just need to check the contents we're interested in.
|
||||
reader->seek(12, SEEK_CUR);
|
||||
|
||||
while (reader->tell() < endpos)
|
||||
{
|
||||
reader->read(&chunk, 4);
|
||||
if (chunk != (('f')|(('m')<<8)|(('t')<<16)|((' ')<<24)))
|
||||
{
|
||||
reader->read(&chunk, 4);
|
||||
reader->seek(LittleLong(chunk), SEEK_CUR);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader->seek(6, SEEK_CUR);
|
||||
reader->read(&track, 2);
|
||||
reader->read(&discid, 4);
|
||||
|
||||
if (CD_InitID (LittleLong(discid)) && CD_CheckTrack (LittleShort(track)))
|
||||
{
|
||||
m_Inited = true;
|
||||
m_Track = track;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MusInfo* CD_OpenSong(int track, int id)
|
||||
{
|
||||
return new CDSong(track, id);
|
||||
}
|
||||
|
||||
MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader)
|
||||
{
|
||||
return new CDDAFile(reader);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
MusInfo* CD_OpenSong(int track, int id)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
1047
libraries/ZMusic/source/musicformats/music_midi.cpp
Normal file
1047
libraries/ZMusic/source/musicformats/music_midi.cpp
Normal file
File diff suppressed because it is too large
Load diff
177
libraries/ZMusic/source/musicformats/music_stream.cpp
Normal file
177
libraries/ZMusic/source/musicformats/music_stream.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
** music_stream.cpp
|
||||
** Plays a streaming song from a StreamSource
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2008 Randy Heit
|
||||
** Copyright 2019 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "zmusic/musinfo.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "streamsources/streamsource.h"
|
||||
|
||||
class StreamSong : public MusInfo
|
||||
{
|
||||
public:
|
||||
StreamSong (StreamSource *source);
|
||||
~StreamSong ();
|
||||
void Play (bool looping, int subsong) override;
|
||||
void Pause () override;
|
||||
void Resume () override;
|
||||
void Stop () override;
|
||||
bool IsPlaying () override;
|
||||
bool IsValid () const override { return m_Source != nullptr; }
|
||||
bool SetPosition (unsigned int pos) override;
|
||||
bool SetSubsong (int subsong) override;
|
||||
std::string GetStats() override;
|
||||
void ChangeSettingInt(const char *name, int value) override { if (m_Source) m_Source->ChangeSettingInt(name, value); }
|
||||
void ChangeSettingNum(const char *name, double value) override { if (m_Source) m_Source->ChangeSettingNum(name, value); }
|
||||
void ChangeSettingString(const char *name, const char *value) override { if(m_Source) m_Source->ChangeSettingString(name, value); }
|
||||
bool ServiceStream(void* buff, int len) override;
|
||||
SoundStreamInfoEx GetStreamInfoEx() const override { return m_Source->GetFormatEx(); }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
StreamSource *m_Source = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
||||
void StreamSong::Play (bool looping, int subsong)
|
||||
{
|
||||
m_Status = STATE_Stopped;
|
||||
m_Looping = looping;
|
||||
|
||||
if (m_Source != nullptr)
|
||||
{
|
||||
m_Source->SetPlayMode(looping);
|
||||
m_Source->SetSubsong(subsong);
|
||||
if (m_Source->Start())
|
||||
{
|
||||
m_Status = STATE_Playing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSong::Pause ()
|
||||
{
|
||||
m_Status = STATE_Paused;
|
||||
}
|
||||
|
||||
void StreamSong::Resume ()
|
||||
{
|
||||
m_Status = STATE_Playing;
|
||||
}
|
||||
|
||||
void StreamSong::Stop ()
|
||||
{
|
||||
m_Status = STATE_Stopped;
|
||||
}
|
||||
|
||||
StreamSong::~StreamSong ()
|
||||
{
|
||||
Stop ();
|
||||
if (m_Source != nullptr)
|
||||
{
|
||||
delete m_Source;
|
||||
m_Source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
StreamSong::StreamSong (StreamSource *source)
|
||||
{
|
||||
m_Source = source;
|
||||
}
|
||||
|
||||
bool StreamSong::IsPlaying ()
|
||||
{
|
||||
if (m_Status != STATE_Stopped)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// StreamSong :: SetPosition
|
||||
//
|
||||
// Sets the position in ms.
|
||||
|
||||
bool StreamSong::SetPosition(unsigned int pos)
|
||||
{
|
||||
if (m_Source != nullptr)
|
||||
{
|
||||
return m_Source->SetPosition(pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool StreamSong::SetSubsong(int subsong)
|
||||
{
|
||||
return m_Source->SetSubsong(subsong);
|
||||
}
|
||||
|
||||
std::string StreamSong::GetStats()
|
||||
{
|
||||
std::string s1, s2;
|
||||
if (m_Source != NULL)
|
||||
{
|
||||
auto stat = m_Source->GetStats();
|
||||
s2 = stat.c_str();
|
||||
}
|
||||
if (s1.empty() && s2.empty()) return "No song loaded\n";
|
||||
if (s1.empty()) return s2;
|
||||
if (s2.empty()) return s1;
|
||||
return s1 + "\n" + s2;
|
||||
}
|
||||
|
||||
bool StreamSong::ServiceStream (void *buff, int len)
|
||||
{
|
||||
bool written = m_Source->GetData(buff, len);
|
||||
if (!written)
|
||||
{
|
||||
m_Status = STATE_Stopped;
|
||||
memset((char*)buff, 0, len);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MusInfo *OpenStreamSong(StreamSource *source)
|
||||
{
|
||||
auto song = new StreamSong(source);
|
||||
if (song->IsValid()) return song;
|
||||
delete song;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
292
libraries/ZMusic/source/musicformats/win32/helperthread.cpp
Normal file
292
libraries/ZMusic/source/musicformats/win32/helperthread.cpp
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
** helperthread.cpp
|
||||
**
|
||||
** Implements FHelperThread, the base class for helper threads. Includes
|
||||
** a message queue for passing messages from the main thread to the
|
||||
** helper thread. (Only used by the CD Audio player)
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2006 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include "helperthread.h"
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ---Constructor---
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FHelperThread::FHelperThread ()
|
||||
{
|
||||
ThreadHandle = NULL;
|
||||
ThreadID = 0;
|
||||
Thread_Events[0] = Thread_Events[1] = 0;
|
||||
memset (Messages, 0, sizeof(Messages));
|
||||
MessageHead = 0;
|
||||
MessageTail = 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ---Destructor---
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FHelperThread::~FHelperThread ()
|
||||
{
|
||||
DestroyThread ();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// LaunchThread
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool FHelperThread::LaunchThread ()
|
||||
{
|
||||
int i;
|
||||
|
||||
MessageHead = MessageTail = 0;
|
||||
for (i = 0; i < MSG_QUEUE_SIZE; i++)
|
||||
{
|
||||
if ((Messages[i].CompletionEvent = CreateEvent (NULL, FALSE, i > 0, NULL)) == NULL)
|
||||
break;
|
||||
}
|
||||
if (i < MSG_QUEUE_SIZE)
|
||||
{
|
||||
for (; i >= 0; i--)
|
||||
{
|
||||
CloseHandle (Messages[i].CompletionEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
InitializeCriticalSection (&Thread_Critical);
|
||||
if ((Thread_Events[0] = CreateEvent (NULL, TRUE, FALSE, NULL)))
|
||||
{
|
||||
if ((Thread_Events[1] = CreateEvent (NULL, TRUE, FALSE, NULL)))
|
||||
{
|
||||
if ((ThreadHandle = CreateThread (NULL, 0, ThreadLaunch, (LPVOID)this, 0, &ThreadID)))
|
||||
{
|
||||
HANDLE waiters[2] = { Messages[0].CompletionEvent, ThreadHandle };
|
||||
|
||||
if (WaitForMultipleObjects (2, waiters, FALSE, INFINITE) == WAIT_OBJECT_0+1)
|
||||
{ // Init failed, and the thread exited
|
||||
DestroyThread ();
|
||||
return false;
|
||||
}
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
// Give the thread a name in the debugger
|
||||
struct
|
||||
{
|
||||
DWORD type;
|
||||
LPCSTR name;
|
||||
DWORD threadID;
|
||||
DWORD flags;
|
||||
} info =
|
||||
{
|
||||
0x1000, ThreadName(), ThreadID, 0
|
||||
};
|
||||
__try
|
||||
{
|
||||
RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR *)&info );
|
||||
}
|
||||
__except(EXCEPTION_CONTINUE_EXECUTION)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
CloseHandle (Thread_Events[1]);
|
||||
}
|
||||
CloseHandle (Thread_Events[0]);
|
||||
}
|
||||
DeleteCriticalSection (&Thread_Critical);
|
||||
for (i = 0; i < MSG_QUEUE_SIZE; i++)
|
||||
{
|
||||
CloseHandle (Messages[i].CompletionEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// DestroyThread
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FHelperThread::DestroyThread ()
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ThreadHandle == NULL)
|
||||
return;
|
||||
|
||||
SetEvent (Thread_Events[THREAD_KillSelf]);
|
||||
// 5 seconds should be sufficient. If not, then we'll crash, but at
|
||||
// least that's better than hanging indefinitely.
|
||||
WaitForSingleObject (ThreadHandle, 5000);
|
||||
CloseHandle (ThreadHandle);
|
||||
|
||||
EnterCriticalSection (&Thread_Critical);
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
CloseHandle (Messages[i].CompletionEvent);
|
||||
}
|
||||
CloseHandle (Thread_Events[0]);
|
||||
CloseHandle (Thread_Events[1]);
|
||||
|
||||
LeaveCriticalSection (&Thread_Critical);
|
||||
DeleteCriticalSection (&Thread_Critical);
|
||||
|
||||
ThreadHandle = NULL;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// HaveThread
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool FHelperThread::HaveThread () const
|
||||
{
|
||||
return ThreadHandle != NULL;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SendMessage
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD FHelperThread::SendMessage (DWORD method,
|
||||
DWORD parm1, DWORD parm2, DWORD parm3, bool wait)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
EnterCriticalSection (&Thread_Critical);
|
||||
if (MessageHead - MessageTail == MSG_QUEUE_SIZE)
|
||||
{ // No room, so wait for oldest message to complete
|
||||
HANDLE waitEvent = Messages[MessageTail].CompletionEvent;
|
||||
LeaveCriticalSection (&Thread_Critical);
|
||||
WaitForSingleObject (waitEvent, 1000);
|
||||
}
|
||||
|
||||
int head = MessageHead++ % MSG_QUEUE_SIZE;
|
||||
Messages[head].Method = method;
|
||||
Messages[head].Parm1 = parm1;
|
||||
Messages[head].Parm2 = parm2;
|
||||
Messages[head].Parm3 = parm3;
|
||||
ResetEvent (Messages[head].CompletionEvent);
|
||||
LeaveCriticalSection (&Thread_Critical);
|
||||
|
||||
SetEvent (Thread_Events[THREAD_NewMessage]);
|
||||
|
||||
if (wait)
|
||||
{
|
||||
WaitForSingleObject (Messages[head].CompletionEvent, INFINITE);
|
||||
return Messages[head].Return;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ThreadLaunch (static)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD WINAPI FHelperThread::ThreadLaunch (LPVOID me)
|
||||
{
|
||||
return ((FHelperThread *)me)->ThreadLoop ();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ThreadLoop
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD FHelperThread::ThreadLoop ()
|
||||
{
|
||||
if (!Init ())
|
||||
{
|
||||
ExitThread (0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetEvent (Messages[0].CompletionEvent);
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
switch (MsgWaitForMultipleObjects (2, Thread_Events,
|
||||
FALSE, INFINITE, QS_ALLEVENTS))
|
||||
{
|
||||
case WAIT_OBJECT_0+1:
|
||||
// We should quit now.
|
||||
Deinit ();
|
||||
ExitThread (0);
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0:
|
||||
// Process any new messages. MessageTail is not updated until *after*
|
||||
// the message is finished processing.
|
||||
for (;;)
|
||||
{
|
||||
EnterCriticalSection (&Thread_Critical);
|
||||
if (MessageHead == MessageTail)
|
||||
{
|
||||
ResetEvent (Thread_Events[0]);
|
||||
LeaveCriticalSection (&Thread_Critical);
|
||||
break; // Resume outer for (Wait for more messages)
|
||||
}
|
||||
int spot = MessageTail % MSG_QUEUE_SIZE;
|
||||
LeaveCriticalSection (&Thread_Critical);
|
||||
|
||||
Messages[spot].Return = Dispatch (Messages[spot].Method,
|
||||
Messages[spot].Parm1, Messages[spot].Parm2,
|
||||
Messages[spot].Parm3);
|
||||
|
||||
// No need to enter critical section, because only the CD thread
|
||||
// is allowed to touch MessageTail or signal the CompletionEvent
|
||||
SetEvent (Messages[spot].CompletionEvent);
|
||||
MessageTail++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DefaultDispatch ();
|
||||
}
|
||||
}
|
||||
}
|
||||
85
libraries/ZMusic/source/musicformats/win32/helperthread.h
Normal file
85
libraries/ZMusic/source/musicformats/win32/helperthread.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
** helperthread.h
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2006 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __HELPERTHREAD_H__
|
||||
#define __HELPERTHREAD_H__
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
class FHelperThread
|
||||
{
|
||||
struct Message
|
||||
{
|
||||
DWORD Method;
|
||||
DWORD Parm1, Parm2, Parm3;
|
||||
HANDLE CompletionEvent; // Set to signalled when message is finished processing
|
||||
DWORD Return;
|
||||
};
|
||||
|
||||
enum { MSG_QUEUE_SIZE = 8 };
|
||||
|
||||
public:
|
||||
FHelperThread ();
|
||||
virtual ~FHelperThread ();
|
||||
void DestroyThread ();
|
||||
|
||||
bool LaunchThread ();
|
||||
DWORD SendMessage (DWORD method, DWORD parm1, DWORD parm2,
|
||||
DWORD parm3, bool wait);
|
||||
bool HaveThread () const;
|
||||
|
||||
protected:
|
||||
enum EThreadEvents { THREAD_NewMessage, THREAD_KillSelf };
|
||||
virtual bool Init () { return true; }
|
||||
virtual void Deinit () {}
|
||||
virtual void DefaultDispatch () {}
|
||||
virtual DWORD Dispatch (DWORD method, DWORD parm1=0, DWORD parm2=0, DWORD parm3=0) = 0;
|
||||
virtual const char *ThreadName () = 0;
|
||||
|
||||
HANDLE ThreadHandle;
|
||||
DWORD ThreadID;
|
||||
HANDLE Thread_Events[2];
|
||||
CRITICAL_SECTION Thread_Critical;
|
||||
Message Messages[MSG_QUEUE_SIZE];
|
||||
DWORD MessageHead;
|
||||
DWORD MessageTail;
|
||||
|
||||
private:
|
||||
void ReleaseSynchronizers ();
|
||||
static DWORD WINAPI ThreadLaunch (LPVOID me);
|
||||
|
||||
DWORD ThreadLoop ();
|
||||
};
|
||||
|
||||
#endif //__HELPERTHREAD_H__
|
||||
762
libraries/ZMusic/source/musicformats/win32/i_cd.cpp
Normal file
762
libraries/ZMusic/source/musicformats/win32/i_cd.cpp
Normal file
|
|
@ -0,0 +1,762 @@
|
|||
/*
|
||||
** i_cd.cpp
|
||||
** Functions for controlling CD playback
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2006 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <stdlib.h>
|
||||
#include "zmusic_internal.h"
|
||||
#include "helperthread.h"
|
||||
#include "i_cd.h"
|
||||
|
||||
enum
|
||||
{
|
||||
CDM_Init, // parm1 = device
|
||||
CDM_Close,
|
||||
CDM_Play, // parm1 = track, parm2 = 1:looping
|
||||
CDM_PlayCD, // parm1 = 1:looping
|
||||
CDM_Replay, // Redos the most recent CDM_Play(CD)
|
||||
CDM_Stop,
|
||||
CDM_Eject,
|
||||
CDM_UnEject,
|
||||
CDM_Pause,
|
||||
CDM_Resume,
|
||||
CDM_GetMode,
|
||||
CDM_CheckTrack, // parm1 = track
|
||||
CDM_GetMediaIdentity,
|
||||
CDM_GetMediaUPC
|
||||
};
|
||||
|
||||
class FCDThread : public FHelperThread
|
||||
{
|
||||
public:
|
||||
FCDThread ();
|
||||
|
||||
protected:
|
||||
bool Init ();
|
||||
void Deinit ();
|
||||
const char *ThreadName ();
|
||||
DWORD Dispatch (DWORD method, DWORD parm1=0, DWORD parm2=0, DWORD parm3=0);
|
||||
void DefaultDispatch ();
|
||||
|
||||
bool IsTrackAudio (DWORD track) const;
|
||||
DWORD GetTrackLength (DWORD track) const;
|
||||
DWORD GetNumTracks () const;
|
||||
|
||||
static LRESULT CALLBACK CD_WndProc (HWND hWnd, UINT message,
|
||||
WPARAM wParam, LPARAM lParam);
|
||||
|
||||
WNDCLASS CD_WindowClass;
|
||||
HWND CD_Window;
|
||||
ATOM CD_WindowAtom;
|
||||
|
||||
bool Looping;
|
||||
DWORD PlayFrom;
|
||||
DWORD PlayTo;
|
||||
UINT DeviceID;
|
||||
};
|
||||
|
||||
#define NOT_INITED ((signed)0x80000000)
|
||||
|
||||
static FCDThread *CDThread;
|
||||
static int Inited = NOT_INITED;
|
||||
static int Enabled = false;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ---Constructor---
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FCDThread::FCDThread ()
|
||||
{
|
||||
memset (&CD_WindowClass, 0, sizeof(CD_WindowClass));
|
||||
CD_Window = NULL;
|
||||
CD_WindowAtom = 0;
|
||||
Inited = NOT_INITED;
|
||||
Looping = false;
|
||||
PlayFrom = 0;
|
||||
PlayTo = 0;
|
||||
DeviceID = 0;
|
||||
LaunchThread ();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ThreadName
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
const char *FCDThread::ThreadName ()
|
||||
{
|
||||
return "CD Player";
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Init
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool FCDThread::Init ()
|
||||
{
|
||||
CD_WindowClass.style = CS_NOCLOSE;
|
||||
CD_WindowClass.lpfnWndProc = CD_WndProc;
|
||||
CD_WindowClass.hInstance = GetModuleHandle(nullptr);
|
||||
CD_WindowClass.lpszClassName = L"ZMusic CD Player";
|
||||
CD_WindowAtom = RegisterClass (&CD_WindowClass);
|
||||
|
||||
if (CD_WindowAtom == 0)
|
||||
return false;
|
||||
|
||||
CD_Window = CreateWindow (
|
||||
(LPCTSTR)(INT_PTR)(int)CD_WindowAtom,
|
||||
CD_WindowClass.lpszClassName,
|
||||
0,
|
||||
0, 0, 10, 10,
|
||||
NULL,
|
||||
NULL,
|
||||
CD_WindowClass.hInstance,
|
||||
NULL);
|
||||
|
||||
if (CD_Window == NULL)
|
||||
{
|
||||
UnregisterClass ((LPCTSTR)(INT_PTR)(int)CD_WindowAtom, CD_WindowClass.hInstance);
|
||||
CD_WindowAtom = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
SetWindowLongPtr (CD_Window, GWLP_USERDATA, (LONG_PTR)this);
|
||||
#else
|
||||
SetWindowLong (CD_Window, GWL_USERDATA, (LONG)(LONG_PTR)this);
|
||||
#endif
|
||||
SetThreadPriority (ThreadHandle, THREAD_PRIORITY_LOWEST);
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Deinit
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FCDThread::Deinit ()
|
||||
{
|
||||
if (CD_Window)
|
||||
{
|
||||
DestroyWindow (CD_Window);
|
||||
CD_Window = NULL;
|
||||
}
|
||||
if (CD_WindowAtom)
|
||||
{
|
||||
UnregisterClass ((LPCTSTR)(INT_PTR)(int)CD_WindowAtom, GetModuleHandle(nullptr));
|
||||
CD_WindowAtom = 0;
|
||||
}
|
||||
if (DeviceID)
|
||||
{
|
||||
Dispatch (CDM_Close);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// DefaultDispatch
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FCDThread::DefaultDispatch ()
|
||||
{
|
||||
MSG wndMsg;
|
||||
|
||||
while (PeekMessage (&wndMsg, NULL, 0, 0, PM_REMOVE))
|
||||
{
|
||||
DispatchMessage (&wndMsg);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Dispatch
|
||||
//
|
||||
// Does the actual work of implementing the public CD interface
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD FCDThread::Dispatch (DWORD method, DWORD parm1, DWORD parm2, DWORD parm3)
|
||||
{
|
||||
MCI_OPEN_PARMS mciOpen;
|
||||
MCI_SET_PARMS mciSetParms;
|
||||
MCI_PLAY_PARMS playParms;
|
||||
MCI_STATUS_PARMS statusParms;
|
||||
MCI_INFO_PARMS infoParms;
|
||||
DWORD firstTrack, lastTrack, numTracks;
|
||||
DWORD length;
|
||||
DWORD openFlags;
|
||||
wchar_t ident[32];
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case CDM_Init:
|
||||
mciOpen.lpstrDeviceType = (LPCWSTR)MCI_DEVTYPE_CD_AUDIO;
|
||||
openFlags = MCI_OPEN_SHAREABLE|MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID|MCI_WAIT;
|
||||
if ((signed)parm1 >= 0)
|
||||
{
|
||||
ident[0] = (char)(parm1 + 'A');
|
||||
ident[1] = ':';
|
||||
ident[2] = 0;
|
||||
mciOpen.lpstrElementName = ident;
|
||||
openFlags |= MCI_OPEN_ELEMENT;
|
||||
}
|
||||
|
||||
while (mciSendCommand (0, MCI_OPEN, openFlags, (DWORD_PTR)&mciOpen) != 0)
|
||||
{
|
||||
if (!(openFlags & MCI_OPEN_ELEMENT))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
openFlags &= ~MCI_OPEN_ELEMENT;
|
||||
}
|
||||
|
||||
DeviceID = mciOpen.wDeviceID;
|
||||
mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF;
|
||||
if (mciSendCommand (DeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&mciSetParms) == 0)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
mciSendCommand (DeviceID, MCI_CLOSE, 0, 0);
|
||||
return FALSE;
|
||||
|
||||
case CDM_Close:
|
||||
Dispatch (CDM_Stop);
|
||||
mciSendCommand (DeviceID, MCI_CLOSE, 0, 0);
|
||||
DeviceID = 0;
|
||||
return 0;
|
||||
|
||||
case CDM_Play:
|
||||
if (!IsTrackAudio (parm1))
|
||||
{
|
||||
//Printf ("Track %d is not audio\n", track);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
length = GetTrackLength (parm1);
|
||||
|
||||
if (length == 0)
|
||||
{ // Couldn't get length of track, so won't be able to play last track
|
||||
PlayTo = MCI_MAKE_TMSF (parm1+1, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayTo = MCI_MAKE_TMSF (parm1,
|
||||
MCI_MSF_MINUTE(length),
|
||||
MCI_MSF_SECOND(length),
|
||||
MCI_MSF_FRAME(length));
|
||||
}
|
||||
PlayFrom = MCI_MAKE_TMSF (parm1, 0, 0, 0);
|
||||
Looping = parm2 & 1;
|
||||
|
||||
// intentional fall-through
|
||||
|
||||
case CDM_Replay:
|
||||
playParms.dwFrom = PlayFrom;
|
||||
playParms.dwTo = PlayTo;
|
||||
playParms.dwCallback = (DWORD_PTR)CD_Window;
|
||||
|
||||
return mciSendCommand (DeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY,
|
||||
(DWORD_PTR)&playParms);
|
||||
|
||||
case CDM_PlayCD:
|
||||
numTracks = GetNumTracks ();
|
||||
if (numTracks == 0)
|
||||
return FALSE;
|
||||
|
||||
for (firstTrack = 1; firstTrack <= numTracks && !IsTrackAudio (firstTrack); firstTrack++)
|
||||
;
|
||||
for (lastTrack = firstTrack; lastTrack <= numTracks && IsTrackAudio (lastTrack); lastTrack++)
|
||||
;
|
||||
|
||||
if (firstTrack > numTracks)
|
||||
return FALSE;
|
||||
if (lastTrack > numTracks)
|
||||
lastTrack = numTracks;
|
||||
|
||||
length = GetTrackLength (lastTrack);
|
||||
if (length == 0)
|
||||
return FALSE;
|
||||
|
||||
Looping = parm1 & 1;
|
||||
PlayFrom = MCI_MAKE_TMSF (firstTrack, 0, 0, 0);
|
||||
PlayTo = MCI_MAKE_TMSF (lastTrack,
|
||||
MCI_MSF_MINUTE(length),
|
||||
MCI_MSF_SECOND(length),
|
||||
MCI_MSF_FRAME(length));
|
||||
|
||||
playParms.dwFrom = PlayFrom;
|
||||
playParms.dwTo = PlayTo;
|
||||
playParms.dwCallback = (DWORD_PTR)CD_Window;
|
||||
|
||||
return mciSendCommand (DeviceID, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY,
|
||||
(DWORD_PTR)&playParms);
|
||||
|
||||
case CDM_Stop:
|
||||
return mciSendCommand (DeviceID, MCI_STOP, 0, 0);
|
||||
|
||||
case CDM_Eject:
|
||||
return mciSendCommand (DeviceID, MCI_SET, MCI_SET_DOOR_OPEN, 0);
|
||||
|
||||
case CDM_UnEject:
|
||||
return mciSendCommand (DeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, 0);
|
||||
|
||||
case CDM_Pause:
|
||||
return mciSendCommand (DeviceID, MCI_PAUSE, 0, 0);
|
||||
|
||||
case CDM_Resume:
|
||||
playParms.dwTo = PlayTo;
|
||||
playParms.dwCallback = (DWORD_PTR)CD_Window;
|
||||
return mciSendCommand (DeviceID, MCI_PLAY, MCI_TO | MCI_NOTIFY, (DWORD_PTR)&playParms);
|
||||
|
||||
case CDM_GetMode:
|
||||
statusParms.dwItem = MCI_STATUS_MODE;
|
||||
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&statusParms))
|
||||
{
|
||||
return CDMode_Unknown;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (statusParms.dwReturn)
|
||||
{
|
||||
case MCI_MODE_NOT_READY: return CDMode_NotReady;
|
||||
case MCI_MODE_PAUSE: return CDMode_Pause;
|
||||
case MCI_MODE_PLAY: return CDMode_Play;
|
||||
case MCI_MODE_STOP: return CDMode_Stop;
|
||||
case MCI_MODE_OPEN: return CDMode_Open;
|
||||
default: return CDMode_Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
case CDM_CheckTrack:
|
||||
return IsTrackAudio (parm1) ? TRUE : FALSE;
|
||||
|
||||
case CDM_GetMediaIdentity:
|
||||
case CDM_GetMediaUPC:
|
||||
wchar_t ident[32];
|
||||
|
||||
infoParms.lpstrReturn = ident;
|
||||
infoParms.dwRetSize = sizeof(ident);
|
||||
if (mciSendCommand (DeviceID, MCI_INFO,
|
||||
method == CDM_GetMediaIdentity ? MCI_WAIT|MCI_INFO_MEDIA_IDENTITY
|
||||
: MCI_WAIT|MCI_INFO_MEDIA_UPC, (DWORD_PTR)&infoParms))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return wcstoul (ident, NULL, 0);
|
||||
}
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// KillThread
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void KillThread ()
|
||||
{
|
||||
if (CDThread != NULL)
|
||||
{
|
||||
CDThread->DestroyThread ();
|
||||
Inited = NOT_INITED;
|
||||
delete CDThread;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Init
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT zmusic_bool CD_Enable (const char *cd_drive)
|
||||
{
|
||||
if (!cd_drive)
|
||||
{
|
||||
// lock the CD system.
|
||||
Enabled = false;
|
||||
CD_Close();
|
||||
return false;
|
||||
}
|
||||
Enabled = true; // this must have been called at least once to consider the use of the CD system
|
||||
if ((cd_drive)[0] == 0 || (cd_drive)[1] != 0)
|
||||
{
|
||||
return CD_Init (-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
char drive = toupper ((cd_drive)[0]);
|
||||
|
||||
if (drive >= 'A' && drive <= 'Z' && !CD_Init(drive - 'A'))
|
||||
{
|
||||
return CD_Init(-1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CD_Init (int device)
|
||||
{
|
||||
if (!Enabled) return false;
|
||||
|
||||
if (CDThread == NULL)
|
||||
{
|
||||
CDThread = new FCDThread;
|
||||
atexit (KillThread);
|
||||
}
|
||||
|
||||
if (Inited != device)
|
||||
{
|
||||
CD_Close ();
|
||||
|
||||
if (CDThread->SendMessage (CDM_Init, device, 0, 0, true))
|
||||
{
|
||||
Inited = device;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_InitID
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool CD_InitID (unsigned int id, int guess)
|
||||
{
|
||||
char drive;
|
||||
|
||||
if (guess < 0 && Inited != NOT_INITED)
|
||||
guess = Inited;
|
||||
|
||||
if (guess >= 0 && CD_Init (guess))
|
||||
{
|
||||
if (CDThread->SendMessage (CDM_GetMediaIdentity, 0, 0, 0, true) == id ||
|
||||
CDThread->SendMessage (CDM_GetMediaUPC, 0, 0, 0, true) == id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
CD_Close ();
|
||||
}
|
||||
|
||||
for (drive = 'V'; drive < 'Z'; drive++)
|
||||
{
|
||||
if (CD_Init (drive - 'A'))
|
||||
{
|
||||
// I don't know which value is stored in a CDDA file, so I try
|
||||
// them both. All the CDs I've tested have had the same value
|
||||
// for both, so it probably doesn't matter.
|
||||
if (CDThread->SendMessage (CDM_GetMediaIdentity, 0, 0, 0, true) == id ||
|
||||
CDThread->SendMessage (CDM_GetMediaUPC, 0, 0, 0, true) == id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
CD_Close ();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Close
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT void CD_Close ()
|
||||
{
|
||||
if (Inited != NOT_INITED)
|
||||
{
|
||||
CDThread->SendMessage (CDM_Close, 0, 0, 0, true);
|
||||
Inited = NOT_INITED;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Eject
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT void CD_Eject ()
|
||||
{
|
||||
if (Inited != NOT_INITED)
|
||||
CDThread->SendMessage (CDM_Eject, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_UnEject
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT zmusic_bool CD_UnEject ()
|
||||
{
|
||||
if (Inited == NOT_INITED)
|
||||
return false;
|
||||
|
||||
return CDThread->SendMessage (CDM_UnEject, 0, 0, 0, true) == 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Stop
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT void CD_Stop ()
|
||||
{
|
||||
if (Inited != NOT_INITED)
|
||||
CDThread->SendMessage (CDM_Stop, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Play
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool CD_Play (int track, bool looping)
|
||||
{
|
||||
if (Inited == NOT_INITED)
|
||||
return false;
|
||||
|
||||
return CDThread->SendMessage (CDM_Play, track, looping ? 1 : 0, 0, true) == 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_PlayNoWait
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void CD_PlayNoWait (int track, bool looping)
|
||||
{
|
||||
if (Inited != NOT_INITED)
|
||||
CDThread->SendMessage (CDM_Play, track, looping ? 1 : 0, 0, false);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_PlayCD
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool CD_PlayCD (bool looping)
|
||||
{
|
||||
if (Inited == NOT_INITED)
|
||||
return false;
|
||||
|
||||
return CDThread->SendMessage (CDM_PlayCD, looping ? 1 : 0, 0, 0, true) == 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_PlayCDNoWait
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void CD_PlayCDNoWait (bool looping)
|
||||
{
|
||||
if (Inited != NOT_INITED)
|
||||
CDThread->SendMessage (CDM_PlayCD, looping ? 1 : 0, 0, 0, false);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Pause
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT void CD_Pause ()
|
||||
{
|
||||
if (Inited != NOT_INITED)
|
||||
CDThread->SendMessage (CDM_Pause, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_Resume
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT zmusic_bool CD_Resume ()
|
||||
{
|
||||
if (Inited == NOT_INITED)
|
||||
return false;
|
||||
|
||||
return CDThread->SendMessage (CDM_Resume, 0, 0, 0, false) == 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_GetMode
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ECDModes CD_GetMode ()
|
||||
{
|
||||
if (Inited == NOT_INITED)
|
||||
return CDMode_Unknown;
|
||||
|
||||
return (ECDModes)CDThread->SendMessage (CDM_GetMode, 0, 0, 0, true);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_CheckTrack
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool CD_CheckTrack (int track)
|
||||
{
|
||||
if (Inited == NOT_INITED)
|
||||
return false;
|
||||
|
||||
return CDThread->SendMessage (CDM_CheckTrack, track, 0, 0, true) != 0;
|
||||
}
|
||||
|
||||
// Functions called only by the helper thread -------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// IsTrackAudio
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool FCDThread::IsTrackAudio (DWORD track) const
|
||||
{
|
||||
MCI_STATUS_PARMS statusParms;
|
||||
|
||||
statusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
|
||||
statusParms.dwTrack = track;
|
||||
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK,
|
||||
(DWORD_PTR)&statusParms))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return statusParms.dwReturn == MCI_CDA_TRACK_AUDIO;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GetTrackLength
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD FCDThread::GetTrackLength (DWORD track) const
|
||||
{
|
||||
MCI_STATUS_PARMS statusParms;
|
||||
|
||||
statusParms.dwItem = MCI_STATUS_LENGTH;
|
||||
statusParms.dwTrack = track;
|
||||
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK,
|
||||
(DWORD_PTR)&statusParms))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (DWORD)statusParms.dwReturn;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GetNumTracks
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DWORD FCDThread::GetNumTracks () const
|
||||
{
|
||||
MCI_STATUS_PARMS statusParms;
|
||||
|
||||
statusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
|
||||
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&statusParms))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (DWORD)statusParms.dwReturn;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// CD_WndProc (static)
|
||||
//
|
||||
// Because MCI (under Win 9x anyway) can't notify windows owned by another
|
||||
// thread, the helper thread creates its own window.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
LRESULT CALLBACK FCDThread::CD_WndProc (HWND hWnd, UINT message,
|
||||
WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case MM_MCINOTIFY:
|
||||
if (wParam == MCI_NOTIFY_SUCCESSFUL)
|
||||
{
|
||||
FCDThread *self = (FCDThread *)(LONG_PTR)GetWindowLongPtr (hWnd, GWLP_USERDATA);
|
||||
// Using SendMessage could deadlock, so don't do that.
|
||||
self->Dispatch (self->Looping ? CDM_Replay : CDM_Stop);
|
||||
}
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return DefWindowProc (hWnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
72
libraries/ZMusic/source/musicformats/win32/i_cd.h
Normal file
72
libraries/ZMusic/source/musicformats/win32/i_cd.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
** i_cd.h
|
||||
** Defines the CD interface
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2006 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __I_CD_H__
|
||||
#define __I_CD_H__
|
||||
|
||||
enum ECDModes
|
||||
{
|
||||
CDMode_Unknown,
|
||||
CDMode_NotReady,
|
||||
CDMode_Pause,
|
||||
CDMode_Play,
|
||||
CDMode_Stop,
|
||||
CDMode_Open
|
||||
};
|
||||
|
||||
// Opens a CD device. If device is non-negative, it specifies which device
|
||||
// to open. 0 is drive A:, 1 is drive B:, etc. If device is not specified,
|
||||
// the user's preference is used to decide which device to open.
|
||||
bool CD_Init (int device = -1);
|
||||
|
||||
// Open a CD device containing a specific CD. Tries device guess first.
|
||||
bool CD_InitID (unsigned int id, int guess=-1);
|
||||
|
||||
// Plays a single track, possibly looping
|
||||
bool CD_Play (int track, bool looping);
|
||||
|
||||
// Plays the first block of audio tracks on a CD, possibly looping
|
||||
bool CD_PlayCD (bool looping);
|
||||
|
||||
// Versions of the above that return as soon as possible
|
||||
void CD_PlayNoWait (int track, bool looping);
|
||||
void CD_PlayCDNoWait (bool looping);
|
||||
|
||||
// Get the CD drive's status (mode)
|
||||
ECDModes CD_GetMode ();
|
||||
|
||||
// Check if a track exists and is audio
|
||||
bool CD_CheckTrack (int track);
|
||||
|
||||
#endif //__I_CD_H__
|
||||
1232
libraries/ZMusic/source/streamsources/music_dumb.cpp
Normal file
1232
libraries/ZMusic/source/streamsources/music_dumb.cpp
Normal file
File diff suppressed because it is too large
Load diff
374
libraries/ZMusic/source/streamsources/music_gme.cpp
Normal file
374
libraries/ZMusic/source/streamsources/music_gme.cpp
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
/*
|
||||
** music_gme.cpp
|
||||
** General game music player, using Game Music Emu for decoding.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2009 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
// Uncomment if you are using the DLL version of GME.
|
||||
//#define GME_DLL
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "streamsource.h"
|
||||
#include <gme/gme.h>
|
||||
#include "fileio.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
class GMESong : public StreamSource
|
||||
{
|
||||
public:
|
||||
GMESong(Music_Emu *emu, int sample_rate);
|
||||
~GMESong();
|
||||
bool SetSubsong(int subsong) override;
|
||||
bool Start() override;
|
||||
void ChangeSettingNum(const char *name, double val) override;
|
||||
std::string GetStats() override;
|
||||
bool GetData(void *buffer, size_t len) override;
|
||||
SoundStreamInfoEx GetFormatEx() override;
|
||||
|
||||
protected:
|
||||
Music_Emu *Emu;
|
||||
gme_info_t *TrackInfo;
|
||||
int SampleRate;
|
||||
int CurrTrack;
|
||||
bool started = false;
|
||||
|
||||
bool StartTrack(int track, bool getcritsec=true);
|
||||
bool GetTrackInfo();
|
||||
int CalcSongLength();
|
||||
};
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// Currently not used.
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GME_CheckFormat
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
const char *GME_CheckFormat(uint32_t id)
|
||||
{
|
||||
return gme_identify_header(&id);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GME_OpenSong
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
StreamSource *GME_OpenSong(MusicIO::FileInterface *reader, const char *fmt, int sample_rate)
|
||||
{
|
||||
gme_type_t type;
|
||||
gme_err_t err;
|
||||
uint8_t *song;
|
||||
Music_Emu *emu;
|
||||
|
||||
type = gme_identify_extension(fmt);
|
||||
if (type == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
emu = gme_new_emu(type, sample_rate);
|
||||
if (emu == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto fpos = reader->tell();
|
||||
auto len = reader->filelength();
|
||||
|
||||
song = new uint8_t[len];
|
||||
if (reader->read(song, len) != len)
|
||||
{
|
||||
delete[] song;
|
||||
gme_delete(emu);
|
||||
reader->seek(fpos, SEEK_SET);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gme_load_data(emu, song, (long)len);
|
||||
delete[] song;
|
||||
|
||||
if (err != nullptr)
|
||||
{
|
||||
gme_delete(emu);
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
gme_set_stereo_depth(emu, std::min(std::max(miscConfig.gme_stereodepth, 0.f), 1.f));
|
||||
gme_set_fade(emu, -1); // Enable infinite loop
|
||||
|
||||
#if GME_VERSION >= 0x602
|
||||
gme_set_autoload_playback_limit(emu, 0);
|
||||
#endif // GME_VERSION >= 0x602
|
||||
|
||||
return new GMESong(emu, sample_rate);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong - Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
GMESong::GMESong(Music_Emu *emu, int sample_rate)
|
||||
{
|
||||
Emu = emu;
|
||||
SampleRate = sample_rate;
|
||||
CurrTrack = 0;
|
||||
TrackInfo = NULL;
|
||||
}
|
||||
|
||||
|
||||
SoundStreamInfoEx GMESong::GetFormatEx()
|
||||
{
|
||||
return { 32*1024, SampleRate, SampleType_Int16, ChannelConfig_Stereo };
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong - Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
GMESong::~GMESong()
|
||||
{
|
||||
if (TrackInfo != NULL)
|
||||
{
|
||||
gme_free_info(TrackInfo);
|
||||
}
|
||||
if (Emu != NULL)
|
||||
{
|
||||
gme_delete(Emu);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: GMEDepthChanged
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void GMESong::ChangeSettingNum(const char *name, double val)
|
||||
{
|
||||
if (Emu != nullptr && !stricmp(name, "gme.stereodepth"))
|
||||
{
|
||||
gme_set_stereo_depth(Emu, std::min(std::max(0., val), 1.));
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: Play
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool GMESong::Start()
|
||||
{
|
||||
return StartTrack(CurrTrack);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: SetSubsong
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool GMESong::SetSubsong(int track)
|
||||
{
|
||||
if (CurrTrack == track)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!started)
|
||||
{
|
||||
CurrTrack = track;
|
||||
return true;
|
||||
}
|
||||
return StartTrack(track);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: StartTrack
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool GMESong::StartTrack(int track, bool getcritsec)
|
||||
{
|
||||
gme_err_t err;
|
||||
|
||||
if (getcritsec)
|
||||
{
|
||||
err = gme_start_track(Emu, track);
|
||||
}
|
||||
else
|
||||
{
|
||||
err = gme_start_track(Emu, track);
|
||||
}
|
||||
if (err != NULL)
|
||||
{
|
||||
// This is called in the data reader thread which may not interact with the UI.
|
||||
// TBD: How to get the message across? An exception may not be used here!
|
||||
// Printf("Could not start track %d: %s\n", track, err);
|
||||
return false;
|
||||
}
|
||||
CurrTrack = track;
|
||||
started = true;
|
||||
GetTrackInfo();
|
||||
if (!m_Looping)
|
||||
{
|
||||
gme_set_fade(Emu, CalcSongLength());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string GMESong::GetStats()
|
||||
{
|
||||
char out[80];
|
||||
|
||||
if (TrackInfo != NULL)
|
||||
{
|
||||
int time = gme_tell(Emu);
|
||||
snprintf(out, 80,
|
||||
"Track: %d Time: %3d:%02d:%03d System: %s",
|
||||
CurrTrack,
|
||||
time/60000,
|
||||
(time/1000) % 60,
|
||||
time % 1000,
|
||||
TrackInfo->system);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: GetTrackInfo
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool GMESong::GetTrackInfo()
|
||||
{
|
||||
gme_err_t err;
|
||||
|
||||
if (TrackInfo != NULL)
|
||||
{
|
||||
gme_free_info(TrackInfo);
|
||||
TrackInfo = NULL;
|
||||
}
|
||||
err = gme_track_info(Emu, &TrackInfo, CurrTrack);
|
||||
if (err != NULL)
|
||||
{
|
||||
// This is called in the data reader thread which may not interact with the UI.
|
||||
// TBD: How to get the message across? An exception may not be used here!
|
||||
//Printf("Could not get track %d info: %s\n", CurrTrack, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: CalcSongLength
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int GMESong::CalcSongLength()
|
||||
{
|
||||
if (TrackInfo == NULL)
|
||||
{
|
||||
return 150000;
|
||||
}
|
||||
if (TrackInfo->length > 0)
|
||||
{
|
||||
return TrackInfo->length;
|
||||
}
|
||||
if (TrackInfo->loop_length > 0)
|
||||
{
|
||||
return TrackInfo->intro_length + TrackInfo->loop_length * 2;
|
||||
}
|
||||
return 150000;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// GMESong :: Read STATIC
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool GMESong::GetData(void *buffer, size_t len)
|
||||
{
|
||||
gme_err_t err;
|
||||
|
||||
if (gme_track_ended(Emu))
|
||||
{
|
||||
if (m_Looping)
|
||||
{
|
||||
StartTrack(CurrTrack, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(buffer, 0, len);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
err = gme_play(Emu, int(len >> 1), (short *)buffer);
|
||||
return (err == NULL);
|
||||
}
|
||||
568
libraries/ZMusic/source/streamsources/music_libsndfile.cpp
Normal file
568
libraries/ZMusic/source/streamsources/music_libsndfile.cpp
Normal file
|
|
@ -0,0 +1,568 @@
|
|||
/*
|
||||
** music_libsndfile.cpp
|
||||
** Uses libsndfile for streaming music formats
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2017 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include "zmusic_internal.h"
|
||||
#include "streamsource.h"
|
||||
#include "zmusic/sounddecoder.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
class SndFileSong : public StreamSource
|
||||
{
|
||||
public:
|
||||
SndFileSong(SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end, bool startass, bool endass);
|
||||
~SndFileSong();
|
||||
std::string GetStats() override;
|
||||
SoundStreamInfoEx GetFormatEx() override;
|
||||
bool GetData(void *buffer, size_t len) override;
|
||||
|
||||
protected:
|
||||
SoundDecoder *Decoder;
|
||||
unsigned int FrameSize;
|
||||
|
||||
uint32_t Loop_Start;
|
||||
uint32_t Loop_End;
|
||||
|
||||
int CalcSongLength();
|
||||
};
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||
|
||||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||||
|
||||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||
|
||||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||
|
||||
// CODE --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// S_ParseTimeTag
|
||||
//
|
||||
// Passed the value of a loop point tag, converts it to numbers.
|
||||
//
|
||||
// This may be of the form 00:00:00.00 (HH:MM:SS.ss) to specify by play
|
||||
// time. Various parts may be left off. The only requirement is that it
|
||||
// contain a colon. e.g. To start the loop at 20 seconds in, you can use
|
||||
// ":20", "0:20", "00:00:20", ":20.0", etc. Values after the decimal are
|
||||
// fractions of a second.
|
||||
//
|
||||
// If you don't include a colon but just have a raw number, then it's
|
||||
// the number of PCM samples at which to loop.
|
||||
//
|
||||
// Returns true if the tag made sense, false if not.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool S_ParseTimeTag(const char* tag, zmusic_bool* as_samples, unsigned int* time)
|
||||
{
|
||||
const int time_count = 3;
|
||||
const char* bit = tag;
|
||||
char ms[3] = { 0 };
|
||||
unsigned int times[time_count] = { 0 };
|
||||
int ms_pos = 0, time_pos = 0;
|
||||
bool pcm = true, in_ms = false;
|
||||
|
||||
for (bit = tag; *bit != '\0'; ++bit)
|
||||
{
|
||||
if (*bit >= '0' && *bit <= '9')
|
||||
{
|
||||
if (in_ms)
|
||||
{
|
||||
// Ignore anything past three fractional digits.
|
||||
if (ms_pos < 3)
|
||||
{
|
||||
ms[ms_pos++] = *bit - '0';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
times[time_pos] = times[time_pos] * 10 + *bit - '0';
|
||||
}
|
||||
}
|
||||
else if (*bit == ':')
|
||||
{
|
||||
if (in_ms)
|
||||
{ // If we already specified milliseconds, we can't take any more parts.
|
||||
return false;
|
||||
}
|
||||
pcm = false;
|
||||
if (++time_pos == time_count)
|
||||
{ // Time too long. (Seriously, starting the loop days in?)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (*bit == '.')
|
||||
{
|
||||
if (pcm || in_ms)
|
||||
{ // It doesn't make sense to have fractional PCM values.
|
||||
// It also doesn't make sense to have more than one dot.
|
||||
return false;
|
||||
}
|
||||
in_ms = true;
|
||||
}
|
||||
else
|
||||
{ // Anything else: We don't understand this.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (pcm)
|
||||
{
|
||||
*as_samples = true;
|
||||
*time = times[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int mytime = 0;
|
||||
|
||||
// Add in hours, minutes, and seconds
|
||||
for (int i = 0; i <= time_pos; ++i)
|
||||
{
|
||||
mytime = mytime * 60 + times[i];
|
||||
}
|
||||
|
||||
// Add in milliseconds
|
||||
mytime = mytime * 1000 + ms[0] * 100 + ms[1] * 10 + ms[2];
|
||||
|
||||
*as_samples = false;
|
||||
*time = mytime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Try to find the LOOP_START/LOOP_END tags in a Vorbis Comment block
|
||||
//
|
||||
// We have to parse through the FLAC or Ogg headers manually, since sndfile
|
||||
// doesn't provide proper access to the comments and we'd rather not require
|
||||
// using libFLAC and libvorbisfile directly.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void ParseVorbisComments(MusicIO::FileInterface *fr, uint32_t *start, zmusic_bool *startass, uint32_t *end, zmusic_bool *endass)
|
||||
{
|
||||
uint8_t vc_data[4];
|
||||
|
||||
// The VC block starts with a 32LE integer for the vendor string length,
|
||||
// followed by the vendor string
|
||||
if(fr->read(vc_data, 4) != 4)
|
||||
return;
|
||||
uint32_t vndr_len = vc_data[0] | (vc_data[1]<<8) | (vc_data[2]<<16) | (vc_data[3]<<24);
|
||||
|
||||
// Skip vendor string
|
||||
if(fr->seek(vndr_len, SEEK_CUR) == -1)
|
||||
return;
|
||||
|
||||
// Following the vendor string is a 32LE integer for the number of
|
||||
// comments, followed by each comment.
|
||||
if(fr->read(vc_data, 4) != 4)
|
||||
return;
|
||||
size_t count = vc_data[0] | (vc_data[1]<<8) | (vc_data[2]<<16) | (vc_data[3]<<24);
|
||||
|
||||
zmusic_bool loopass = false;
|
||||
uint32_t looplen = 0;
|
||||
bool endfound = false;
|
||||
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
// Each comment is a 32LE integer for the comment length, followed by
|
||||
// the comment text (not null terminated!)
|
||||
if(fr->read(vc_data, 4) != 4)
|
||||
return;
|
||||
uint32_t length = vc_data[0] | (vc_data[1]<<8) | (vc_data[2]<<16) | (vc_data[3]<<24);
|
||||
|
||||
if(length >= 128)
|
||||
{
|
||||
// If the comment is "big", skip it
|
||||
if(fr->seek(length, SEEK_CUR) == -1)
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
char strdat[128];
|
||||
if(fr->read(strdat, length) != (long)length)
|
||||
return;
|
||||
strdat[length] = 0;
|
||||
|
||||
static const char* loopStartTags[] = { "LOOP_START=", "LOOPSTART=", "LOOP=" };
|
||||
static const char* loopEndTags[] = { "LOOP_END=", "LOOPEND=" };
|
||||
static const char* loopLengthTags[] = { "LOOP_LENGTH=", "LOOPLENGTH=" };
|
||||
|
||||
for (auto tag : loopStartTags)
|
||||
{
|
||||
const size_t tagLength = strlen(tag);
|
||||
|
||||
if (!strnicmp(strdat, tag, tagLength))
|
||||
{
|
||||
S_ParseTimeTag(strdat + tagLength, startass, start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto tag : loopEndTags)
|
||||
{
|
||||
const size_t tagLength = strlen(tag);
|
||||
|
||||
if (!strnicmp(strdat, tag, tagLength))
|
||||
{
|
||||
S_ParseTimeTag(strdat + tagLength, endass, end);
|
||||
endfound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto tag : loopLengthTags)
|
||||
{
|
||||
const size_t tagLength = strlen(tag);
|
||||
|
||||
if (!strnicmp(strdat, tag, tagLength))
|
||||
{
|
||||
S_ParseTimeTag(strdat + tagLength, &loopass, &looplen);
|
||||
*end += *start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use loop length only if no end defined.
|
||||
if (!endfound && looplen && loopass == *startass)
|
||||
{
|
||||
*endass = loopass;
|
||||
*end = *start + looplen;
|
||||
}
|
||||
}
|
||||
|
||||
static void FindFlacComments(MusicIO::FileInterface *fr, uint32_t *loop_start, zmusic_bool *startass, uint32_t *loop_end, zmusic_bool *endass)
|
||||
{
|
||||
// Already verified the fLaC marker, so we're 4 bytes into the file
|
||||
bool lastblock = false;
|
||||
uint8_t header[4];
|
||||
|
||||
while(!lastblock && fr->read(header, 4) == 4)
|
||||
{
|
||||
// The first byte of the block header contains the type and a flag
|
||||
// indicating the last metadata block
|
||||
char blocktype = header[0]&0x7f;
|
||||
lastblock = !!(header[0]&0x80);
|
||||
// Following the type is a 24BE integer for the size of the block
|
||||
uint32_t blocksize = (header[1]<<16) | (header[2]<<8) | header[3];
|
||||
|
||||
// FLAC__METADATA_TYPE_VORBIS_COMMENT is 4
|
||||
if(blocktype == 4)
|
||||
{
|
||||
ParseVorbisComments(fr, loop_start, startass, loop_end, endass);
|
||||
return;
|
||||
}
|
||||
|
||||
if(fr->seek(blocksize, SEEK_CUR) == -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void FindOggComments(MusicIO::FileInterface *fr, uint32_t *loop_start, zmusic_bool *startass, uint32_t *loop_end, zmusic_bool *endass)
|
||||
{
|
||||
uint8_t ogghead[27];
|
||||
|
||||
// We already read and verified the OggS marker, so skip the first 4 bytes
|
||||
// of the Ogg page header.
|
||||
while(fr->read(ogghead+4, 23) == 23)
|
||||
{
|
||||
// The 19th byte of the Ogg header is a 32LE integer for the page
|
||||
// number, and the 27th is a uint8 for the number of segments in the
|
||||
// page.
|
||||
uint32_t ogg_pagenum = ogghead[18] | (ogghead[19]<<8) | (ogghead[20]<<16) |
|
||||
(ogghead[21]<<24);
|
||||
uint8_t ogg_segments = ogghead[26];
|
||||
|
||||
// Following the Ogg page header is a series of uint8s for the length of
|
||||
// each segment in the page. The page segment data follows contiguously
|
||||
// after.
|
||||
uint8_t segsizes[256];
|
||||
if(fr->read(segsizes, ogg_segments) != ogg_segments)
|
||||
break;
|
||||
|
||||
// Find the segment with the Vorbis Comment packet (type 3) or Opus tags.
|
||||
bool vorbis_comments = false;
|
||||
for(int i = 0; i < ogg_segments; ++i)
|
||||
{
|
||||
uint8_t segsize = segsizes[i];
|
||||
|
||||
if(segsize > 16)
|
||||
{
|
||||
uint8_t vorbhead[8];
|
||||
if(fr->read(vorbhead, 8) != 8)
|
||||
return;
|
||||
|
||||
if(vorbhead[0] == 3 && memcmp(vorbhead + 1, "vorbis", 6) == 0)
|
||||
{
|
||||
// Seek back because the vorbis tag is only 7 bytes long.
|
||||
if(fr->seek(-1, SEEK_CUR) == -1)
|
||||
return;
|
||||
segsize++;
|
||||
|
||||
vorbis_comments = true;
|
||||
}
|
||||
else if(memcmp(vorbhead, "OpusTags", 8) == 0)
|
||||
vorbis_comments = true;
|
||||
|
||||
if(vorbis_comments)
|
||||
{
|
||||
// If the packet is 'laced', it spans multiple segments (a
|
||||
// segment size of 255 indicates the next segment continues
|
||||
// the packet, ending with a size less than 255). Vorbis
|
||||
// packets always start and end on segment boundaries. A
|
||||
// packet that's an exact multiple of 255 ends with a
|
||||
// segment of 0 size.
|
||||
while(segsize == 255 && ++i < ogg_segments)
|
||||
segsize = segsizes[i];
|
||||
|
||||
// TODO: A Vorbis packet can theoretically span multiple
|
||||
// Ogg pages (e.g. start in the last segment of one page
|
||||
// and end in the first segment of a following page). That
|
||||
// will require extra logic to decode as the VC block will
|
||||
// be broken up with non-Vorbis data in-between. For now,
|
||||
// just handle the common case where it's all in one page.
|
||||
if(i < ogg_segments)
|
||||
ParseVorbisComments(fr, loop_start, startass, loop_end, endass);
|
||||
return;
|
||||
}
|
||||
|
||||
segsize -= 8;
|
||||
}
|
||||
if(fr->seek(segsize, SEEK_CUR) == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't keep looking after the third page
|
||||
if(ogg_pagenum >= 2)
|
||||
break;
|
||||
|
||||
if(fr->read(ogghead, 4) != 4 || memcmp(ogghead, "OggS", 4) != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FindLoopTags(MusicIO::FileInterface *fr, uint32_t *start, zmusic_bool *startass, uint32_t *end, zmusic_bool *endass)
|
||||
{
|
||||
uint8_t signature[4];
|
||||
|
||||
fr->read(signature, 4);
|
||||
if(memcmp(signature, "fLaC", 4) == 0)
|
||||
FindFlacComments(fr, start, startass, end, endass);
|
||||
else if(memcmp(signature, "OggS", 4) == 0)
|
||||
FindOggComments(fr, start, startass, end, endass);
|
||||
}
|
||||
|
||||
DLL_EXPORT void FindLoopTags(const uint8_t* data, size_t size, uint32_t* start, zmusic_bool* startass, uint32_t* end, zmusic_bool* endass)
|
||||
{
|
||||
MusicIO::FileInterface* reader = new MusicIO::MemoryReader(data, (long)size);
|
||||
FindLoopTags(reader, start, startass, end, endass);
|
||||
reader->close();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SndFile_OpenSong
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
StreamSource *SndFile_OpenSong(MusicIO::FileInterface *fr)
|
||||
{
|
||||
fr->seek(0, SEEK_SET);
|
||||
|
||||
uint32_t loop_start = 0, loop_end = ~0u;
|
||||
zmusic_bool startass = false, endass = false;
|
||||
FindLoopTags(fr, &loop_start, &startass, &loop_end, &endass);
|
||||
|
||||
fr->seek(0, SEEK_SET);
|
||||
auto decoder = SoundDecoder::CreateDecoder(fr);
|
||||
if (decoder == nullptr) return nullptr; // If this fails the file reader has not been taken over and the caller needs to clean up. This is to allow further analysis of the passed file.
|
||||
return new SndFileSong(decoder, loop_start, loop_end, startass, endass);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SndFileSong - Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static int32_t Scale(int32_t a, int32_t b, int32_t c)
|
||||
{
|
||||
return (int32_t)(((int64_t)a * b) / c);
|
||||
}
|
||||
|
||||
SndFileSong::SndFileSong(SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end, bool startass, bool endass)
|
||||
{
|
||||
ChannelConfig chanconf;
|
||||
SampleType stype;
|
||||
int srate;
|
||||
|
||||
decoder->getInfo(&srate, &chanconf, &stype);
|
||||
|
||||
if (!startass) loop_start = Scale(loop_start, srate, 1000);
|
||||
if (!endass) loop_end = Scale(loop_end, srate, 1000);
|
||||
|
||||
const uint32_t sampleLength = (uint32_t)decoder->getSampleLength();
|
||||
Loop_Start = loop_start;
|
||||
Loop_End = sampleLength == 0 ? loop_end : std::min<uint32_t>(loop_end, sampleLength);
|
||||
Decoder = decoder;
|
||||
FrameSize = ZMusic_ChannelCount(chanconf) * ZMusic_SampleTypeSize(stype);
|
||||
}
|
||||
|
||||
SoundStreamInfoEx SndFileSong::GetFormatEx()
|
||||
{
|
||||
ChannelConfig chanconf;
|
||||
SampleType stype;
|
||||
int srate;
|
||||
|
||||
Decoder->getInfo(&srate, &chanconf, &stype);
|
||||
return { 64/*snd_streambuffersize*/ * 1024, srate, stype, chanconf };
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SndFileSong - Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SndFileSong::~SndFileSong()
|
||||
{
|
||||
if (Decoder != nullptr)
|
||||
{
|
||||
delete Decoder;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SndFileSong :: GetStats
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
std::string SndFileSong::GetStats()
|
||||
{
|
||||
char out[80];
|
||||
|
||||
ChannelConfig chanconf;
|
||||
SampleType stype;
|
||||
int srate;
|
||||
Decoder->getInfo(&srate, &chanconf, &stype);
|
||||
|
||||
size_t SamplePos = Decoder->getSampleOffset();
|
||||
int time = int (SamplePos / srate);
|
||||
|
||||
snprintf(out, 80,
|
||||
"Track: %s, %dHz Time: %02d:%02d",
|
||||
ZMusic_ChannelConfigName(chanconf), srate,
|
||||
time/60,
|
||||
time % 60);
|
||||
return out;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// SndFileSong :: Read STATIC
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool SndFileSong::GetData(void *vbuff, size_t len)
|
||||
{
|
||||
char *buff = (char*)vbuff;
|
||||
|
||||
size_t currentpos = Decoder->getSampleOffset();
|
||||
size_t framestoread = len / FrameSize;
|
||||
bool err = false;
|
||||
if (!m_Looping)
|
||||
{
|
||||
size_t maxpos = Decoder->getSampleLength();
|
||||
if (currentpos == maxpos)
|
||||
{
|
||||
memset(buff, 0, len);
|
||||
return false;
|
||||
}
|
||||
if (currentpos + framestoread > maxpos)
|
||||
{
|
||||
size_t got = Decoder->read(buff, (maxpos - currentpos) * FrameSize);
|
||||
memset(buff + got, 0, len - got);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t got = Decoder->read(buff, len);
|
||||
err = (got != len);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This looks a bit more complicated than necessary because libmpg123 will not read the full requested length for the last block in the file.
|
||||
if (currentpos + framestoread > Loop_End)
|
||||
{
|
||||
// Loop can be very short, make sure the current position doesn't exceed it
|
||||
if (currentpos < Loop_End)
|
||||
{
|
||||
size_t endblock = (Loop_End - currentpos) * FrameSize;
|
||||
size_t endlen = Decoder->read(buff, endblock);
|
||||
|
||||
// Even if zero bytes was read give it a chance to start from the beginning
|
||||
buff += endlen;
|
||||
len -= endlen;
|
||||
}
|
||||
|
||||
Decoder->seek(Loop_Start, false, true);
|
||||
}
|
||||
while (len > 0)
|
||||
{
|
||||
size_t readlen = Decoder->read(buff, len);
|
||||
if (readlen == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
buff += readlen;
|
||||
len -= readlen;
|
||||
if (len > 0)
|
||||
{
|
||||
Decoder->seek(Loop_Start, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
180
libraries/ZMusic/source/streamsources/music_libxmp.cpp
Normal file
180
libraries/ZMusic/source/streamsources/music_libxmp.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
** music_libxmp.cpp
|
||||
** libxmp module player.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2024 Cacodemon345
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "streamsource.h"
|
||||
|
||||
#define LIBXMP_STATIC 1
|
||||
#include "../libxmp/include/xmp.h"
|
||||
#include "zmusic/m_swap.h"
|
||||
#include "zmusic/mididefs.h"
|
||||
#include "zmusic/midiconfig.h"
|
||||
#include "fileio.h"
|
||||
|
||||
extern DumbConfig dumbConfig;
|
||||
|
||||
static unsigned long xmp_read(void *dest, unsigned long len, unsigned long nmemb, void *priv)
|
||||
{
|
||||
if (len == 0 || nmemb == 0)
|
||||
return (unsigned long)0;
|
||||
|
||||
MusicIO::FileInterface* interface = (MusicIO::FileInterface*)priv;
|
||||
|
||||
auto origpos = interface->tell();
|
||||
auto length = interface->read(dest, (int32_t)(len * nmemb));
|
||||
|
||||
if (length != len * nmemb)
|
||||
{
|
||||
// Let's hope the compiler doesn't misoptimize this.
|
||||
interface->seek(origpos + (length / len) * len, SEEK_SET);
|
||||
}
|
||||
return length / len;
|
||||
}
|
||||
|
||||
static struct xmp_callbacks callbacks =
|
||||
{
|
||||
xmp_read,
|
||||
[](void *priv, long offset, int whence) -> int { return ((MusicIO::FileInterface*)priv)->seek(offset, whence); },
|
||||
[](void *priv) -> long { return ((MusicIO::FileInterface*)priv)->tell(); },
|
||||
[](void *priv) -> int { return 0; }
|
||||
};
|
||||
|
||||
class XMPSong : public StreamSource
|
||||
{
|
||||
private:
|
||||
xmp_context context = nullptr;
|
||||
int samplerate = 44100;
|
||||
int subsong = 0;
|
||||
|
||||
// libxmp can't output in float.
|
||||
std::vector<int16_t> int16_buffer;
|
||||
|
||||
public:
|
||||
XMPSong(xmp_context ctx, int samplerate);
|
||||
~XMPSong();
|
||||
bool SetSubsong(int subsong) override;
|
||||
bool Start() override;
|
||||
SoundStreamInfoEx GetFormatEx() override;
|
||||
|
||||
protected:
|
||||
bool GetData(void *buffer, size_t len) override;
|
||||
};
|
||||
|
||||
XMPSong::XMPSong(xmp_context ctx, int rate)
|
||||
{
|
||||
context = ctx;
|
||||
samplerate = (dumbConfig.mod_samplerate != 0) ? dumbConfig.mod_samplerate : rate;
|
||||
xmp_set_player(context, XMP_PLAYER_VOLUME, 100);
|
||||
xmp_set_player(context, XMP_PLAYER_INTERP, dumbConfig.mod_interp);
|
||||
|
||||
int16_buffer.reserve(16 * 1024);
|
||||
}
|
||||
|
||||
XMPSong::~XMPSong()
|
||||
{
|
||||
xmp_end_player(context);
|
||||
xmp_free_context(context);
|
||||
}
|
||||
|
||||
SoundStreamInfoEx XMPSong::GetFormatEx()
|
||||
{
|
||||
return { 32 * 1024, samplerate, SampleType_Float32, ChannelConfig_Stereo };
|
||||
}
|
||||
|
||||
bool XMPSong::SetSubsong(int subsong)
|
||||
{
|
||||
this->subsong = subsong;
|
||||
if (xmp_get_player(context, XMP_PLAYER_STATE) >= XMP_STATE_PLAYING)
|
||||
return xmp_set_position(context, subsong) >= 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XMPSong::GetData(void *buffer, size_t len)
|
||||
{
|
||||
if ((len / 4) > int16_buffer.size())
|
||||
int16_buffer.resize(len / 4);
|
||||
|
||||
int ret = xmp_play_buffer(context, (void*)int16_buffer.data(), len / 2, m_Looping? INT_MAX : 0);
|
||||
xmp_set_player(context, XMP_PLAYER_INTERP, dumbConfig.mod_interp);
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
float* soundbuffer = (float*)buffer;
|
||||
for (unsigned int i = 0; i < len / 4; i++)
|
||||
{
|
||||
soundbuffer[i] = ((int16_buffer[i] < 0.) ? (int16_buffer[i] / 32768.) : (int16_buffer[i] / 32767.)) * dumbConfig.mod_dumb_mastervolume;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret < 0 && m_Looping)
|
||||
{
|
||||
xmp_restart_module(context);
|
||||
xmp_set_position(context, subsong);
|
||||
return true;
|
||||
}
|
||||
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
bool XMPSong::Start()
|
||||
{
|
||||
int ret = xmp_start_player(context, samplerate, 0);
|
||||
if (ret >= 0)
|
||||
xmp_set_position(context, subsong);
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
StreamSource* XMP_OpenSong(MusicIO::FileInterface* reader, int samplerate)
|
||||
{
|
||||
if (xmp_test_module_from_callbacks((void*)reader, callbacks, nullptr) < 0)
|
||||
return nullptr;
|
||||
|
||||
xmp_context ctx = xmp_create_context();
|
||||
if (!ctx)
|
||||
return nullptr;
|
||||
|
||||
reader->seek(0, SEEK_SET);
|
||||
|
||||
if (xmp_load_module_from_callbacks(ctx, (void*)reader, callbacks) < 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new XMPSong(ctx, samplerate);
|
||||
}
|
||||
|
||||
159
libraries/ZMusic/source/streamsources/music_opl.cpp
Normal file
159
libraries/ZMusic/source/streamsources/music_opl.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
** music_opl.cpp
|
||||
** Plays raw OPL formats
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "zmusic_internal.h"
|
||||
|
||||
#ifdef HAVE_OPL
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "streamsource.h"
|
||||
#include "oplsynth/opl.h"
|
||||
#include "oplsynth/opl_mus_player.h"
|
||||
#include "fileio.h"
|
||||
#include "zmusic/midiconfig.h"
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// OPL file played by a software OPL2 synth and streamed through the sound system
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class OPLMUSSong : public StreamSource
|
||||
{
|
||||
public:
|
||||
OPLMUSSong (MusicIO::FileInterface *reader, OPLConfig *config);
|
||||
~OPLMUSSong ();
|
||||
bool Start() override;
|
||||
void ChangeSettingInt(const char *name, int value) override;
|
||||
SoundStreamInfoEx GetFormatEx() override;
|
||||
|
||||
protected:
|
||||
bool GetData(void *buffer, size_t len) override;
|
||||
|
||||
OPLmusicFile *Music;
|
||||
int current_opl_core;
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPLMUSSong::OPLMUSSong(MusicIO::FileInterface* reader, OPLConfig* config)
|
||||
{
|
||||
const char* error = nullptr;
|
||||
reader->seek(0, SEEK_END);
|
||||
auto fs = reader->tell();
|
||||
reader->seek(0, SEEK_SET);
|
||||
std::vector<uint8_t> data(fs);
|
||||
reader->read(data.data(), (int)data.size());
|
||||
Music = new OPLmusicFile(data.data(), data.size(), config->core, config->numchips, error);
|
||||
if (error)
|
||||
{
|
||||
delete Music;
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
current_opl_core = config->core;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
SoundStreamInfoEx OPLMUSSong::GetFormatEx()
|
||||
{
|
||||
int samples = int(OPL_SAMPLE_RATE / 14);
|
||||
return { samples * 4, int(OPL_SAMPLE_RATE), SampleType_Float32,
|
||||
current_opl_core == 0? ChannelConfig_Mono:ChannelConfig_Stereo };
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
OPLMUSSong::~OPLMUSSong ()
|
||||
{
|
||||
if (Music != NULL)
|
||||
{
|
||||
delete Music;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void OPLMUSSong::ChangeSettingInt(const char * name, int val)
|
||||
{
|
||||
if (!strcmp(name, "opl.numchips"))
|
||||
Music->ResetChips (val);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool OPLMUSSong::Start()
|
||||
{
|
||||
Music->SetLooping (m_Looping);
|
||||
Music->Restart ();
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool OPLMUSSong::GetData(void *buffer, size_t len)
|
||||
{
|
||||
return Music->ServiceStream(buffer, int(len)) ? len : 0;
|
||||
}
|
||||
|
||||
StreamSource *OPL_OpenSong(MusicIO::FileInterface* reader, OPLConfig *config)
|
||||
{
|
||||
return new OPLMUSSong(reader, config);
|
||||
}
|
||||
#endif
|
||||
354
libraries/ZMusic/source/streamsources/music_xa.cpp
Normal file
354
libraries/ZMusic/source/streamsources/music_xa.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
#include <algorithm>
|
||||
#include "streamsource.h"
|
||||
#include "fileio.h"
|
||||
|
||||
/**
|
||||
* PlayStation XA (ADPCM) source support for MultiVoc
|
||||
* Adapted and remixed from superxa2wav
|
||||
*
|
||||
* taken from EDuke32 and adapted for GZDoom by Christoph Oelckers
|
||||
*/
|
||||
|
||||
|
||||
//#define NO_XA_HEADER
|
||||
|
||||
enum
|
||||
{
|
||||
kNumOfSamples = 224,
|
||||
kNumOfSGs = 18,
|
||||
TTYWidth = 80,
|
||||
|
||||
kBufSize = (kNumOfSGs*kNumOfSamples),
|
||||
kSamplesMono = (kNumOfSGs*kNumOfSamples),
|
||||
kSamplesStereo = (kNumOfSGs*kNumOfSamples/2),
|
||||
|
||||
/* ADPCM */
|
||||
XA_DATA_START = (0x44-48)
|
||||
};
|
||||
|
||||
inline float constexpr DblToPCMF(double dt) { return float(dt) * (1.f/32768.f); }
|
||||
|
||||
typedef struct {
|
||||
MusicIO::FileInterface *reader;
|
||||
size_t committed;
|
||||
size_t length;
|
||||
bool blockIsMono;
|
||||
bool blockIs18K;
|
||||
bool finished;
|
||||
|
||||
double t1, t2;
|
||||
double t1_x, t2_x;
|
||||
|
||||
float block[kBufSize];
|
||||
} xa_data;
|
||||
|
||||
typedef int8_t SoundGroup[128];
|
||||
|
||||
typedef struct XASector {
|
||||
int8_t sectorFiller[48];
|
||||
SoundGroup SoundGroups[18];
|
||||
} XASector;
|
||||
|
||||
static double K0[4] = {
|
||||
0.0,
|
||||
0.9375,
|
||||
1.796875,
|
||||
1.53125
|
||||
};
|
||||
static double K1[4] = {
|
||||
0.0,
|
||||
0.0,
|
||||
-0.8125,
|
||||
-0.859375
|
||||
};
|
||||
|
||||
|
||||
|
||||
static int8_t getSoundData(int8_t *buf, int32_t unit, int32_t sample)
|
||||
{
|
||||
int8_t ret;
|
||||
int8_t *p;
|
||||
int32_t offset, shift;
|
||||
|
||||
p = buf;
|
||||
shift = (unit%2) * 4;
|
||||
|
||||
offset = 16 + (unit / 2) + (sample * 4);
|
||||
p += offset;
|
||||
|
||||
ret = (*p >> shift) & 0x0F;
|
||||
|
||||
if (ret > 7) {
|
||||
ret -= 16;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int8_t getFilter(const int8_t *buf, int32_t unit)
|
||||
{
|
||||
return (*(buf + 4 + unit) >> 4) & 0x03;
|
||||
}
|
||||
|
||||
|
||||
static int8_t getRange(const int8_t *buf, int32_t unit)
|
||||
{
|
||||
return *(buf + 4 + unit) & 0x0F;
|
||||
}
|
||||
|
||||
|
||||
static void decodeSoundSectMono(XASector *ssct, xa_data * xad)
|
||||
{
|
||||
size_t count = 0;
|
||||
int8_t snddat, filt, range;
|
||||
int32_t unit, sample;
|
||||
int32_t sndgrp;
|
||||
double tmp2, tmp3, tmp4, tmp5;
|
||||
auto &decodeBuf = xad->block;
|
||||
|
||||
for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
|
||||
{
|
||||
for (unit = 0; unit < 8; unit++)
|
||||
{
|
||||
range = getRange(ssct->SoundGroups[sndgrp], unit);
|
||||
filt = getFilter(ssct->SoundGroups[sndgrp], unit);
|
||||
for (sample = 0; sample < 28; sample++)
|
||||
{
|
||||
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
|
||||
tmp2 = (double)(1 << (12 - range));
|
||||
tmp3 = (double)snddat * tmp2;
|
||||
tmp4 = xad->t1 * K0[filt];
|
||||
tmp5 = xad->t2 * K1[filt];
|
||||
xad->t2 = xad->t1;
|
||||
xad->t1 = tmp3 + tmp4 + tmp5;
|
||||
decodeBuf[count++] = DblToPCMF(xad->t1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decodeSoundSectStereo(XASector *ssct, xa_data * xad)
|
||||
{
|
||||
size_t count = 0;
|
||||
int8_t snddat, filt, range;
|
||||
int8_t filt1, range1;
|
||||
int32_t unit, sample;
|
||||
int32_t sndgrp;
|
||||
double tmp2, tmp3, tmp4, tmp5;
|
||||
auto &decodeBuf = xad->block;
|
||||
|
||||
for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
|
||||
{
|
||||
for (unit = 0; unit < 8; unit+= 2)
|
||||
{
|
||||
range = getRange(ssct->SoundGroups[sndgrp], unit);
|
||||
filt = getFilter(ssct->SoundGroups[sndgrp], unit);
|
||||
range1 = getRange(ssct->SoundGroups[sndgrp], unit+1);
|
||||
filt1 = getFilter(ssct->SoundGroups[sndgrp], unit+1);
|
||||
|
||||
for (sample = 0; sample < 28; sample++)
|
||||
{
|
||||
// Channel 1
|
||||
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
|
||||
tmp2 = (double)(1 << (12 - range));
|
||||
tmp3 = (double)snddat * tmp2;
|
||||
tmp4 = xad->t1 * K0[filt];
|
||||
tmp5 = xad->t2 * K1[filt];
|
||||
xad->t2 = xad->t1;
|
||||
xad->t1 = tmp3 + tmp4 + tmp5;
|
||||
decodeBuf[count++] = DblToPCMF(xad->t1);
|
||||
|
||||
// Channel 2
|
||||
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit+1, sample);
|
||||
tmp2 = (double)(1 << (12 - range1));
|
||||
tmp3 = (double)snddat * tmp2;
|
||||
tmp4 = xad->t1_x * K0[filt1];
|
||||
tmp5 = xad->t2_x * K1[filt1];
|
||||
xad->t2_x = xad->t1_x;
|
||||
xad->t1_x = tmp3 + tmp4 + tmp5;
|
||||
decodeBuf[count++] = DblToPCMF(xad->t1_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Get one decoded block of data
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void getNextXABlock(xa_data *xad, bool looping )
|
||||
{
|
||||
XASector ssct;
|
||||
int coding;
|
||||
const int SUBMODE_REAL_TIME_SECTOR = (1 << 6);
|
||||
const int SUBMODE_FORM = (1 << 5);
|
||||
const int SUBMODE_AUDIO_DATA = (1 << 2);
|
||||
|
||||
do
|
||||
{
|
||||
size_t bytes = xad->length - xad->reader->tell();
|
||||
|
||||
if (sizeof(XASector) < bytes)
|
||||
bytes = sizeof(XASector);
|
||||
|
||||
xad->reader->read(&ssct, (int)bytes);
|
||||
}
|
||||
while (ssct.sectorFiller[46] != (SUBMODE_REAL_TIME_SECTOR | SUBMODE_FORM | SUBMODE_AUDIO_DATA));
|
||||
|
||||
coding = ssct.sectorFiller[47];
|
||||
|
||||
xad->committed = 0;
|
||||
xad->blockIsMono = (coding & 3) == 0;
|
||||
xad->blockIs18K = (((coding >> 2) & 3) == 1);
|
||||
|
||||
if (!xad->blockIsMono)
|
||||
{
|
||||
decodeSoundSectStereo(&ssct, xad);
|
||||
}
|
||||
else
|
||||
{
|
||||
decodeSoundSectMono(&ssct, xad);
|
||||
}
|
||||
|
||||
if (xad->length == xad->reader->tell())
|
||||
{
|
||||
if (looping)
|
||||
{
|
||||
xad->reader->seek(XA_DATA_START, SEEK_SET);
|
||||
xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
|
||||
}
|
||||
else
|
||||
xad->finished = true;
|
||||
}
|
||||
|
||||
xad->finished = false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XASong
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class XASong : public StreamSource
|
||||
{
|
||||
public:
|
||||
XASong(MusicIO::FileInterface *readr);
|
||||
SoundStreamInfoEx GetFormatEx() override;
|
||||
bool Start() override;
|
||||
bool GetData(void *buffer, size_t len) override;
|
||||
|
||||
protected:
|
||||
xa_data xad;
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XASong - Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
XASong::XASong(MusicIO::FileInterface * reader)
|
||||
{
|
||||
reader->seek(0, SEEK_END);
|
||||
xad.length = reader->tell();
|
||||
reader->seek(XA_DATA_START, SEEK_SET);
|
||||
xad.reader = reader;
|
||||
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
|
||||
|
||||
getNextXABlock(&xad, false);
|
||||
}
|
||||
|
||||
SoundStreamInfoEx XASong::GetFormatEx()
|
||||
{
|
||||
auto SampleRate = xad.blockIs18K? 18900 : 37800;
|
||||
return { 64*1024, SampleRate, SampleType_Float32, ChannelConfig_Stereo };
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XASong :: Play
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool XASong::Start()
|
||||
{
|
||||
if (xad.finished && m_Looping)
|
||||
{
|
||||
xad.reader->seek(XA_DATA_START, SEEK_SET);
|
||||
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
|
||||
xad.finished = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XASong :: Read
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool XASong::GetData(void *vbuff, size_t len)
|
||||
{
|
||||
float *dest = (float*)vbuff;
|
||||
while (len > 0)
|
||||
{
|
||||
auto ptr = xad.committed;
|
||||
auto block = xad.block + ptr;
|
||||
if (ptr < kBufSize)
|
||||
{
|
||||
// commit the data
|
||||
if (xad.blockIsMono)
|
||||
{
|
||||
size_t numsamples = len / 8;
|
||||
size_t availdata = kBufSize - ptr;
|
||||
|
||||
for(size_t tocopy = std::min(numsamples, availdata); tocopy > 0; tocopy--)
|
||||
{
|
||||
float f = *block++;
|
||||
*dest++ = f;
|
||||
*dest++ = f;
|
||||
len -= 8;
|
||||
ptr++;
|
||||
}
|
||||
xad.committed = ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t availdata = (kBufSize - ptr) * 4;
|
||||
size_t tocopy = std::min(availdata, len);
|
||||
memcpy(dest, block, tocopy);
|
||||
dest += tocopy / 4;
|
||||
len -= tocopy;
|
||||
xad.committed += tocopy / 4;
|
||||
}
|
||||
}
|
||||
if (xad.finished)
|
||||
{
|
||||
memset(dest, 0, len);
|
||||
return true;
|
||||
}
|
||||
if (len > 0)
|
||||
{
|
||||
// we ran out of data and need more
|
||||
getNextXABlock(&xad, m_Looping);
|
||||
// repeat until done.
|
||||
}
|
||||
else break;
|
||||
|
||||
}
|
||||
return !xad.finished;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// XA_OpenSong
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
StreamSource *XA_OpenSong(MusicIO::FileInterface *reader)
|
||||
{
|
||||
return new XASong(reader);
|
||||
}
|
||||
|
||||
40
libraries/ZMusic/source/streamsources/streamsource.h
Normal file
40
libraries/ZMusic/source/streamsources/streamsource.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
// Anything streamed to the sound system as raw wave data, except MIDIs --------------------
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "zmusic/mididefs.h" // for StreamSourceInfo
|
||||
#include "zmusic/midiconfig.h"
|
||||
|
||||
class StreamSource
|
||||
{
|
||||
protected:
|
||||
bool m_Looping = true;
|
||||
int m_OutputRate;
|
||||
|
||||
public:
|
||||
|
||||
StreamSource (int outputRate) { m_OutputRate = outputRate; }
|
||||
virtual ~StreamSource () {}
|
||||
virtual void SetPlayMode(bool looping) { m_Looping = looping; }
|
||||
virtual bool Start() { return true; }
|
||||
virtual bool SetPosition(unsigned position) { return false; }
|
||||
virtual bool SetSubsong(int subsong) { return false; }
|
||||
virtual bool GetData(void *buffer, size_t len) = 0;
|
||||
virtual SoundStreamInfoEx GetFormatEx() = 0;
|
||||
virtual std::string GetStats() { return ""; }
|
||||
virtual void ChangeSettingInt(const char *name, int value) { }
|
||||
virtual void ChangeSettingNum(const char *name, double value) { }
|
||||
virtual void ChangeSettingString(const char *name, const char *value) { }
|
||||
|
||||
protected:
|
||||
StreamSource() = default;
|
||||
};
|
||||
|
||||
|
||||
StreamSource *MOD_OpenSong(MusicIO::FileInterface* reader, int samplerate);
|
||||
StreamSource *XMP_OpenSong(MusicIO::FileInterface* reader, int samplerate);
|
||||
StreamSource* GME_OpenSong(MusicIO::FileInterface* reader, const char* fmt, int sample_rate);
|
||||
StreamSource *SndFile_OpenSong(MusicIO::FileInterface* fr);
|
||||
StreamSource* XA_OpenSong(MusicIO::FileInterface* reader);
|
||||
StreamSource* OPL_OpenSong(MusicIO::FileInterface* reader, OPLConfig *config);
|
||||
1196
libraries/ZMusic/source/zmusic/configuration.cpp
Normal file
1196
libraries/ZMusic/source/zmusic/configuration.cpp
Normal file
File diff suppressed because it is too large
Load diff
163
libraries/ZMusic/source/zmusic/critsec.cpp
Normal file
163
libraries/ZMusic/source/zmusic/critsec.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
**
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2005-2016 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
// Brought back for ZMusic because std::mutex under Windows pulls in the
|
||||
// ENTIRE multithreading library, all combined more than 200kb object code!
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef _WINNT_
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class FInternalCriticalSection
|
||||
{
|
||||
public:
|
||||
FInternalCriticalSection()
|
||||
{
|
||||
InitializeCriticalSection(&CritSec);
|
||||
}
|
||||
~FInternalCriticalSection()
|
||||
{
|
||||
DeleteCriticalSection(&CritSec);
|
||||
}
|
||||
void Enter()
|
||||
{
|
||||
EnterCriticalSection(&CritSec);
|
||||
}
|
||||
void Leave()
|
||||
{
|
||||
LeaveCriticalSection(&CritSec);
|
||||
}
|
||||
#if 0
|
||||
// SDL has no equivalent functionality, so better not use it on Windows.
|
||||
bool TryEnter()
|
||||
{
|
||||
return TryEnterCriticalSection(&CritSec) != 0;
|
||||
}
|
||||
#endif
|
||||
private:
|
||||
CRITICAL_SECTION CritSec;
|
||||
};
|
||||
|
||||
|
||||
FInternalCriticalSection *CreateCriticalSection()
|
||||
{
|
||||
return new FInternalCriticalSection();
|
||||
}
|
||||
|
||||
void DeleteCriticalSection(FInternalCriticalSection *c)
|
||||
{
|
||||
delete c;
|
||||
}
|
||||
|
||||
void EnterCriticalSection(FInternalCriticalSection *c)
|
||||
{
|
||||
c->Enter();
|
||||
}
|
||||
|
||||
void LeaveCriticalSection(FInternalCriticalSection *c)
|
||||
{
|
||||
c->Leave();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include "critsec.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
class FInternalCriticalSection
|
||||
{
|
||||
public:
|
||||
FInternalCriticalSection();
|
||||
~FInternalCriticalSection();
|
||||
|
||||
void Enter();
|
||||
void Leave();
|
||||
|
||||
private:
|
||||
pthread_mutex_t m_mutex;
|
||||
|
||||
};
|
||||
|
||||
// TODO: add error handling
|
||||
|
||||
FInternalCriticalSection::FInternalCriticalSection()
|
||||
{
|
||||
pthread_mutexattr_t attributes;
|
||||
pthread_mutexattr_init(&attributes);
|
||||
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE);
|
||||
|
||||
pthread_mutex_init(&m_mutex, &attributes);
|
||||
|
||||
pthread_mutexattr_destroy(&attributes);
|
||||
}
|
||||
|
||||
FInternalCriticalSection::~FInternalCriticalSection()
|
||||
{
|
||||
pthread_mutex_destroy(&m_mutex);
|
||||
}
|
||||
|
||||
void FInternalCriticalSection::Enter()
|
||||
{
|
||||
pthread_mutex_lock(&m_mutex);
|
||||
}
|
||||
|
||||
void FInternalCriticalSection::Leave()
|
||||
{
|
||||
pthread_mutex_unlock(&m_mutex);
|
||||
}
|
||||
|
||||
|
||||
FInternalCriticalSection *CreateCriticalSection()
|
||||
{
|
||||
return new FInternalCriticalSection();
|
||||
}
|
||||
|
||||
void DeleteCriticalSection(FInternalCriticalSection *c)
|
||||
{
|
||||
delete c;
|
||||
}
|
||||
|
||||
void EnterCriticalSection(FInternalCriticalSection *c)
|
||||
{
|
||||
c->Enter();
|
||||
}
|
||||
|
||||
void LeaveCriticalSection(FInternalCriticalSection *c)
|
||||
{
|
||||
c->Leave();
|
||||
}
|
||||
|
||||
#endif
|
||||
37
libraries/ZMusic/source/zmusic/critsec.h
Normal file
37
libraries/ZMusic/source/zmusic/critsec.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
// System independent critical sections without polluting the namespace with the operating system headers.
|
||||
class FInternalCriticalSection;
|
||||
FInternalCriticalSection *CreateCriticalSection();
|
||||
void DeleteCriticalSection(FInternalCriticalSection *c);
|
||||
void EnterCriticalSection(FInternalCriticalSection *c);
|
||||
void LeaveCriticalSection(FInternalCriticalSection *c);
|
||||
|
||||
// This is just a convenience wrapper around the function interface adjusted to use std::lock_guard
|
||||
class FCriticalSection
|
||||
{
|
||||
public:
|
||||
FCriticalSection()
|
||||
{
|
||||
c = CreateCriticalSection();
|
||||
}
|
||||
|
||||
~FCriticalSection()
|
||||
{
|
||||
DeleteCriticalSection(c);
|
||||
}
|
||||
|
||||
void lock()
|
||||
{
|
||||
EnterCriticalSection(c);
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
LeaveCriticalSection(c);
|
||||
}
|
||||
|
||||
private:
|
||||
FInternalCriticalSection *c;
|
||||
|
||||
};
|
||||
389
libraries/ZMusic/source/zmusic/fileio.h
Normal file
389
libraries/ZMusic/source/zmusic/fileio.h
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
The common sound font reader interface. Used by GUS, Timidity++ and WildMidi
|
||||
backends for reading sound font configurations.
|
||||
|
||||
The FileInterface is also used by streaming sound formats.
|
||||
|
||||
Copyright (C) 2019 Christoph Oelckers
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#if defined _WIN32 && !defined _WINDOWS_ // only define this if windows.h is not included.
|
||||
// I'd rather not include Windows.h for just this. This header is not supposed to pollute everything it touches.
|
||||
extern "C" __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned CodePage, unsigned long dwFlags, const char* lpMultiByteStr, int cbMultiByte, const wchar_t* lpWideCharStr, int cchWideChar);
|
||||
enum
|
||||
{
|
||||
CP_UTF8 = 65001
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace MusicIO
|
||||
{
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// This class defines a common file wrapper interface which allows these
|
||||
// libraries to work with any kind of file access API, e.g. stdio (provided below),
|
||||
// Win32, POSIX, iostream or custom implementations (like GZDoom's FileReader.)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct FileInterface
|
||||
{
|
||||
std::string filename;
|
||||
long length = -1;
|
||||
|
||||
// It's really too bad that the using code requires long instead of size_t.
|
||||
// Fortunately 2GB files are unlikely to come by here.
|
||||
protected:
|
||||
//
|
||||
virtual ~FileInterface() {}
|
||||
public:
|
||||
virtual char* gets(char* buff, int n) = 0;
|
||||
virtual long read(void* buff, int32_t size) = 0;
|
||||
virtual long seek(long offset, int whence) = 0;
|
||||
virtual long tell() = 0;
|
||||
virtual void close()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
long filelength()
|
||||
{
|
||||
if (length == -1)
|
||||
{
|
||||
long pos = tell();
|
||||
seek(0, SEEK_END);
|
||||
length = tell();
|
||||
seek(pos, SEEK_SET);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Inplementation of the FileInterface for stdio's FILE*.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct StdioFileReader : public FileInterface
|
||||
{
|
||||
FILE* f = nullptr;
|
||||
|
||||
~StdioFileReader()
|
||||
{
|
||||
if (f) fclose(f);
|
||||
}
|
||||
char* gets(char* buff, int n) override
|
||||
{
|
||||
if (!f) return nullptr;
|
||||
return fgets(buff, n, f);
|
||||
}
|
||||
long read(void* buff, int32_t size) override
|
||||
{
|
||||
if (!f) return 0;
|
||||
return (long)fread(buff, 1, size, f);
|
||||
}
|
||||
long seek(long offset, int whence) override
|
||||
{
|
||||
if (!f) return 0;
|
||||
return fseek(f, offset, whence);
|
||||
}
|
||||
long tell() override
|
||||
{
|
||||
if (!f) return 0;
|
||||
return ftell(f);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Inplementation of the FileInterface for a block of memory
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct MemoryReader : public FileInterface
|
||||
{
|
||||
const uint8_t *mData;
|
||||
long mLength;
|
||||
long mPos;
|
||||
|
||||
MemoryReader(const uint8_t *data, long length)
|
||||
: mData(data), mLength(length), mPos(0)
|
||||
{
|
||||
}
|
||||
|
||||
char* gets(char* strbuf, int len) override
|
||||
{
|
||||
if (len > mLength - mPos) len = mLength - mPos;
|
||||
if (len <= 0) return NULL;
|
||||
|
||||
char *p = strbuf;
|
||||
while (len > 1)
|
||||
{
|
||||
if (mData[mPos] == 0)
|
||||
{
|
||||
mPos++;
|
||||
break;
|
||||
}
|
||||
if (mData[mPos] != '\r')
|
||||
{
|
||||
*p++ = mData[mPos];
|
||||
len--;
|
||||
if (mData[mPos] == '\n')
|
||||
{
|
||||
mPos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mPos++;
|
||||
}
|
||||
if (p == strbuf) return nullptr;
|
||||
*p++ = 0;
|
||||
return strbuf;
|
||||
}
|
||||
long read(void* buff, int32_t size) override
|
||||
{
|
||||
long len = long(size);
|
||||
if (len > mLength - mPos) len = mLength - mPos;
|
||||
if (len < 0) len = 0;
|
||||
memcpy(buff, mData + mPos, len);
|
||||
mPos += len;
|
||||
return len;
|
||||
}
|
||||
long seek(long offset, int whence) override
|
||||
{
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_CUR:
|
||||
offset += mPos;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
offset += mLength;
|
||||
break;
|
||||
|
||||
}
|
||||
if (offset < 0 || offset > mLength) return -1;
|
||||
mPos = offset;
|
||||
return 0;
|
||||
}
|
||||
long tell() override
|
||||
{
|
||||
return mPos;
|
||||
}
|
||||
protected:
|
||||
MemoryReader() {}
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Inplementation of the FileInterface for an std::vector owned by the reader
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct VectorReader : public MemoryReader
|
||||
{
|
||||
std::vector<uint8_t> mVector;
|
||||
|
||||
template <class getFunc>
|
||||
VectorReader(getFunc getter) // read contents to a buffer and return a reader to it
|
||||
{
|
||||
getter(mVector);
|
||||
mData = mVector.data();
|
||||
mLength = (long)mVector.size();
|
||||
mPos = 0;
|
||||
}
|
||||
VectorReader(const uint8_t* data, size_t size)
|
||||
{
|
||||
mVector.resize(size);
|
||||
memcpy(mVector.data(), data, size);
|
||||
mData = mVector.data();
|
||||
mLength = (long)size;
|
||||
mPos = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// The following two functions are needed to allow using UTF-8 in the file interface.
|
||||
// fopen on Windows is only safe for ASCII.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
#ifdef _WIN32
|
||||
inline std::wstring wideString(const char *filename)
|
||||
{
|
||||
std::wstring filePathW;
|
||||
auto len = strlen(filename);
|
||||
filePathW.resize(len);
|
||||
int newSize = MultiByteToWideChar(CP_UTF8, 0, filename, (int)len, const_cast<wchar_t*>(filePathW.c_str()), (int)len);
|
||||
filePathW.resize(newSize);
|
||||
return filePathW;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline FILE* utf8_fopen(const char* filename, const char *mode)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return fopen(filename, mode);
|
||||
#else
|
||||
auto fn = wideString(filename);
|
||||
auto mo = wideString(mode);
|
||||
return _wfopen(fn.c_str(), mo.c_str());
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
inline bool fileExists(const char *fn)
|
||||
{
|
||||
FILE *f = utf8_fopen(fn, "rb");
|
||||
if (!f) return false;
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// This class providea a framework for reading sound fonts.
|
||||
// This is needed when the sound font data is not read from
|
||||
// the file system. e.g. zipped GUS patch sets.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class SoundFontReaderInterface
|
||||
{
|
||||
protected:
|
||||
virtual ~SoundFontReaderInterface() {}
|
||||
public:
|
||||
virtual struct FileInterface* open_file(const char* fn) = 0;
|
||||
virtual void add_search_path(const char* path) = 0;
|
||||
virtual void close() { delete this; }
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// A basic sound font reader for reading data from the file system.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class FileSystemSoundFontReader : public SoundFontReaderInterface
|
||||
{
|
||||
protected:
|
||||
std::vector<std::string> mPaths;
|
||||
std::string mBaseFile;
|
||||
bool mAllowAbsolutePaths;
|
||||
|
||||
bool IsAbsPath(const char *name)
|
||||
{
|
||||
if (name[0] == '/' || name[0] == '\\') return true;
|
||||
#ifdef _WIN32
|
||||
/* [A-Za-z]: (for Windows) */
|
||||
if (isalpha(name[0]) && name[1] == ':') return true;
|
||||
#endif /* _WIN32 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
FileSystemSoundFontReader(const char *configfilename, bool allowabs = false)
|
||||
{
|
||||
// Note that this does not add the directory the base file is in to the search path!
|
||||
// The caller of this has to do it themselves!
|
||||
mBaseFile = configfilename;
|
||||
mAllowAbsolutePaths = allowabs;
|
||||
}
|
||||
|
||||
struct FileInterface* open_file(const char* fn) override
|
||||
{
|
||||
FILE *f = nullptr;
|
||||
std::string fullname;
|
||||
if (!fn)
|
||||
{
|
||||
f = utf8_fopen(mBaseFile.c_str(), "rb");
|
||||
fullname = mBaseFile;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsAbsPath(fn))
|
||||
{
|
||||
for(int i = (int)mPaths.size()-1; i>=0; i--)
|
||||
{
|
||||
fullname = mPaths[i] + fn;
|
||||
f = utf8_fopen(fullname.c_str(), "rb");
|
||||
if (f) break;
|
||||
}
|
||||
}
|
||||
if (!f) f = fopen(fn, "rb");
|
||||
}
|
||||
if (!f) return nullptr;
|
||||
auto tf = new StdioFileReader;
|
||||
tf->f = f;
|
||||
tf->filename = fullname;
|
||||
return tf;
|
||||
}
|
||||
|
||||
void add_search_path(const char* path) override
|
||||
{
|
||||
std::string p = path;
|
||||
if (p.back() != '/' && p.back() != '\\') p += '/'; // always let it end with a slash.
|
||||
mPaths.push_back(p);
|
||||
}
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// This reader exists to trick Timidity config readers into accepting
|
||||
// a loose SF2 file by providing a fake config pointing to the given file.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class SF2Reader : public FileSystemSoundFontReader
|
||||
{
|
||||
std::string mMainConfigForSF2;
|
||||
|
||||
public:
|
||||
SF2Reader(const char *filename)
|
||||
: FileSystemSoundFontReader(filename)
|
||||
{
|
||||
mMainConfigForSF2 = "soundfont \"" + mBaseFile + "\"\n";
|
||||
}
|
||||
|
||||
struct FileInterface* open_file(const char* fn) override
|
||||
{
|
||||
if (fn == nullptr)
|
||||
{
|
||||
return new MemoryReader((uint8_t*)mMainConfigForSF2.c_str(), (long)mMainConfigForSF2.length());
|
||||
}
|
||||
else return FileSystemSoundFontReader::open_file(fn);
|
||||
}
|
||||
};
|
||||
|
||||
MusicIO::SoundFontReaderInterface* ClientOpenSoundFont(const char* name, int type);
|
||||
|
||||
}
|
||||
|
||||
255
libraries/ZMusic/source/zmusic/m_swap.h
Normal file
255
libraries/ZMusic/source/zmusic/m_swap.h
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
//
|
||||
// DESCRIPTION:
|
||||
// Endianess handling, swapping 16bit and 32bit.
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#ifndef __M_SWAP_H__
|
||||
#define __M_SWAP_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// Endianess handling.
|
||||
// WAD files are stored little endian.
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libkern/OSByteOrder.h>
|
||||
|
||||
inline short LittleShort(short x)
|
||||
{
|
||||
return (short)OSSwapLittleToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort(unsigned short x)
|
||||
{
|
||||
return OSSwapLittleToHostInt16(x);
|
||||
}
|
||||
|
||||
inline short LittleShort(int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort(unsigned int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline int LittleLong(int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt32((uint32_t)x);
|
||||
}
|
||||
|
||||
inline unsigned int LittleLong(unsigned int x)
|
||||
{
|
||||
return OSSwapLittleToHostInt32(x);
|
||||
}
|
||||
|
||||
inline short BigShort(short x)
|
||||
{
|
||||
return (short)OSSwapBigToHostInt16((uint16_t)x);
|
||||
}
|
||||
|
||||
inline unsigned short BigShort(unsigned short x)
|
||||
{
|
||||
return OSSwapBigToHostInt16(x);
|
||||
}
|
||||
|
||||
inline int BigLong(int x)
|
||||
{
|
||||
return OSSwapBigToHostInt32((uint32_t)x);
|
||||
}
|
||||
|
||||
inline unsigned int BigLong(unsigned int x)
|
||||
{
|
||||
return OSSwapBigToHostInt32(x);
|
||||
}
|
||||
|
||||
#elif defined __BIG_ENDIAN__
|
||||
|
||||
// Swap 16bit, that is, MSB and LSB byte.
|
||||
// No masking with 0xFF should be necessary.
|
||||
inline short LittleShort (short x)
|
||||
{
|
||||
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort (unsigned short x)
|
||||
{
|
||||
return (unsigned short)((x>>8) | (x<<8));
|
||||
}
|
||||
|
||||
inline short LittleShort (int x)
|
||||
{
|
||||
return LittleShort((short)x);
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort (unsigned int x)
|
||||
{
|
||||
return LittleShort((unsigned short)x);
|
||||
}
|
||||
|
||||
// Swapping 32bit.
|
||||
inline unsigned int LittleLong (unsigned int x)
|
||||
{
|
||||
return (unsigned int)(
|
||||
(x>>24)
|
||||
| ((x>>8) & 0xff00)
|
||||
| ((x<<8) & 0xff0000)
|
||||
| (x<<24));
|
||||
}
|
||||
|
||||
inline int LittleLong (int x)
|
||||
{
|
||||
return (int)(
|
||||
(((unsigned int)x)>>24)
|
||||
| ((((unsigned int)x)>>8) & 0xff00)
|
||||
| ((((unsigned int)x)<<8) & 0xff0000)
|
||||
| (((unsigned int)x)<<24));
|
||||
}
|
||||
|
||||
inline short BigShort(short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned short BigShort(unsigned short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned int BigLong(unsigned int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline int BigLong(int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline short LittleShort(short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned short LittleShort(unsigned short x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline unsigned int LittleLong(unsigned int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
inline int LittleLong(int x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
inline short BigShort(short x)
|
||||
{
|
||||
return (short)_byteswap_ushort((unsigned short)x);
|
||||
}
|
||||
|
||||
inline unsigned short BigShort(unsigned short x)
|
||||
{
|
||||
return _byteswap_ushort(x);
|
||||
}
|
||||
|
||||
inline int BigLong(int x)
|
||||
{
|
||||
return (int)_byteswap_ulong((unsigned long)x);
|
||||
}
|
||||
|
||||
inline unsigned int BigLong(unsigned int x)
|
||||
{
|
||||
return (unsigned int)_byteswap_ulong((unsigned long)x);
|
||||
}
|
||||
#pragma warning (default: 4035)
|
||||
|
||||
#else
|
||||
|
||||
inline short BigShort (short x)
|
||||
{
|
||||
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
|
||||
}
|
||||
|
||||
inline unsigned short BigShort (unsigned short x)
|
||||
{
|
||||
return (unsigned short)((x>>8) | (x<<8));
|
||||
}
|
||||
|
||||
inline unsigned int BigLong (unsigned int x)
|
||||
{
|
||||
return (unsigned int)(
|
||||
(x>>24)
|
||||
| ((x>>8) & 0xff00)
|
||||
| ((x<<8) & 0xff0000)
|
||||
| (x<<24));
|
||||
}
|
||||
|
||||
inline int BigLong (int x)
|
||||
{
|
||||
return (int)(
|
||||
(((unsigned int)x)>>24)
|
||||
| ((((unsigned int)x)>>8) & 0xff00)
|
||||
| ((((unsigned int)x)<<8) & 0xff0000)
|
||||
| (((unsigned int)x)<<24));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // __BIG_ENDIAN__
|
||||
|
||||
// These may be destructive so they should create errors
|
||||
unsigned long BigLong(unsigned long) = delete;
|
||||
long BigLong(long) = delete;
|
||||
unsigned long LittleLong(unsigned long) = delete;
|
||||
long LittleLong(long) = delete;
|
||||
|
||||
|
||||
// Data accessors, since some data is highly likely to be unaligned.
|
||||
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)
|
||||
inline int GetShort(const unsigned char *foo)
|
||||
{
|
||||
return *(const short *)foo;
|
||||
}
|
||||
inline int GetInt(const unsigned char *foo)
|
||||
{
|
||||
return *(const int *)foo;
|
||||
}
|
||||
#else
|
||||
inline int GetShort(const unsigned char *foo)
|
||||
{
|
||||
return short(foo[0] | (foo[1] << 8));
|
||||
}
|
||||
inline int GetInt(const unsigned char *foo)
|
||||
{
|
||||
return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24));
|
||||
}
|
||||
#endif
|
||||
inline int GetBigInt(const unsigned char *foo)
|
||||
{
|
||||
return int((foo[0] << 24) | (foo[1] << 16) | (foo[2] << 8) | foo[3]);
|
||||
}
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
inline int GetNativeInt(const unsigned char *foo)
|
||||
{
|
||||
return GetBigInt(foo);
|
||||
}
|
||||
#else
|
||||
inline int GetNativeInt(const unsigned char *foo)
|
||||
{
|
||||
return GetInt(foo);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // __M_SWAP_H__
|
||||
169
libraries/ZMusic/source/zmusic/midiconfig.h
Normal file
169
libraries/ZMusic/source/zmusic/midiconfig.h
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "zmusic_internal.h"
|
||||
#include "fileio.h"
|
||||
|
||||
// Note: Bools here are stored as ints to allow having a simpler interface.
|
||||
|
||||
struct ADLConfig
|
||||
{
|
||||
int adl_chips_count = 6;
|
||||
int adl_emulator_id = 0;
|
||||
int adl_bank = 14;
|
||||
int adl_volume_model = 0; // Automatical volume model (by bank properties)
|
||||
int adl_chan_alloc = -1; // Automatical channel allocation mode
|
||||
int adl_run_at_pcm_rate = 0;
|
||||
int adl_fullpan = 1;
|
||||
int adl_use_custom_bank = false;
|
||||
int adl_auto_arpeggio = false;
|
||||
float adl_gain = 1.0f;
|
||||
std::string adl_custom_bank;
|
||||
int adl_use_genmidi = false;
|
||||
int adl_genmidi_set = false;
|
||||
uint8_t adl_genmidi_bank[36 * 175]; // it really is 'struct GenMidiInstrument OPLinstruments[GENMIDI_NUM_TOTAL]'; but since this is a public header it cannot pull in a dependency from oplsynth.
|
||||
};
|
||||
|
||||
struct FluidConfig
|
||||
{
|
||||
std::string fluid_lib;
|
||||
std::string fluid_patchset;
|
||||
int fluid_reverb = false;
|
||||
int fluid_chorus = false;
|
||||
int fluid_voices = 128;
|
||||
int fluid_interp = 1;
|
||||
int fluid_samplerate = 0;
|
||||
int fluid_threads = 1;
|
||||
int fluid_chorus_voices = 3;
|
||||
int fluid_chorus_type = 0;
|
||||
float fluid_gain = 0.5f;
|
||||
float fluid_reverb_roomsize = 0.61f;
|
||||
float fluid_reverb_damping = 0.23f;
|
||||
float fluid_reverb_width = 0.76f;
|
||||
float fluid_reverb_level = 0.57f;
|
||||
float fluid_chorus_level = 1.2f;
|
||||
float fluid_chorus_speed = 0.3f;
|
||||
float fluid_chorus_depth = 8;
|
||||
};
|
||||
|
||||
struct OPLConfig
|
||||
{
|
||||
int numchips = 2;
|
||||
int core = 0;
|
||||
int fullpan = true;
|
||||
int genmidiset = false;
|
||||
uint8_t OPLinstruments[36 * 175]; // it really is 'struct GenMidiInstrument OPLinstruments[GENMIDI_NUM_TOTAL]'; but since this is a public header it cannot pull in a dependency from oplsynth.
|
||||
float gain = 1.0f;
|
||||
};
|
||||
|
||||
struct OpnConfig
|
||||
{
|
||||
int opn_chips_count = 8;
|
||||
int opn_emulator_id = 0;
|
||||
int opn_volume_model = 0; // Automatical volume model (by bank properties)
|
||||
int opn_chan_alloc = -1; // Automatical channel allocation mode
|
||||
int opn_run_at_pcm_rate = false;
|
||||
int opn_fullpan = 1;
|
||||
int opn_use_custom_bank = false;
|
||||
int opn_auto_arpeggio = false;
|
||||
float opn_gain = 1.0f;
|
||||
std::string opn_custom_bank;
|
||||
std::vector<uint8_t> default_bank;
|
||||
};
|
||||
|
||||
namespace Timidity
|
||||
{
|
||||
class Instruments;
|
||||
class SoundFontReaderInterface;
|
||||
}
|
||||
|
||||
struct GUSConfig
|
||||
{
|
||||
int midi_voices = 32;
|
||||
int gus_memsize = 0;
|
||||
int gus_dmxgus = false;
|
||||
std::string gus_patchdir;
|
||||
std::string gus_config;
|
||||
std::vector<uint8_t> dmxgus; // can contain the contents of a DMXGUS lump that may be used as the instrument set. In this case gus_patchdir must point to the location of the GUS data and gus_dmxgus must be true.
|
||||
|
||||
// This is the instrument cache for the GUS synth.
|
||||
MusicIO::SoundFontReaderInterface *reader;
|
||||
std::string readerName;
|
||||
std::string loadedConfig;
|
||||
std::unique_ptr<Timidity::Instruments> instruments;
|
||||
};
|
||||
|
||||
namespace TimidityPlus
|
||||
{
|
||||
class Instruments;
|
||||
class SoundFontReaderInterface;
|
||||
}
|
||||
|
||||
struct TimidityConfig
|
||||
{
|
||||
std::string timidity_config;
|
||||
|
||||
MusicIO::SoundFontReaderInterface* reader;
|
||||
std::string readerName;
|
||||
std::string loadedConfig;
|
||||
std::shared_ptr<TimidityPlus::Instruments> instruments; // this is held both by the config and the device
|
||||
|
||||
};
|
||||
|
||||
namespace WildMidi
|
||||
{
|
||||
struct Instruments;
|
||||
class SoundFontReaderInterface;
|
||||
}
|
||||
|
||||
struct WildMidiConfig
|
||||
{
|
||||
bool reverb = false;
|
||||
bool enhanced_resampling = true;
|
||||
std::string config;
|
||||
|
||||
MusicIO::SoundFontReaderInterface* reader;
|
||||
std::string readerName;
|
||||
std::string loadedConfig;
|
||||
std::shared_ptr<WildMidi::Instruments> instruments; // this is held both by the config and the device
|
||||
|
||||
};
|
||||
|
||||
struct DumbConfig
|
||||
{
|
||||
int mod_samplerate;
|
||||
int mod_volramp = 2;
|
||||
int mod_interp = 2;
|
||||
int mod_autochip;
|
||||
int mod_autochip_size_force = 100;
|
||||
int mod_autochip_size_scan = 500;
|
||||
int mod_autochip_scan_threshold = 12;
|
||||
int mod_preferred_player = 0;
|
||||
float mod_dumb_mastervolume = 1;
|
||||
};
|
||||
|
||||
struct MiscConfig
|
||||
{
|
||||
int snd_midiprecache;
|
||||
float gme_stereodepth;
|
||||
int snd_streambuffersize = 64;
|
||||
int snd_mididevice;
|
||||
int snd_outputrate = 44100;
|
||||
float snd_musicvolume = 1.f;
|
||||
float relative_volume = 1.f;
|
||||
float snd_mastervolume = 1.f;
|
||||
};
|
||||
|
||||
extern ADLConfig adlConfig;
|
||||
extern FluidConfig fluidConfig;
|
||||
extern OPLConfig oplConfig;
|
||||
extern OpnConfig opnConfig;
|
||||
extern GUSConfig gusConfig;
|
||||
extern TimidityConfig timidityConfig;
|
||||
extern WildMidiConfig wildMidiConfig;
|
||||
extern DumbConfig dumbConfig;
|
||||
extern MiscConfig miscConfig;
|
||||
extern ZMusicCallbacks musicCallbacks;
|
||||
|
||||
27
libraries/ZMusic/source/zmusic/mididefs.h
Normal file
27
libraries/ZMusic/source/zmusic/mididefs.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_MIDI_EVENTS = 128
|
||||
};
|
||||
|
||||
inline constexpr uint8_t MEVENT_EVENTTYPE(uint32_t x) { return ((uint8_t)((x) >> 24)); }
|
||||
inline constexpr uint32_t MEVENT_EVENTPARM(uint32_t x) { return ((x) & 0xffffff); }
|
||||
|
||||
enum EMidiEvent : uint8_t
|
||||
{
|
||||
MEVENT_TEMPO = 1,
|
||||
MEVENT_NOP = 2,
|
||||
MEVENT_LONGMSG = 128,
|
||||
};
|
||||
|
||||
#ifndef MAKE_ID
|
||||
#ifndef __BIG_ENDIAN__
|
||||
#define MAKE_ID(a,b,c,d) ((uint32_t)((a)|((b)<<8)|((c)<<16)|((d)<<24)))
|
||||
#else
|
||||
#define MAKE_ID(a,b,c,d) ((uint32_t)((d)|((c)<<8)|((b)<<16)|((a)<<24)))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
78
libraries/ZMusic/source/zmusic/mus2midi.h
Normal file
78
libraries/ZMusic/source/zmusic/mus2midi.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
** mus2midi.h
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2006 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MUS2MIDI_H__
|
||||
#define __MUS2MIDI_H__
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define MIDI_SYSEX ((uint8_t)0xF0) // SysEx begin
|
||||
#define MIDI_SYSEXEND ((uint8_t)0xF7) // SysEx end
|
||||
#define MIDI_META ((uint8_t)0xFF) // Meta event begin
|
||||
#define MIDI_META_TEMPO ((uint8_t)0x51)
|
||||
#define MIDI_META_EOT ((uint8_t)0x2F) // End-of-track
|
||||
#define MIDI_META_SSPEC ((uint8_t)0x7F) // System-specific event
|
||||
|
||||
#define MIDI_NOTEOFF ((uint8_t)0x80) // + note + velocity
|
||||
#define MIDI_NOTEON ((uint8_t)0x90) // + note + velocity
|
||||
#define MIDI_POLYPRESS ((uint8_t)0xA0) // + pressure (2 bytes)
|
||||
#define MIDI_CTRLCHANGE ((uint8_t)0xB0) // + ctrlr + value
|
||||
#define MIDI_PRGMCHANGE ((uint8_t)0xC0) // + new patch
|
||||
#define MIDI_CHANPRESS ((uint8_t)0xD0) // + pressure (1 byte)
|
||||
#define MIDI_PITCHBEND ((uint8_t)0xE0) // + pitch bend (2 bytes)
|
||||
|
||||
#define MUS_NOTEOFF ((uint8_t)0x00)
|
||||
#define MUS_NOTEON ((uint8_t)0x10)
|
||||
#define MUS_PITCHBEND ((uint8_t)0x20)
|
||||
#define MUS_SYSEVENT ((uint8_t)0x30)
|
||||
#define MUS_CTRLCHANGE ((uint8_t)0x40)
|
||||
#define MUS_SCOREEND ((uint8_t)0x60)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t Magic;
|
||||
uint16_t SongLen;
|
||||
uint16_t SongStart;
|
||||
uint16_t NumChans;
|
||||
uint16_t NumSecondaryChans;
|
||||
uint16_t NumInstruments;
|
||||
uint16_t Pad;
|
||||
// uint16_t UsedInstruments[NumInstruments];
|
||||
} MUSHeader;
|
||||
|
||||
#endif //__MUS2MIDI_H__
|
||||
44
libraries/ZMusic/source/zmusic/musinfo.h
Normal file
44
libraries/ZMusic/source/zmusic/musinfo.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include "mididefs.h"
|
||||
#include "zmusic/zmusic_internal.h"
|
||||
#include "critsec.h"
|
||||
|
||||
// The base music class. Everything is derived from this --------------------
|
||||
|
||||
class MusInfo
|
||||
{
|
||||
public:
|
||||
MusInfo() = default;
|
||||
virtual ~MusInfo() {}
|
||||
virtual void MusicVolumeChanged() {} // snd_musicvolume changed
|
||||
virtual void Play (bool looping, int subsong) = 0;
|
||||
virtual void Pause () = 0;
|
||||
virtual void Resume () = 0;
|
||||
virtual void Stop () = 0;
|
||||
virtual bool IsPlaying () = 0;
|
||||
virtual bool IsMIDI() const { return false; }
|
||||
virtual bool IsValid () const = 0;
|
||||
virtual bool SetPosition(unsigned int ms) { return false; }
|
||||
virtual bool SetSubsong (int subsong) { return false; }
|
||||
virtual void Update() {}
|
||||
virtual int GetDeviceType() const { return MDEV_DEFAULT; } // MDEV_DEFAULT stands in for anything that cannot change playback parameters which needs a restart.
|
||||
virtual std::string GetStats() { return "No stats available for this song"; }
|
||||
virtual MusInfo* GetWaveDumper(const char* filename, int rate) { return nullptr; }
|
||||
virtual void ChangeSettingInt(const char* setting, int value) {} // FluidSynth settings
|
||||
virtual void ChangeSettingNum(const char* setting, double value) {} // "
|
||||
virtual void ChangeSettingString(const char* setting, const char* value) {} // "
|
||||
virtual bool ServiceStream(void *buff, int len) { return false; }
|
||||
virtual SoundStreamInfoEx GetStreamInfoEx() const = 0;
|
||||
|
||||
enum EState
|
||||
{
|
||||
STATE_Stopped,
|
||||
STATE_Playing,
|
||||
STATE_Paused
|
||||
} m_Status = STATE_Stopped;
|
||||
bool m_Looping = false;
|
||||
FCriticalSection CritSec;
|
||||
};
|
||||
28
libraries/ZMusic/source/zmusic/sounddecoder.h
Normal file
28
libraries/ZMusic/source/zmusic/sounddecoder.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "zmusic_internal.h"
|
||||
#include <vector>
|
||||
|
||||
struct SoundDecoder
|
||||
{
|
||||
static SoundDecoder* CreateDecoder(MusicIO::FileInterface* reader);
|
||||
|
||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0;
|
||||
|
||||
virtual size_t read(char *buffer, size_t bytes) = 0;
|
||||
virtual std::vector<uint8_t> readAll();
|
||||
virtual bool seek(size_t ms_offset, bool ms, bool mayrestart) = 0;
|
||||
virtual size_t getSampleOffset() = 0;
|
||||
virtual size_t getSampleLength() { return 0; }
|
||||
virtual bool open(MusicIO::FileInterface* reader) = 0;
|
||||
|
||||
SoundDecoder() { }
|
||||
virtual ~SoundDecoder() { }
|
||||
|
||||
protected:
|
||||
friend class SoundRenderer;
|
||||
|
||||
// Make non-copyable
|
||||
SoundDecoder(const SoundDecoder &rhs) = delete;
|
||||
SoundDecoder& operator=(const SoundDecoder &rhs) = delete;
|
||||
};
|
||||
549
libraries/ZMusic/source/zmusic/zmusic.cpp
Normal file
549
libraries/ZMusic/source/zmusic/zmusic.cpp
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
/*
|
||||
** i_music.cpp
|
||||
** Plays music
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2016 Randy Heit
|
||||
** Copyright 2005-2019 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <miniz.h>
|
||||
#include "m_swap.h"
|
||||
#include "zmusic_internal.h"
|
||||
#include "midiconfig.h"
|
||||
#include "musinfo.h"
|
||||
#include "streamsources/streamsource.h"
|
||||
#include "midisources/midisource.h"
|
||||
#include "critsec.h"
|
||||
|
||||
#define GZIP_ID1 31
|
||||
#define GZIP_ID2 139
|
||||
#define GZIP_CM 8
|
||||
#define GZIP_ID MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0)
|
||||
|
||||
#define GZIP_FTEXT 1
|
||||
#define GZIP_FHCRC 2
|
||||
#define GZIP_FEXTRA 4
|
||||
#define GZIP_FNAME 8
|
||||
#define GZIP_FCOMMENT 16
|
||||
|
||||
class MIDIDevice;
|
||||
class OPLmusicFile;
|
||||
class StreamSource;
|
||||
class MusInfo;
|
||||
|
||||
MusInfo *OpenStreamSong(StreamSource *source);
|
||||
const char *GME_CheckFormat(uint32_t header);
|
||||
MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader);
|
||||
MusInfo* CD_OpenSong(int track, int id);
|
||||
MusInfo* CreateMIDIStreamer(MIDISource *source, EMidiDevice devtype, const char* args);
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ungzip
|
||||
//
|
||||
// VGZ files are compressed with gzip, so we need to uncompress them before
|
||||
// handing them to GME.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static bool ungzip(uint8_t *data, int complen, std::vector<uint8_t> &newdata)
|
||||
{
|
||||
const uint8_t *max = data + complen - 8;
|
||||
const uint8_t *compstart = data + 10;
|
||||
uint8_t flags = data[3];
|
||||
unsigned isize;
|
||||
z_stream stream;
|
||||
int err;
|
||||
|
||||
// Find start of compressed data stream
|
||||
if (flags & GZIP_FEXTRA)
|
||||
{
|
||||
compstart += 2 + LittleShort(*(uint16_t *)(data + 10));
|
||||
}
|
||||
if (flags & GZIP_FNAME)
|
||||
{
|
||||
while (compstart < max && *compstart != 0)
|
||||
{
|
||||
compstart++;
|
||||
}
|
||||
}
|
||||
if (flags & GZIP_FCOMMENT)
|
||||
{
|
||||
while (compstart < max && *compstart != 0)
|
||||
{
|
||||
compstart++;
|
||||
}
|
||||
}
|
||||
if (flags & GZIP_FHCRC)
|
||||
{
|
||||
compstart += 2;
|
||||
}
|
||||
if (compstart >= max - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decompress
|
||||
isize = LittleLong(*(uint32_t *)(data + complen - 4));
|
||||
newdata.resize(isize);
|
||||
|
||||
stream.next_in = (Bytef *)compstart;
|
||||
stream.avail_in = (uInt)(max - compstart);
|
||||
stream.next_out = &newdata[0];
|
||||
stream.avail_out = isize;
|
||||
stream.zalloc = (alloc_func)0;
|
||||
stream.zfree = (free_func)0;
|
||||
|
||||
err = inflateInit2(&stream, -MAX_WBITS);
|
||||
if (err != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
err = inflate(&stream, Z_FINISH);
|
||||
if (err != Z_STREAM_END)
|
||||
{
|
||||
inflateEnd(&stream);
|
||||
return false;
|
||||
}
|
||||
err = inflateEnd(&stream);
|
||||
if (err != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// identify a music lump's type and set up a player for it
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static MusInfo *ZMusic_OpenSongInternal (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args)
|
||||
{
|
||||
MusInfo *info = nullptr;
|
||||
StreamSource *streamsource = nullptr;
|
||||
const char *fmt;
|
||||
uint32_t id[32/4];
|
||||
|
||||
if(reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0)
|
||||
{
|
||||
SetError("Unable to read header");
|
||||
reader->close();
|
||||
return nullptr;
|
||||
}
|
||||
try
|
||||
{
|
||||
// Check for gzip compression. Some formats are expected to have players
|
||||
// that can handle it, so it simplifies things if we make all songs
|
||||
// gzippable.
|
||||
if ((id[0] & MAKE_ID(255, 255, 255, 0)) == GZIP_ID)
|
||||
{
|
||||
// swap out the reader with one that reads the decompressed content.
|
||||
auto zreader = new MusicIO::VectorReader([reader](std::vector<uint8_t>& array)
|
||||
{
|
||||
bool res = false;
|
||||
auto len = reader->filelength();
|
||||
uint8_t* gzipped = new uint8_t[len];
|
||||
if (reader->read(gzipped, len) == len)
|
||||
{
|
||||
res = ungzip(gzipped, (int)len, array);
|
||||
}
|
||||
delete[] gzipped;
|
||||
});
|
||||
reader->close();
|
||||
reader = zreader;
|
||||
|
||||
|
||||
if (reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0)
|
||||
{
|
||||
reader->close();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
EMIDIType miditype = ZMusic_IdentifyMIDIType(id, sizeof(id));
|
||||
if (miditype != MIDI_NOTMIDI)
|
||||
{
|
||||
std::vector<uint8_t> data(reader->filelength());
|
||||
if (reader->read(data.data(), (long)data.size()) != (long)data.size())
|
||||
{
|
||||
SetError("Failed to read MIDI data");
|
||||
reader->close();
|
||||
return nullptr;
|
||||
}
|
||||
auto source = ZMusic_CreateMIDISource(data.data(), data.size(), miditype);
|
||||
if (source == nullptr)
|
||||
{
|
||||
reader->close();
|
||||
return nullptr;
|
||||
}
|
||||
if (!source->isValid())
|
||||
{
|
||||
SetError("Invalid data in MIDI file");
|
||||
delete source;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifndef HAVE_SYSTEM_MIDI
|
||||
// some platforms don't support MDEV_STANDARD so map to MDEV_SNDSYS
|
||||
if (device == MDEV_STANDARD)
|
||||
device = MDEV_SNDSYS;
|
||||
#endif
|
||||
|
||||
info = CreateMIDIStreamer(source, device, Args? Args : "");
|
||||
}
|
||||
|
||||
// Check for CDDA "format"
|
||||
else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'D', 'A')))
|
||||
{
|
||||
// This is a CDDA file
|
||||
info = CDDA_OpenSong(reader);
|
||||
}
|
||||
|
||||
// Check for various raw OPL formats
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_OPL
|
||||
if (
|
||||
(id[0] == MAKE_ID('R', 'A', 'W', 'A') && id[1] == MAKE_ID('D', 'A', 'T', 'A')) || // Rdos Raw OPL
|
||||
(id[0] == MAKE_ID('D', 'B', 'R', 'A') && id[1] == MAKE_ID('W', 'O', 'P', 'L')) || // DosBox Raw OPL
|
||||
(id[0] == MAKE_ID('A', 'D', 'L', 'I') && *((uint8_t*)id + 4) == 'B')) // Martin Fernandez's modified IMF
|
||||
{
|
||||
streamsource = OPL_OpenSong(reader, &oplConfig);
|
||||
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
|
||||
{
|
||||
streamsource = XA_OpenSong(reader); // this takes over the reader.
|
||||
reader = nullptr; // We do not own this anymore.
|
||||
}
|
||||
// Check for game music
|
||||
else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0')
|
||||
{
|
||||
streamsource = GME_OpenSong(reader, fmt, miscConfig.snd_outputrate);
|
||||
}
|
||||
// Check for module formats
|
||||
else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('D', 'S', 'M', 'F')))
|
||||
{
|
||||
streamsource = MOD_OpenSong(reader, miscConfig.snd_outputrate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// give the calling app an option to select between XMP and DUMB.
|
||||
if (dumbConfig.mod_preferred_player != 0)
|
||||
{
|
||||
streamsource = MOD_OpenSong(reader, miscConfig.snd_outputrate);
|
||||
}
|
||||
if (!streamsource)
|
||||
{
|
||||
reader->seek(0, SEEK_SET);
|
||||
streamsource = XMP_OpenSong(reader, miscConfig.snd_outputrate);
|
||||
if (!streamsource && dumbConfig.mod_preferred_player == 0)
|
||||
{
|
||||
reader->seek(0, SEEK_SET);
|
||||
streamsource = MOD_OpenSong(reader, miscConfig.snd_outputrate);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (streamsource == nullptr)
|
||||
{
|
||||
streamsource = SndFile_OpenSong(reader); // this only takes over the reader if it succeeds. We need to look out for this.
|
||||
if (streamsource != nullptr) reader = nullptr;
|
||||
}
|
||||
|
||||
if (streamsource)
|
||||
{
|
||||
info = OpenStreamSong(streamsource);
|
||||
}
|
||||
}
|
||||
|
||||
if (!info)
|
||||
{
|
||||
// File could not be identified as music.
|
||||
if (reader) reader->close();
|
||||
SetError("Unable to identify as music");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (info && !info->IsValid())
|
||||
{
|
||||
delete info;
|
||||
SetError("Unable to identify as music");
|
||||
info = nullptr;
|
||||
}
|
||||
if (reader) reader->close();
|
||||
return info;
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
// Make sure the reader is closed if this function abnormally terminates
|
||||
if (reader) reader->close();
|
||||
SetError(ex.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSongFile(const char* filename, EMidiDevice device, const char* Args)
|
||||
{
|
||||
auto f = MusicIO::utf8_fopen(filename, "rb");
|
||||
if (!f)
|
||||
{
|
||||
SetError("File not found");
|
||||
return nullptr;
|
||||
}
|
||||
auto fr = new MusicIO::StdioFileReader;
|
||||
fr->f = f;
|
||||
return ZMusic_OpenSongInternal(fr, device, Args);
|
||||
}
|
||||
|
||||
DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSongMem(const void* mem, size_t size, EMidiDevice device, const char* Args)
|
||||
{
|
||||
if (!mem || !size)
|
||||
{
|
||||
SetError("Invalid data");
|
||||
return nullptr;
|
||||
}
|
||||
// Data must be copied because it may be used as a streaming source and we cannot guarantee that the client memory stays valid. We also have no means to free it.
|
||||
auto mr = new MusicIO::VectorReader((uint8_t*)mem, (long)size);
|
||||
return ZMusic_OpenSongInternal(mr, device, Args);
|
||||
}
|
||||
|
||||
DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSong(ZMusicCustomReader* reader, EMidiDevice device, const char* Args)
|
||||
{
|
||||
if (!reader)
|
||||
{
|
||||
SetError("No reader protocol specified");
|
||||
return nullptr;
|
||||
}
|
||||
auto cr = new CustomFileReader(reader); // Oh no! We just put another wrapper around the client's wrapper!
|
||||
return ZMusic_OpenSongInternal(cr, device, Args);
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// play CD music
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT MusInfo *ZMusic_OpenCDSong (int track, int id)
|
||||
{
|
||||
MusInfo *info = CD_OpenSong (track, id);
|
||||
|
||||
if (info && !info->IsValid ())
|
||||
{
|
||||
delete info;
|
||||
info = nullptr;
|
||||
SetError("Unable to open CD Audio");
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// streaming callback
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_FillStream(MusInfo* song, void* buff, int len)
|
||||
{
|
||||
if (song == nullptr) return false;
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
return song->ServiceStream(buff, len);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// starts playback
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_Start(MusInfo *song, int subsong, zmusic_bool loop)
|
||||
{
|
||||
if (!song) return true; // Starting a null song is not an error! It just won't play anything.
|
||||
try
|
||||
{
|
||||
song->Play(loop, subsong);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception & ex)
|
||||
{
|
||||
SetError(ex.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Utilities
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
DLL_EXPORT void ZMusic_Pause(MusInfo *song)
|
||||
{
|
||||
if (!song) return;
|
||||
song->Pause();
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_Resume(MusInfo *song)
|
||||
{
|
||||
if (!song) return;
|
||||
song->Resume();
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_Update(MusInfo *song)
|
||||
{
|
||||
if (!song) return;
|
||||
song->Update();
|
||||
}
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_IsPlaying(MusInfo *song)
|
||||
{
|
||||
if (!song) return false;
|
||||
return song->IsPlaying();
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_Stop(MusInfo *song)
|
||||
{
|
||||
if (!song) return;
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
song->Stop();
|
||||
}
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_SetSubsong(MusInfo *song, int subsong)
|
||||
{
|
||||
if (!song) return false;
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
return song->SetSubsong(subsong);
|
||||
}
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_IsLooping(MusInfo *song)
|
||||
{
|
||||
if (!song) return false;
|
||||
return song->m_Looping;
|
||||
}
|
||||
|
||||
DLL_EXPORT int ZMusic_GetDeviceType(MusInfo* song)
|
||||
{
|
||||
if (!song) return false;
|
||||
return song->GetDeviceType();
|
||||
}
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_IsMIDI(MusInfo *song)
|
||||
{
|
||||
if (!song) return false;
|
||||
return song->IsMIDI();
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_GetStreamInfo(MusInfo *song, SoundStreamInfo *fmt)
|
||||
{
|
||||
if (!fmt) return;
|
||||
*fmt = {};
|
||||
|
||||
if (!song)
|
||||
return;
|
||||
|
||||
SoundStreamInfoEx fmtex;
|
||||
{
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
fmtex = song->GetStreamInfoEx();
|
||||
}
|
||||
if (fmtex.mSampleRate > 0)
|
||||
{
|
||||
fmt->mBufferSize = fmtex.mBufferSize;
|
||||
fmt->mSampleRate = fmtex.mSampleRate;
|
||||
fmt->mNumChannels = ZMusic_ChannelCount(fmtex.mChannelConfig);
|
||||
if (fmtex.mSampleType == SampleType_Int16)
|
||||
fmt->mNumChannels *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_GetStreamInfoEx(MusInfo *song, SoundStreamInfoEx *fmt)
|
||||
{
|
||||
if (!fmt) return;
|
||||
if (!song) *fmt = {};
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
*fmt = song->GetStreamInfoEx();
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_Close(MusInfo *song)
|
||||
{
|
||||
if (!song) return;
|
||||
delete song;
|
||||
}
|
||||
|
||||
DLL_EXPORT void ZMusic_VolumeChanged(MusInfo *song)
|
||||
{
|
||||
if (!song) return;
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
song->MusicVolumeChanged();
|
||||
}
|
||||
|
||||
static std::string staticErrorMessage;
|
||||
|
||||
DLL_EXPORT const char *ZMusic_GetStats(MusInfo *song)
|
||||
{
|
||||
if (!song) return "";
|
||||
std::lock_guard<FCriticalSection> lock(song->CritSec);
|
||||
staticErrorMessage = song->GetStats();
|
||||
return staticErrorMessage.c_str();
|
||||
}
|
||||
|
||||
void SetError(const char* msg)
|
||||
{
|
||||
staticErrorMessage = msg;
|
||||
}
|
||||
|
||||
DLL_EXPORT const char* ZMusic_GetLastError()
|
||||
{
|
||||
return staticErrorMessage.c_str();
|
||||
}
|
||||
|
||||
DLL_EXPORT zmusic_bool ZMusic_WriteSMF(MIDISource* source, const char *fn, int looplimit)
|
||||
{
|
||||
std::vector<uint8_t> midi;
|
||||
bool success;
|
||||
|
||||
if (!source) return false;
|
||||
source->CreateSMF(midi, 1);
|
||||
auto f = MusicIO::utf8_fopen(fn, "wt");
|
||||
if (f == nullptr) return false;
|
||||
success = (fwrite(&midi[0], 1, midi.size(), f) == midi.size());
|
||||
fclose(f);
|
||||
return success;
|
||||
}
|
||||
82
libraries/ZMusic/source/zmusic/zmusic_internal.h
Normal file
82
libraries/ZMusic/source/zmusic/zmusic_internal.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
#define ZMUSIC_INTERNAL
|
||||
|
||||
#if defined(_MSC_VER) && !defined(ZMUSIC_STATIC)
|
||||
#define DLL_EXPORT __declspec(dllexport)
|
||||
#define DLL_IMPORT __declspec(dllexport) // without this the compiler complains.
|
||||
#else
|
||||
#define DLL_EXPORT
|
||||
#define DLL_IMPORT
|
||||
#endif
|
||||
|
||||
typedef class MIDISource *ZMusic_MidiSource;
|
||||
typedef class MusInfo *ZMusic_MusicStream;
|
||||
|
||||
// Build two configurations - lite and full.
|
||||
// Lite only uses FluidSynth for MIDI playback and is licensed under the LGPL v2.1
|
||||
// Full uses all MIDI synths and is licensed under the GPL v3.
|
||||
|
||||
#ifndef ZMUSIC_LITE
|
||||
#define HAVE_GUS // legally viable but not really useful
|
||||
#define HAVE_TIMIDITY // GPL v2.0
|
||||
#define HAVE_OPL // GPL v3.0
|
||||
#define HAVE_ADL // GPL v3.0
|
||||
#define HAVE_OPN // GPL v3.0
|
||||
#define HAVE_WILDMIDI // LGPL v3.0
|
||||
#endif
|
||||
|
||||
#include "zmusic.h"
|
||||
#include "fileio.h"
|
||||
|
||||
void SetError(const char *text);
|
||||
|
||||
struct CustomFileReader : public MusicIO::FileInterface
|
||||
{
|
||||
ZMusicCustomReader* cr;
|
||||
|
||||
CustomFileReader(ZMusicCustomReader* zr) : cr(zr) {}
|
||||
virtual char* gets(char* buff, int n) { return cr->gets(cr, buff, n); }
|
||||
virtual long read(void* buff, int32_t size) { return cr->read(cr, buff, size); }
|
||||
virtual long seek(long offset, int whence) { return cr->seek(cr, offset, whence); }
|
||||
virtual long tell() { return cr->tell(cr); }
|
||||
virtual void close()
|
||||
{
|
||||
cr->close(cr);
|
||||
delete this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
void ZMusic_Printf(int type, const char* msg, ...);
|
||||
|
||||
inline uint8_t ZMusic_SampleTypeSize(SampleType stype)
|
||||
{
|
||||
switch(stype)
|
||||
{
|
||||
case SampleType_UInt8: return sizeof(uint8_t);
|
||||
case SampleType_Int16: return sizeof(int16_t);
|
||||
case SampleType_Float32: return sizeof(float);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline uint8_t ZMusic_ChannelCount(ChannelConfig chans)
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case ChannelConfig_Mono: return 1;
|
||||
case ChannelConfig_Stereo: return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline const char *ZMusic_ChannelConfigName(ChannelConfig chans)
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case ChannelConfig_Mono: return "Mono";
|
||||
case ChannelConfig_Stereo: return "Stereo";
|
||||
}
|
||||
return "(unknown)";
|
||||
}
|
||||
31
libraries/ZMusic/thirdparty/CMakeLists.txt
vendored
Normal file
31
libraries/ZMusic/thirdparty/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
add_subdirectory(miniz)
|
||||
# GME is not currently released in a way that's conducive to using as a system
|
||||
# library. Nevertheless at least one person tried, and so the ability to use a
|
||||
# system copy exists soley to placate people following distro guidelines to the
|
||||
# letter without regard as to why the vendored copy is forced.
|
||||
# [Blzut3] Commented out per request from Graf Zahl.
|
||||
#option(FORCE_INTERNAL_GME "Use internal gme (it is highly unlikely this should be turned off)" ON)
|
||||
#mark_as_advanced(FORCE_INTERNAL_GME GME_INCLUDE_DIR GME_LIBRARY)
|
||||
#find_package(GME QUIET)
|
||||
#if(GME_FOUND AND NOT FORCE_INTERNAL_GME)
|
||||
# message(STATUS "Using system gme library, includes found at ${GME_INCLUDE_DIRS}")
|
||||
# set_property(TARGET gme PROPERTY IMPORTED_GLOBAL TRUE)
|
||||
# determine_package_config_dependency(ZMUSIC_PACKAGE_DEPENDENCIES TARGET gme MODULE GME)
|
||||
#else()
|
||||
# message(STATUS "Using internal gme library")
|
||||
# Use MAME as it's a balanced emulator: well-accurate, but doesn't eats lot of CPU
|
||||
# Nuked OPN2 is very accurate emulator, but it eats too much CPU for the workflow
|
||||
set(GME_YM2612_EMU "Nuked" CACHE STRING "Which YM2612 emulator to use: \"Nuked\" (LGPLv2.1+), \"MAME\" (GPLv2+), or \"GENS\" (LGPLv2.1+)")
|
||||
mark_as_advanced(GME_YM2612_EMU)
|
||||
add_subdirectory(game-music-emu)
|
||||
#endif()
|
||||
|
||||
add_subdirectory(dumb)
|
||||
add_subdirectory(adlmidi)
|
||||
add_subdirectory(opnmidi)
|
||||
add_subdirectory(timidity)
|
||||
add_subdirectory(timidityplus)
|
||||
add_subdirectory(wildmidi)
|
||||
add_subdirectory(oplsynth)
|
||||
add_subdirectory(libxmp)
|
||||
add_subdirectory(fluidsynth/src)
|
||||
58
libraries/ZMusic/thirdparty/adlmidi/CMakeLists.txt
vendored
Normal file
58
libraries/ZMusic/thirdparty/adlmidi/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
make_release_only()
|
||||
|
||||
add_library(adl OBJECT)
|
||||
|
||||
target_sources(adl
|
||||
PRIVATE
|
||||
adlmidi_midiplay.cpp
|
||||
adlmidi_opl3.cpp
|
||||
adlmidi_private.cpp
|
||||
adlmidi.cpp
|
||||
adlmidi_load.cpp
|
||||
inst_db.cpp
|
||||
chips/opal_opl3.cpp
|
||||
chips/opal/opal.c
|
||||
chips/dosbox/dbopl.cpp
|
||||
chips/nuked_opl3_v174.cpp
|
||||
chips/java_opl3.cpp
|
||||
chips/dosbox_opl3.cpp
|
||||
chips/nuked_opl3.cpp
|
||||
chips/nuked/nukedopl3_174.c
|
||||
chips/nuked/nukedopl3.c
|
||||
chips/ym3812_lle.cpp
|
||||
chips/ym3812_lle/nopl2.c
|
||||
chips/ym3812_lle/nuked_fmopl2.c
|
||||
chips/ymf262_lle.cpp
|
||||
chips/ymf262_lle/nopl3.c
|
||||
chips/ymf262_lle/nuked_fmopl3.c
|
||||
chips/esfmu_opl3.cpp
|
||||
chips/esfmu/esfm.c
|
||||
chips/esfmu/esfm_registers.c
|
||||
chips/mame_opl2.cpp
|
||||
# chips/mame/mame_fmopl.cpp # Duplicate of ../oplsynth/fmopl.cpp
|
||||
wopl/wopl_file.c
|
||||
)
|
||||
|
||||
if(COMPILER_SUPPORTS_CXX14)
|
||||
set(YMFM_SOURCES
|
||||
chips/ymfm_opl2.cpp
|
||||
chips/ymfm_opl3.cpp
|
||||
chips/ymfm/ymfm_adpcm.cpp
|
||||
chips/ymfm/ymfm_misc.cpp
|
||||
chips/ymfm/ymfm_opl.cpp
|
||||
chips/ymfm/ymfm_pcm.cpp
|
||||
chips/ymfm/ymfm_ssg.cpp
|
||||
)
|
||||
if(DEFINED FLAG_CPP14)
|
||||
set_source_files_properties(${YMFM_SOURCES} COMPILE_FLAGS ${FLAG_CPP14})
|
||||
endif()
|
||||
target_sources(adl PRIVATE ${YMFM_SOURCES})
|
||||
else()
|
||||
target_compile_definitions(adl PUBLIC -DADLMIDI_DISABLE_YMFM_EMULATOR)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(adl PRIVATE ADLMIDI_DISABLE_MIDI_SEQUENCER ADLMIDI_ENABLE_OPL2_LLE_EMULATOR ADLMIDI_ENABLE_OPL3_LLE_EMULATOR)
|
||||
|
||||
target_include_directories(adl PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
use_fast_math(adl)
|
||||
1926
libraries/ZMusic/thirdparty/adlmidi/adlmidi.cpp
vendored
Normal file
1926
libraries/ZMusic/thirdparty/adlmidi/adlmidi.cpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
1498
libraries/ZMusic/thirdparty/adlmidi/adlmidi.h
vendored
Normal file
1498
libraries/ZMusic/thirdparty/adlmidi/adlmidi.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
127
libraries/ZMusic/thirdparty/adlmidi/adlmidi_bankmap.h
vendored
Normal file
127
libraries/ZMusic/thirdparty/adlmidi/adlmidi_bankmap.h
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_BANKMAP_H
|
||||
#define ADLMIDI_BANKMAP_H
|
||||
|
||||
#include <list>
|
||||
#include <utility>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "adlmidi_ptr.hpp"
|
||||
|
||||
/**
|
||||
* A simple hash map which accepts bank numbers as keys, can be reserved to a
|
||||
* fixed size, offers O(1) search and insertion, has a hash function to
|
||||
* optimize for the worst case, and has some good cache locality properties.
|
||||
*/
|
||||
template <class T>
|
||||
class BasicBankMap
|
||||
{
|
||||
public:
|
||||
typedef size_t key_type; /* the bank identifier */
|
||||
typedef T mapped_type;
|
||||
typedef std::pair<key_type, T> value_type;
|
||||
|
||||
BasicBankMap();
|
||||
void reserve(size_t capacity);
|
||||
|
||||
size_t size() const
|
||||
{ return m_size; }
|
||||
size_t capacity() const
|
||||
{ return m_capacity; }
|
||||
bool empty() const
|
||||
{ return m_size == 0; }
|
||||
|
||||
class iterator;
|
||||
iterator begin() const;
|
||||
iterator end() const;
|
||||
|
||||
struct do_not_expand_t {};
|
||||
|
||||
iterator find(key_type key);
|
||||
void erase(iterator it);
|
||||
std::pair<iterator, bool> insert(const value_type &value);
|
||||
std::pair<iterator, bool> insert(const value_type &value, do_not_expand_t);
|
||||
void clear();
|
||||
|
||||
T &operator[](key_type key);
|
||||
|
||||
private:
|
||||
struct Slot;
|
||||
enum { minimum_allocation = 4 };
|
||||
enum
|
||||
{
|
||||
hash_bits = 8, /* worst case # of collisions: 128^2/2^hash_bits */
|
||||
hash_buckets = 1 << hash_bits
|
||||
};
|
||||
|
||||
public:
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
iterator();
|
||||
value_type &operator*() const { return slot->value; }
|
||||
value_type *operator->() const { return &slot->value; }
|
||||
iterator &operator++();
|
||||
bool operator==(const iterator &o) const;
|
||||
bool operator!=(const iterator &o) const;
|
||||
void to_ptrs(void *ptrs[3]);
|
||||
static iterator from_ptrs(void *const ptrs[3]);
|
||||
private:
|
||||
Slot **buckets;
|
||||
Slot *slot;
|
||||
size_t index;
|
||||
iterator(Slot **buckets, Slot *slot, size_t index);
|
||||
#ifdef _MSC_VER
|
||||
template<class _T>
|
||||
friend class BasicBankMap;
|
||||
#else
|
||||
friend class BasicBankMap<T>;
|
||||
#endif
|
||||
};
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
Slot *next, *prev;
|
||||
value_type value;
|
||||
Slot() : next(NULL), prev(NULL) {}
|
||||
};
|
||||
AdlMIDI_SPtrArray<Slot *> m_buckets;
|
||||
std::list< AdlMIDI_SPtrArray<Slot> > m_allocations;
|
||||
Slot *m_freeslots;
|
||||
size_t m_size;
|
||||
size_t m_capacity;
|
||||
static size_t hash(key_type key);
|
||||
Slot *allocate_slot();
|
||||
Slot *ensure_allocate_slot();
|
||||
void free_slot(Slot *slot);
|
||||
Slot *bucket_find(size_t index, key_type key);
|
||||
void bucket_add(size_t index, Slot *slot);
|
||||
void bucket_remove(size_t index, Slot *slot);
|
||||
};
|
||||
|
||||
#include "adlmidi_bankmap.tcc"
|
||||
|
||||
#endif // ADLMIDI_BANKMAP_H
|
||||
283
libraries/ZMusic/thirdparty/adlmidi/adlmidi_bankmap.tcc
vendored
Normal file
283
libraries/ZMusic/thirdparty/adlmidi/adlmidi_bankmap.tcc
vendored
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "adlmidi_bankmap.h"
|
||||
#include <cassert>
|
||||
|
||||
template <class T>
|
||||
inline BasicBankMap<T>::BasicBankMap()
|
||||
: m_freeslots(NULL),
|
||||
m_size(0),
|
||||
m_capacity(0)
|
||||
{
|
||||
m_buckets.reset(new Slot *[hash_buckets]());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline size_t BasicBankMap<T>::hash(key_type key)
|
||||
{
|
||||
// disregard the 0 high bit in LSB
|
||||
key = key_type(key & 127) | key_type((key >> 8) << 7);
|
||||
// take low part as hash value
|
||||
return key & (hash_buckets - 1);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::reserve(size_t capacity)
|
||||
{
|
||||
if(m_capacity >= capacity)
|
||||
return;
|
||||
|
||||
size_t need = capacity - m_capacity;
|
||||
const size_t minalloc = static_cast<size_t>(minimum_allocation);
|
||||
need = (need < minalloc) ? minalloc : need;
|
||||
|
||||
AdlMIDI_SPtrArray<Slot> slotz;
|
||||
slotz.reset(new Slot[need]);
|
||||
m_allocations.push_back(slotz);
|
||||
m_capacity += need;
|
||||
|
||||
for(size_t i = need; i-- > 0;)
|
||||
free_slot(&slotz[i]);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::iterator
|
||||
BasicBankMap<T>::begin() const
|
||||
{
|
||||
iterator it(m_buckets.get(), NULL, 0);
|
||||
while(it.index < hash_buckets && !(it.slot = m_buckets[it.index]))
|
||||
++it.index;
|
||||
return it;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::iterator
|
||||
BasicBankMap<T>::end() const
|
||||
{
|
||||
iterator it(m_buckets.get(), NULL, hash_buckets);
|
||||
return it;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::iterator BasicBankMap<T>::find(key_type key)
|
||||
{
|
||||
size_t index = hash(key);
|
||||
Slot *slot = bucket_find(index, key);
|
||||
if(!slot)
|
||||
return end();
|
||||
return iterator(m_buckets.get(), slot, index);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::erase(iterator it)
|
||||
{
|
||||
bucket_remove(it.index, it.slot);
|
||||
free_slot(it.slot);
|
||||
--m_size;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline BasicBankMap<T>::iterator::iterator()
|
||||
: buckets(NULL), slot(NULL), index(0)
|
||||
{
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline BasicBankMap<T>::iterator::iterator(Slot **buckets, Slot *slot, size_t index)
|
||||
: buckets(buckets), slot(slot), index(index)
|
||||
{
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::iterator &
|
||||
BasicBankMap<T>::iterator::operator++()
|
||||
{
|
||||
if(slot->next)
|
||||
slot = slot->next;
|
||||
else {
|
||||
Slot *slot = NULL;
|
||||
++index;
|
||||
while(index < hash_buckets && !(slot = buckets[index]))
|
||||
++index;
|
||||
this->slot = slot;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool BasicBankMap<T>::iterator::operator==(const iterator &o) const
|
||||
{
|
||||
return buckets == o.buckets && slot == o.slot && index == o.index;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool BasicBankMap<T>::iterator::operator!=(const iterator &o) const
|
||||
{
|
||||
return !operator==(o);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::iterator::to_ptrs(void *ptrs[3])
|
||||
{
|
||||
ptrs[0] = buckets;
|
||||
ptrs[1] = slot;
|
||||
ptrs[2] = (void *)index;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::iterator
|
||||
BasicBankMap<T>::iterator::from_ptrs(void *const ptrs[3])
|
||||
{
|
||||
iterator it;
|
||||
it.buckets = (Slot **)ptrs[0];
|
||||
it.slot = (Slot *)ptrs[1];
|
||||
it.index = (size_t)ptrs[2];
|
||||
return it;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::pair<typename BasicBankMap<T>::iterator, bool>
|
||||
BasicBankMap<T>::insert(const value_type &value)
|
||||
{
|
||||
size_t index = hash(value.first);
|
||||
Slot *slot = bucket_find(index, value.first);
|
||||
if(slot)
|
||||
return std::make_pair(iterator(m_buckets.get(), slot, index), false);
|
||||
slot = allocate_slot();
|
||||
if(!slot) {
|
||||
reserve(m_capacity + minimum_allocation);
|
||||
slot = ensure_allocate_slot();
|
||||
}
|
||||
slot->value = value;
|
||||
bucket_add(index, slot);
|
||||
++m_size;
|
||||
return std::make_pair(iterator(m_buckets.get(), slot, index), true);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::pair<typename BasicBankMap<T>::iterator, bool>
|
||||
BasicBankMap<T>::insert(const value_type &value, do_not_expand_t)
|
||||
{
|
||||
size_t index = hash(value.first);
|
||||
Slot *slot = bucket_find(index, value.first);
|
||||
if(slot)
|
||||
return std::make_pair(iterator(m_buckets.get(), slot, index), false);
|
||||
slot = allocate_slot();
|
||||
if(!slot)
|
||||
return std::make_pair(end(), false);
|
||||
slot->value = value;
|
||||
bucket_add(index, slot);
|
||||
++m_size;
|
||||
return std::make_pair(iterator(m_buckets.get(), slot, index), true);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::clear()
|
||||
{
|
||||
for(size_t i = 0; i < hash_buckets; ++i) {
|
||||
Slot *slot = m_buckets[i];
|
||||
while (Slot *cur = slot) {
|
||||
slot = slot->next;
|
||||
free_slot(cur);
|
||||
}
|
||||
m_buckets[i] = NULL;
|
||||
}
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline T &BasicBankMap<T>::operator[](key_type key)
|
||||
{
|
||||
return insert(value_type(key, T())).first->second;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::Slot *
|
||||
BasicBankMap<T>::allocate_slot()
|
||||
{
|
||||
Slot *slot = m_freeslots;
|
||||
if(!slot)
|
||||
return NULL;
|
||||
Slot *next = slot->next;
|
||||
if(next)
|
||||
next->prev = NULL;
|
||||
m_freeslots = next;
|
||||
return slot;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline typename BasicBankMap<T>::Slot *
|
||||
BasicBankMap<T>::ensure_allocate_slot()
|
||||
{
|
||||
Slot *slot = allocate_slot();
|
||||
assert(slot);
|
||||
return slot;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::free_slot(Slot *slot)
|
||||
{
|
||||
Slot *next = m_freeslots;
|
||||
if(next)
|
||||
next->prev = slot;
|
||||
slot->prev = NULL;
|
||||
slot->next = next;
|
||||
m_freeslots = slot;
|
||||
m_freeslots->value.second = T();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename BasicBankMap<T>::Slot *
|
||||
BasicBankMap<T>::bucket_find(size_t index, key_type key)
|
||||
{
|
||||
Slot *slot = m_buckets[index];
|
||||
while(slot && slot->value.first != key)
|
||||
slot = slot->next;
|
||||
return slot;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::bucket_add(size_t index, Slot *slot)
|
||||
{
|
||||
assert(slot);
|
||||
Slot *next = m_buckets[index];
|
||||
if(next)
|
||||
next->prev = slot;
|
||||
slot->next = next;
|
||||
m_buckets[index] = slot;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void BasicBankMap<T>::bucket_remove(size_t index, Slot *slot)
|
||||
{
|
||||
assert(slot);
|
||||
Slot *prev = slot->prev;
|
||||
Slot *next = slot->next;
|
||||
if(!prev)
|
||||
m_buckets[index] = next;
|
||||
else
|
||||
prev->next = next;
|
||||
if(next)
|
||||
next->prev = prev;
|
||||
}
|
||||
115
libraries/ZMusic/thirdparty/adlmidi/adlmidi_cvt.hpp
vendored
Normal file
115
libraries/ZMusic/thirdparty/adlmidi/adlmidi_cvt.hpp
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "oplinst.h"
|
||||
#include "wopl/wopl_file.h"
|
||||
#include <cmath>
|
||||
|
||||
template <class WOPLI>
|
||||
static void cvt_generic_to_FMIns(OplInstMeta &ins, const WOPLI &in)
|
||||
{
|
||||
ins.voice2_fine_tune = 0.0;
|
||||
int voice2_fine_tune = in.second_voice_detune;
|
||||
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
// Simulate behavior of DMX second voice detune
|
||||
ins.voice2_fine_tune = (double)(((voice2_fine_tune + 128) >> 1) - 64) / 32.0;
|
||||
}
|
||||
|
||||
ins.midiVelocityOffset = in.midi_velocity_offset;
|
||||
ins.drumTone = in.percussion_key_number;
|
||||
ins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? OplInstMeta::Flag_Pseudo4op : 0;
|
||||
ins.flags|= (in.inst_flags & WOPL_Ins_4op) && ((in.inst_flags & WOPL_Ins_Pseudo4op) == 0) ? OplInstMeta::Flag_Real4op : 0;
|
||||
ins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? OplInstMeta::Flag_NoSound : 0;
|
||||
ins.flags|= in.inst_flags & WOPL_RhythmModeMask;
|
||||
|
||||
for(size_t op = 0, slt = 0; op < 4; op++, slt++)
|
||||
{
|
||||
ins.op[slt].carrier_E862 =
|
||||
((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
|
||||
| ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
|
||||
| ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
|
||||
ins.op[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL
|
||||
|
||||
op++;
|
||||
ins.op[slt].modulator_E862 =
|
||||
((static_cast<uint32_t>(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel
|
||||
| ((static_cast<uint32_t>(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec
|
||||
| ((static_cast<uint32_t>(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM
|
||||
ins.op[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL
|
||||
}
|
||||
|
||||
ins.op[0].noteOffset = static_cast<int8_t>(in.note_offset1);
|
||||
ins.op[0].feedconn = in.fb_conn1_C0;
|
||||
ins.op[1].noteOffset = static_cast<int8_t>(in.note_offset2);
|
||||
ins.op[1].feedconn = in.fb_conn2_C0;
|
||||
|
||||
ins.soundKeyOnMs = in.delay_on_ms;
|
||||
ins.soundKeyOffMs = in.delay_off_ms;
|
||||
}
|
||||
|
||||
template <class WOPLI>
|
||||
static void cvt_FMIns_to_generic(WOPLI &ins, const OplInstMeta &in)
|
||||
{
|
||||
ins.second_voice_detune = 0;
|
||||
double voice2_fine_tune = in.voice2_fine_tune;
|
||||
if(voice2_fine_tune != 0)
|
||||
{
|
||||
int m = (int)(voice2_fine_tune * 32.0);
|
||||
m += 64;
|
||||
m <<= 1;
|
||||
m -= 128;
|
||||
ins.second_voice_detune = (uint8_t)m;
|
||||
}
|
||||
|
||||
ins.midi_velocity_offset = in.midiVelocityOffset;
|
||||
ins.percussion_key_number = in.drumTone;
|
||||
ins.inst_flags = (in.flags & (OplInstMeta::Flag_Pseudo4op|OplInstMeta::Flag_Real4op)) ? WOPL_Ins_4op : 0;
|
||||
ins.inst_flags|= (in.flags & OplInstMeta::Flag_Pseudo4op) ? WOPL_Ins_Pseudo4op : 0;
|
||||
ins.inst_flags|= (in.flags & OplInstMeta::Flag_NoSound) ? WOPL_Ins_IsBlank : 0;
|
||||
ins.inst_flags |= in.flags & OplInstMeta::Mask_RhythmMode;
|
||||
|
||||
for(size_t op = 0; op < 4; op++)
|
||||
{
|
||||
const OplTimbre &in2op = in.op[(op < 2) ? 0 : 1];
|
||||
uint32_t regE862 = ((op & 1) == 0) ? in2op.carrier_E862 : in2op.modulator_E862;
|
||||
uint8_t reg40 = ((op & 1) == 0) ? in2op.carrier_40 : in2op.modulator_40;
|
||||
|
||||
ins.operators[op].waveform_E0 = static_cast<uint8_t>(regE862 >> 24);
|
||||
ins.operators[op].susrel_80 = static_cast<uint8_t>(regE862 >> 16);
|
||||
ins.operators[op].atdec_60 = static_cast<uint8_t>(regE862 >> 8);
|
||||
ins.operators[op].avekf_20 = static_cast<uint8_t>(regE862 >> 0);
|
||||
ins.operators[op].ksl_l_40 = reg40;
|
||||
}
|
||||
|
||||
ins.note_offset1 = in.op[0].noteOffset;
|
||||
ins.fb_conn1_C0 = in.op[0].feedconn;
|
||||
ins.note_offset2 = in.op[1].noteOffset;
|
||||
ins.fb_conn2_C0 = in.op[1].feedconn;
|
||||
|
||||
ins.delay_on_ms = in.soundKeyOnMs;
|
||||
ins.delay_off_ms = in.soundKeyOffMs;
|
||||
}
|
||||
98
libraries/ZMusic/thirdparty/adlmidi/adlmidi_db.h
vendored
Normal file
98
libraries/ZMusic/thirdparty/adlmidi/adlmidi_db.h
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ADLDATA_DB_H
|
||||
#define ADLDATA_DB_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
|
||||
#if !defined(_MSC_VER) && !defined(__APPLE__) && !defined(__aarch64__) && !defined(__3DS__)
|
||||
#define ATTRIB_PACKED __attribute__((__packed__))
|
||||
#else
|
||||
#define ATTRIB_PACKED
|
||||
#endif
|
||||
|
||||
typedef uint16_t bank_count_t;
|
||||
typedef int16_t midi_bank_idx_t;
|
||||
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
extern const size_t g_embeddedBanksCount;
|
||||
#endif
|
||||
|
||||
namespace BanksDump
|
||||
{
|
||||
|
||||
struct BankEntry
|
||||
{
|
||||
uint16_t bankSetup;
|
||||
bank_count_t banksMelodicCount;
|
||||
bank_count_t banksPercussionCount;
|
||||
const char *title;
|
||||
bank_count_t banksOffsetMelodic;
|
||||
bank_count_t banksOffsetPercussive;
|
||||
} ATTRIB_PACKED;
|
||||
|
||||
struct MidiBank
|
||||
{
|
||||
uint8_t msb;
|
||||
uint8_t lsb;
|
||||
midi_bank_idx_t insts[128];
|
||||
} ATTRIB_PACKED;
|
||||
|
||||
struct InstrumentEntry
|
||||
{
|
||||
int16_t noteOffset1;
|
||||
int16_t noteOffset2;
|
||||
int8_t midiVelocityOffset;
|
||||
uint8_t percussionKeyNumber;
|
||||
uint8_t instFlags;
|
||||
int8_t secondVoiceDetune;
|
||||
uint16_t fbConn;
|
||||
uint16_t delay_on_ms;
|
||||
uint16_t delay_off_ms;
|
||||
int16_t ops[4];
|
||||
} ATTRIB_PACKED;
|
||||
|
||||
struct Operator
|
||||
{
|
||||
uint32_t d_E862;
|
||||
uint8_t d_40;
|
||||
} ATTRIB_PACKED;
|
||||
|
||||
} /* namespace BanksDump */
|
||||
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
extern const char* const g_embeddedBankNames[];
|
||||
extern const BanksDump::BankEntry g_embeddedBanks[];
|
||||
extern const size_t g_embeddedBanksMidiIndex[];
|
||||
extern const BanksDump::MidiBank g_embeddedBanksMidi[];
|
||||
extern const BanksDump::InstrumentEntry g_embeddedBanksInstruments[];
|
||||
extern const BanksDump::Operator g_embeddedBanksOperators[];
|
||||
#endif
|
||||
|
||||
#endif // ADLDATA_DB_H
|
||||
306
libraries/ZMusic/thirdparty/adlmidi/adlmidi_load.cpp
vendored
Normal file
306
libraries/ZMusic/thirdparty/adlmidi/adlmidi_load.cpp
vendored
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "adlmidi_midiplay.hpp"
|
||||
#include "adlmidi_opl3.hpp"
|
||||
#include "adlmidi_private.hpp"
|
||||
#include "adlmidi_cvt.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
#include "midi_sequencer.hpp"
|
||||
#endif
|
||||
#include "wopl/wopl_file.h"
|
||||
|
||||
bool MIDIplay::LoadBank(const std::string &filename)
|
||||
{
|
||||
FileAndMemReader file;
|
||||
file.openFile(filename.c_str());
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(const void *data, size_t size)
|
||||
{
|
||||
FileAndMemReader file;
|
||||
file.openData(data, size);
|
||||
return LoadBank(file);
|
||||
}
|
||||
|
||||
void cvt_ADLI_to_FMIns(OplInstMeta &ins, const ADL_Instrument &in)
|
||||
{
|
||||
return cvt_generic_to_FMIns(ins, in);
|
||||
}
|
||||
|
||||
void cvt_FMIns_to_ADLI(ADL_Instrument &ins, const OplInstMeta &in)
|
||||
{
|
||||
cvt_FMIns_to_generic(ins, in);
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadBank(FileAndMemReader &fr)
|
||||
{
|
||||
int err = 0;
|
||||
WOPLFile *wopl = NULL;
|
||||
char *raw_file_data = NULL;
|
||||
size_t fsize;
|
||||
if(!fr.isValid())
|
||||
{
|
||||
errorStringOut = "Custom bank: Invalid data stream!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read complete bank file into the memory
|
||||
fsize = fr.fileSize();
|
||||
fr.seek(0, FileAndMemReader::SET);
|
||||
// Allocate necessary memory block
|
||||
raw_file_data = (char*)malloc(fsize);
|
||||
if(!raw_file_data)
|
||||
{
|
||||
errorStringOut = "Custom bank: Out of memory before of read!";
|
||||
return false;
|
||||
}
|
||||
fr.read(raw_file_data, 1, fsize);
|
||||
|
||||
// Parse bank file from the memory
|
||||
wopl = WOPL_LoadBankFromMem((void*)raw_file_data, fsize, &err);
|
||||
//Free the buffer no more needed
|
||||
free(raw_file_data);
|
||||
|
||||
// Check for any erros
|
||||
if(!wopl)
|
||||
{
|
||||
switch(err)
|
||||
{
|
||||
case WOPL_ERR_BAD_MAGIC:
|
||||
errorStringOut = "Custom bank: Invalid magic!";
|
||||
return false;
|
||||
case WOPL_ERR_UNEXPECTED_ENDING:
|
||||
errorStringOut = "Custom bank: Unexpected ending!";
|
||||
return false;
|
||||
case WOPL_ERR_INVALID_BANKS_COUNT:
|
||||
errorStringOut = "Custom bank: Invalid banks count!";
|
||||
return false;
|
||||
case WOPL_ERR_NEWER_VERSION:
|
||||
errorStringOut = "Custom bank: Version is newer than supported by this library!";
|
||||
return false;
|
||||
case WOPL_ERR_OUT_OF_MEMORY:
|
||||
errorStringOut = "Custom bank: Out of memory!";
|
||||
return false;
|
||||
default:
|
||||
errorStringOut = "Custom bank: Unknown error!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Synth &synth = *m_synth;
|
||||
|
||||
synth.setEmbeddedBank(m_setup.bankId);
|
||||
|
||||
synth.m_insBankSetup.scaleModulators = false;
|
||||
synth.m_insBankSetup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0;
|
||||
synth.m_insBankSetup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0;
|
||||
synth.m_insBankSetup.mt32defaults = (wopl->opl_flags & WOPL_FLAG_MT32) != 0;
|
||||
synth.m_insBankSetup.volumeModel = wopl->volume_model;
|
||||
m_setup.deepTremoloMode = -1;
|
||||
m_setup.deepVibratoMode = -1;
|
||||
m_setup.volumeScaleModel = ADLMIDI_VolumeModel_AUTO;
|
||||
|
||||
uint16_t slots_counts[2] = {wopl->banks_count_melodic, wopl->banks_count_percussion};
|
||||
WOPLBank *slots_src_ins[2] = { wopl->banks_melodic, wopl->banks_percussive };
|
||||
|
||||
for(size_t ss = 0; ss < 2; ss++)
|
||||
{
|
||||
for(size_t i = 0; i < slots_counts[ss]; i++)
|
||||
{
|
||||
size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) +
|
||||
(slots_src_ins[ss][i].bank_midi_lsb) +
|
||||
(ss ? size_t(Synth::PercussionTag) : 0);
|
||||
Synth::Bank &bank = synth.m_insBanks[bankno];
|
||||
for(int j = 0; j < 128; j++)
|
||||
{
|
||||
OplInstMeta &ins = bank.ins[j];
|
||||
std::memset(&ins, 0, sizeof(OplInstMeta));
|
||||
WOPLInstrument &inIns = slots_src_ins[ss][i].ins[j];
|
||||
cvt_generic_to_FMIns(ins, inIns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synth.m_embeddedBank = Synth::CustomBankTag; // Use dynamic banks!
|
||||
//Percussion offset is count of instruments multipled to count of melodic banks
|
||||
applySetup();
|
||||
|
||||
WOPL_Free(wopl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
|
||||
bool MIDIplay::LoadMIDI_pre()
|
||||
{
|
||||
#ifdef DISABLE_EMBEDDED_BANKS
|
||||
Synth &synth = *m_synth;
|
||||
if((synth.m_embeddedBank != Synth::CustomBankTag) || synth.m_insBanks.empty())
|
||||
{
|
||||
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
/**** Set all properties BEFORE starting of actial file reading! ****/
|
||||
resetMIDI();
|
||||
applySetup();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadMIDI_post()
|
||||
{
|
||||
Synth &synth = *m_synth;
|
||||
MidiSequencer &seq = *m_sequencer;
|
||||
MidiSequencer::FileFormat format = seq.getFormat();
|
||||
if(format == MidiSequencer::Format_CMF)
|
||||
{
|
||||
const std::vector<MidiSequencer::CmfInstrument> &instruments = seq.getRawCmfInstruments();
|
||||
synth.m_insBanks.clear();//Clean up old banks
|
||||
|
||||
uint16_t ins_count = static_cast<uint16_t>(instruments.size());
|
||||
for(uint16_t i = 0; i < ins_count; ++i)
|
||||
{
|
||||
const uint8_t *insData = instruments[i].data;
|
||||
size_t bank = i / 256;
|
||||
bank = ((bank & 127) + ((bank >> 7) << 8));
|
||||
if(bank > 127 + (127 << 8))
|
||||
break;
|
||||
bank += (i % 256 < 128) ? 0 : size_t(Synth::PercussionTag);
|
||||
|
||||
/*std::printf("Ins %3u: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n",
|
||||
i, InsData[0],InsData[1],InsData[2],InsData[3], InsData[4],InsData[5],InsData[6],InsData[7],
|
||||
InsData[8],InsData[9],InsData[10],InsData[11], InsData[12],InsData[13],InsData[14],InsData[15]);*/
|
||||
OplInstMeta &adlins = synth.m_insBanks[bank].ins[i % 128];
|
||||
OplTimbre adl;
|
||||
adl.modulator_E862 =
|
||||
((static_cast<uint32_t>(insData[8] & 0x07) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(insData[6]) << 16) & 0x00FF0000) //Sustain/Release
|
||||
| ((static_cast<uint32_t>(insData[4]) << 8) & 0x0000FF00) //Attack/Decay
|
||||
| ((static_cast<uint32_t>(insData[0]) << 0) & 0x000000FF); //MultKEVA
|
||||
adl.carrier_E862 =
|
||||
((static_cast<uint32_t>(insData[9] & 0x07) << 24) & 0xFF000000) //WaveForm
|
||||
| ((static_cast<uint32_t>(insData[7]) << 16) & 0x00FF0000) //Sustain/Release
|
||||
| ((static_cast<uint32_t>(insData[5]) << 8) & 0x0000FF00) //Attack/Decay
|
||||
| ((static_cast<uint32_t>(insData[1]) << 0) & 0x000000FF); //MultKEVA
|
||||
adl.modulator_40 = insData[2];
|
||||
adl.carrier_40 = insData[3];
|
||||
adl.feedconn = insData[10] & 0x0F;
|
||||
adl.noteOffset = 0;
|
||||
adlins.op[0] = adl;
|
||||
adlins.op[1] = adl;
|
||||
adlins.soundKeyOnMs = 1000;
|
||||
adlins.soundKeyOffMs = 500;
|
||||
adlins.drumTone = 0;
|
||||
adlins.flags = 0;
|
||||
adlins.voice2_fine_tune = 0.0;
|
||||
}
|
||||
|
||||
synth.m_embeddedBank = Synth::CustomBankTag; // Ignore AdlBank number, use dynamic banks instead
|
||||
//std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo);
|
||||
synth.m_rhythmMode = true;
|
||||
synth.m_musicMode = Synth::MODE_CMF;
|
||||
synth.m_volumeScale = Synth::VOLUME_NATIVE;
|
||||
|
||||
synth.m_numChips = 1;
|
||||
synth.m_numFourOps = 0;
|
||||
}
|
||||
else if(format == MidiSequencer::Format_RSXX)
|
||||
{
|
||||
//opl.CartoonersVolumes = true;
|
||||
synth.m_musicMode = Synth::MODE_RSXX;
|
||||
synth.m_volumeScale = Synth::VOLUME_NATIVE;
|
||||
|
||||
synth.m_numChips = 1;
|
||||
synth.m_numFourOps = 0;
|
||||
}
|
||||
else if(format == MidiSequencer::Format_IMF)
|
||||
{
|
||||
//std::fprintf(stderr, "Done reading IMF file\n");
|
||||
synth.m_numFourOps = 0; //Don't use 4-operator channels for IMF playing!
|
||||
synth.m_rhythmMode = false;//Don't enforce rhythm-mode when it's unneeded
|
||||
synth.m_musicMode = Synth::MODE_IMF;
|
||||
|
||||
synth.m_numChips = 1;
|
||||
synth.m_numFourOps = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(format == MidiSequencer::Format_XMIDI)
|
||||
synth.m_musicMode = Synth::MODE_XMIDI;
|
||||
|
||||
synth.m_numChips = m_setup.numChips;
|
||||
if(m_setup.numFourOps < 0)
|
||||
adlCalculateFourOpChannels(this, true);
|
||||
}
|
||||
|
||||
resetMIDIDefaults();
|
||||
|
||||
m_setup.tick_skip_samples_delay = 0;
|
||||
chipReset(); // Reset OPL3 chip
|
||||
//opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously)
|
||||
m_chipChannels.clear();
|
||||
m_chipChannels.resize(synth.m_numChannels);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadMIDI(const std::string &filename)
|
||||
{
|
||||
FileAndMemReader file;
|
||||
file.openFile(filename.c_str());
|
||||
if(!LoadMIDI_pre())
|
||||
return false;
|
||||
MidiSequencer &seq = *m_sequencer;
|
||||
if(!seq.loadMIDI(file))
|
||||
{
|
||||
errorStringOut = seq.getErrorString();
|
||||
return false;
|
||||
}
|
||||
if(!LoadMIDI_post())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MIDIplay::LoadMIDI(const void *data, size_t size)
|
||||
{
|
||||
FileAndMemReader file;
|
||||
file.openData(data, size);
|
||||
if(!LoadMIDI_pre())
|
||||
return false;
|
||||
MidiSequencer &seq = *m_sequencer;
|
||||
if(!seq.loadMIDI(file))
|
||||
{
|
||||
errorStringOut = seq.getErrorString();
|
||||
return false;
|
||||
}
|
||||
if(!LoadMIDI_post())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
|
||||
2082
libraries/ZMusic/thirdparty/adlmidi/adlmidi_midiplay.cpp
vendored
Normal file
2082
libraries/ZMusic/thirdparty/adlmidi/adlmidi_midiplay.cpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
1094
libraries/ZMusic/thirdparty/adlmidi/adlmidi_midiplay.hpp
vendored
Normal file
1094
libraries/ZMusic/thirdparty/adlmidi/adlmidi_midiplay.hpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
2020
libraries/ZMusic/thirdparty/adlmidi/adlmidi_opl3.cpp
vendored
Normal file
2020
libraries/ZMusic/thirdparty/adlmidi/adlmidi_opl3.cpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
430
libraries/ZMusic/thirdparty/adlmidi/adlmidi_opl3.hpp
vendored
Normal file
430
libraries/ZMusic/thirdparty/adlmidi/adlmidi_opl3.hpp
vendored
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_OPL3_HPP
|
||||
#define ADLMIDI_OPL3_HPP
|
||||
|
||||
#include "oplinst.h"
|
||||
#include "adlmidi_ptr.hpp"
|
||||
#include "adlmidi_private.hpp"
|
||||
#include "adlmidi_bankmap.h"
|
||||
|
||||
#define BEND_COEFFICIENT 172.4387
|
||||
|
||||
#define OPL3_CHANNELS_MELODIC_BASE 0
|
||||
#define OPL3_CHANNELS_RHYTHM_BASE 18
|
||||
|
||||
#define NUM_OF_CHANNELS 23
|
||||
#define NUM_OF_4OP_CHANNELS 6
|
||||
#define NUM_OF_2OP_CHANNELS 18
|
||||
#define NUM_OF_2x2_CHANNELS 9
|
||||
#define NUM_OF_OPL2_CHANNELS 9
|
||||
#define NUM_OF_RM_CHANNELS 5
|
||||
|
||||
/**
|
||||
* @brief OPL3 Chip management class
|
||||
*/
|
||||
class OPL3
|
||||
{
|
||||
friend class MIDIplay;
|
||||
friend class AdlInstrumentTester;
|
||||
friend int adlCalculateFourOpChannels(MIDIplay *play, bool silent);
|
||||
public:
|
||||
enum
|
||||
{
|
||||
PercussionTag = 1 << 15,
|
||||
CustomBankTag = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
//! Total number of chip channels between all running emulators
|
||||
uint32_t m_numChannels;
|
||||
//! Just a padding. Reserved.
|
||||
char _padding[4];
|
||||
//! Running chip emulators
|
||||
std::vector<AdlMIDI_SPtr<OPLChipBase > > m_chips;
|
||||
|
||||
private:
|
||||
//! Cached patch data, needed by Touch()
|
||||
std::vector<OplTimbre> m_insCache;
|
||||
//! Value written to B0, cached, needed by NoteOff.
|
||||
/*! Contains Key on/off state, octave block and frequency number values
|
||||
*/
|
||||
std::vector<uint32_t> m_keyBlockFNumCache;
|
||||
//! Cached BD registry value (flags register: DeepTremolo, DeepVibrato, and RhythmMode)
|
||||
std::vector<uint32_t> m_regBD;
|
||||
//! Cached C0 register value (primarily for the panning state)
|
||||
std::vector<uint8_t> m_regC0;
|
||||
|
||||
#ifdef ADLMIDI_ENABLE_HW_SERIAL
|
||||
bool m_serial;
|
||||
std::string m_serialName;
|
||||
unsigned m_serialBaud;
|
||||
unsigned m_serialProtocol;
|
||||
#endif
|
||||
//! Does loaded emulator supports soft panning?
|
||||
bool m_softPanningSup;
|
||||
//! Current type of chip
|
||||
int m_currentChipType;
|
||||
//! Number channels per chip
|
||||
size_t m_perChipChannels;
|
||||
|
||||
/*!
|
||||
* \brief Current state of the synth (if values matched to setup, chips and arrays won't be fully re-created)
|
||||
*/
|
||||
struct State
|
||||
{
|
||||
int emulator;
|
||||
uint32_t numChips;
|
||||
unsigned long pcm_rate;
|
||||
|
||||
State()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
emulator = -2;
|
||||
numChips = 0;
|
||||
pcm_rate = 0;
|
||||
}
|
||||
|
||||
bool cmp_rate(unsigned long rate)
|
||||
{
|
||||
bool ret = pcm_rate != rate;
|
||||
|
||||
if(ret)
|
||||
pcm_rate = rate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool cmp(int emu, uint32_t chips)
|
||||
{
|
||||
bool ret = emu != emulator || chips != numChips;
|
||||
|
||||
if(ret)
|
||||
{
|
||||
emulator = emu;
|
||||
numChips = chips;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} m_curState;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief MIDI bank entry
|
||||
*/
|
||||
struct Bank
|
||||
{
|
||||
//! MIDI Bank instruments
|
||||
OplInstMeta ins[128];
|
||||
};
|
||||
typedef BasicBankMap<Bank> BankMap;
|
||||
//! MIDI bank instruments data
|
||||
BankMap m_insBanks;
|
||||
//! MIDI bank-wide setup
|
||||
OplBankSetup m_insBankSetup;
|
||||
|
||||
public:
|
||||
//! Blank instrument template
|
||||
static const OplInstMeta m_emptyInstrument;
|
||||
//! Total number of running concurrent emulated chips
|
||||
uint32_t m_numChips;
|
||||
//! Currently running embedded bank number. "CustomBankTag" means usign of the custom bank.
|
||||
uint32_t m_embeddedBank;
|
||||
//! Total number of needed four-operator channels in all running chips
|
||||
uint32_t m_numFourOps;
|
||||
//! Turn global Deep Tremolo mode on
|
||||
bool m_deepTremoloMode;
|
||||
//! Turn global Deep Vibrato mode on
|
||||
bool m_deepVibratoMode;
|
||||
//! Use Rhythm Mode percussions
|
||||
bool m_rhythmMode;
|
||||
//! Carriers-only are scaled by default by volume level. This flag will tell to scale modulators too.
|
||||
bool m_scaleModulators;
|
||||
//! Run emulator at PCM rate if that possible. Reduces sounding accuracy, but decreases CPU usage on lower rates.
|
||||
bool m_runAtPcmRate;
|
||||
//! Enable soft panning
|
||||
bool m_softPanning;
|
||||
//! Master volume, controlled via SysEx (0...127)
|
||||
uint8_t m_masterVolume;
|
||||
|
||||
//! Just a padding. Reserved.
|
||||
char _padding2[3];
|
||||
|
||||
/**
|
||||
* @brief Music playing mode
|
||||
*/
|
||||
enum MusicMode
|
||||
{
|
||||
//! MIDI mode
|
||||
MODE_MIDI,
|
||||
//! AIL XMIDI mode
|
||||
MODE_XMIDI,
|
||||
//! Id-Software Music mode
|
||||
MODE_IMF,
|
||||
//! Creative Music Files mode
|
||||
MODE_CMF,
|
||||
//! EA-MUS (a.k.a. RSXX) mode
|
||||
MODE_RSXX
|
||||
} m_musicMode;
|
||||
|
||||
/**
|
||||
* @brief Volume models enum
|
||||
*/
|
||||
enum VolumesScale
|
||||
{
|
||||
//! Generic volume model (linearization of logarithmic scale)
|
||||
VOLUME_Generic,
|
||||
//! OPL3 native logarithmic scale
|
||||
VOLUME_NATIVE,
|
||||
//! DMX volume scale logarithmic table
|
||||
VOLUME_DMX,
|
||||
//! Apoge Sound System volume scaling model
|
||||
VOLUME_APOGEE,
|
||||
//! Windows 9x SB16 driver volume scale table
|
||||
VOLUME_9X,
|
||||
//! DMX model with a fixed bug of AM voices
|
||||
VOLUME_DMX_FIXED,
|
||||
//! Apogee model with a fixed bug of AM voices
|
||||
VOLUME_APOGEE_FIXED,
|
||||
//! Audio Interfaces Library volume scaling model
|
||||
VOLUME_AIL,
|
||||
//! Windows 9x Generic FM driver volume scale table
|
||||
VOLUME_9X_GENERIC_FM,
|
||||
//! HMI Sound Operating System volume scale table
|
||||
VOLUME_HMI,
|
||||
//! HMI Sound Operating System volume scale model, older variant
|
||||
VOLUME_HMI_OLD
|
||||
} m_volumeScale;
|
||||
|
||||
//! Channel allocation algorithm
|
||||
ADLMIDI_ChannelAlloc m_channelAlloc;
|
||||
|
||||
//! Reserved
|
||||
char _padding3[8];
|
||||
|
||||
/**
|
||||
* @brief Channel categiry enumeration
|
||||
*/
|
||||
enum ChanCat
|
||||
{
|
||||
//! Regular melodic/percussion channel
|
||||
ChanCat_Regular = 0,
|
||||
//! Four-op first part
|
||||
ChanCat_4op_First = 1,
|
||||
//! Four-op second part
|
||||
ChanCat_4op_Second = 2,
|
||||
//! Rhythm-mode Bass drum
|
||||
ChanCat_Rhythm_Bass = 3,
|
||||
//! Rhythm-mode Snare drum
|
||||
ChanCat_Rhythm_Snare = 4,
|
||||
//! Rhythm-mode Tom-Tom
|
||||
ChanCat_Rhythm_Tom = 5,
|
||||
//! Rhythm-mode Cymbal
|
||||
ChanCat_Rhythm_Cymbal = 6,
|
||||
//! Rhythm-mode Hi-Hat
|
||||
ChanCat_Rhythm_HiHat = 7,
|
||||
//! Rhythm-mode Secondary channel
|
||||
ChanCat_Rhythm_Secondary = 8,
|
||||
//! Here is no channel used (OPL2 only)
|
||||
ChanCat_None = 9
|
||||
};
|
||||
|
||||
//! Category of the channel
|
||||
/*! 1 = quad-first, 2 = quad-second, 0 = regular
|
||||
3 = percussion BassDrum
|
||||
4 = percussion Snare
|
||||
5 = percussion Tom
|
||||
6 = percussion Crash cymbal
|
||||
7 = percussion Hihat
|
||||
8 = percussion Secondary
|
||||
*/
|
||||
std::vector<uint32_t> m_channelCategory;
|
||||
|
||||
|
||||
/**
|
||||
* @brief C.O. Constructor
|
||||
*/
|
||||
OPL3();
|
||||
|
||||
/**
|
||||
* @brief C.O. Destructor
|
||||
*/
|
||||
~OPL3();
|
||||
|
||||
/**
|
||||
* @brief Checks are setup locked to be changed on the fly or not
|
||||
* @return true when setup on the fly is locked
|
||||
*/
|
||||
bool setupLocked();
|
||||
|
||||
/**
|
||||
* @brief Choose one of embedded banks
|
||||
* @param bank ID of the bank
|
||||
*/
|
||||
void setEmbeddedBank(uint32_t bank);
|
||||
|
||||
/**
|
||||
* @brief Write data to OPL3 chip register
|
||||
* @param chip Index of emulated chip. In hardware OPL3 builds, this parameter is ignored
|
||||
* @param address Register address to write
|
||||
* @param value Value to write
|
||||
*/
|
||||
void writeReg(size_t chip, uint16_t address, uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief Write data to OPL3 chip register
|
||||
* @param chip Index of emulated chip. In hardware OPL3 builds, this parameter is ignored
|
||||
* @param address Register address to write
|
||||
* @param value Value to write
|
||||
*/
|
||||
void writeRegI(size_t chip, uint32_t address, uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Write to soft panning control of OPL3 chip emulator
|
||||
* @param chip Index of emulated chip.
|
||||
* @param address Register of channel to write
|
||||
* @param value Value to write
|
||||
*/
|
||||
void writePan(size_t chip, uint32_t address, uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Off the note in specified chip channel
|
||||
* @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)])
|
||||
*/
|
||||
void noteOff(size_t c);
|
||||
|
||||
/**
|
||||
* @brief On the note in specified chip channel with specified frequency of the tone
|
||||
* @param c1 Channel of chip [or master 4-op channel] (Emulated chip choosing by next formula: [c = ch + (chipId * 23)])
|
||||
* @param c2 Second 4-op channel of chip, unused for 2op (Emulated chip choosing by next formula: [c = ch + (chipId * 23)])
|
||||
* @param tone The tone to play (integer part - MIDI halftone, decimal part - relative bend offset)
|
||||
*/
|
||||
void noteOn(size_t c1, size_t c2, double tone);
|
||||
|
||||
/**
|
||||
* @brief Change setup of instrument in specified chip channel
|
||||
* @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)])
|
||||
* @param velocity Note velocity (from 0 to 127)
|
||||
* @param channelVolume Channel volume level (from 0 to 127)
|
||||
* @param channelExpression Channel expression level (from 0 to 127)
|
||||
* @param brightness CC74 Brightness level (from 0 to 127)
|
||||
* @param isDrum Is this a drum note? This flag is needed for some volume model algorithms
|
||||
*/
|
||||
void touchNote(size_t c,
|
||||
uint_fast32_t velocity,
|
||||
uint_fast32_t channelVolume = 127,
|
||||
uint_fast32_t channelExpression = 127,
|
||||
uint_fast32_t brightness = 127,
|
||||
bool isDrum = false);
|
||||
|
||||
/**
|
||||
* @brief Set the instrument into specified chip channel
|
||||
* @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)])
|
||||
* @param instrument Instrument data to set into the chip channel
|
||||
*/
|
||||
void setPatch(size_t c, const OplTimbre &instrument);
|
||||
|
||||
/**
|
||||
* @brief Set panpot position
|
||||
* @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)])
|
||||
* @param value 3-bit panpot value
|
||||
*/
|
||||
void setPan(size_t c, uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief Shut up all chip channels
|
||||
*/
|
||||
void silenceAll();
|
||||
|
||||
/**
|
||||
* @brief Commit updated flag states to chip registers
|
||||
*/
|
||||
void updateChannelCategories();
|
||||
|
||||
/**
|
||||
* @brief commit deepTremolo and deepVibrato flags
|
||||
*/
|
||||
void commitDeepFlags();
|
||||
|
||||
/**
|
||||
* @brief Set the volume scaling model
|
||||
* @param volumeModel Type of volume scale model scale
|
||||
*/
|
||||
void setVolumeScaleModel(ADLMIDI_VolumeModels volumeModel);
|
||||
|
||||
/**
|
||||
* @brief Get the volume scaling model
|
||||
*/
|
||||
ADLMIDI_VolumeModels getVolumeScaleModel();
|
||||
|
||||
/**
|
||||
* @brief Clean up all running emulated chip instances
|
||||
*/
|
||||
void clearChips();
|
||||
|
||||
/**
|
||||
* @brief Reset chip properties and initialize them
|
||||
* @param emulator Type of chip emulator
|
||||
* @param PCM_RATE Output sample rate to generate on output
|
||||
* @param audioTickHandler PCM-accurate clock hook
|
||||
*/
|
||||
void reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler);
|
||||
|
||||
void initChip(size_t chip);
|
||||
|
||||
#ifdef ADLMIDI_ENABLE_HW_SERIAL
|
||||
/**
|
||||
* @brief Reset chip properties for hardware use
|
||||
* @param emulator
|
||||
* @param PCM_RATE
|
||||
* @param audioTickHandler
|
||||
*/
|
||||
void resetSerial(const std::string &serialName, unsigned int baud, unsigned int protocol);
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check emulator availability
|
||||
* @param emulator Emulator ID (ADL_Emulator)
|
||||
* @return true when emulator is available
|
||||
*/
|
||||
extern bool adl_isEmulatorAvailable(int emulator);
|
||||
|
||||
/**
|
||||
* @brief Find highest emulator
|
||||
* @return The ADL_Emulator enum value which contains ID of highest emulator
|
||||
*/
|
||||
extern int adl_getHighestEmulator();
|
||||
|
||||
/**
|
||||
* @brief Find lowest emulator
|
||||
* @return The ADL_Emulator enum value which contains ID of lowest emulator
|
||||
*/
|
||||
extern int adl_getLowestEmulator();
|
||||
|
||||
#endif // ADLMIDI_OPL3_HPP
|
||||
127
libraries/ZMusic/thirdparty/adlmidi/adlmidi_private.cpp
vendored
Normal file
127
libraries/ZMusic/thirdparty/adlmidi/adlmidi_private.cpp
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "adlmidi_midiplay.hpp"
|
||||
#include "adlmidi_opl3.hpp"
|
||||
#include "adlmidi_private.hpp"
|
||||
#include "wopl/wopl_file.h"
|
||||
|
||||
|
||||
std::string ADLMIDI_ErrorString;
|
||||
|
||||
// Generator callback on audio rate ticks
|
||||
|
||||
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate)
|
||||
{
|
||||
reinterpret_cast<MIDIplay *>(instance)->AudioTick(chipId, rate);
|
||||
}
|
||||
#endif
|
||||
|
||||
int adlCalculateFourOpChannels(MIDIplay *play, bool silent)
|
||||
{
|
||||
Synth &synth = *play->m_synth;
|
||||
size_t n_fourop[2] = {0, 0}, n_total[2] = {0, 0};
|
||||
bool rhythmModeNeeded = false;
|
||||
size_t numFourOps = 0;
|
||||
|
||||
//Automatically calculate how much 4-operator channels is necessary
|
||||
{
|
||||
//For custom bank
|
||||
Synth::BankMap::iterator it = synth.m_insBanks.begin();
|
||||
Synth::BankMap::iterator end = synth.m_insBanks.end();
|
||||
for(; it != end; ++it)
|
||||
{
|
||||
size_t bank = it->first;
|
||||
size_t div = (bank & Synth::PercussionTag) ? 1 : 0;
|
||||
for(size_t i = 0; i < 128; ++i)
|
||||
{
|
||||
OplInstMeta &ins = it->second.ins[i];
|
||||
if(ins.flags & OplInstMeta::Flag_NoSound)
|
||||
continue;
|
||||
if((ins.flags & OplInstMeta::Flag_Real4op) != 0)
|
||||
++n_fourop[div];
|
||||
++n_total[div];
|
||||
if(div && ((ins.flags & OplInstMeta::Mask_RhythmMode) != 0))
|
||||
rhythmModeNeeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All 2ops (no 4ops)
|
||||
if((n_fourop[0] == 0) && (n_fourop[1] == 0))
|
||||
numFourOps = 0;
|
||||
// All 2op melodics and Some (or All) 4op drums
|
||||
else if((n_fourop[0] == 0) && (n_fourop[1] > 0))
|
||||
numFourOps = 2;
|
||||
// Many 4op melodics
|
||||
else if((n_fourop[0] >= (n_total[0] * 7) / 8))
|
||||
numFourOps = 6;
|
||||
// Few 4op melodics
|
||||
else if(n_fourop[0] > 0)
|
||||
numFourOps = 4;
|
||||
|
||||
synth.m_numFourOps = static_cast<unsigned>(numFourOps * synth.m_numChips);
|
||||
|
||||
// Update channel categories and set up four-operator channels
|
||||
if(!silent)
|
||||
synth.updateChannelCategories();
|
||||
|
||||
// Set rhythm mode when it needed
|
||||
synth.m_rhythmMode = rhythmModeNeeded;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
void adlFromInstrument(const BanksDump::InstrumentEntry &instIn, OplInstMeta &instOut)
|
||||
{
|
||||
instOut.voice2_fine_tune = 0.0;
|
||||
if(instIn.secondVoiceDetune != 0)
|
||||
instOut.voice2_fine_tune = (double)((((int)instIn.secondVoiceDetune + 128) >> 1) - 64) / 32.0;
|
||||
|
||||
instOut.midiVelocityOffset = instIn.midiVelocityOffset;
|
||||
instOut.drumTone = instIn.percussionKeyNumber;
|
||||
instOut.flags = (instIn.instFlags & WOPL_Ins_4op) && (instIn.instFlags & WOPL_Ins_Pseudo4op) ? OplInstMeta::Flag_Pseudo4op : 0;
|
||||
instOut.flags|= (instIn.instFlags & WOPL_Ins_4op) && ((instIn.instFlags & WOPL_Ins_Pseudo4op) == 0) ? OplInstMeta::Flag_Real4op : 0;
|
||||
instOut.flags|= (instIn.instFlags & WOPL_Ins_IsBlank) ? OplInstMeta::Flag_NoSound : 0;
|
||||
instOut.flags|= instIn.instFlags & WOPL_RhythmModeMask;
|
||||
|
||||
for(size_t op = 0; op < 2; op++)
|
||||
{
|
||||
if((instIn.ops[(op * 2) + 0] < 0) || (instIn.ops[(op * 2) + 1] < 0))
|
||||
break;
|
||||
const BanksDump::Operator &op1 = g_embeddedBanksOperators[instIn.ops[(op * 2) + 0]];
|
||||
const BanksDump::Operator &op2 = g_embeddedBanksOperators[instIn.ops[(op * 2) + 1]];
|
||||
instOut.op[op].modulator_E862 = op1.d_E862;
|
||||
instOut.op[op].modulator_40 = op1.d_40;
|
||||
instOut.op[op].carrier_E862 = op2.d_E862;
|
||||
instOut.op[op].carrier_40 = op2.d_40;
|
||||
instOut.op[op].feedconn = (instIn.fbConn >> (op * 8)) & 0xFF;
|
||||
instOut.op[op].noteOffset = static_cast<int8_t>(op == 0 ? instIn.noteOffset1 : instIn.noteOffset2);
|
||||
}
|
||||
|
||||
instOut.soundKeyOnMs = instIn.delay_on_ms;
|
||||
instOut.soundKeyOffMs = instIn.delay_off_ms;
|
||||
}
|
||||
#endif
|
||||
237
libraries/ZMusic/thirdparty/adlmidi/adlmidi_private.hpp
vendored
Normal file
237
libraries/ZMusic/thirdparty/adlmidi/adlmidi_private.hpp
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_PRIVATE_HPP
|
||||
#define ADLMIDI_PRIVATE_HPP
|
||||
|
||||
// Setup compiler defines useful for exporting required public API symbols in gme.cpp
|
||||
#ifndef ADLMIDI_EXPORT
|
||||
# if defined (_WIN32) && defined(ADLMIDI_BUILD_DLL)
|
||||
# define ADLMIDI_EXPORT __declspec(dllexport)
|
||||
# elif defined (LIBADLMIDI_VISIBILITY) && defined (__GNUC__)
|
||||
# define ADLMIDI_EXPORT __attribute__((visibility ("default")))
|
||||
# else
|
||||
# define ADLMIDI_EXPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX 1
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(__WATCOMC__)
|
||||
# undef NO_OLDNAMES
|
||||
# include <stdint.h>
|
||||
# ifdef _MSC_VER
|
||||
# ifdef _WIN64
|
||||
typedef __int64 ssize_t;
|
||||
# else
|
||||
typedef __int32 ssize_t;
|
||||
# endif
|
||||
# else
|
||||
# ifdef _WIN64
|
||||
typedef int64_t ssize_t;
|
||||
# else
|
||||
typedef int32_t ssize_t;
|
||||
# endif
|
||||
# endif
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(__DJGPP__) || (defined(__WATCOMC__) && (defined(__DOS__) || defined(__DOS4G__) || defined(__DOS4GNZ__)))
|
||||
# include <conio.h>
|
||||
# ifdef __DJGPP__
|
||||
# include <pc.h>
|
||||
# include <dpmi.h>
|
||||
# include <go32.h>
|
||||
# include <sys/farptr.h>
|
||||
# include <dos.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
//#ifdef __WATCOMC__
|
||||
//#include <myset.h> //TODO: Implemnet a workaround for OpenWatcom to fix a crash while using those containers
|
||||
//#include <mymap.h>
|
||||
//#else
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <new> // nothrow
|
||||
//#endif
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <vector> // vector
|
||||
#include <deque> // deque
|
||||
#include <cmath> // exp, log, ceil
|
||||
#if defined(__WATCOMC__)
|
||||
#include <math.h> // round, sqrt
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits> // numeric_limit
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#include <deque>
|
||||
#include <algorithm>
|
||||
|
||||
/*
|
||||
* Workaround for some compilers are has no those macros in their headers!
|
||||
*/
|
||||
#ifndef INT8_MIN
|
||||
#define INT8_MIN (-0x7f - 1)
|
||||
#endif
|
||||
#ifndef INT16_MIN
|
||||
#define INT16_MIN (-0x7fff - 1)
|
||||
#endif
|
||||
#ifndef INT32_MIN
|
||||
#define INT32_MIN (-0x7fffffff - 1)
|
||||
#endif
|
||||
#ifndef INT8_MAX
|
||||
#define INT8_MAX 0x7f
|
||||
#endif
|
||||
#ifndef INT16_MAX
|
||||
#define INT16_MAX 0x7fff
|
||||
#endif
|
||||
#ifndef INT32_MAX
|
||||
#define INT32_MAX 0x7fffffff
|
||||
#endif
|
||||
|
||||
class FileAndMemReader;
|
||||
|
||||
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
// Rename class to avoid ABI collisions
|
||||
#define BW_MidiSequencer AdlMidiSequencer
|
||||
class BW_MidiSequencer;
|
||||
typedef BW_MidiSequencer MidiSequencer;
|
||||
typedef struct BW_MidiRtInterface BW_MidiRtInterface;
|
||||
#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER
|
||||
|
||||
class OPL3;
|
||||
class OPLChipBase;
|
||||
|
||||
typedef class OPL3 Synth;
|
||||
|
||||
#include "oplinst.h"
|
||||
#include "adlmidi_db.h"
|
||||
|
||||
#define ADLMIDI_BUILD
|
||||
#include "adlmidi.h" //Main API
|
||||
|
||||
#include "adlmidi_ptr.hpp"
|
||||
|
||||
class MIDIplay;
|
||||
|
||||
#define ADL_UNUSED(x) (void)x
|
||||
|
||||
#ifdef ENABLE_HW_OPL_DOS
|
||||
#define ADL_MAX_CHIPS 1
|
||||
#define ADL_MAX_CHIPS_STR "1" //Why not just "#MaxCards" ? Watcom fails to pass this with "syntax error" :-P
|
||||
#else
|
||||
#define ADL_MAX_CHIPS 100
|
||||
#define ADL_MAX_CHIPS_STR "100"
|
||||
#endif
|
||||
|
||||
extern std::string ADLMIDI_ErrorString;
|
||||
|
||||
/*
|
||||
Sample conversions to various formats
|
||||
*/
|
||||
template <class Real>
|
||||
inline Real adl_cvtReal(int32_t x)
|
||||
{
|
||||
return static_cast<Real>(x) * (static_cast<Real>(1) / static_cast<Real>(INT16_MAX));
|
||||
}
|
||||
|
||||
inline int32_t adl_cvtS16(int32_t x)
|
||||
{
|
||||
x = (x < INT16_MIN) ? (INT16_MIN) : x;
|
||||
x = (x > INT16_MAX) ? (INT16_MAX) : x;
|
||||
return x;
|
||||
}
|
||||
|
||||
inline int32_t adl_cvtS8(int32_t x)
|
||||
{
|
||||
return adl_cvtS16(x) / 256;
|
||||
}
|
||||
inline int32_t adl_cvtS24(int32_t x)
|
||||
{
|
||||
return adl_cvtS16(x) * 256;
|
||||
}
|
||||
inline int32_t adl_cvtS32(int32_t x)
|
||||
{
|
||||
return adl_cvtS16(x) * 65536;
|
||||
}
|
||||
inline int32_t adl_cvtU16(int32_t x)
|
||||
{
|
||||
return adl_cvtS16(x) - INT16_MIN;
|
||||
}
|
||||
inline int32_t adl_cvtU8(int32_t x)
|
||||
{
|
||||
return (adl_cvtS16(x) / 256) - INT8_MIN;
|
||||
}
|
||||
inline int32_t adl_cvtU24(int32_t x)
|
||||
{
|
||||
enum { int24_min = -(1 << 23) };
|
||||
return adl_cvtS24(x) - int24_min;
|
||||
}
|
||||
inline int32_t adl_cvtU32(int32_t x)
|
||||
{
|
||||
// unsigned operation because overflow on signed integers is undefined
|
||||
return (uint32_t)adl_cvtS32(x) - (uint32_t)INT32_MIN;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void adl_fill_vector(std::vector<T > &v, const T &value)
|
||||
{
|
||||
for(typename std::vector<T>::iterator it = v.begin(); it != v.end(); ++it)
|
||||
*it = value;
|
||||
}
|
||||
|
||||
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
|
||||
extern void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Automatically calculate and enable necessary count of 4-op channels on emulated chips
|
||||
* @param device Library context
|
||||
* @param silent Don't re-count channel categories
|
||||
* @return Always 0
|
||||
*/
|
||||
extern int adlCalculateFourOpChannels(MIDIplay *play, bool silent = false);
|
||||
|
||||
#ifndef DISABLE_EMBEDDED_BANKS
|
||||
extern void adlFromInstrument(const BanksDump::InstrumentEntry &instIn, OplInstMeta &instOut);
|
||||
#endif
|
||||
|
||||
#endif // ADLMIDI_PRIVATE_HPP
|
||||
217
libraries/ZMusic/thirdparty/adlmidi/adlmidi_ptr.hpp
vendored
Normal file
217
libraries/ZMusic/thirdparty/adlmidi/adlmidi_ptr.hpp
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADLMIDI_PTR_HPP_THING
|
||||
#define ADLMIDI_PTR_HPP_THING
|
||||
|
||||
#include <algorithm> // swap
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
Generic deleters for smart pointers
|
||||
*/
|
||||
template <class T>
|
||||
struct ADLMIDI_DefaultDelete
|
||||
{
|
||||
void operator()(T *x) { delete x; }
|
||||
};
|
||||
template <class T>
|
||||
struct ADLMIDI_DefaultArrayDelete
|
||||
{
|
||||
void operator()(T *x) { delete[] x; }
|
||||
};
|
||||
struct ADLMIDI_CDelete
|
||||
{
|
||||
void operator()(void *x) { free(x); }
|
||||
};
|
||||
|
||||
/*
|
||||
Safe unique pointer for C++98, non-copyable but swappable.
|
||||
*/
|
||||
template< class T, class Deleter = ADLMIDI_DefaultDelete<T> >
|
||||
class AdlMIDI_UPtr
|
||||
{
|
||||
T *m_p;
|
||||
public:
|
||||
explicit AdlMIDI_UPtr(T *p = NULL)
|
||||
: m_p(p) {}
|
||||
~AdlMIDI_UPtr()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset(T *p = NULL)
|
||||
{
|
||||
if(p != m_p) {
|
||||
if(m_p) {
|
||||
Deleter del;
|
||||
del(m_p);
|
||||
}
|
||||
m_p = p;
|
||||
}
|
||||
}
|
||||
|
||||
void swap(AdlMIDI_UPtr &other)
|
||||
{
|
||||
std::swap(m_p, other.m_p);
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
T &operator*() const
|
||||
{
|
||||
return *m_p;
|
||||
}
|
||||
T *operator->() const
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
T &operator[](size_t index) const
|
||||
{
|
||||
return m_p[index];
|
||||
}
|
||||
private:
|
||||
AdlMIDI_UPtr(const AdlMIDI_UPtr &);
|
||||
AdlMIDI_UPtr &operator=(const AdlMIDI_UPtr &);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void swap(AdlMIDI_UPtr<T> &a, AdlMIDI_UPtr<T> &b)
|
||||
{
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
/**
|
||||
Unique pointer for arrays.
|
||||
*/
|
||||
template<class T>
|
||||
class AdlMIDI_UPtrArray :
|
||||
public AdlMIDI_UPtr< T, ADLMIDI_DefaultArrayDelete<T> >
|
||||
{
|
||||
public:
|
||||
explicit AdlMIDI_UPtrArray(T *p = NULL)
|
||||
: AdlMIDI_UPtr< T, ADLMIDI_DefaultArrayDelete<T> >(p) {}
|
||||
};
|
||||
|
||||
/**
|
||||
Unique pointer for C memory.
|
||||
*/
|
||||
template<class T>
|
||||
class AdlMIDI_CPtr :
|
||||
public AdlMIDI_UPtr< T, ADLMIDI_CDelete >
|
||||
{
|
||||
public:
|
||||
explicit AdlMIDI_CPtr(T *p = NULL)
|
||||
: AdlMIDI_UPtr< T, ADLMIDI_CDelete >(p) {}
|
||||
};
|
||||
|
||||
/*
|
||||
Shared pointer with non-atomic counter
|
||||
FAQ: Why not std::shared_ptr? Because of Android NDK now doesn't supports it
|
||||
*/
|
||||
template< class T, class Deleter = ADLMIDI_DefaultDelete<T> >
|
||||
class AdlMIDI_SPtr
|
||||
{
|
||||
T *m_p;
|
||||
size_t *m_counter;
|
||||
public:
|
||||
explicit AdlMIDI_SPtr(T *p = NULL)
|
||||
: m_p(p), m_counter(p ? new size_t(1) : NULL) {}
|
||||
~AdlMIDI_SPtr()
|
||||
{
|
||||
reset(NULL);
|
||||
}
|
||||
|
||||
AdlMIDI_SPtr(const AdlMIDI_SPtr &other)
|
||||
: m_p(other.m_p), m_counter(other.m_counter)
|
||||
{
|
||||
if(m_counter)
|
||||
++*m_counter;
|
||||
}
|
||||
|
||||
AdlMIDI_SPtr &operator=(const AdlMIDI_SPtr &other)
|
||||
{
|
||||
if(this == &other)
|
||||
return *this;
|
||||
reset();
|
||||
m_p = other.m_p;
|
||||
m_counter = other.m_counter;
|
||||
if(m_counter)
|
||||
++*m_counter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset(T *p = NULL)
|
||||
{
|
||||
if(p != m_p) {
|
||||
if(m_p && --*m_counter == 0) {
|
||||
Deleter del;
|
||||
del(m_p);
|
||||
if(!p) {
|
||||
delete m_counter;
|
||||
m_counter = NULL;
|
||||
}
|
||||
}
|
||||
m_p = p;
|
||||
if(p) {
|
||||
if(!m_counter)
|
||||
m_counter = new size_t;
|
||||
*m_counter = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
T &operator*() const
|
||||
{
|
||||
return *m_p;
|
||||
}
|
||||
T *operator->() const
|
||||
{
|
||||
return m_p;
|
||||
}
|
||||
T &operator[](size_t index) const
|
||||
{
|
||||
return m_p[index];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Shared pointer for arrays.
|
||||
*/
|
||||
template<class T>
|
||||
class AdlMIDI_SPtrArray :
|
||||
public AdlMIDI_SPtr< T, ADLMIDI_DefaultArrayDelete<T> >
|
||||
{
|
||||
public:
|
||||
explicit AdlMIDI_SPtrArray(T *p = NULL)
|
||||
: AdlMIDI_SPtr< T, ADLMIDI_DefaultArrayDelete<T> >(p) {}
|
||||
};
|
||||
|
||||
#endif //ADLMIDI_PTR_HPP_THING
|
||||
170
libraries/ZMusic/thirdparty/adlmidi/chips/common/mutex.hpp
vendored
Normal file
170
libraries/ZMusic/thirdparty/adlmidi/chips/common/mutex.hpp
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation
|
||||
*
|
||||
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
||||
* ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
|
||||
*
|
||||
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
||||
* http://iki.fi/bisqwit/source/adlmidi.html
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DOSBOX_NO_MUTEX
|
||||
# if defined(USE_LIBOGC_MUTEX)
|
||||
# include <ogc/mutex.h>
|
||||
typedef mutex_t MutexNativeObject;
|
||||
# elif defined(USE_WUT_MUTEX)
|
||||
# if __cplusplus < 201103L || (defined(_MSC_VER) && _MSC_VER < 1900)
|
||||
# define static_assert(x, y)
|
||||
# endif
|
||||
# include <coreinit/mutex.h>
|
||||
typedef OSMutex MutexNativeObject;
|
||||
# elif !defined(_WIN32)
|
||||
# include <pthread.h>
|
||||
typedef pthread_mutex_t MutexNativeObject;
|
||||
# else
|
||||
# include <windows.h>
|
||||
typedef CRITICAL_SECTION MutexNativeObject;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
class Mutex
|
||||
{
|
||||
public:
|
||||
Mutex();
|
||||
~Mutex();
|
||||
void lock();
|
||||
void unlock();
|
||||
private:
|
||||
#if !defined(DOSBOX_NO_MUTEX)
|
||||
MutexNativeObject m;
|
||||
#endif
|
||||
Mutex(const Mutex &);
|
||||
Mutex &operator=(const Mutex &);
|
||||
};
|
||||
|
||||
class MutexHolder
|
||||
{
|
||||
public:
|
||||
explicit MutexHolder(Mutex &m) : m(m) { m.lock(); }
|
||||
~MutexHolder() { m.unlock(); }
|
||||
private:
|
||||
Mutex &m;
|
||||
MutexHolder(const MutexHolder &);
|
||||
MutexHolder &operator=(const MutexHolder &);
|
||||
};
|
||||
|
||||
#if defined(DOSBOX_NO_MUTEX) // No mutex, just a dummy
|
||||
|
||||
inline Mutex::Mutex()
|
||||
{}
|
||||
|
||||
inline Mutex::~Mutex()
|
||||
{}
|
||||
|
||||
inline void Mutex::lock()
|
||||
{}
|
||||
|
||||
inline void Mutex::unlock()
|
||||
{}
|
||||
|
||||
#elif defined(USE_WUT_MUTEX)
|
||||
|
||||
inline Mutex::Mutex()
|
||||
{
|
||||
OSInitMutex(&m);
|
||||
}
|
||||
|
||||
inline Mutex::~Mutex()
|
||||
{}
|
||||
|
||||
inline void Mutex::lock()
|
||||
{
|
||||
OSLockMutex(&m);
|
||||
}
|
||||
|
||||
inline void Mutex::unlock()
|
||||
{
|
||||
OSUnlockMutex(&m);
|
||||
}
|
||||
|
||||
#elif defined(USE_LIBOGC_MUTEX)
|
||||
|
||||
inline Mutex::Mutex()
|
||||
{
|
||||
m = LWP_MUTEX_NULL;
|
||||
LWP_MutexInit(&m, 0);
|
||||
}
|
||||
|
||||
inline Mutex::~Mutex()
|
||||
{
|
||||
LWP_MutexDestroy(m);
|
||||
}
|
||||
|
||||
inline void Mutex::lock()
|
||||
{
|
||||
LWP_MutexLock(m);
|
||||
}
|
||||
|
||||
inline void Mutex::unlock()
|
||||
{
|
||||
LWP_MutexUnlock(m);
|
||||
}
|
||||
|
||||
#elif !defined(_WIN32) // pthread
|
||||
|
||||
inline Mutex::Mutex()
|
||||
{
|
||||
pthread_mutex_init(&m, NULL);
|
||||
}
|
||||
|
||||
inline Mutex::~Mutex()
|
||||
{
|
||||
pthread_mutex_destroy(&m);
|
||||
}
|
||||
|
||||
inline void Mutex::lock()
|
||||
{
|
||||
pthread_mutex_lock(&m);
|
||||
}
|
||||
|
||||
inline void Mutex::unlock()
|
||||
{
|
||||
pthread_mutex_unlock(&m);
|
||||
}
|
||||
|
||||
#else // Win32
|
||||
|
||||
inline Mutex::Mutex()
|
||||
{
|
||||
InitializeCriticalSection(&m);
|
||||
}
|
||||
|
||||
inline Mutex::~Mutex()
|
||||
{
|
||||
DeleteCriticalSection(&m);
|
||||
}
|
||||
|
||||
inline void Mutex::lock()
|
||||
{
|
||||
EnterCriticalSection(&m);
|
||||
}
|
||||
|
||||
inline void Mutex::unlock()
|
||||
{
|
||||
LeaveCriticalSection(&m);
|
||||
}
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue