Merge commit '9f3cb3d92e' as 'libraries/ZMusic'
This commit is contained in:
commit
8f1f0d5a02
852 changed files with 381622 additions and 0 deletions
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)";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue