Merge commit '9f3cb3d92e' as 'libraries/ZMusic'

This commit is contained in:
Rachael Alexanderson 2025-07-30 00:24:15 -04:00
commit 8f1f0d5a02
852 changed files with 381622 additions and 0 deletions

View 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/.+")

File diff suppressed because it is too large Load diff

Binary file not shown.

View 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

View 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 */

View 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

View 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

View 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 */

View 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

View 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;
}

View 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;
}

View 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();

View 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"

View 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++);
}
}

View 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

View 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

View 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

View 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

View 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

View 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.
}

View file

@ -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);
}

View 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

View 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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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()
{
}

View file

@ -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

View 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

View 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;
}
}

View 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 */

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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

File diff suppressed because it is too large Load diff

View 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;
}

View 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 ();
}
}
}

View 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__

View 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);
}
}

View 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__

File diff suppressed because it is too large Load diff

View 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);
}

View 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;
}

View 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);
}

View 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

View 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);
}

View 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);

File diff suppressed because it is too large Load diff

View 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

View 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;
};

View 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);
}

View 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__

View 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;

View 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

View 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__

View 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;
};

View 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;
};

View 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;
}

View 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)";
}