diff --git a/CMakeLists.txt b/CMakeLists.txt index 18bbfae4e..886c2f238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,18 @@ option(EDGE_SANITIZE "Enable code sanitizing" OFF) option(EDGE_PROFILING "Enable Profiling" OFF) option(EDGE_COAL_SUPPORT "Enable support for COAL scripting" ON) option(EDGE_DEHACKED_SUPPORT "Enable support for Dehacked patch conversion" ON) +option(EDGE_DOOM_SFX_SUPPORT "Enable support for Doom/PC Speaker sound format" ON) +option(EDGE_FLAC_SUPPORT "Enable support for FLAC music format" ON) +option(EDGE_IMF_SUPPORT "Enable support for IMF music format" ON) +option(EDGE_MP3_SUPPORT "Enable support for MP3 music format" ON) +option(EDGE_MUS_SUPPORT "Enable support for MUS MIDI format" ON) +option(EDGE_OGG_SUPPORT "Enable support for OGG music format" ON) +option(EDGE_RAD_SUPPORT "Enable support for Reality Adlib Tracker v2 music" ON) +option(EDGE_SID_SUPPORT "Enable support for C64 SID music" ON) +option(EDGE_TRACKER_SUPPORT "Enable support for Tracker music (MOD/XM/S3M/IT)" ON) +option(EDGE_XMI_SUPPORT "Enable support for XMI MIDI format" ON) option(EDGE_VWAD_SUPPORT "Enable support for k8vavoom VWAD archives" ON) +option(EDGE_WAV_SUPPORT "Enable support for WAV sound format" ON) include("${CMAKE_SOURCE_DIR}/cmake/EDGEClassic.cmake") @@ -151,9 +162,53 @@ if (EDGE_DEHACKED_SUPPORT) add_definitions(-DEDGE_DEHACKED_SUPPORT) endif() +if (EDGE_DOOM_SFX_SUPPORT) + add_definitions(-DEDGE_DOOM_SFX_SUPPORT) +endif() + +if (EDGE_FLAC_SUPPORT) + add_definitions(-DEDGE_FLAC_SUPPORT) +endif() + +if (EDGE_IMF_SUPPORT) + add_definitions(-DEDGE_IMF_SUPPORT) +endif() + +if (EDGE_MP3_SUPPORT) + add_definitions(-DEDGE_MP3_SUPPORT) +endif() + +if (EDGE_MUS_SUPPORT) + add_definitions(-DEDGE_MUS_SUPPORT) +endif() + +if (EDGE_OGG_SUPPORT) + add_definitions(-DEDGE_OGG_SUPPORT) +endif() + +if (EDGE_RAD_SUPPORT) + add_definitions(-DEDGE_RAD_SUPPORT) +endif() + +if (EDGE_SID_SUPPORT) + add_definitions(-DEDGE_SID_SUPPORT) +endif() + +if (EDGE_TRACKER_SUPPORT) + add_definitions(-DEDGE_TRACKER_SUPPORT) +endif() + if (EDGE_VWAD_SUPPORT) add_definitions(-DEDGE_VWAD_SUPPORT) endif() +if (EDGE_XMI_SUPPORT) + add_definitions(-DEDGE_XMI_SUPPORT) +endif() + +if (EDGE_WAV_SUPPORT) + add_definitions(-DEDGE_WAV_SUPPORT) +endif() + add_subdirectory(libraries) add_subdirectory(source_files) \ No newline at end of file diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index e6e262feb..be5c91f42 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,5 +1,7 @@ add_subdirectory(almostequals) -add_subdirectory(crsid) +if (EDGE_SID_SUPPORT) + add_subdirectory(crsid) +endif() add_subdirectory(dr_libs) add_subdirectory(fmmidi) if (NOT EDGE_GL_ES2) @@ -8,9 +10,13 @@ else() add_subdirectory(gl4es) endif() add_subdirectory(hmm) -add_subdirectory(ibxm) +if (EDGE_TRACKER_SUPPORT) + add_subdirectory(ibxm) +endif() add_subdirectory(libemidi) -add_subdirectory(libRAD) +if (EDGE_IMF_SUPPORT OR EDGE_RAD_SUPPORT) + add_subdirectory(libRAD) +endif() if (EDGE_VWAD_SUPPORT) add_subdirectory(libvwad) endif() diff --git a/libraries/dr_libs/dr_libs.cc b/libraries/dr_libs/dr_libs.cc index ee2e8f2a9..b48bc0b22 100644 --- a/libraries/dr_libs/dr_libs.cc +++ b/libraries/dr_libs/dr_libs.cc @@ -1,11 +1,18 @@ +#if EDGE_WAV_SUPPORT #define DR_WAV_NO_STDIO #define DR_WAV_IMPLEMENTATION -#define DR_FLAC_NO_STDIO -#define DR_FLAC_NO_CRC -#define DR_FLAC_IMPLEMENTATION +#include "dr_wav.h" +#endif + +#if EDGE_MP3_SUPPORT #define DR_MP3_NO_STDIO #define DR_MP3_IMPLEMENTATION +#include "dr_mp3.h" +#endif +#if EDGE_FLAC_SUPPORT +#define DR_FLAC_NO_STDIO +#define DR_FLAC_NO_CRC +#define DR_FLAC_IMPLEMENTATION #include "dr_flac.h" -#include "dr_mp3.h" -#include "dr_wav.h" \ No newline at end of file +#endif \ No newline at end of file diff --git a/libraries/libRAD/CMakeLists.txt b/libraries/libRAD/CMakeLists.txt index dbbeaae86..bd7f4424d 100644 --- a/libraries/libRAD/CMakeLists.txt +++ b/libraries/libRAD/CMakeLists.txt @@ -2,10 +2,15 @@ # libRAD ########################################## +set (LIBRAD_SOURCES opal.cpp) + +if (EDGE_RAD_SUPPORT) + set (LIBRAD_SOURCES ${LIBRAD_SOURCES} radplay.cpp) +endif() + add_library( libRAD - opal.cpp - radplay.cpp + ${LIBRAD_SOURCES} ) target_include_directories(libRAD PUBLIC ./) \ No newline at end of file diff --git a/source_files/ddf/ddf_playlist.cc b/source_files/ddf/ddf_playlist.cc index f2d391276..653dd17dc 100644 --- a/source_files/ddf/ddf_playlist.cc +++ b/source_files/ddf/ddf_playlist.cc @@ -24,6 +24,40 @@ static PlaylistEntry *dynamic_plentry; PlaylistEntryContainer playlist; +static std::vector supported_music_types; + +static void InitializeMusicTypes() +{ + supported_music_types.push_back("UNKNOWN"); + supported_music_types.push_back("MIDI"); +#if EDGE_MUS_SUPPORT + supported_music_types.push_back("MUS"); +#endif +#if EDGE_OGG_SUPPORT + supported_music_types.push_back("OGG"); +#endif +#if EDGE_MP3_SUPPORT + supported_music_types.push_back("MP3"); +#endif +#if EDGE_SID_SUPPORT + supported_music_types.push_back("SID"); +#endif +#if EDGE_FLAC_SUPPORT + supported_music_types.push_back("FLAC"); +#endif +#if EDGE_TRACKER_SUPPORT + supported_music_types.push_back("TRACKER"); +#endif +#if EDGE_RAD_SUPPORT + supported_music_types.push_back("RAD"); +#endif +#if EDGE_IMF_SUPPORT + supported_music_types.push_back("IMF280"); + supported_music_types.push_back("IMF560"); + supported_music_types.push_back("IMF700"); +#endif +} + // // DDFMusicParseInfo // @@ -31,8 +65,9 @@ PlaylistEntryContainer playlist; // static void DDFMusicParseInfo(const char *info) { - static const char *const musstrtype[] = {"UNKNOWN", "MIDI", "MUS", "OGG", "MP3", "SID", "FLAC", - "IBXM", "RAD", "IMF280", "IMF560", "IMF700", nullptr}; + if (supported_music_types.empty()) + InitializeMusicTypes(); + static const char *const musinftype[] = {"UNKNOWN", "LUMP", "FILE", "PACK", nullptr}; char charbuff[256]; @@ -60,7 +95,7 @@ static void DDFMusicParseInfo(const char *info) charbuff[i] = 0; i = kDDFMusicUnknown; - while (i != kTotalDDFMusicTypes && epi::StringCaseCompareASCII(charbuff, musstrtype[i]) != 0) + while (i != kTotalDDFMusicTypes && epi::StringCaseCompareASCII(charbuff, supported_music_types[i]) != 0) i++; if (i == kTotalDDFMusicTypes) diff --git a/source_files/ddf/ddf_playlist.h b/source_files/ddf/ddf_playlist.h index 20d61b3d8..fc96e717a 100644 --- a/source_files/ddf/ddf_playlist.h +++ b/source_files/ddf/ddf_playlist.h @@ -30,16 +30,32 @@ enum DDFMusicType { kDDFMusicUnknown = 0, kDDFMusicMIDI, +#if EDGE_MUS_SUPPORT kDDFMusicMUS, +#endif +#if EDGE_OGG_SUPPORT kDDFMusicOGG, +#endif +#if EDGE_MP3_SUPPORT kDDFMusicMP3, +#endif +#if EDGE_SID_SUPPORT kDDFMusicSID, +#endif +#if EDGE_FLAC_SUPPORT kDDFMusicFLAC, +#endif +#if EDGE_TRACKER_SUPPORT kDDFMusicIBXM, +#endif +#if EDGE_RAD_SUPPORT kDDFMusicRAD, +#endif +#if EDGE_IMF_SUPPORT kDDFMusicIMF280, kDDFMusicIMF560, kDDFMusicIMF700, +#endif kTotalDDFMusicTypes }; diff --git a/source_files/edge/CMakeLists.txt b/source_files/edge/CMakeLists.txt index 77f1636ab..a6abe811d 100644 --- a/source_files/edge/CMakeLists.txt +++ b/source_files/edge/CMakeLists.txt @@ -81,18 +81,11 @@ set (EDGE_SOURCE_FILES s_blit.cc s_cache.cc s_sound.cc - s_mp3.cc s_music.cc s_ogg.cc s_emidi.cc s_tsf.cc s_fmm.cc - s_ibxm.cc - s_imf.cc - s_rad.cc - s_sid.cc - s_wav.cc - s_flac.cc sv_chunk.cc sv_glob.cc sv_level.cc @@ -128,6 +121,42 @@ if (EDGE_DEHACKED_SUPPORT) set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} l_deh.cc) endif() +if (EDGE_DOOM_SFX_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_doom.cc) +endif() + +if (EDGE_FLAC_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_flac.cc) +endif() + +if (EDGE_IMF_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_imf.cc) +endif() + +if (EDGE_MP3_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_mp3.cc) +endif() + +if (EDGE_OGG_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_ogg.cc) +endif() + +if (EDGE_RAD_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_rad.cc) +endif() + +if (EDGE_SID_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_sid.cc) +endif() + +if (EDGE_TRACKER_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_ibxm.cc) +endif() + +if (EDGE_WAV_SUPPORT) + set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} s_wav.cc) +endif() + if (EMSCRIPTEN) set (EDGE_SOURCE_FILES ${EDGE_SOURCE_FILES} i_web.cc) else() @@ -141,13 +170,10 @@ set (EDGE_LINK_LIBRARIES edge_tracy ${SDL2_LIBRARIES} almostequals - crsid dr_libs fmmidi HandmadeMath - ibxm libemidi - libRAD lua miniz pl_mpeg @@ -165,6 +191,18 @@ if (EDGE_DEHACKED_SUPPORT) set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} edge_deh) endif() +if (EDGE_IMF_SUPPORT OR EDGE_RAD_SUPPORT) + set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} libRAD) +endif() + +if (EDGE_SID_SUPPORT) + set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} crsid) +endif() + +if (EDGE_TRACKER_SUPPORT) + set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} ibxm) +endif() + if (EDGE_VWAD_SUPPORT) set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} libvwad) endif() diff --git a/source_files/edge/m_option.cc b/source_files/edge/m_option.cc index ea1d3d8c4..b57d0d2fe 100644 --- a/source_files/edge/m_option.cc +++ b/source_files/edge/m_option.cc @@ -529,8 +529,10 @@ static OptionMenuItem soundoptions[] = { {kOptionMenuItemTypeSwitch, "MIDI Player", "TinySoundFont/FMMIDI/Emu de MIDI (OPLL Mode)/Emu de MIDI (SCC-PSG Mode)", 4, &var_midi_player, OptionMenuChangeMidiPlayer, nullptr}, {kOptionMenuItemTypeFunction, "TinySoundFont Bank", nullptr, 0, nullptr, OptionMenuChangeSoundfont, nullptr}, +#if EDGE_DOOM_SFX_SUPPORT {kOptionMenuItemTypeBoolean, "PC Speaker Mode", YesNo, 2, &pc_speaker_mode, OptionMenuChangePCSpeakerMode, "Music will be Off while this is enabled"}, +#endif {kOptionMenuItemTypePlain, "", nullptr, 0, nullptr, nullptr, nullptr}, {kOptionMenuItemTypeBoolean, "Dynamic Reverb", YesNo, 2, &dynamic_reverb, nullptr, nullptr}, {kOptionMenuItemTypePlain, "", nullptr, 0, nullptr, nullptr, nullptr}, diff --git a/source_files/edge/midi_convert_mus.hpp b/source_files/edge/midi_convert_mus.hpp deleted file mode 100644 index d4910cfa2..000000000 --- a/source_files/edge/midi_convert_mus.hpp +++ /dev/null @@ -1,480 +0,0 @@ -/* - * MUS2MIDI: MUS to MIDI Library - * - * Copyright (C) 2014 Bret Curtis - * Copyright (C) WildMIDI Developers 2015-2016 - * ADLMIDI Library API: Copyright (c) 2015-2023 Vitaly Novichkov - * - * Copyright (c) 2024 The EDGE Team. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#pragma once - -#include -#include -#include -#include - -static constexpr uint8_t kMusFrequency = 140; -static constexpr int kMusTempo = 0x00068A1B; /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ - /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ -static constexpr uint16_t kMusDivision = 0x0101; /* 257 for 140Hz files with a 140MPQN */ - /* 0x0088 -> 136 for 70Hz files with a 140MPQN */ - /* 0x010B -> 267 for 70hz files with a 70MPQN */ - /* 0x01F9 -> 505 for 140hz files with a 70MPQN */ - -/* New - * QLS: MPQN/1000000 = 0.428571 - * TDPS: QLS/PPQN = 0.428571/136 = 0.003151257 - * PPQN: 136 - * - * QLS: MPQN/1000000 = 0.428571 - * TDPS: QLS/PPQN = 0.428571/257 = 0.001667591 - * PPQN: 257 - * - * QLS: MPQN/1000000 = 0.857142 - * TDPS: QLS/PPQN = 0.857142/267 = 0.00321027 - * PPQN: 267 - * - * QLS: MPQN/1000000 = 0.857142 - * TDPS: QLS/PPQN = 0.857142/505 = 0.001697311 - * PPQN: 505 - * - * Old - * QLS: MPQN/1000000 = 1.745673 - * TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick) - * PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN - * = 1.745673) (PPQN = 89.000001682) - * - */ - -enum MusEvent -{ - kMusEventKeyOff = 0, - kMusEventKeyOn = 1, - kMusEventPitchWheel = 2, - kMusEventChannelMode = 3, - kMusEventControllerChange = 4, - kMusEventEnd = 6, -}; - -static constexpr uint8_t kMusMidiMaxChannels = 16; - -static constexpr char kMusHeader[] = {'M', 'U', 'S', 0x1A}; - -static constexpr uint8_t kMusToMidiMap[] = { - /* MIDI Number Description */ - 0, /* 0 program change */ - 0, /* 1 bank selection */ - 0x01, /* 2 Modulation pot (frequency vibrato depth) */ - 0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */ - 0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */ - 0x0B, /* 5 Expression pot */ - 0x5B, /* 6 Reverb depth */ - 0x5D, /* 7 Chorus depth */ - 0x40, /* 8 Sustain pedal */ - 0x43, /* 9 Soft pedal */ - 0x78, /* 10 All sounds off */ - 0x7B, /* 11 All notes off */ - 0x7E, /* 12 Mono (use numchannels + 1) */ - 0x7F, /* 13 Poly */ - 0x79, /* 14 reset all controllers */ -}; - -struct MusHeader -{ - char ID[4]; /* identifier: "MUS" 0x1A */ - uint16_t scoreLen; - uint16_t scoreStart; - uint16_t channels; /* count of primary channels */ - uint16_t sec_channels; /* count of secondary channels */ - uint16_t instrCnt; -}; - -struct MidiHeaderChunk -{ - char name[4]; - int32_t length; - int16_t format; /* make 0 */ - int16_t ntracks; /* make 1 */ - int16_t division; /* 0xe250 ?? */ -}; - -struct MidiTrackChunk -{ - char name[4]; - int32_t length; -}; - -struct MusConversionContext -{ - uint8_t *src, *src_ptr; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; -}; - -static constexpr uint16_t kMusDestinationChunkSize = 8192; -static void MusToMidiResizeDestination(struct MusConversionContext *ctx) -{ - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kMusDestinationChunkSize); - ctx->dstsize += kMusDestinationChunkSize; - ctx->dstrem += kMusDestinationChunkSize; - ctx->dst_ptr = ctx->dst + pos; -} - -static void MusToMidiWrite1(struct MusConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - MusToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void MusToMidiWrite2(struct MusConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - MusToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (val >> 8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 2; -} - -static void MusToMidiWrite4(struct MusConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 4) - MusToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (uint8_t)((val >> 24) & 0xff); - *ctx->dst_ptr++ = (uint8_t)((val >> 16) & 0xff); - *ctx->dst_ptr++ = (uint8_t)((val >> 8) & 0xff); - *ctx->dst_ptr++ = (uint8_t)((val & 0xff)); - ctx->dstrem -= 4; -} - -static void MusToMidiSeekDestination(struct MusConversionContext *ctx, uint32_t pos) -{ - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - MusToMidiResizeDestination(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void MusToMidiSkipDestination(struct MusConversionContext *ctx, int32_t pos) -{ - size_t newpos; - ctx->dst_ptr += pos; - newpos = ctx->dst_ptr - ctx->dst; - while (ctx->dstsize < newpos) - MusToMidiResizeDestination(ctx); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t MusToMidiGetDestinationPosition(struct MusConversionContext *ctx) -{ - return (uint32_t)(ctx->dst_ptr - ctx->dst); -} - -/* writes a variable length integer to a buffer, and returns bytes written */ -static int32_t MusToMidiWriteVariableLength(int32_t value, uint8_t *out) -{ - int32_t buffer, count = 0; - - buffer = value & 0x7f; - while ((value >>= 7) > 0) - { - buffer <<= 8; - buffer += 0x80; - buffer += (value & 0x7f); - } - - while (1) - { - ++count; - *out = (uint8_t)buffer; - ++out; - if (buffer & 0x80) - buffer >>= 8; - else - break; - } - return (count); -} - -#define EDGE_MUS_READ_SHORT(b) ((b)[0] | ((b)[1] << 8)) -#define EDGE_MUS_READ_INT(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) - -static int ConvertMusToMidi(uint8_t *in, uint32_t insize, uint8_t **out, uint32_t *outsize, uint16_t frequency) -{ - struct MusConversionContext ctx; - MusHeader header; - uint8_t *cur, *end; - uint32_t track_size_pos, begin_track_pos, current_pos; - int32_t delta_time; /* Delta time for midi event */ - int temp, ret = -1; - int channel_volume[kMusMidiMaxChannels]; - int channelMap[kMusMidiMaxChannels], currentChannel; - - if (insize < sizeof(MusHeader)) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", - * 0);*/ - return (-1); - } - - if (!frequency) - frequency = kMusFrequency; - - /* read the MUS header and set our location */ - memcpy(header.ID, in, 4); - header.scoreLen = EDGE_MUS_READ_SHORT(&in[4]); - header.scoreStart = EDGE_MUS_READ_SHORT(&in[6]); - header.channels = EDGE_MUS_READ_SHORT(&in[8]); - header.sec_channels = EDGE_MUS_READ_SHORT(&in[10]); - header.instrCnt = EDGE_MUS_READ_SHORT(&in[12]); - - if (memcmp(header.ID, kMusHeader, 4)) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, nullptr, - * 0);*/ - return (-1); - } - if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", - * 0);*/ - return (-1); - } - /* channel #15 should be excluded in the numchannels field: */ - if (header.channels > kMusMidiMaxChannels - 1) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, nullptr, - * 0);*/ - return (-1); - } - - memset(&ctx, 0, sizeof(struct MusConversionContext)); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - - ctx.dst = (uint8_t *)calloc(kMusDestinationChunkSize, sizeof(uint8_t)); - ctx.dst_ptr = ctx.dst; - ctx.dstsize = kMusDestinationChunkSize; - ctx.dstrem = kMusDestinationChunkSize; - - /* Map channel 15 to 9 (percussions) */ - for (temp = 0; temp < kMusMidiMaxChannels; ++temp) - { - channelMap[temp] = -1; - channel_volume[temp] = 0x40; - } - channelMap[15] = 9; - - /* Header is 14 bytes long and add the rest as well */ - MusToMidiWrite1(&ctx, 'M'); - MusToMidiWrite1(&ctx, 'T'); - MusToMidiWrite1(&ctx, 'h'); - MusToMidiWrite1(&ctx, 'd'); - MusToMidiWrite4(&ctx, 6); /* length of header */ - MusToMidiWrite2(&ctx, 0); /* MIDI type (always 0) */ - MusToMidiWrite2(&ctx, 1); /* MUS files only have 1 track */ - MusToMidiWrite2(&ctx, kMusDivision); /* division */ - - /* Write out track header and track length position for later */ - begin_track_pos = MusToMidiGetDestinationPosition(&ctx); - MusToMidiWrite1(&ctx, 'M'); - MusToMidiWrite1(&ctx, 'T'); - MusToMidiWrite1(&ctx, 'r'); - MusToMidiWrite1(&ctx, 'k'); - track_size_pos = MusToMidiGetDestinationPosition(&ctx); - MusToMidiSkipDestination(&ctx, 4); - - /* write tempo: microseconds per quarter note */ - MusToMidiWrite1(&ctx, 0x00); /* delta time */ - MusToMidiWrite1(&ctx, 0xff); /* sys command */ - MusToMidiWrite2(&ctx, 0x5103); /* command - set tempo */ - MusToMidiWrite1(&ctx, kMusTempo & 0x000000ff); - MusToMidiWrite1(&ctx, (kMusTempo & 0x0000ff00) >> 8); - MusToMidiWrite1(&ctx, (kMusTempo & 0x00ff0000) >> 16); - - /* Percussions channel starts out at volume 100 */ - MusToMidiWrite1(&ctx, 0x00); - MusToMidiWrite1(&ctx, 0xB9); - MusToMidiWrite1(&ctx, 0x07); - MusToMidiWrite1(&ctx, 100); - - /* get current position in source, and end of position */ - cur = in + header.scoreStart; - end = cur + header.scoreLen; - - currentChannel = 0; - delta_time = 0; - - /* main loop */ - while (cur < end) - { - /*printf("LOOP DEBUG: %d\r\n",iterator++);*/ - uint8_t channel; - uint8_t event; - uint8_t temp_buffer[32]; /* temp buffer for current iterator */ - uint8_t *out_local = temp_buffer; - uint8_t status, bit1, bit2, bitc = 2; - - /* read in current bit */ - event = *cur++; - channel = (event & 15); /* current channel */ - - /* write variable length delta time */ - out_local += MusToMidiWriteVariableLength(delta_time, out_local); - - /* set all channels to 127 (max) volume */ - if (channelMap[channel] < 0) - { - *out_local++ = 0xB0 + currentChannel; - *out_local++ = 0x07; - *out_local++ = 100; - *out_local++ = 0x00; - channelMap[channel] = currentChannel++; - if (currentChannel == 9) - ++currentChannel; - } - status = channelMap[channel]; - - /* handle events */ - switch ((event & 122) >> 4) - { - case kMusEventKeyOff: - status |= 0x80; - bit1 = *cur++; - bit2 = 0x40; - break; - case kMusEventKeyOn: - status |= 0x90; - bit1 = *cur & 127; - if (*cur++ & 128) /* volume bit? */ - channel_volume[channelMap[channel]] = *cur++; - bit2 = channel_volume[channelMap[channel]]; - break; - case kMusEventPitchWheel: - status |= 0xE0; - bit1 = (*cur & 1) >> 6; - bit2 = (*cur++ >> 1) & 127; - break; - case kMusEventChannelMode: - status |= 0xB0; - if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) - { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = kMusToMidiMap[*cur++]; - bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; - break; - case kMusEventControllerChange: - if (*cur == 0) - { - cur++; - status |= 0xC0; - bit1 = *cur++; - bit2 = 0; /* silence bogus warnings */ - bitc = 1; - } - else - { - status |= 0xB0; - if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) - { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = kMusToMidiMap[*cur++]; - bit2 = *cur++; - } - break; - case kMusEventEnd: /* End */ - status = 0xff; - bit1 = 0x2f; - bit2 = 0x00; - if (cur != end) - { /* should we error here or report-only? */ - /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes", - __FUNCTION__, __LINE__, (long)(cur - end));*/ - } - break; - case 5: /* Unknown */ - case 7: /* Unknown */ - default: /* shouldn't happen */ - /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)", - __FUNCTION__, __LINE__, event);*/ - goto _end; - } - - /* write it out */ - *out_local++ = status; - *out_local++ = bit1; - if (bitc == 2) - *out_local++ = bit2; - - /* write out our temp buffer */ - if (out_local != temp_buffer) - { - if (ctx.dstrem < sizeof(temp_buffer)) - MusToMidiResizeDestination(&ctx); - - memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); - ctx.dst_ptr += out_local - temp_buffer; - ctx.dstrem -= (uint32_t)(out_local - temp_buffer); - } - - if (event & 128) - { - delta_time = 0; - do - { - delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency)); - } while ((*cur++ & 128)); - } - else - { - delta_time = 0; - } - } - - /* write out track length */ - current_pos = MusToMidiGetDestinationPosition(&ctx); - MusToMidiSeekDestination(&ctx, track_size_pos); - MusToMidiWrite4(&ctx, current_pos - begin_track_pos - sizeof(MidiTrackChunk)); - MusToMidiSeekDestination(&ctx, current_pos); /* reseek to end position */ - - *out = ctx.dst; - *outsize = ctx.dstsize - ctx.dstrem; - ret = 0; - -_end: /* cleanup */ - if (ret < 0) - { - free(ctx.dst); - *out = nullptr; - *outsize = 0; - } - - return (ret); -} diff --git a/source_files/edge/midi_convert_xmi.hpp b/source_files/edge/midi_convert_xmi.hpp deleted file mode 100644 index 3937f9b60..000000000 --- a/source_files/edge/midi_convert_xmi.hpp +++ /dev/null @@ -1,1442 +0,0 @@ -/* - * XMIDI: Miles XMIDI to MID Library - * - * Copyright (C) 2001 Ryan Nunn - * Copyright (C) 2014 Bret Curtis - * Copyright (C) WildMIDI Developers 2015-2016 - * Copyright (c) 2015-2022 Vitaly Novichkov - * Copyright (c) 2024 The EDGE Team. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/* XMIDI Converter */ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -enum XMIConversionType -{ - kXMINoConversion = 0x00, - kXMIConvertMt32ToGm = 0x01, - kXMIConvertMt32ToGs = 0x02, - kXMIConvertMt32ToGs127 = 0x03, - kXMIConvertMt32ToGs127Drum = 0x04, - kXMIConvertGs127ToGs = 0x05 -}; - -enum XMIStatusByte -{ - kXMIStatusNoteOff = 0x8, - kXMIStatusNoteOn = 0x9, - kXMIStatusAftertouch = 0xA, - kXMIStatusController = 0xB, - kXMIStatusProgramChange = 0xC, - kXMIStatusPressure = 0xD, - kXMIStatusPitchWheel = 0xE, - kXMIStatusSysex = 0xF -}; - -struct XMIToMidiEvent -{ - int32_t time; - uint8_t status; - uint8_t data[2]; - uint32_t len; - uint8_t *buffer; - XMIToMidiEvent *next; -}; - -struct MidiDescriptor -{ - uint16_t type; - uint16_t tracks; -}; - -struct XMIToMidiConversionContext -{ - uint8_t *src, *src_ptr, *src_end; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; - uint32_t convert_type; - MidiDescriptor info; - int bank127[16]; - XMIToMidiEvent **events; - int16_t *timing; - XMIToMidiEvent *list; - XMIToMidiEvent *current; - std::vector> *dyn_out; - std::vector *dyn_out_cur; -}; - -struct XMIToMidiBranch -{ - unsigned count; - uint8_t id[128]; - uint32_t offset[128]; -}; - -/* forward declarations of private functions */ -static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist); -static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time); /* List manipulation */ -static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ -static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ -static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value); /* Variable length quantity */ -static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, - const int size); -static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, - const uint8_t status); -static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn); -static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist); -static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx); -static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber); -static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx); - -static uint32_t XMIToMidiRead1(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0; - assert(ctx->src_ptr + 1 < ctx->src_end); - b0 = *ctx->src_ptr++; - return (b0); -} - -static uint32_t XMIToMidiRead2(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0, b1; - assert(ctx->src_ptr + 2 < ctx->src_end); - b0 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - return (b0 + ((uint32_t)b1 << 8)); -} - -static uint32_t XMIToMidiRead4(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0, b1, b2, b3; - assert(ctx->src_ptr + 4 < ctx->src_end); - b3 = *ctx->src_ptr++; - b2 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - b0 = *ctx->src_ptr++; - return (b0 + ((uint32_t)b1 << 8) + ((uint32_t)b2 << 16) + ((uint32_t)b3 << 24)); -} - -static uint32_t XMIToMidiRead4LittleEndian(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0, b1, b2, b3; - assert(ctx->src_ptr + 4 < ctx->src_end); - b3 = *ctx->src_ptr++; - b2 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - b0 = *ctx->src_ptr++; - return (b3 + ((uint32_t)b2 << 8) + ((uint32_t)b1 << 16) + ((uint32_t)b0 << 24)); -} - -static void XMIToMidiCopy(struct XMIToMidiConversionContext *ctx, char *b, uint32_t len) -{ - assert(ctx->src_ptr + len < ctx->src_end); - memcpy(b, ctx->src_ptr, len); - ctx->src_ptr += len; -} - -static constexpr uint16_t kDestinationChunkSize = 8192; -static void XMIToMidiResizeDestination(struct XMIToMidiConversionContext *ctx) -{ - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - if (ctx->dyn_out && ctx->dyn_out_cur) - { - ctx->dyn_out_cur->resize(ctx->dstsize + kDestinationChunkSize); - ctx->dst = ctx->dyn_out_cur->data(); - } - else - ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kDestinationChunkSize); - ctx->dstsize += kDestinationChunkSize; - ctx->dstrem += kDestinationChunkSize; - ctx->dst_ptr = ctx->dst + pos; -} - -static void XMIToMidiWrite1(struct XMIToMidiConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - XMIToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void XMIToMidiWrite2(struct XMIToMidiConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - XMIToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (val >> 8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 2; -} - -static void XMIToMidiWrite4(struct XMIToMidiConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 4) - XMIToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (val >> 24) & 0xff; - *ctx->dst_ptr++ = (val >> 16) & 0xff; - *ctx->dst_ptr++ = (val >> 8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 4; -} - -static void XMIToMidiSeekSource(struct XMIToMidiConversionContext *ctx, uint32_t pos) -{ - ctx->src_ptr = ctx->src + pos; -} - -static void XMIToMidiSeekDestination(struct XMIToMidiConversionContext *ctx, uint32_t pos) -{ - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - XMIToMidiResizeDestination(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void XMIToMidiSkipSource(struct XMIToMidiConversionContext *ctx, int32_t pos) -{ - ctx->src_ptr += pos; -} - -static void XMIToMidiSkipDestination(struct XMIToMidiConversionContext *ctx, int32_t pos) -{ - size_t newpos; - ctx->dst_ptr += pos; - newpos = ctx->dst_ptr - ctx->dst; - while (ctx->dstsize < newpos) - XMIToMidiResizeDestination(ctx); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t XMIToMidiGetSourceSize(struct XMIToMidiConversionContext *ctx) -{ - return (ctx->srcsize); -} - -static uint32_t XMIToMidiGetSourcePosition(struct XMIToMidiConversionContext *ctx) -{ - return (uint32_t)(ctx->src_ptr - ctx->src); -} - -static uint32_t XMIToMidiGetDestinationPosition(struct XMIToMidiConversionContext *ctx) -{ - return (uint32_t)(ctx->dst_ptr - ctx->dst); -} - -/* This is a default set of patches to convert from MT32 to GM - * The index is the MT32 Patch number and the value is the GM Patch - * This is only suitable for music that doesn't do timbre changes - * XMIDIs that contain Timbre changes will not convert properly. - */ -static constexpr char Mt32ToGmMap[128] = { - 0, /* 0 Piano 1 */ - 1, /* 1 Piano 2 */ - 2, /* 2 Piano 3 (synth) */ - 4, /* 3 EPiano 1 */ - 4, /* 4 EPiano 2 */ - 5, /* 5 EPiano 3 */ - 5, /* 6 EPiano 4 */ - 3, /* 7 Honkytonk */ - 16, /* 8 Organ 1 */ - 17, /* 9 Organ 2 */ - 18, /* 10 Organ 3 */ - 16, /* 11 Organ 4 */ - 19, /* 12 Pipe Organ 1 */ - 19, /* 13 Pipe Organ 2 */ - 19, /* 14 Pipe Organ 3 */ - 21, /* 15 Accordion */ - 6, /* 16 Harpsichord 1 */ - 6, /* 17 Harpsichord 2 */ - 6, /* 18 Harpsichord 3 */ - 7, /* 19 Clavinet 1 */ - 7, /* 20 Clavinet 2 */ - 7, /* 21 Clavinet 3 */ - 8, /* 22 Celesta 1 */ - 8, /* 23 Celesta 2 */ - 62, /* 24 Synthbrass 1 (62) */ - 63, /* 25 Synthbrass 2 (63) */ - 62, /* 26 Synthbrass 3 Bank 8 */ - 63, /* 27 Synthbrass 4 Bank 8 */ - 38, /* 28 Synthbass 1 */ - 39, /* 29 Synthbass 2 */ - 38, /* 30 Synthbass 3 Bank 8 */ - 39, /* 31 Synthbass 4 Bank 8 */ - 88, /* 32 Fantasy */ - 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ - 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be - ChoirAhhs(52)??? */ - 92, /* 35 Glass */ - 97, /* 36 Soundtrack */ - 99, /* 37 Atmosphere */ - 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular - Bells(14) would be better. It is! */ - 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ - 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ - 96, /* 41 IceRain */ - 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ - 95, /* 43 EchoPans, no equiv, setting to SweepPad */ - 81, /* 44 DoctorSolo Bank 8 */ - 87, /* 45 SchoolDaze, no real equiv */ - 112, /* 46 Bell Singer */ - 80, /* 47 SquareWave */ - 48, /* 48 Strings 1 */ - 48, /* 49 Strings 2 - should be 49 */ - 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should - be 50 */ - 45, /* 51 Pizzicato Strings */ - 40, /* 52 Violin 1 */ - 40, /* 53 Violin 2 ? Viola */ - 42, /* 54 Cello 1 */ - 42, /* 55 Cello 2 */ - 43, /* 56 Contrabass */ - 46, /* 57 Harp 1 */ - 46, /* 58 Harp 2 */ - 24, /* 59 Guitar 1 (Nylon) */ - 25, /* 60 Guitar 2 (Steel) */ - 26, /* 61 Elec Guitar 1 */ - 27, /* 62 Elec Guitar 2 */ - 104, /* 63 Sitar */ - 32, /* 64 Acou Bass 1 */ - 32, /* 65 Acou Bass 2 */ - 33, /* 66 Elec Bass 1 */ - 34, /* 67 Elec Bass 2 */ - 36, /* 68 Slap Bass 1 */ - 37, /* 69 Slap Bass 2 */ - 35, /* 70 Fretless Bass 1 */ - 35, /* 71 Fretless Bass 2 */ - 73, /* 72 Flute 1 */ - 73, /* 73 Flute 2 */ - 72, /* 74 Piccolo 1 */ - 72, /* 75 Piccolo 2 */ - 74, /* 76 Recorder */ - 75, /* 77 Pan Pipes */ - 64, /* 78 Sax 1 */ - 65, /* 79 Sax 2 */ - 66, /* 80 Sax 3 */ - 67, /* 81 Sax 4 */ - 71, /* 82 Clarinet 1 */ - 71, /* 83 Clarinet 2 */ - 68, /* 84 Oboe */ - 69, /* 85 English Horn (Cor Anglais) */ - 70, /* 86 Bassoon */ - 22, /* 87 Harmonica */ - 56, /* 88 Trumpet 1 */ - 56, /* 89 Trumpet 2 */ - 57, /* 90 Trombone 1 */ - 57, /* 91 Trombone 2 */ - 60, /* 92 French Horn 1 */ - 60, /* 93 French Horn 2 */ - 58, /* 94 Tuba */ - 61, /* 95 Brass Section 1 */ - 61, /* 96 Brass Section 2 */ - 11, /* 97 Vibes 1 */ - 11, /* 98 Vibes 2 */ - 99, /* 99 Syn Mallet Bank 1 */ - 112, /* 100 WindBell no real equiv Set to TinkleBell(112) */ - 9, /* 101 Glockenspiel */ - 14, /* 102 Tubular Bells */ - 13, /* 103 Xylophone */ - 12, /* 104 Marimba */ - 107, /* 105 Koto */ - 111, /* 106 Sho?? set to Shanai(111) */ - 77, /* 107 Shakauhachi */ - 78, /* 108 Whistle 1 */ - 78, /* 109 Whistle 2 */ - 76, /* 110 Bottle Blow */ - 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */ - 47, /* 112 Timpani */ - 117, /* 113 Melodic Tom */ - 116, /* 114 Deap Snare no equiv, set to Taiko(116) */ - 118, /* 115 Electric Perc 1 */ - 118, /* 116 Electric Perc 2 */ - 116, /* 117 Taiko */ - 115, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ - 119, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ - 115, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ - 112, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ - 55, /* 122 Orchestral Hit */ - 124, /* 123 Telephone */ - 123, /* 124 BirdTweet */ - 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ - 98, /* 126 Water Bell set to Crystal Pad(98) */ - 121 /* 127 Jungle Tune set to Breath Noise */ -}; - -/* Same as above, except include patch changes - * so GS instruments can be used */ -static constexpr char Mt32ToGsMap[256] = { - 0, - 0, /* 0 Piano 1 */ - 1, - 0, /* 1 Piano 2 */ - 2, - 0, /* 2 Piano 3 (synth) */ - 4, - 0, /* 3 EPiano 1 */ - 4, - 0, /* 4 EPiano 2 */ - 5, - 0, /* 5 EPiano 3 */ - 5, - 0, /* 6 EPiano 4 */ - 3, - 0, /* 7 Honkytonk */ - 16, - 0, /* 8 Organ 1 */ - 17, - 0, /* 9 Organ 2 */ - 18, - 0, /* 10 Organ 3 */ - 16, - 0, /* 11 Organ 4 */ - 19, - 0, /* 12 Pipe Organ 1 */ - 19, - 0, /* 13 Pipe Organ 2 */ - 19, - 0, /* 14 Pipe Organ 3 */ - 21, - 0, /* 15 Accordion */ - 6, - 0, /* 16 Harpsichord 1 */ - 6, - 0, /* 17 Harpsichord 2 */ - 6, - 0, /* 18 Harpsichord 3 */ - 7, - 0, /* 19 Clavinet 1 */ - 7, - 0, /* 20 Clavinet 2 */ - 7, - 0, /* 21 Clavinet 3 */ - 8, - 0, /* 22 Celesta 1 */ - 8, - 0, /* 23 Celesta 2 */ - 62, - 0, /* 24 Synthbrass 1 (62) */ - 63, - 0, /* 25 Synthbrass 2 (63) */ - 62, - 0, /* 26 Synthbrass 3 Bank 8 */ - 63, - 0, /* 27 Synthbrass 4 Bank 8 */ - 38, - 0, /* 28 Synthbass 1 */ - 39, - 0, /* 29 Synthbass 2 */ - 38, - 0, /* 30 Synthbass 3 Bank 8 */ - 39, - 0, /* 31 Synthbass 4 Bank 8 */ - 88, - 0, /* 32 Fantasy */ - 90, - 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ - 52, - 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be - ChoirAhhs(52)??? */ - 92, - 0, /* 35 Glass */ - 97, - 0, /* 36 Soundtrack */ - 99, - 0, /* 37 Atmosphere */ - 14, - 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular - Bells(14) would be better. It is! */ - 54, - 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ - 98, - 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ - 96, - 0, /* 41 IceRain */ - 68, - 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ - 95, - 0, /* 43 EchoPans, no equiv, setting to SweepPad */ - 81, - 0, /* 44 DoctorSolo Bank 8 */ - 87, - 0, /* 45 SchoolDaze, no real equiv */ - 112, - 0, /* 46 Bell Singer */ - 80, - 0, /* 47 SquareWave */ - 48, - 0, /* 48 Strings 1 */ - 48, - 0, /* 49 Strings 2 - should be 49 */ - 44, - 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should - be 50 */ - 45, - 0, /* 51 Pizzicato Strings */ - 40, - 0, /* 52 Violin 1 */ - 40, - 0, /* 53 Violin 2 ? Viola */ - 42, - 0, /* 54 Cello 1 */ - 42, - 0, /* 55 Cello 2 */ - 43, - 0, /* 56 Contrabass */ - 46, - 0, /* 57 Harp 1 */ - 46, - 0, /* 58 Harp 2 */ - 24, - 0, /* 59 Guitar 1 (Nylon) */ - 25, - 0, /* 60 Guitar 2 (Steel) */ - 26, - 0, /* 61 Elec Guitar 1 */ - 27, - 0, /* 62 Elec Guitar 2 */ - 104, - 0, /* 63 Sitar */ - 32, - 0, /* 64 Acou Bass 1 */ - 32, - 0, /* 65 Acou Bass 2 */ - 33, - 0, /* 66 Elec Bass 1 */ - 34, - 0, /* 67 Elec Bass 2 */ - 36, - 0, /* 68 Slap Bass 1 */ - 37, - 0, /* 69 Slap Bass 2 */ - 35, - 0, /* 70 Fretless Bass 1 */ - 35, - 0, /* 71 Fretless Bass 2 */ - 73, - 0, /* 72 Flute 1 */ - 73, - 0, /* 73 Flute 2 */ - 72, - 0, /* 74 Piccolo 1 */ - 72, - 0, /* 75 Piccolo 2 */ - 74, - 0, /* 76 Recorder */ - 75, - 0, /* 77 Pan Pipes */ - 64, - 0, /* 78 Sax 1 */ - 65, - 0, /* 79 Sax 2 */ - 66, - 0, /* 80 Sax 3 */ - 67, - 0, /* 81 Sax 4 */ - 71, - 0, /* 82 Clarinet 1 */ - 71, - 0, /* 83 Clarinet 2 */ - 68, - 0, /* 84 Oboe */ - 69, - 0, /* 85 English Horn (Cor Anglais) */ - 70, - 0, /* 86 Bassoon */ - 22, - 0, /* 87 Harmonica */ - 56, - 0, /* 88 Trumpet 1 */ - 56, - 0, /* 89 Trumpet 2 */ - 57, - 0, /* 90 Trombone 1 */ - 57, - 0, /* 91 Trombone 2 */ - 60, - 0, /* 92 French Horn 1 */ - 60, - 0, /* 93 French Horn 2 */ - 58, - 0, /* 94 Tuba */ - 61, - 0, /* 95 Brass Section 1 */ - 61, - 0, /* 96 Brass Section 2 */ - 11, - 0, /* 97 Vibes 1 */ - 11, - 0, /* 98 Vibes 2 */ - 99, - 0, /* 99 Syn Mallet Bank 1 */ - 112, - 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */ - 9, - 0, /* 101 Glockenspiel */ - 14, - 0, /* 102 Tubular Bells */ - 13, - 0, /* 103 Xylophone */ - 12, - 0, /* 104 Marimba */ - 107, - 0, /* 105 Koto */ - 111, - 0, /* 106 Sho?? set to Shanai(111) */ - 77, - 0, /* 107 Shakauhachi */ - 78, - 0, /* 108 Whistle 1 */ - 78, - 0, /* 109 Whistle 2 */ - 76, - 0, /* 110 Bottle Blow */ - 76, - 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */ - 47, - 0, /* 112 Timpani */ - 117, - 0, /* 113 Melodic Tom */ - 116, - 0, /* 114 Deap Snare no equiv, set to Taiko(116) */ - 118, - 0, /* 115 Electric Perc 1 */ - 118, - 0, /* 116 Electric Perc 2 */ - 116, - 0, /* 117 Taiko */ - 115, - 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ - 119, - 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ - 115, - 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ - 112, - 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ - 55, - 0, /* 122 Orchestral Hit */ - 124, - 0, /* 123 Telephone */ - 123, - 0, /* 124 BirdTweet */ - 94, - 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ - 98, - 0, /* 126 Water Bell set to Crystal Pad(98) */ - 121, - 0 /* 127 Jungle Tune set to Breath Noise */ -}; - -static int ConvertXMIToMidi(uint8_t *in, uint32_t insize, std::vector> &out, uint32_t convert_type) -{ - struct XMIToMidiConversionContext ctx; - unsigned int i; - int ret = -1; - - if (convert_type > kXMIConvertMt32ToGs) - { - /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", - * __FUNCTION__, __LINE__, convert_type);*/ - return (ret); - } - - memset(&ctx, 0, sizeof(struct XMIToMidiConversionContext)); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - ctx.src_end = ctx.src + insize; - ctx.convert_type = convert_type; - ctx.dyn_out = &out; - - if (XMIToMidiParseXMI(&ctx) < 0) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, nullptr, - * 0);*/ - goto _end; - } - - if (XMIToMidiExtractTracks(&ctx, 0) < 0) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, nullptr, - * 0);*/ - goto _end; - } - - for (i = 0; i < ctx.info.tracks; i++) - { - out.push_back(std::vector()); - ctx.dyn_out_cur = &out.back(); - ctx.dyn_out_cur->resize(kDestinationChunkSize); - ctx.dst = ctx.dyn_out_cur->data(); - ctx.dst_ptr = ctx.dst; - ctx.dstsize = kDestinationChunkSize; - ctx.dstrem = kDestinationChunkSize; - - /* Header is 14 bytes long and add the rest as well */ - XMIToMidiWrite1(&ctx, 'M'); - XMIToMidiWrite1(&ctx, 'T'); - XMIToMidiWrite1(&ctx, 'h'); - XMIToMidiWrite1(&ctx, 'd'); - - XMIToMidiWrite4(&ctx, 6); - - XMIToMidiWrite2(&ctx, ctx.info.type); - XMIToMidiWrite2(&ctx, 1); - XMIToMidiWrite2(&ctx, ctx.timing[i]); /* write divisions from track0 */ - - XMIToMidiConvertListToMidiTrack(&ctx, ctx.events[i]); - ctx.dyn_out_cur->resize(ctx.dstsize - ctx.dstrem); - } - - ret = 0; - -_end: /* cleanup */ - if (ret < 0) - { - out.clear(); - } - if (ctx.events) - { - for (i = 0; i < ctx.info.tracks; i++) - XMIToMidiDeleteEventList(ctx.events[i]); - free(ctx.events); - } - free(ctx.timing); - - return (ret); -} - -static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist) -{ - XMIToMidiEvent *event; - XMIToMidiEvent *next; - - next = mlist; - - while ((event = next) != nullptr) - { - next = event->next; - free(event->buffer); - free(event); - } -} - -/* Sets current to the new event and updates list */ -static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time) -{ - if (!ctx->list) - { - ctx->list = ctx->current = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - ctx->current->time = (time < 0) ? 0 : time; - return; - } - - if (time < 0) - { - XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - event->next = ctx->list; - ctx->list = ctx->current = event; - return; - } - - if (ctx->current->time > time) - ctx->current = ctx->list; - - while (ctx->current->next) - { - if (ctx->current->next->time > time) - { - XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - event->next = ctx->current->next; - ctx->current->next = event; - ctx->current = event; - ctx->current->time = time; - return; - } - - ctx->current = ctx->current->next; - } - - ctx->current->next = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - ctx->current = ctx->current->next; - ctx->current->time = time; -} - -/* Conventional Variable Length Quantity */ -static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant) -{ - int i; - uint32_t data; - - *quant = 0; - for (i = 0; i < 4; i++) - { - if (ctx->src_ptr + 1 >= ctx->src + ctx->srcsize) - break; - data = XMIToMidiRead1(ctx); - *quant <<= 7; - *quant |= data & 0x7F; - - if (!(data & 0x80)) - { - i++; - break; - } - } - return (i); -} - -/* XMIDI Delta Variable Length Quantity */ -static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant) -{ - int i; - int32_t data; - - *quant = 0; - for (i = 0; XMIToMidiGetSourcePosition(ctx) != XMIToMidiGetSourceSize(ctx); ++i) - { - data = XMIToMidiRead1(ctx); - if (data & 0x80) - { - XMIToMidiSkipSource(ctx, -1); - break; - } - *quant += data; - } - return (i); -} - -static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value) -{ - int32_t buffer; - int i = 1, j; - buffer = value & 0x7F; - while (value >>= 7) - { - buffer <<= 8; - buffer |= ((value & 0x7F) | 0x80); - i++; - } - for (j = 0; j < i; j++) - { - XMIToMidiWrite1(ctx, buffer & 0xFF); - buffer >>= 8; - } - - return (i); -} - -/* Converts Events - * - * Source is at the first data byte - * size 1 is single data byte - * size 2 is dual data byte - * size 3 is XMI Note on - * Returns bytes converted */ -static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, - const int size) -{ - uint32_t delta = 0; - int32_t data; - XMIToMidiEvent *prev; - int i; - - data = XMIToMidiRead1(ctx); - - /*HACK!*/ - if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) - { - data = 32; /*Change XMI 114 controller into XG bank*/ - } - - /* Bank changes are handled here */ - if ((status >> 4) == 0xB && data == 0) - { - data = XMIToMidiRead1(ctx); - - ctx->bank127[status & 0xF] = 0; - - if (ctx->convert_type == kXMIConvertMt32ToGm || ctx->convert_type == kXMIConvertMt32ToGs || - ctx->convert_type == kXMIConvertMt32ToGs127 || - (ctx->convert_type == kXMIConvertMt32ToGs127Drum && (status & 0xF) == 9)) - return (2); - - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = status; - ctx->current->data[0] = 0; - ctx->current->data[1] = data == 127 ? 0 : data; /*HACK:*/ - - if (ctx->convert_type == kXMIConvertGs127ToGs && data == 127) - ctx->bank127[status & 0xF] = 1; - - return (2); - } - - /* Handling for patch change mt32 conversion, probably should go elsewhere - */ - if ((status >> 4) == 0xC && (status & 0xF) != 9 && ctx->convert_type != kXMINoConversion) - { - if (ctx->convert_type == kXMIConvertMt32ToGm) - { - data = Mt32ToGmMap[data]; - } - else if ((ctx->convert_type == kXMIConvertGs127ToGs && ctx->bank127[status & 0xF]) || - ctx->convert_type == kXMIConvertMt32ToGs || ctx->convert_type == kXMIConvertMt32ToGs127Drum) - { - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = 0xB0 | (status & 0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = Mt32ToGsMap[data * 2 + 1]; - - data = Mt32ToGsMap[data * 2]; - } - else if (ctx->convert_type == kXMIConvertMt32ToGs127) - { - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = 0xB0 | (status & 0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - } - /* Drum track handling */ - else if ((status >> 4) == 0xC && (status & 0xF) == 9 && - (ctx->convert_type == kXMIConvertMt32ToGs127Drum || ctx->convert_type == kXMIConvertMt32ToGs127)) - { - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = status; - - ctx->current->data[0] = data; - - if (size == 1) - return (1); - - ctx->current->data[1] = XMIToMidiRead1(ctx); - - if (size == 2) - return (2); - - /* XMI Note On handling */ - prev = ctx->current; - i = XMIToMidiGetVlq(ctx, &delta); - XMIToMidiCreateNewEvent(ctx, time + delta * 3); - - ctx->current->status = status; - ctx->current->data[0] = data; - ctx->current->data[1] = 0; - ctx->current = prev; - - return (i + 2); -} - -/* Simple routine to convert system messages */ -static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, - const uint8_t status) -{ - int32_t i = 0; - - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = status; - - /* Handling of Meta events */ - if (status == 0xFF) - { - ctx->current->data[0] = XMIToMidiRead1(ctx); - i++; - } - - i += XMIToMidiGetVlq(ctx, &ctx->current->len); - - if (!ctx->current->len) - return (i); - - ctx->current->buffer = (uint8_t *)malloc(sizeof(uint8_t) * ctx->current->len); - XMIToMidiCopy(ctx, (char *)ctx->current->buffer, ctx->current->len); - - return (i + ctx->current->len); -} - -/* XMIDI and Midi to List - * Returns XMIDI PPQN */ -static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn) -{ - int32_t time = 0; - uint32_t data; - int32_t end = 0; - int32_t tempo = 500000; - int32_t tempo_set = 0; - uint32_t status = 0; - uint32_t file_size = XMIToMidiGetSourceSize(ctx); - uint32_t begin = XMIToMidiGetSourcePosition(ctx); - - /* Set Drum track to correct setting if required */ - if (ctx->convert_type == kXMIConvertMt32ToGs127) - { - XMIToMidiCreateNewEvent(ctx, 0); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - while (!end && XMIToMidiGetSourcePosition(ctx) < file_size) - { - uint32_t offset = XMIToMidiGetSourcePosition(ctx) - begin; - - /* search for branch to this offset */ - for (unsigned i = 0, n = rbrn->count; i < n; ++i) - { - if (offset == rbrn->offset[i]) - { - unsigned id = rbrn->id[i]; - - XMIToMidiCreateNewEvent(ctx, time); - - uint8_t *marker = (uint8_t *)malloc(sizeof(uint8_t) * 8); - memcpy(marker, ":XBRN:", 6); - const char hex[] = "0123456789ABCDEF"; - marker[6] = hex[id >> 4]; - marker[7] = hex[id & 15]; - - ctx->current->status = 0xFF; - ctx->current->data[0] = 0x06; - ctx->current->len = 8; - - ctx->current->buffer = marker; - } - } - - XMIToMidiGetVlq2(ctx, &data); - time += data * 3; - - status = XMIToMidiRead1(ctx); - - switch (status >> 4) - { - case kXMIStatusNoteOn: - XMIToMidiConvertEvent(ctx, time, status, 3); - break; - - /* 2 byte data */ - case kXMIStatusNoteOff: - case kXMIStatusAftertouch: - case kXMIStatusController: - case kXMIStatusPitchWheel: - XMIToMidiConvertEvent(ctx, time, status, 2); - break; - - /* 1 byte data */ - case kXMIStatusProgramChange: - case kXMIStatusPressure: - XMIToMidiConvertEvent(ctx, time, status, 1); - break; - - case kXMIStatusSysex: - if (status == 0xFF) - { - int32_t pos = XMIToMidiGetSourcePosition(ctx); - uint32_t dat = XMIToMidiRead1(ctx); - - if (dat == 0x2F) /* End */ - end = 1; - else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ - { - XMIToMidiSkipSource(ctx, 1); - tempo = XMIToMidiRead1(ctx) << 16; - tempo += XMIToMidiRead1(ctx) << 8; - tempo += XMIToMidiRead1(ctx); - tempo *= 3; - tempo_set = 1; - } - else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ - { - XMIToMidiGetVlq(ctx, &dat); - XMIToMidiSkipSource(ctx, dat); - break; - } - - XMIToMidiSeekSource(ctx, pos); - } - XMIToMidiConvertSystemMessage(ctx, time, status); - break; - - default: - break; - } - } - return ((tempo * 3) / 25000); -} - -/* Converts and event list to a MidiTrack - * Returns bytes of the array - * buf can be nullptr */ -static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist) -{ - int32_t time = 0; - XMIToMidiEvent *event; - uint32_t delta; - uint8_t last_status = 0; - uint32_t i = 8; - uint32_t j; - uint32_t size_pos, cur_pos; - int end = 0; - - XMIToMidiWrite1(ctx, 'M'); - XMIToMidiWrite1(ctx, 'T'); - XMIToMidiWrite1(ctx, 'r'); - XMIToMidiWrite1(ctx, 'k'); - - size_pos = XMIToMidiGetDestinationPosition(ctx); - XMIToMidiSkipDestination(ctx, 4); - - for (event = mlist; event && !end; event = event->next) - { - delta = (event->time - time); - time = event->time; - - i += XMIToMidiPutVlq(ctx, delta); - - if ((event->status != last_status) || (event->status >= 0xF0)) - { - XMIToMidiWrite1(ctx, event->status); - i++; - } - - last_status = event->status; - - switch (event->status >> 4) - { - /* 2 bytes data - * Note off, Note on, Aftertouch, Controller and Pitch Wheel */ - case 0x8: - case 0x9: - case 0xA: - case 0xB: - case 0xE: - XMIToMidiWrite1(ctx, event->data[0]); - XMIToMidiWrite1(ctx, event->data[1]); - i += 2; - break; - - /* 1 bytes data - * Program Change and Channel Pressure */ - case 0xC: - case 0xD: - XMIToMidiWrite1(ctx, event->data[0]); - i++; - break; - - /* Variable length - * SysEx */ - case 0xF: - if (event->status == 0xFF) - { - if (event->data[0] == 0x2f) - end = 1; - XMIToMidiWrite1(ctx, event->data[0]); - i++; - } - i += XMIToMidiPutVlq(ctx, event->len); - if (event->len) - { - for (j = 0; j < event->len; j++) - { - XMIToMidiWrite1(ctx, event->buffer[j]); - i++; - } - } - break; - - /* Never occur */ - default: - /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ - break; - } - } - - cur_pos = XMIToMidiGetDestinationPosition(ctx); - XMIToMidiSeekDestination(ctx, size_pos); - XMIToMidiWrite4(ctx, i - 8); - XMIToMidiSeekDestination(ctx, cur_pos); - - return (i); -} - -/* Assumes correct xmidi */ -static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx) -{ - uint32_t num = 0; - signed short ppqn; - uint32_t len = 0; - int32_t begin; - char buf[32]; - uint32_t branch[128]; - - /* clear branch points */ - for (unsigned i = 0; i < 128; ++i) - branch[i] = ~0u; - - while (XMIToMidiGetSourcePosition(ctx) < XMIToMidiGetSourceSize(ctx) && num != ctx->info.tracks) - { - /* Read first 4 bytes of name */ - XMIToMidiCopy(ctx, buf, 4); - len = XMIToMidiRead4(ctx); - - /* Skip the FORM entries */ - if (!memcmp(buf, "FORM", 4)) - { - XMIToMidiSkipSource(ctx, 4); - XMIToMidiCopy(ctx, buf, 4); - len = XMIToMidiRead4(ctx); - } - - if (!memcmp(buf, "RBRN", 4)) - { - begin = XMIToMidiGetSourcePosition(ctx); - uint32_t count; - - if (len < 2) - { - /* insufficient data */ - goto rbrn_nodata; - } - - count = XMIToMidiRead2(ctx); - if (len - 2 < 6 * count) - { - /* insufficient data */ - goto rbrn_nodata; - } - - for (uint32_t i = 0; i < count; ++i) - { - /* read branch point as byte offset */ - uint32_t ctlvalue = XMIToMidiRead2(ctx); - uint32_t evtoffset = XMIToMidiRead4LittleEndian(ctx); - if (ctlvalue < 128) - branch[ctlvalue] = evtoffset; - } - - rbrn_nodata: - XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); - continue; - } - - if (memcmp(buf, "EVNT", 4)) - { - XMIToMidiSkipSource(ctx, (len + 1) & ~1); - continue; - } - - ctx->list = nullptr; - begin = XMIToMidiGetSourcePosition(ctx); - - /* Rearrange branches as structure */ - XMIToMidiBranch rbrn; - rbrn.count = 0; - for (unsigned i = 0; i < 128; ++i) - { - if (branch[i] != ~0u) - { - unsigned index = rbrn.count; - rbrn.id[index] = i; - rbrn.offset[index] = branch[i]; - rbrn.count = index + 1; - } - } - - /* Convert it */ - if ((ppqn = XMIToMidiConvertFiletoList(ctx, &rbrn)) == 0) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, nullptr, - * 0);*/ - break; - } - ctx->timing[num] = ppqn; - ctx->events[num] = ctx->list; - - /* Increment Counter */ - num++; - - /* go to start of next track */ - XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); - - /* clear branch points */ - for (unsigned i = 0; i < 128; ++i) - branch[i] = ~0u; - } - - /* Return how many were converted */ - return (num); -} - -static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx) -{ - uint32_t i; - uint32_t start; - uint32_t len; - uint32_t chunk_len; - uint32_t file_size; - char buf[32]; - - file_size = XMIToMidiGetSourceSize(ctx); - if (XMIToMidiGetSourcePosition(ctx) + 8 > file_size) - { - badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too - short)", 0);*/ - return (-1); - } - - /* Read first 4 bytes of header */ - XMIToMidiCopy(ctx, buf, 4); - - /* Could be XMIDI */ - if (!memcmp(buf, "FORM", 4)) - { - /* Read length of */ - len = XMIToMidiRead4(ctx); - - start = XMIToMidiGetSourcePosition(ctx); - if (start + 4 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - /* XDIRless XMIDI, we can handle them here. */ - if (!memcmp(buf, "XMID", 4)) - { - /*_WM_DEBUG_MSG("Warning: XMIDI without XDIR");*/ - ctx->info.tracks = 1; - } - /* Not an XMIDI that we recognise */ - else if (memcmp(buf, "XDIR", 4)) - { - goto badfile; - } - else - { /* Seems Valid */ - ctx->info.tracks = 0; - - for (i = 4; i < len; i++) - { - /* check too short files */ - if (XMIToMidiGetSourcePosition(ctx) + 10 > file_size) - break; - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - /* Read length of chunk */ - chunk_len = XMIToMidiRead4(ctx); - - /* Add eight bytes */ - i += 8; - - if (memcmp(buf, "INFO", 4)) - { - /* Must align */ - XMIToMidiSkipSource(ctx, (chunk_len + 1) & ~1); - i += (chunk_len + 1) & ~1; - continue; - } - - /* Must be at least 2 bytes long */ - if (chunk_len < 2) - break; - - ctx->info.tracks = XMIToMidiRead2(ctx); - break; - } - - /* Didn't get to fill the header */ - if (ctx->info.tracks == 0) - { - goto badfile; - } - - /* Ok now to start part 2 - * Goto the right place */ - XMIToMidiSeekSource(ctx, start + ((len + 1) & ~1)); - if (XMIToMidiGetSourcePosition(ctx) + 12 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - if (memcmp(buf, "CAT ", 4)) - { - /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found - \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ - return (-1); - } - - /* Now read length of this track */ - XMIToMidiRead4(ctx); - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - if (memcmp(buf, "XMID", 4)) - { - /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found - \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ - return (-1); - } - - /* Valid XMID */ - ctx->datastart = XMIToMidiGetSourcePosition(ctx); - return (0); - } - } - - return (-1); -} - -static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber) -{ - uint32_t i; - - ctx->events = (XMIToMidiEvent **)calloc(ctx->info.tracks, sizeof(XMIToMidiEvent *)); - ctx->timing = (int16_t *)calloc(ctx->info.tracks, sizeof(int16_t)); - /* type-2 for multi-tracks, type-0 otherwise */ - ctx->info.type = (ctx->info.tracks > 1 && (dstTrackNumber < 0 || ctx->info.tracks >= dstTrackNumber)) ? 2 : 0; - - XMIToMidiSeekSource(ctx, ctx->datastart); - i = XMIToMidiExtractTracksFromXMI(ctx); - - if (i != ctx->info.tracks) - { - /*_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from - XMIDI", ctx->info.tracks, i);*/ - return (-1); - } - - return (0); -} diff --git a/source_files/edge/midi_sequencer.h b/source_files/edge/midi_sequencer.h deleted file mode 100644 index 53667ccc5..000000000 --- a/source_files/edge/midi_sequencer.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * BW_Midi_Sequencer - MIDI Sequencer for C++ - * - * Copyright (c) 2015-2022 Vitaly Novichkov - * Copyright (c) 2024 The EDGE Team. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE - * OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#pragma once - -#include -#include - -/*! Raw MIDI event hook */ -typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, - size_t len); -/*! PCM render */ -typedef void (*PcmRender)(void *userdata, uint8_t *stream, size_t length); -/*! Library internal debug messages */ -typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); -/*! Loop Start event hook */ -typedef void (*LoopStartHook)(void *userdata); -/*! Loop Start event hook */ -typedef void (*LoopEndHook)(void *userdata); -typedef void (*SongStartHook)(void *userdata); - -/*! Note-On MIDI event */ -typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); -/*! Note-Off MIDI event */ -typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); -/*! Note-Off MIDI event with a velocity */ -typedef void (*RtNoteOffVel)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); -/*! Note aftertouch MIDI event */ -typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); -/*! Channel aftertouch MIDI event */ -typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); -/*! Controller change MIDI event */ -typedef void (*RtControllerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); -/*! Patch change MIDI event */ -typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); -/*! Pitch bend MIDI event */ -typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); -/*! System Exclusive MIDI event */ -typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); -/*! Meta event hook */ -typedef void (*MetaEventHook)(void *userdata, uint8_t type, const uint8_t *data, size_t len); -/*! Device Switch MIDI event */ -typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); -/*! Get the channels offset for current MIDI device */ -typedef size_t (*RtCurrentDevice)(void *userdata, size_t track); -/*! [Non-Standard] Pass raw OPL3 data to the chip (when playing IMF files) - */ -typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); - -/** - \brief Real-Time MIDI interface between Sequencer and the Synthesizer - */ -struct MidiRealTimeInterface -{ - /*! MIDI event hook which catches all MIDI events */ - RawEventHook onEvent; - /*! User data which will be passed through On-Event hook */ - void *onEvent_userdata; - - /*! PCM render hook which catches passing of loop start point */ - PcmRender onPcmRender; - /*! User data which will be passed through On-PCM-render hook */ - void *onPcmRender_userdata; - - /*! Sample rate */ - uint32_t pcmSampleRate; - - /*! Size of one sample in bytes */ - uint32_t pcmFrameSize; - - /*! Debug message hook */ - DebugMessageHook onDebugMessage; - /*! User data which will be passed through Debug Message hook */ - void *onDebugMessage_userdata; - - /*! Loop start hook which catches passing of loop start point */ - LoopStartHook onloopStart; - /*! User data which will be passed through On-LoopStart hook */ - void *onloopStart_userdata; - - /*! Loop start hook which catches passing of loop start point */ - LoopEndHook onloopEnd; - /*! User data which will be passed through On-LoopStart hook */ - void *onloopEnd_userdata; - - /*! Song start hook which is calling when starting playing song at begin - */ - SongStartHook onSongStart; - /*! User data which will be passed through On-SongStart hook */ - void *onSongStart_userdata; - - /*! MIDI Run Time event calls user data */ - void *rtUserData; - - /*************************************************** - * Standard MIDI events. All of them are required! * - ***************************************************/ - - /*! Note-On MIDI event hook */ - RtNoteOn rt_noteOn; - /*! Note-Off MIDI event hook */ - RtNoteOff rt_noteOff; - - /*! Note-Off MIDI event hook with a velocity */ - RtNoteOffVel rt_noteOffVel; - - /*! Note aftertouch MIDI event hook */ - RtNoteAfterTouch rt_noteAfterTouch; - - /*! Channel aftertouch MIDI event hook */ - RtChannelAfterTouch rt_channelAfterTouch; - - /*! Controller change MIDI event hook */ - RtControllerChange rt_controllerChange; - - /*! Patch change MIDI event hook */ - RtPatchChange rt_patchChange; - - /*! Pitch bend MIDI event hook */ - RtPitchBend rt_pitchBend; - - /*! System Exclusive MIDI event hook */ - RtSysEx rt_systemExclusive; - - /******************* - * Optional events * - *******************/ - - /*! Meta event hook which catches all meta events */ - MetaEventHook rt_metaEvent; - - /*! Device Switch MIDI event hook */ - RtDeviceSwitch rt_deviceSwitch; - - /*! Get the channels offset for current MIDI device hook. Returms - * multiple to 16 value. */ - RtCurrentDevice rt_currentDevice; - - /****************************************** - * NonStandard events. There are optional * - ******************************************/ - /*! [Non-Standard] Pass raw OPL3 data to the chip hook */ - RtRawOPL rt_rawOPL; -}; \ No newline at end of file diff --git a/source_files/edge/midi_sequencer.hpp b/source_files/edge/midi_sequencer.hpp deleted file mode 100644 index 45e682ef4..000000000 --- a/source_files/edge/midi_sequencer.hpp +++ /dev/null @@ -1,909 +0,0 @@ -/* - * BW_Midi_Sequencer - MIDI Sequencer for C++ - * - * Copyright (c) 2015-2022 Vitaly Novichkov - * Copyright (c) 2024 The EDGE Team. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE - * OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#pragma once - -#include -#include -#include - -#include "epi_file.h" -#include "midi_sequencer.h" - -// MidiFraction is a stripped down version of -// Bisqwit's Fraction class with the following -// copyright: -/* - * Fraction number handling. - * Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/) - * - * The license of this file is in Public Domain: - * https://bisqwit.iki.fi/src/index.html - * - * "... and orphan source code files are copyrighted public domain." - */ - -class MidiFraction -{ - uint64_t num1_, num2_; - void Optim(); - - public: - MidiFraction() : num1_(0), num2_(1) - { - } - MidiFraction(uint64_t value) : num1_(value), num2_(1) - { - } - MidiFraction(uint64_t n, uint64_t d) : num1_(n), num2_(d) - { - } - inline double Value() const - { - return Nom() / (double)Denom(); - } - MidiFraction &operator*=(const MidiFraction &b) - { - num1_ *= b.Nom(); - num2_ *= b.Denom(); - Optim(); - return *this; - } - MidiFraction operator*(const MidiFraction &b) const - { - MidiFraction tmp(*this); - tmp *= b; - return tmp; - } - const uint64_t &Nom() const - { - return num1_; - } - const uint64_t &Denom() const - { - return num2_; - } -}; - -void MidiFraction::Optim() -{ - /* Euclidean algorithm */ - uint64_t n1, n2, nn1, nn2; - - nn1 = num1_; - nn2 = num2_; - - if (nn1 < nn2) - n1 = num1_, n2 = num2_; - else - n1 = num2_, n2 = num1_; - - if (!num1_) - { - num2_ = 1; - return; - } - for (;;) - { - uint64_t tmp = n2 % n1; - if (!tmp) - break; - n2 = n1; - n1 = tmp; - } - num1_ /= n1; - num2_ /= n1; -} - -MidiFraction operator*(const uint64_t bla, const MidiFraction &b) -{ - return MidiFraction(bla) * b; -} - -class MidiSequencer -{ - /** - * @brief MIDI Event utility container - */ - struct MidiEvent - { - /** - * @brief Main MIDI event types - */ - enum Types - { - //! Unknown event - kUnknown = 0x00, - //! Note-Off event - kNoteOff = 0x08, // size == 2 - //! Note-On event - kNoteOn = 0x09, // size == 2 - //! Note After-Touch event - kNoteTouch = 0x0A, // size == 2 - //! Controller change event - kControlChange = 0x0B, // size == 2 - //! Patch change event - kPatchChange = 0x0C, // size == 1 - //! Channel After-Touch event - kChannelAftertouch = 0x0D, // size == 1 - //! Pitch-bend change event - kPitchWheel = 0x0E, // size == 2 - - //! System Exclusive message, type 1 - kSysex = 0xF0, // size == len - //! Sys Com Song Position Pntr [LSB, MSB] - kSysComSongPositionPointer = 0xF2, // size == 2 - //! Sys Com Song Select(Song #) [0-127] - kSysComSongSelect = 0xF3, // size == 1 - //! System Exclusive message, type 2 - kSysex2 = 0xF7, // size == len - //! Special event - kSpecial = 0xFF - }; - /** - * @brief Special MIDI event sub-types - */ - enum SubTypes - { - //! Sequension number - kSequensionNumber = 0x00, // size == 2 - //! Text label - kText = 0x01, // size == len - //! Copyright notice - kCopyright = 0x02, // size == len - //! Sequence track title - kSequenceTrackTitle = 0x03, // size == len - //! Instrument title - kInstrumentTitle = 0x04, // size == len - //! Lyrics text fragment - kLyrics = 0x05, // size == len - //! MIDI Marker - kMarker = 0x06, // size == len - //! Cue Point - kCuePoint = 0x07, // size == len - //! [Non-Standard] Device Switch - kDeviceSwitch = 0x09, // size == len - //! MIDI Channel prefix - kMidiChannelPrefix = 0x20, // size == 1 - - //! End of Track event - kEndTrack = 0x2F, // size == 0 - //! Tempo change event - kTempoChange = 0x51, // size == 3 - //! SMPTE offset - kSmpteOffset = 0x54, // size == 5 - //! Time signature - kTimeSignature = 0x55, // size == 4 - //! Key signature - kKeySignature = 0x59, // size == 2 - //! Sequencer specs - kSequencerSpec = 0x7F, // size == len - - /* Non-standard, internal ADLMIDI usage only */ - //! [Non-Standard] Loop Start point - kLoopStart = 0xE1, // size == 0 - //! [Non-Standard] Loop End point - kLoopEnd = 0xE2, // size == 0 - //! [Non-Standard] Raw OPL data - kRawOPL = 0xE3, // size == 0 - - //! [Non-Standard] Loop Start point with support of multi-loops - kLoopStackBegin = 0xE4, // size == 1 - //! [Non-Standard] Loop End point with - //! support of multi-loops - kLoopStackEnd = 0xE5, // size == 0 - //! [Non-Standard] Loop End point with - //! support of multi-loops - kLoopStackBreak = 0xE6, // size == 0 - //! [Non-Standard] Callback Trigger - kCallbackTrigger = 0xE7, // size == 1 - - // Built-in hooks - kSongBeginHook = 0x101 - }; - //! Main type of event - uint_fast16_t type = kUnknown; - //! Sub-type of the event - uint_fast16_t sub_type = kUnknown; - //! Targeted MIDI channel - uint_fast16_t channel = 0; - //! Is valid event - uint_fast16_t is_valid = 1; - //! Reserved 5 bytes padding - uint_fast16_t padding[4]; - //! Absolute tick position (Used for the tempo calculation only) - uint64_t absolute_tick_position = 0; - //! Raw data of this event - std::vector data; - }; - - /** - * @brief A track position event contains a chain of MIDI events until next - * delay value - * - * Created with purpose to sort events by type in the same position - * (for example, to keep controllers always first than note on events or - * lower than note-off events) - */ - class MidiTrackRow - { - public: - MidiTrackRow(); - //! Clear MIDI row data - void Clear(); - //! Absolute time position in seconds - double time_; - //! Delay to next event in ticks - uint64_t delay_; - //! Absolute position in ticks - uint64_t absolute_position_; - //! Delay to next event in seconds - double time_delay_; - //! List of MIDI events in the current row - std::vector events_; - /** - * @brief Sort events in this position - * @param note_states Buffer of currently pressed/released note keys in - * the track - */ - void SortEvents(bool *note_states = nullptr); - }; - - /** - * @brief Tempo change point entry. Used in the MIDI data building function - * only. - */ - struct TempoChangePoint - { - uint64_t absolute_position; - MidiFraction tempo; - }; - - /** - * @brief Song position context - */ - struct Position - { - //! Was track began playing - bool began = false; - //! Reserved - char padding[7] = {0, 0, 0, 0, 0, 0, 0}; - //! Waiting time before next event in seconds - double wait = 0.0; - //! Absolute time position on the track in seconds - double absolute_time_position = 0.0; - //! Track information - struct TrackInfo - { - //! Delay to next event in a track - uint64_t delay = 0; - //! Last handled event type - int32_t lastHandledEvent = 0; - //! Reserved - char padding2[4]; - //! MIDI Events queue position iterator - std::list::iterator pos; - }; - std::vector track; - }; - - //! MIDI Output interface context - const MidiRealTimeInterface *midi_output_interface_; - - /** - * @brief Prepare internal events storage for track data building - * @param track_count Count of tracks - */ - void BuildSMFSetupReset(size_t track_count); - - /** - * @brief Build MIDI track data from the raw track data storage - * @return true if everything successfully processed, or false on any error - */ - bool BuildSMFTrackData(const std::vector> &track_data); - - /** - * @brief Build the time line from off loaded events - * @param tempos Pre-collected list of tempo events - * @param loop_start_ticks Global loop start tick (give zero if no global - * loop presented) - * @param loop_end_ticks Global loop end tick (give zero if no global loop - * presented) - */ - void BuildTimeLine(const std::vector &tempos, uint64_t loop_start_ticks = 0, - uint64_t loop_end_ticks = 0); - - /** - * @brief Parse one event from raw MIDI track stream - * @param [_inout] ptr pointer to pointer to current position on the raw - * data track - * @param [_in] end address to end of raw track data, needed to validate - * position and size - * @param [_inout] status status of the track processing - * @return Parsed MIDI event entry - */ - MidiEvent ParseEvent(const uint8_t **ptr, const uint8_t *end, int &status); - - /** - * @brief Process MIDI events on the current tick moment - * @param is_seek is a seeking process - * @return returns false on reaching end of the song - */ - bool ProcessEvents(bool is_seek = false); - - /** - * @brief Handle one event from the chain - * @param tk MIDI track - * @param evt MIDI event entry - * @param status Recent event type, -1 returned when end of track event was - * handled. - */ - void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status); - - public: - /** - * @brief MIDI marker entry - */ - struct MidiMarkerEntry - { - //! Label - std::string label; - //! Position time in seconds - double position_time; - //! Position time in MIDI ticks - uint64_t position_ticks; - }; - - /** - * @brief The FileFormat enum - */ - enum FileFormat - { - //! MIDI format - kFormatMidi, - //! Id-Software Music File - kFormatIMF, - //! EA-MUS format - kFormatRSXX, - //! AIL's XMIDI format (act same as MIDI, but with exceptions) - kFormatXMidi - }; - - /** - * @brief Format of loop points implemented by CC events - */ - enum LoopFormat - { - kLoopDefault, - kLoopRpgMaker = 1, - kLoopEMidi, - kLoopHmi - }; - - private: - //! Music file format type. MIDI is default. - FileFormat midi_format_; - //! SMF format identifier. - unsigned midi_smf_format_; - //! Loop points format - LoopFormat midi_loop_format_; - - //! Current position - Position midi_current_position_; - //! Track begin position - Position midi_track_begin_position_; - //! Loop start point - Position midi_loop_begin_position_; - - //! Is looping enabled or not - bool midi_loop_enabled_; - //! Don't process loop: trigger hooks only if they are set - bool midi_loop_hooks_only_; - - //! Full song length in seconds - double midi_full_song_time_length_; - //! Delay after song playd before rejecting the output stream requests - double midi_post_song_wait_delay_; - - //! Global loop start time - double midi_loop_start_time_; - //! Global loop end time - double midi_loop_end_time_; - - //! Pre-processed track data storage - std::vector> midi_track_data_; - - //! Title of music - std::string midi_music_title_; - //! Copyright notice of music - std::string midi_music_copyright_; - //! List of track titles - std::vector midi_music_track_titles_; - //! List of MIDI markers - std::vector midi_music_markers_; - - //! Time of one tick - MidiFraction midi_individual_tick_delta_; - //! Current tempo - MidiFraction midi_tempo_; - - //! Tempo multiplier factor - double midi_tempo_multiplier_; - //! Is song at end - bool midi_at_end_; - - //! Set the number of loops limit. Lesser than 0 - loop infinite - int midi_loop_count_; - - //! The number of track of multi-track file (for exmaple, XMI) to load - int midi_load_track_number_; - - //! The XMI-specific list of raw songs, converted into SMF format - std::vector> midi_raw_songs_data_; - - /** - * @brief Loop stack entry - */ - struct LoopStackEntry - { - //! is infinite loop - bool infinity = false; - //! Count of loops left to break. <0 - infinite loop - int loops = 0; - //! Start position snapshot to return back - Position start_position; - //! Loop start tick - uint64_t start = 0; - //! Loop end tick - uint64_t end = 0; - }; - - class LoopState - { - public: - //! Loop start has reached - bool caught_start_; - //! Loop end has reached, reset on handling - bool caught_end_; - - //! Loop start has reached - bool caught_stack_start_; - //! Loop next has reached, reset on handling - bool caught_stack_end_; - //! Loop break has reached, reset on handling - bool caught_stack_break_; - //! Skip next stack loop start event handling - bool skip_stack_start_; - - //! Are loop points invalid? - bool invalid_loop_; /*Loop points are invalid (loopStart after loopEnd - or loopStart and loopEnd are on same place)*/ - - //! Is look got temporarily broken because of post-end seek? - bool temporary_broken_; - - //! How much times the loop should start repeat? For example, if you - //! want to loop song twice, set value 1 - int loops_count_; - - //! how many loops left until finish the song - int loops_left_; - - //! Stack of nested loops - std::vector stack_; - //! Current level on the loop stack (<0 - out of loop, 0++ - the index - //! in the loop stack) - int stack_level_; - - /** - * @brief Reset loop state to initial - */ - void Reset() - { - caught_start_ = false; - caught_end_ = false; - caught_stack_start_ = false; - caught_stack_end_ = false; - caught_stack_break_ = false; - skip_stack_start_ = false; - loops_left_ = loops_count_; - } - - void FullReset() - { - loops_count_ = -1; - Reset(); - invalid_loop_ = false; - temporary_broken_ = false; - stack_.clear(); - stack_level_ = -1; - } - - bool IsStackEnd() - { - if (caught_stack_end_ && (stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) - { - const LoopStackEntry &e = stack_[(size_t)(stack_level_)]; - if (e.infinity || (!e.infinity && e.loops > 0)) - return true; - } - return false; - } - - void StackUp(int count = 1) - { - stack_level_ += count; - } - - void StackDown(int count = 1) - { - stack_level_ -= count; - } - - LoopStackEntry &GetCurrentStack() - { - if ((stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) - return stack_[(size_t)(stack_level_)]; - if (stack_.empty()) - { - LoopStackEntry d; - d.loops = 0; - d.infinity = 0; - d.start = 0; - d.end = 0; - stack_.push_back(d); - } - return stack_[0]; - } - }; - - LoopState midi_loop_; - - //! Whether the nth track has playback disabled - std::vector midi_track_disabled_; - //! Index of solo track, or max for disabled - size_t midi_track_solo_; - //! MIDI channel disable (exception for extra port-prefix-based channels) - bool m_channelDisable[16]; - - /** - * @brief Handler of callback trigger events - * @param userdata Pointer to user data (usually, context of something) - * @param trigger Value of the event which triggered this callback. - * @param track Identifier of the track which triggered this callback. - */ - typedef void (*TriggerHandler)(void *userdata, unsigned trigger, size_t track); - - //! Handler of callback trigger events - TriggerHandler midi_trigger_handler_; - //! User data of callback trigger events - void *midi_trigger_userdata_; - - //! File parsing errors string (adding into midi_error_string_ on aborting - //! of the process) - std::string midi_parsing_errors_string_; - //! Common error string - std::string midi_error_string_; - - class SequencerTime - { - public: - //! Time buffer - double time_rest_; - //! Sample rate - uint32_t sample_rate_; - //! Size of one frame in bytes - uint32_t frame_size_; - //! Minimum possible delay, granuality - double minimum_delay_; - //! Last delay - double delay_; - - void Init() - { - sample_rate_ = 44100; - frame_size_ = 2; - Reset(); - } - - void Reset() - { - time_rest_ = 0.0; - minimum_delay_ = 1.0 / (double)(sample_rate_); - delay_ = 0.0; - } - }; - - SequencerTime midi_time_; - - public: - MidiSequencer(); - virtual ~MidiSequencer(); - - /** - * @brief Sets the RT interface - * @param intrf Pre-Initialized interface structure (pointer will be taken) - */ - void SetInterface(const MidiRealTimeInterface *intrf); - - /** - * @brief Runs ticking in a sync with audio streaming. Use this together - * with onPcmRender hook to easily play MIDI. - * @param stream pointer to the output PCM stream - * @param length length of the buffer in bytes - * @return Count of recorded data in bytes - */ - int PlayStream(uint8_t *stream, size_t length); - - /** - * @brief Returns file format type of currently loaded file - * @return File format type enumeration - */ - FileFormat GetFormat(); - - /** - * @brief Returns the number of tracks - * @return Track count - */ - size_t GetTrackCount() const; - - /** - * @brief Sets whether a track is playing - * @param track Track identifier - * @param enable Whether to enable track playback - * @return true on success, false if there was no such track - */ - bool SetTrackEnabled(size_t track, bool enable); - - /** - * @brief Disable/enable a channel is sounding - * @param channel Channel number from 0 to 15 - * @param enable Enable the channel playback - * @return true on success, false if there was no such channel - */ - bool SetChannelEnabled(size_t channel, bool enable); - - /** - * @brief Enables or disables solo on a track - * @param track Identifier of solo track, or max to disable - */ - void SetSoloTrack(size_t track); - - /** - * @brief Set the song number of a multi-song file (such as XMI) - * @param trackNumber Identifier of the song to load (or -1 to mix all songs - * as one song) - */ - void SetSongNum(int track); - - /** - * @brief Retrive the number of songs in a currently opened file - * @return Number of songs in the file. If 1 or less, means, the file has - * only one song inside. - */ - int GetSongsCount(); - - /** - * @brief Defines a handler for callback trigger events - * @param handler Handler to invoke from the sequencer when triggered, or - * nullptr. - * @param userdata Instance of the library - */ - void SetTriggerHandler(TriggerHandler handler, void *userdata); - - /** - * @brief Get string that describes reason of error - * @return Error string - */ - const std::string &GetErrorString(); - - /** - * @brief Check is loop enabled - * @return true if loop enabled - */ - bool GetLoopEnabled(); - - /** - * @brief Switch loop on/off - * @param enabled Enable loop - */ - void SetLoopEnabled(bool enabled); - - /** - * @brief Get the number of loops set - * @return number of loops or -1 if loop infinite - */ - int GetLoopsCount(); - - /** - * @brief How many times song should loop - * @param loops count or -1 to loop infinite - */ - void SetLoopsCount(int loops); - - /** - * @brief Switch loop hooks-only mode on/off - * @param enabled Don't loop: trigger hooks only without loop - */ - void SetLoopHooksOnly(bool enabled); - - /** - * @brief Get music title - * @return music title string - */ - const std::string &GetMusicTitle(); - - /** - * @brief Get music copyright notice - * @return music copyright notice string - */ - const std::string &GetMusicCopyright(); - - /** - * @brief Get list of track titles - * @return array of track title strings - */ - const std::vector &GetTrackTitles(); - - /** - * @brief Get list of MIDI markers - * @return Array of MIDI marker structures - */ - const std::vector &GetMarkers(); - - /** - * @brief Is position of song at end - * @return true if end of song was reached - */ - bool PositionAtEnd(); - - /** - * @brief Load MIDI file from a memory block - * @param data Pointer to memory block with MIDI data - * @param size Size of source memory block - * @param rate For IMF formats, the proper playback rate in Hz - * @return true if file successfully opened, false on any error - */ - bool LoadMidi(const uint8_t *data, size_t size, uint16_t rate = 0); - - /** - * @brief Load MIDI file by using FileAndMemReader interface - * @param mfr mem_file_c with opened source file - * @param rate For IMF formats, the proper playback rate in Hz - * @return true if file successfully opened, false on any error - */ - bool LoadMidi(epi::MemFile *mfr, uint16_t rate); - - /** - * @brief Periodic tick handler. - * @param s seconds since last call - * @param granularity don't expect intervals smaller than this, in seconds - * @return desired number of seconds until next call - */ - double Tick(double s, double granularity); - - /** - * @brief Change current position to specified time position in seconds - * @param granularity don't expect intervals smaller than this, in seconds - * @param seconds Absolute time position in seconds - * @return desired number of seconds until next call of Tick() - */ - double Seek(double seconds, const double granularity); - - /** - * @brief Gives current time position in seconds - * @return Current time position in seconds - */ - double Tell(); - - /** - * @brief Gives time length of current song in seconds - * @return Time length of current song in seconds - */ - double TimeLength(); - - /** - * @brief Gives loop start time position in seconds - * @return Loop start time position in seconds or -1 if song has no loop - * points - */ - double GetLoopStart(); - - /** - * @brief Gives loop end time position in seconds - * @return Loop end time position in seconds or -1 if song has no loop - * points - */ - double GetLoopEnd(); - - /** - * @brief Return to begin of current song - */ - void Rewind(); - - /** - * @brief Get current tempor multiplier value - * @return - */ - double GetTempoMultiplier(); - - /** - * @brief Set tempo multiplier - * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - - * slower - */ - void SetTempo(double tempo); - - private: - /** - * @brief Load file as Id-software-Music-File (Wolfenstein) - * @param mfr mem_file_c with opened source file - * @param rate For IMF formats, the proper playback rate in Hz - * @return true on successful load - */ - bool ParseIMF(epi::MemFile *mfr, uint16_t rate); - - /** - * @brief Load file as EA MUS - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseRSXX(epi::MemFile *mfr); - - /** - * @brief Load file as GMD/MUS files (ScummVM) - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseGMF(epi::MemFile *mfr); - - /** - * @brief Load file as Standard MIDI file - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseSMF(epi::MemFile *mfr); - - /** - * @brief Load file as RIFF MIDI - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseRMI(epi::MemFile *mfr); - - /** - * @brief Load file as DMX MUS file (Doom) - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseMUS(epi::MemFile *mfr); - - /** - * @brief Load file as AIL eXtended MIdi - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseXMI(epi::MemFile *mfr); -}; \ No newline at end of file diff --git a/source_files/edge/s_cache.cc b/source_files/edge/s_cache.cc index a68faa702..bdb5b7edf 100644 --- a/source_files/edge/s_cache.cc +++ b/source_files/edge/s_cache.cc @@ -40,8 +40,15 @@ #include "m_random.h" #include "p_mobj.h" #include "r_defs.h" +#if EDGE_DOOM_SFX_SUPPORT +#include "s_doom.h" +#endif +#if EDGE_MP3_SUPPORT #include "s_mp3.h" +#endif +#if EDGE_OGG_SUPPORT #include "s_ogg.h" +#endif #include "s_sound.h" #include "s_wav.h" #include "snd_data.h" @@ -63,55 +70,34 @@ static void LoadSilence(SoundData *buf) memset(buf->data_, 0, length * sizeof(int16_t) * 2); } - -static bool LoadDoom(SoundData *buf, const uint8_t *lump, int length) +#if EDGE_DOOM_SFX_SUPPORT +static bool LoadDoom(SoundData *buf, uint8_t *lump, int length) { - buf->frequency_ = lump[2] + (lump[3] << 8); - - if (buf->frequency_ < 8000 || buf->frequency_ > 48000) - LogWarning("Sound Load: weird frequency: %d Hz\n", buf->frequency_); - - if (buf->frequency_ < 4000) - buf->frequency_ = 4000; - - length -= 8; - - if (length <= 0) - return false; - - buf->Allocate(length); - - // convert to signed 16-bit format - const uint8_t *src = lump + 8; - const uint8_t *s_end = src + length; - - int16_t *dest = buf->data_; - int16_t out = 0; - - for (; src < s_end; src++) - { - out = (*src ^ 0x80) << 8; - *dest++ = out; - *dest++ = out; - } - - return true; + return LoadDoomSound(buf, lump, length); } - -static bool LoadWav(SoundData *buf, uint8_t *lump, int length, bool pc_speaker) +static bool LoadPCSpeaker(SoundData *buf, uint8_t *lump, int length) { - return LoadWAVSound(buf, lump, length, pc_speaker); + return LoadPCSpeakerSound(buf, lump, length); } - +#endif +#if EDGE_WAV_SUPPORT +static bool LoadWav(SoundData *buf, uint8_t *lump, int length) +{ + return LoadWAVSound(buf, lump, length); +} +#endif +#if EDGE_OGG_SUPPORT static bool LoadOGG(SoundData *buf, const uint8_t *lump, int length) { return LoadOGGSound(buf, lump, length); } - +#endif +#if EDGE_MP3_SUPPORT static bool LoadMP3(SoundData *buf, const uint8_t *lump, int length) { return LoadMP3Sound(buf, lump, length); } +#endif //---------------------------------------------------------------------------- @@ -232,31 +218,35 @@ static bool DoCacheLoad(SoundEffectDefinition *def, SoundData *buf) switch (fmt) { +#if EDGE_WAV_SUPPORT case kSoundWAV: - OK = LoadWav(buf, data, length, false); + OK = LoadWav(buf, data, length); break; - +#endif +#if EDGE_OGG_SUPPORT case kSoundOGG: OK = LoadOGG(buf, data, length); break; - +#endif +#if EDGE_MP3_SUPPORT case kSoundMP3: OK = LoadMP3(buf, data, length); break; - +#endif +#if EDGE_DOOM_SFX_SUPPORT // Double-check first byte here because pack filename detection could // return kSoundPCSpeaker for either case kSoundPCSpeaker: if (data[0] == 0x3) OK = LoadDoom(buf, data, length); else - OK = LoadWav(buf, data, length, true); + OK = LoadPCSpeaker(buf, data, length); break; case kSoundDoom: OK = LoadDoom(buf, data, length); break; - +#endif default: OK = false; break; diff --git a/source_files/edge/s_doom.cc b/source_files/edge/s_doom.cc new file mode 100644 index 000000000..d52f98038 --- /dev/null +++ b/source_files/edge/s_doom.cc @@ -0,0 +1,139 @@ +//---------------------------------------------------------------------------- +// EDGE Doom/PC Speaker Sound Loader +//---------------------------------------------------------------------------- +// +// Copyright (c) 2024 The EDGE Team. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//---------------------------------------------------------------------------- + +#include "s_doom.h" + +#include "epi.h" +#include "epi_endian.h" +#include "epi_file.h" +#include "epi_filesystem.h" +#include "s_blit.h" +#include "s_cache.h" +#include "snd_gather.h" +#include "w_wad.h" + +extern int sound_device_frequency; + +bool LoadDoomSound(SoundData *buf, uint8_t *data, int length) +{ + buf->frequency_ = data[2] + (data[3] << 8); + + if (buf->frequency_ < 8000 || buf->frequency_ > 48000) + LogWarning("Sound Load: weird frequency: %d Hz\n", buf->frequency_); + + if (buf->frequency_ < 4000) + buf->frequency_ = 4000; + + length -= 8; + + if (length <= 0) + return false; + + buf->Allocate(length); + + // convert to signed 16-bit format + const uint8_t *src = data + 8; + const uint8_t *s_end = src + length; + + int16_t *dest = buf->data_; + int16_t out = 0; + + for (; src < s_end; src++) + { + out = (*src ^ 0x80) << 8; + *dest++ = out; + *dest++ = out; + } + + return true; +} + +static constexpr int kPCInterruptTimer = 1193181; +static constexpr uint8_t kPCVolume = 20; +static constexpr uint8_t kPCRate = 140; +static constexpr uint16_t kFrequencyTable[128] = { + 0, 6818, 6628, 6449, 6279, 6087, 5906, 5736, 5575, 5423, 5279, 5120, 4971, 4830, 4697, 4554, + 4435, 4307, 4186, 4058, 3950, 3836, 3728, 3615, 3519, 3418, 3323, 3224, 3131, 3043, 2960, 2875, + 2794, 2711, 2633, 2560, 2485, 2415, 2348, 2281, 2213, 2153, 2089, 2032, 1975, 1918, 1864, 1810, + 1757, 1709, 1659, 1612, 1565, 1521, 1478, 1435, 1395, 1355, 1316, 1280, 1242, 1207, 1173, 1140, + 1107, 1075, 1045, 1015, 986, 959, 931, 905, 879, 854, 829, 806, 783, 760, 739, 718, + 697, 677, 658, 640, 621, 604, 586, 570, 553, 538, 522, 507, 493, 479, 465, 452, + 439, 427, 415, 403, 391, 380, 369, 359, 348, 339, 329, 319, 310, 302, 293, 285, + 276, 269, 261, 253, 246, 239, 232, 226, 219, 213, 207, 201, 195, 190, 184, 179}; + +bool LoadPCSpeakerSound(SoundData *buf, uint8_t *data, int length) +{ + if (length < 4) + { + LogWarning("Invalid PC Speaker Sound (too short)\n"); + return false; + } + int sign = -1; + uint32_t tone, i, phase_length, phase_tic = 0; + uint32_t samples_per_byte = sound_device_frequency/kPCRate; + uint16_t zeroed, total_samples; + memcpy(&zeroed, data, 2); + memcpy(&total_samples, data+2, 2); + if (zeroed != 0) + { + LogWarning("Invalid PC Speaker Sound (bad magic number)\n"); + return false; + } + if (total_samples < 4 || total_samples > length - 4) + { + LogWarning("Invalid PC Speaker Sound (bad sample count)\n"); + return false; + } + data += 4; + length -= 4; + buf->Allocate(length * samples_per_byte); + int16_t *dst = buf->data_; + while (length--) + { + if (*data > 128) + { + LogWarning("Invalid PC Speaker Sound (bad tone value %d)\n", *data); + return false; + } + tone = kFrequencyTable[*data++]; + phase_length = (sound_device_frequency*tone)/(2*kPCInterruptTimer); + for (i=0; i= phase_length) + { + sign = -sign; + phase_tic = 0; + } + } else { + phase_tic = 0; + *dst++ = 0; + *dst++ = 0; + } + } + } + buf->frequency_ = sound_device_frequency; + return true; +} + +//--- editor settings --- +// vi:ts=4:sw=4:noexpandtab diff --git a/source_files/edge/s_doom.h b/source_files/edge/s_doom.h new file mode 100644 index 000000000..7b2881bee --- /dev/null +++ b/source_files/edge/s_doom.h @@ -0,0 +1,28 @@ +//---------------------------------------------------------------------------- +// EDGE Doom/PC Speaker Sound Loader (HEADER) +//---------------------------------------------------------------------------- +// +// Copyright (c) 2024 The EDGE Team. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//---------------------------------------------------------------------------- + +#pragma once + +#include "snd_data.h" + +bool LoadDoomSound(SoundData *buf, uint8_t *data, int length); + +bool LoadPCSpeakerSound(SoundData *buf, uint8_t *data, int length); + +//--- editor settings --- +// vi:ts=4:sw=4:noexpandtab diff --git a/source_files/edge/s_emidi.cc b/source_files/edge/s_emidi.cc index 05d929b88..ceb886d99 100644 --- a/source_files/edge/s_emidi.cc +++ b/source_files/edge/s_emidi.cc @@ -29,13 +29,13 @@ #include "i_movie.h" #include "i_system.h" #include "m_misc.h" +#include "s_blit.h" // clang-format off #define MidiFraction EMIDIFraction #define MidiSequencer EMIDISequencer typedef struct MidiRealTimeInterface EMIDIInterface; -#include "midi_sequencer_impl.hpp" +#include "s_midi.h" // clang-format on -#include "s_blit.h" extern int sound_device_frequency; diff --git a/source_files/edge/s_fmm.cc b/source_files/edge/s_fmm.cc index 125c2c74b..926e3f2e8 100644 --- a/source_files/edge/s_fmm.cc +++ b/source_files/edge/s_fmm.cc @@ -29,13 +29,13 @@ #include "i_system.h" #include "m_misc.h" #include "midisynth.hpp" +#include "s_blit.h" // clang-format off #define MidiFraction FMMFraction #define MidiSequencer FMMSequencer typedef struct MidiRealTimeInterface FMMInterface; -#include "midi_sequencer_impl.hpp" +#include "s_midi.h" // clang-format on -#include "s_blit.h" extern int sound_device_frequency; diff --git a/source_files/edge/s_imf.cc b/source_files/edge/s_imf.cc index fa364b033..17ec5b5ec 100644 --- a/source_files/edge/s_imf.cc +++ b/source_files/edge/s_imf.cc @@ -24,17 +24,17 @@ #include "i_movie.h" #include "i_system.h" #include "m_misc.h" -// clang-format off -#define MidiFraction IMFFraction -#define MidiSequencer IMFSequencer -typedef struct MidiRealTimeInterface IMFInterface; -#include "midi_sequencer_impl.hpp" -// clang-format on #include "ddf_playlist.h" #include "epi_str_compare.h" #include "epi_str_util.h" #include "opal.h" #include "s_blit.h" +// clang-format off +#define MidiFraction IMFFraction +#define MidiSequencer IMFSequencer +typedef struct MidiRealTimeInterface IMFInterface; +#include "s_midi.h" +// clang-format on #include "s_music.h" #include "snd_types.h" #include "w_files.h" diff --git a/source_files/edge/midi_sequencer_impl.hpp b/source_files/edge/s_midi.h similarity index 51% rename from source_files/edge/midi_sequencer_impl.hpp rename to source_files/edge/s_midi.h index 3559e18ea..fe82fd923 100644 --- a/source_files/edge/midi_sequencer_impl.hpp +++ b/source_files/edge/s_midi.h @@ -32,14 +32,2891 @@ #include // std::copy #include // std::back_inserter +#include #include #include -#include "midi_convert_mus.hpp" -#include "midi_convert_xmi.hpp" -#include "midi_sequencer.hpp" #include "stb_sprintf.h" +#if EDGE_MUS_SUPPORT +static constexpr uint8_t kMusFrequency = 140; +static constexpr int kMusTempo = 0x00068A1B; /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ + /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ +static constexpr uint16_t kMusDivision = 0x0101; /* 257 for 140Hz files with a 140MPQN */ + /* 0x0088 -> 136 for 70Hz files with a 140MPQN */ + /* 0x010B -> 267 for 70hz files with a 70MPQN */ + /* 0x01F9 -> 505 for 140hz files with a 70MPQN */ + +/* New + * QLS: MPQN/1000000 = 0.428571 + * TDPS: QLS/PPQN = 0.428571/136 = 0.003151257 + * PPQN: 136 + * + * QLS: MPQN/1000000 = 0.428571 + * TDPS: QLS/PPQN = 0.428571/257 = 0.001667591 + * PPQN: 257 + * + * QLS: MPQN/1000000 = 0.857142 + * TDPS: QLS/PPQN = 0.857142/267 = 0.00321027 + * PPQN: 267 + * + * QLS: MPQN/1000000 = 0.857142 + * TDPS: QLS/PPQN = 0.857142/505 = 0.001697311 + * PPQN: 505 + * + * Old + * QLS: MPQN/1000000 = 1.745673 + * TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick) + * PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN + * = 1.745673) (PPQN = 89.000001682) + * + */ + +enum MusEvent +{ + kMusEventKeyOff = 0, + kMusEventKeyOn = 1, + kMusEventPitchWheel = 2, + kMusEventChannelMode = 3, + kMusEventControllerChange = 4, + kMusEventEnd = 6, +}; + +static constexpr uint8_t kMusMidiMaxChannels = 16; + +static constexpr char kMusHeader[] = {'M', 'U', 'S', 0x1A}; + +static constexpr uint8_t kMusToMidiMap[] = { + /* MIDI Number Description */ + 0, /* 0 program change */ + 0, /* 1 bank selection */ + 0x01, /* 2 Modulation pot (frequency vibrato depth) */ + 0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */ + 0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */ + 0x0B, /* 5 Expression pot */ + 0x5B, /* 6 Reverb depth */ + 0x5D, /* 7 Chorus depth */ + 0x40, /* 8 Sustain pedal */ + 0x43, /* 9 Soft pedal */ + 0x78, /* 10 All sounds off */ + 0x7B, /* 11 All notes off */ + 0x7E, /* 12 Mono (use numchannels + 1) */ + 0x7F, /* 13 Poly */ + 0x79, /* 14 reset all controllers */ +}; + +struct MusHeader +{ + char ID[4]; /* identifier: "MUS" 0x1A */ + uint16_t scoreLen; + uint16_t scoreStart; + uint16_t channels; /* count of primary channels */ + uint16_t sec_channels; /* count of secondary channels */ + uint16_t instrCnt; +}; + +struct MidiHeaderChunk +{ + char name[4]; + int32_t length; + int16_t format; /* make 0 */ + int16_t ntracks; /* make 1 */ + int16_t division; /* 0xe250 ?? */ +}; + +struct MidiTrackChunk +{ + char name[4]; + int32_t length; +}; + +struct MusConversionContext +{ + uint8_t *src, *src_ptr; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; +}; + +static constexpr uint16_t kMusDestinationChunkSize = 8192; +static void MusToMidiResizeDestination(struct MusConversionContext *ctx) +{ + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kMusDestinationChunkSize); + ctx->dstsize += kMusDestinationChunkSize; + ctx->dstrem += kMusDestinationChunkSize; + ctx->dst_ptr = ctx->dst + pos; +} + +static void MusToMidiWrite1(struct MusConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + MusToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void MusToMidiWrite2(struct MusConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + MusToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (val >> 8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void MusToMidiWrite4(struct MusConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + MusToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (uint8_t)((val >> 24) & 0xff); + *ctx->dst_ptr++ = (uint8_t)((val >> 16) & 0xff); + *ctx->dst_ptr++ = (uint8_t)((val >> 8) & 0xff); + *ctx->dst_ptr++ = (uint8_t)((val & 0xff)); + ctx->dstrem -= 4; +} + +static void MusToMidiSeekDestination(struct MusConversionContext *ctx, uint32_t pos) +{ + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + MusToMidiResizeDestination(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void MusToMidiSkipDestination(struct MusConversionContext *ctx, int32_t pos) +{ + size_t newpos; + ctx->dst_ptr += pos; + newpos = ctx->dst_ptr - ctx->dst; + while (ctx->dstsize < newpos) + MusToMidiResizeDestination(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t MusToMidiGetDestinationPosition(struct MusConversionContext *ctx) +{ + return (uint32_t)(ctx->dst_ptr - ctx->dst); +} + +/* writes a variable length integer to a buffer, and returns bytes written */ +static int32_t MusToMidiWriteVariableLength(int32_t value, uint8_t *out) +{ + int32_t buffer, count = 0; + + buffer = value & 0x7f; + while ((value >>= 7) > 0) + { + buffer <<= 8; + buffer += 0x80; + buffer += (value & 0x7f); + } + + while (1) + { + ++count; + *out = (uint8_t)buffer; + ++out; + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + return (count); +} + +#define EDGE_MUS_READ_SHORT(b) ((b)[0] | ((b)[1] << 8)) +#define EDGE_MUS_READ_INT(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) + +static int ConvertMusToMidi(uint8_t *in, uint32_t insize, uint8_t **out, uint32_t *outsize, uint16_t frequency) +{ + struct MusConversionContext ctx; + MusHeader header; + uint8_t *cur, *end; + uint32_t track_size_pos, begin_track_pos, current_pos; + int32_t delta_time; /* Delta time for midi event */ + int temp, ret = -1; + int channel_volume[kMusMidiMaxChannels]; + int channelMap[kMusMidiMaxChannels], currentChannel; + + if (insize < sizeof(MusHeader)) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", + * 0);*/ + return (-1); + } + + if (!frequency) + frequency = kMusFrequency; + + /* read the MUS header and set our location */ + memcpy(header.ID, in, 4); + header.scoreLen = EDGE_MUS_READ_SHORT(&in[4]); + header.scoreStart = EDGE_MUS_READ_SHORT(&in[6]); + header.channels = EDGE_MUS_READ_SHORT(&in[8]); + header.sec_channels = EDGE_MUS_READ_SHORT(&in[10]); + header.instrCnt = EDGE_MUS_READ_SHORT(&in[12]); + + if (memcmp(header.ID, kMusHeader, 4)) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, nullptr, + * 0);*/ + return (-1); + } + if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", + * 0);*/ + return (-1); + } + /* channel #15 should be excluded in the numchannels field: */ + if (header.channels > kMusMidiMaxChannels - 1) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, nullptr, + * 0);*/ + return (-1); + } + + memset(&ctx, 0, sizeof(struct MusConversionContext)); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + + ctx.dst = (uint8_t *)calloc(kMusDestinationChunkSize, sizeof(uint8_t)); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = kMusDestinationChunkSize; + ctx.dstrem = kMusDestinationChunkSize; + + /* Map channel 15 to 9 (percussions) */ + for (temp = 0; temp < kMusMidiMaxChannels; ++temp) + { + channelMap[temp] = -1; + channel_volume[temp] = 0x40; + } + channelMap[15] = 9; + + /* Header is 14 bytes long and add the rest as well */ + MusToMidiWrite1(&ctx, 'M'); + MusToMidiWrite1(&ctx, 'T'); + MusToMidiWrite1(&ctx, 'h'); + MusToMidiWrite1(&ctx, 'd'); + MusToMidiWrite4(&ctx, 6); /* length of header */ + MusToMidiWrite2(&ctx, 0); /* MIDI type (always 0) */ + MusToMidiWrite2(&ctx, 1); /* MUS files only have 1 track */ + MusToMidiWrite2(&ctx, kMusDivision); /* division */ + + /* Write out track header and track length position for later */ + begin_track_pos = MusToMidiGetDestinationPosition(&ctx); + MusToMidiWrite1(&ctx, 'M'); + MusToMidiWrite1(&ctx, 'T'); + MusToMidiWrite1(&ctx, 'r'); + MusToMidiWrite1(&ctx, 'k'); + track_size_pos = MusToMidiGetDestinationPosition(&ctx); + MusToMidiSkipDestination(&ctx, 4); + + /* write tempo: microseconds per quarter note */ + MusToMidiWrite1(&ctx, 0x00); /* delta time */ + MusToMidiWrite1(&ctx, 0xff); /* sys command */ + MusToMidiWrite2(&ctx, 0x5103); /* command - set tempo */ + MusToMidiWrite1(&ctx, kMusTempo & 0x000000ff); + MusToMidiWrite1(&ctx, (kMusTempo & 0x0000ff00) >> 8); + MusToMidiWrite1(&ctx, (kMusTempo & 0x00ff0000) >> 16); + + /* Percussions channel starts out at volume 100 */ + MusToMidiWrite1(&ctx, 0x00); + MusToMidiWrite1(&ctx, 0xB9); + MusToMidiWrite1(&ctx, 0x07); + MusToMidiWrite1(&ctx, 100); + + /* get current position in source, and end of position */ + cur = in + header.scoreStart; + end = cur + header.scoreLen; + + currentChannel = 0; + delta_time = 0; + + /* main loop */ + while (cur < end) + { + /*printf("LOOP DEBUG: %d\r\n",iterator++);*/ + uint8_t channel; + uint8_t event; + uint8_t temp_buffer[32]; /* temp buffer for current iterator */ + uint8_t *out_local = temp_buffer; + uint8_t status, bit1, bit2, bitc = 2; + + /* read in current bit */ + event = *cur++; + channel = (event & 15); /* current channel */ + + /* write variable length delta time */ + out_local += MusToMidiWriteVariableLength(delta_time, out_local); + + /* set all channels to 127 (max) volume */ + if (channelMap[channel] < 0) + { + *out_local++ = 0xB0 + currentChannel; + *out_local++ = 0x07; + *out_local++ = 100; + *out_local++ = 0x00; + channelMap[channel] = currentChannel++; + if (currentChannel == 9) + ++currentChannel; + } + status = channelMap[channel]; + + /* handle events */ + switch ((event & 122) >> 4) + { + case kMusEventKeyOff: + status |= 0x80; + bit1 = *cur++; + bit2 = 0x40; + break; + case kMusEventKeyOn: + status |= 0x90; + bit1 = *cur & 127; + if (*cur++ & 128) /* volume bit? */ + channel_volume[channelMap[channel]] = *cur++; + bit2 = channel_volume[channelMap[channel]]; + break; + case kMusEventPitchWheel: + status |= 0xE0; + bit1 = (*cur & 1) >> 6; + bit2 = (*cur++ >> 1) & 127; + break; + case kMusEventChannelMode: + status |= 0xB0; + if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) + { + /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", + __FUNCTION__, __LINE__, *cur);*/ + goto _end; + } + bit1 = kMusToMidiMap[*cur++]; + bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; + break; + case kMusEventControllerChange: + if (*cur == 0) + { + cur++; + status |= 0xC0; + bit1 = *cur++; + bit2 = 0; /* silence bogus warnings */ + bitc = 1; + } + else + { + status |= 0xB0; + if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) + { + /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", + __FUNCTION__, __LINE__, *cur);*/ + goto _end; + } + bit1 = kMusToMidiMap[*cur++]; + bit2 = *cur++; + } + break; + case kMusEventEnd: /* End */ + status = 0xff; + bit1 = 0x2f; + bit2 = 0x00; + if (cur != end) + { /* should we error here or report-only? */ + /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes", + __FUNCTION__, __LINE__, (long)(cur - end));*/ + } + break; + case 5: /* Unknown */ + case 7: /* Unknown */ + default: /* shouldn't happen */ + /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)", + __FUNCTION__, __LINE__, event);*/ + goto _end; + } + + /* write it out */ + *out_local++ = status; + *out_local++ = bit1; + if (bitc == 2) + *out_local++ = bit2; + + /* write out our temp buffer */ + if (out_local != temp_buffer) + { + if (ctx.dstrem < sizeof(temp_buffer)) + MusToMidiResizeDestination(&ctx); + + memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); + ctx.dst_ptr += out_local - temp_buffer; + ctx.dstrem -= (uint32_t)(out_local - temp_buffer); + } + + if (event & 128) + { + delta_time = 0; + do + { + delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency)); + } while ((*cur++ & 128)); + } + else + { + delta_time = 0; + } + } + + /* write out track length */ + current_pos = MusToMidiGetDestinationPosition(&ctx); + MusToMidiSeekDestination(&ctx, track_size_pos); + MusToMidiWrite4(&ctx, current_pos - begin_track_pos - sizeof(MidiTrackChunk)); + MusToMidiSeekDestination(&ctx, current_pos); /* reseek to end position */ + + *out = ctx.dst; + *outsize = ctx.dstsize - ctx.dstrem; + ret = 0; + +_end: /* cleanup */ + if (ret < 0) + { + free(ctx.dst); + *out = nullptr; + *outsize = 0; + } + + return (ret); +} +#endif + +#if EDGE_XMI_SUPPORT +enum XMIConversionType +{ + kXMINoConversion = 0x00, + kXMIConvertMt32ToGm = 0x01, + kXMIConvertMt32ToGs = 0x02, + kXMIConvertMt32ToGs127 = 0x03, + kXMIConvertMt32ToGs127Drum = 0x04, + kXMIConvertGs127ToGs = 0x05 +}; + +enum XMIStatusByte +{ + kXMIStatusNoteOff = 0x8, + kXMIStatusNoteOn = 0x9, + kXMIStatusAftertouch = 0xA, + kXMIStatusController = 0xB, + kXMIStatusProgramChange = 0xC, + kXMIStatusPressure = 0xD, + kXMIStatusPitchWheel = 0xE, + kXMIStatusSysex = 0xF +}; + +struct XMIToMidiEvent +{ + int32_t time; + uint8_t status; + uint8_t data[2]; + uint32_t len; + uint8_t *buffer; + XMIToMidiEvent *next; +}; + +struct MidiDescriptor +{ + uint16_t type; + uint16_t tracks; +}; + +struct XMIToMidiConversionContext +{ + uint8_t *src, *src_ptr, *src_end; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; + uint32_t convert_type; + MidiDescriptor info; + int bank127[16]; + XMIToMidiEvent **events; + int16_t *timing; + XMIToMidiEvent *list; + XMIToMidiEvent *current; + std::vector> *dyn_out; + std::vector *dyn_out_cur; +}; + +struct XMIToMidiBranch +{ + unsigned count; + uint8_t id[128]; + uint32_t offset[128]; +}; + +/* forward declarations of private functions */ +static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist); +static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time); /* List manipulation */ +static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ +static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ +static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value); /* Variable length quantity */ +static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, + const int size); +static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, + const uint8_t status); +static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn); +static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist); +static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx); +static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber); +static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx); + +static uint32_t XMIToMidiRead1(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0; + assert(ctx->src_ptr + 1 < ctx->src_end); + b0 = *ctx->src_ptr++; + return (b0); +} + +static uint32_t XMIToMidiRead2(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0, b1; + assert(ctx->src_ptr + 2 < ctx->src_end); + b0 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1 << 8)); +} + +static uint32_t XMIToMidiRead4(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0, b1, b2, b3; + assert(ctx->src_ptr + 4 < ctx->src_end); + b3 = *ctx->src_ptr++; + b2 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + b0 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1 << 8) + ((uint32_t)b2 << 16) + ((uint32_t)b3 << 24)); +} + +static uint32_t XMIToMidiRead4LittleEndian(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0, b1, b2, b3; + assert(ctx->src_ptr + 4 < ctx->src_end); + b3 = *ctx->src_ptr++; + b2 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + b0 = *ctx->src_ptr++; + return (b3 + ((uint32_t)b2 << 8) + ((uint32_t)b1 << 16) + ((uint32_t)b0 << 24)); +} + +static void XMIToMidiCopy(struct XMIToMidiConversionContext *ctx, char *b, uint32_t len) +{ + assert(ctx->src_ptr + len < ctx->src_end); + memcpy(b, ctx->src_ptr, len); + ctx->src_ptr += len; +} + +static constexpr uint16_t kDestinationChunkSize = 8192; +static void XMIToMidiResizeDestination(struct XMIToMidiConversionContext *ctx) +{ + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + if (ctx->dyn_out && ctx->dyn_out_cur) + { + ctx->dyn_out_cur->resize(ctx->dstsize + kDestinationChunkSize); + ctx->dst = ctx->dyn_out_cur->data(); + } + else + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kDestinationChunkSize); + ctx->dstsize += kDestinationChunkSize; + ctx->dstrem += kDestinationChunkSize; + ctx->dst_ptr = ctx->dst + pos; +} + +static void XMIToMidiWrite1(struct XMIToMidiConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + XMIToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void XMIToMidiWrite2(struct XMIToMidiConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + XMIToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (val >> 8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void XMIToMidiWrite4(struct XMIToMidiConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + XMIToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (val >> 24) & 0xff; + *ctx->dst_ptr++ = (val >> 16) & 0xff; + *ctx->dst_ptr++ = (val >> 8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 4; +} + +static void XMIToMidiSeekSource(struct XMIToMidiConversionContext *ctx, uint32_t pos) +{ + ctx->src_ptr = ctx->src + pos; +} + +static void XMIToMidiSeekDestination(struct XMIToMidiConversionContext *ctx, uint32_t pos) +{ + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + XMIToMidiResizeDestination(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void XMIToMidiSkipSource(struct XMIToMidiConversionContext *ctx, int32_t pos) +{ + ctx->src_ptr += pos; +} + +static void XMIToMidiSkipDestination(struct XMIToMidiConversionContext *ctx, int32_t pos) +{ + size_t newpos; + ctx->dst_ptr += pos; + newpos = ctx->dst_ptr - ctx->dst; + while (ctx->dstsize < newpos) + XMIToMidiResizeDestination(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t XMIToMidiGetSourceSize(struct XMIToMidiConversionContext *ctx) +{ + return (ctx->srcsize); +} + +static uint32_t XMIToMidiGetSourcePosition(struct XMIToMidiConversionContext *ctx) +{ + return (uint32_t)(ctx->src_ptr - ctx->src); +} + +static uint32_t XMIToMidiGetDestinationPosition(struct XMIToMidiConversionContext *ctx) +{ + return (uint32_t)(ctx->dst_ptr - ctx->dst); +} + +/* This is a default set of patches to convert from MT32 to GM + * The index is the MT32 Patch number and the value is the GM Patch + * This is only suitable for music that doesn't do timbre changes + * XMIDIs that contain Timbre changes will not convert properly. + */ +static constexpr char Mt32ToGmMap[128] = { + 0, /* 0 Piano 1 */ + 1, /* 1 Piano 2 */ + 2, /* 2 Piano 3 (synth) */ + 4, /* 3 EPiano 1 */ + 4, /* 4 EPiano 2 */ + 5, /* 5 EPiano 3 */ + 5, /* 6 EPiano 4 */ + 3, /* 7 Honkytonk */ + 16, /* 8 Organ 1 */ + 17, /* 9 Organ 2 */ + 18, /* 10 Organ 3 */ + 16, /* 11 Organ 4 */ + 19, /* 12 Pipe Organ 1 */ + 19, /* 13 Pipe Organ 2 */ + 19, /* 14 Pipe Organ 3 */ + 21, /* 15 Accordion */ + 6, /* 16 Harpsichord 1 */ + 6, /* 17 Harpsichord 2 */ + 6, /* 18 Harpsichord 3 */ + 7, /* 19 Clavinet 1 */ + 7, /* 20 Clavinet 2 */ + 7, /* 21 Clavinet 3 */ + 8, /* 22 Celesta 1 */ + 8, /* 23 Celesta 2 */ + 62, /* 24 Synthbrass 1 (62) */ + 63, /* 25 Synthbrass 2 (63) */ + 62, /* 26 Synthbrass 3 Bank 8 */ + 63, /* 27 Synthbrass 4 Bank 8 */ + 38, /* 28 Synthbass 1 */ + 39, /* 29 Synthbass 2 */ + 38, /* 30 Synthbass 3 Bank 8 */ + 39, /* 31 Synthbass 4 Bank 8 */ + 88, /* 32 Fantasy */ + 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ + 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be + ChoirAhhs(52)??? */ + 92, /* 35 Glass */ + 97, /* 36 Soundtrack */ + 99, /* 37 Atmosphere */ + 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular + Bells(14) would be better. It is! */ + 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ + 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ + 96, /* 41 IceRain */ + 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ + 95, /* 43 EchoPans, no equiv, setting to SweepPad */ + 81, /* 44 DoctorSolo Bank 8 */ + 87, /* 45 SchoolDaze, no real equiv */ + 112, /* 46 Bell Singer */ + 80, /* 47 SquareWave */ + 48, /* 48 Strings 1 */ + 48, /* 49 Strings 2 - should be 49 */ + 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should + be 50 */ + 45, /* 51 Pizzicato Strings */ + 40, /* 52 Violin 1 */ + 40, /* 53 Violin 2 ? Viola */ + 42, /* 54 Cello 1 */ + 42, /* 55 Cello 2 */ + 43, /* 56 Contrabass */ + 46, /* 57 Harp 1 */ + 46, /* 58 Harp 2 */ + 24, /* 59 Guitar 1 (Nylon) */ + 25, /* 60 Guitar 2 (Steel) */ + 26, /* 61 Elec Guitar 1 */ + 27, /* 62 Elec Guitar 2 */ + 104, /* 63 Sitar */ + 32, /* 64 Acou Bass 1 */ + 32, /* 65 Acou Bass 2 */ + 33, /* 66 Elec Bass 1 */ + 34, /* 67 Elec Bass 2 */ + 36, /* 68 Slap Bass 1 */ + 37, /* 69 Slap Bass 2 */ + 35, /* 70 Fretless Bass 1 */ + 35, /* 71 Fretless Bass 2 */ + 73, /* 72 Flute 1 */ + 73, /* 73 Flute 2 */ + 72, /* 74 Piccolo 1 */ + 72, /* 75 Piccolo 2 */ + 74, /* 76 Recorder */ + 75, /* 77 Pan Pipes */ + 64, /* 78 Sax 1 */ + 65, /* 79 Sax 2 */ + 66, /* 80 Sax 3 */ + 67, /* 81 Sax 4 */ + 71, /* 82 Clarinet 1 */ + 71, /* 83 Clarinet 2 */ + 68, /* 84 Oboe */ + 69, /* 85 English Horn (Cor Anglais) */ + 70, /* 86 Bassoon */ + 22, /* 87 Harmonica */ + 56, /* 88 Trumpet 1 */ + 56, /* 89 Trumpet 2 */ + 57, /* 90 Trombone 1 */ + 57, /* 91 Trombone 2 */ + 60, /* 92 French Horn 1 */ + 60, /* 93 French Horn 2 */ + 58, /* 94 Tuba */ + 61, /* 95 Brass Section 1 */ + 61, /* 96 Brass Section 2 */ + 11, /* 97 Vibes 1 */ + 11, /* 98 Vibes 2 */ + 99, /* 99 Syn Mallet Bank 1 */ + 112, /* 100 WindBell no real equiv Set to TinkleBell(112) */ + 9, /* 101 Glockenspiel */ + 14, /* 102 Tubular Bells */ + 13, /* 103 Xylophone */ + 12, /* 104 Marimba */ + 107, /* 105 Koto */ + 111, /* 106 Sho?? set to Shanai(111) */ + 77, /* 107 Shakauhachi */ + 78, /* 108 Whistle 1 */ + 78, /* 109 Whistle 2 */ + 76, /* 110 Bottle Blow */ + 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */ + 47, /* 112 Timpani */ + 117, /* 113 Melodic Tom */ + 116, /* 114 Deap Snare no equiv, set to Taiko(116) */ + 118, /* 115 Electric Perc 1 */ + 118, /* 116 Electric Perc 2 */ + 116, /* 117 Taiko */ + 115, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ + 119, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ + 115, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ + 112, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ + 55, /* 122 Orchestral Hit */ + 124, /* 123 Telephone */ + 123, /* 124 BirdTweet */ + 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ + 98, /* 126 Water Bell set to Crystal Pad(98) */ + 121 /* 127 Jungle Tune set to Breath Noise */ +}; + +/* Same as above, except include patch changes + * so GS instruments can be used */ +static constexpr char Mt32ToGsMap[256] = { + 0, + 0, /* 0 Piano 1 */ + 1, + 0, /* 1 Piano 2 */ + 2, + 0, /* 2 Piano 3 (synth) */ + 4, + 0, /* 3 EPiano 1 */ + 4, + 0, /* 4 EPiano 2 */ + 5, + 0, /* 5 EPiano 3 */ + 5, + 0, /* 6 EPiano 4 */ + 3, + 0, /* 7 Honkytonk */ + 16, + 0, /* 8 Organ 1 */ + 17, + 0, /* 9 Organ 2 */ + 18, + 0, /* 10 Organ 3 */ + 16, + 0, /* 11 Organ 4 */ + 19, + 0, /* 12 Pipe Organ 1 */ + 19, + 0, /* 13 Pipe Organ 2 */ + 19, + 0, /* 14 Pipe Organ 3 */ + 21, + 0, /* 15 Accordion */ + 6, + 0, /* 16 Harpsichord 1 */ + 6, + 0, /* 17 Harpsichord 2 */ + 6, + 0, /* 18 Harpsichord 3 */ + 7, + 0, /* 19 Clavinet 1 */ + 7, + 0, /* 20 Clavinet 2 */ + 7, + 0, /* 21 Clavinet 3 */ + 8, + 0, /* 22 Celesta 1 */ + 8, + 0, /* 23 Celesta 2 */ + 62, + 0, /* 24 Synthbrass 1 (62) */ + 63, + 0, /* 25 Synthbrass 2 (63) */ + 62, + 0, /* 26 Synthbrass 3 Bank 8 */ + 63, + 0, /* 27 Synthbrass 4 Bank 8 */ + 38, + 0, /* 28 Synthbass 1 */ + 39, + 0, /* 29 Synthbass 2 */ + 38, + 0, /* 30 Synthbass 3 Bank 8 */ + 39, + 0, /* 31 Synthbass 4 Bank 8 */ + 88, + 0, /* 32 Fantasy */ + 90, + 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ + 52, + 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be + ChoirAhhs(52)??? */ + 92, + 0, /* 35 Glass */ + 97, + 0, /* 36 Soundtrack */ + 99, + 0, /* 37 Atmosphere */ + 14, + 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular + Bells(14) would be better. It is! */ + 54, + 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ + 98, + 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ + 96, + 0, /* 41 IceRain */ + 68, + 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ + 95, + 0, /* 43 EchoPans, no equiv, setting to SweepPad */ + 81, + 0, /* 44 DoctorSolo Bank 8 */ + 87, + 0, /* 45 SchoolDaze, no real equiv */ + 112, + 0, /* 46 Bell Singer */ + 80, + 0, /* 47 SquareWave */ + 48, + 0, /* 48 Strings 1 */ + 48, + 0, /* 49 Strings 2 - should be 49 */ + 44, + 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should + be 50 */ + 45, + 0, /* 51 Pizzicato Strings */ + 40, + 0, /* 52 Violin 1 */ + 40, + 0, /* 53 Violin 2 ? Viola */ + 42, + 0, /* 54 Cello 1 */ + 42, + 0, /* 55 Cello 2 */ + 43, + 0, /* 56 Contrabass */ + 46, + 0, /* 57 Harp 1 */ + 46, + 0, /* 58 Harp 2 */ + 24, + 0, /* 59 Guitar 1 (Nylon) */ + 25, + 0, /* 60 Guitar 2 (Steel) */ + 26, + 0, /* 61 Elec Guitar 1 */ + 27, + 0, /* 62 Elec Guitar 2 */ + 104, + 0, /* 63 Sitar */ + 32, + 0, /* 64 Acou Bass 1 */ + 32, + 0, /* 65 Acou Bass 2 */ + 33, + 0, /* 66 Elec Bass 1 */ + 34, + 0, /* 67 Elec Bass 2 */ + 36, + 0, /* 68 Slap Bass 1 */ + 37, + 0, /* 69 Slap Bass 2 */ + 35, + 0, /* 70 Fretless Bass 1 */ + 35, + 0, /* 71 Fretless Bass 2 */ + 73, + 0, /* 72 Flute 1 */ + 73, + 0, /* 73 Flute 2 */ + 72, + 0, /* 74 Piccolo 1 */ + 72, + 0, /* 75 Piccolo 2 */ + 74, + 0, /* 76 Recorder */ + 75, + 0, /* 77 Pan Pipes */ + 64, + 0, /* 78 Sax 1 */ + 65, + 0, /* 79 Sax 2 */ + 66, + 0, /* 80 Sax 3 */ + 67, + 0, /* 81 Sax 4 */ + 71, + 0, /* 82 Clarinet 1 */ + 71, + 0, /* 83 Clarinet 2 */ + 68, + 0, /* 84 Oboe */ + 69, + 0, /* 85 English Horn (Cor Anglais) */ + 70, + 0, /* 86 Bassoon */ + 22, + 0, /* 87 Harmonica */ + 56, + 0, /* 88 Trumpet 1 */ + 56, + 0, /* 89 Trumpet 2 */ + 57, + 0, /* 90 Trombone 1 */ + 57, + 0, /* 91 Trombone 2 */ + 60, + 0, /* 92 French Horn 1 */ + 60, + 0, /* 93 French Horn 2 */ + 58, + 0, /* 94 Tuba */ + 61, + 0, /* 95 Brass Section 1 */ + 61, + 0, /* 96 Brass Section 2 */ + 11, + 0, /* 97 Vibes 1 */ + 11, + 0, /* 98 Vibes 2 */ + 99, + 0, /* 99 Syn Mallet Bank 1 */ + 112, + 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */ + 9, + 0, /* 101 Glockenspiel */ + 14, + 0, /* 102 Tubular Bells */ + 13, + 0, /* 103 Xylophone */ + 12, + 0, /* 104 Marimba */ + 107, + 0, /* 105 Koto */ + 111, + 0, /* 106 Sho?? set to Shanai(111) */ + 77, + 0, /* 107 Shakauhachi */ + 78, + 0, /* 108 Whistle 1 */ + 78, + 0, /* 109 Whistle 2 */ + 76, + 0, /* 110 Bottle Blow */ + 76, + 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */ + 47, + 0, /* 112 Timpani */ + 117, + 0, /* 113 Melodic Tom */ + 116, + 0, /* 114 Deap Snare no equiv, set to Taiko(116) */ + 118, + 0, /* 115 Electric Perc 1 */ + 118, + 0, /* 116 Electric Perc 2 */ + 116, + 0, /* 117 Taiko */ + 115, + 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ + 119, + 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ + 115, + 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ + 112, + 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ + 55, + 0, /* 122 Orchestral Hit */ + 124, + 0, /* 123 Telephone */ + 123, + 0, /* 124 BirdTweet */ + 94, + 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ + 98, + 0, /* 126 Water Bell set to Crystal Pad(98) */ + 121, + 0 /* 127 Jungle Tune set to Breath Noise */ +}; + +static int ConvertXMIToMidi(uint8_t *in, uint32_t insize, std::vector> &out, uint32_t convert_type) +{ + struct XMIToMidiConversionContext ctx; + unsigned int i; + int ret = -1; + + if (convert_type > kXMIConvertMt32ToGs) + { + /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", + * __FUNCTION__, __LINE__, convert_type);*/ + return (ret); + } + + memset(&ctx, 0, sizeof(struct XMIToMidiConversionContext)); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + ctx.src_end = ctx.src + insize; + ctx.convert_type = convert_type; + ctx.dyn_out = &out; + + if (XMIToMidiParseXMI(&ctx) < 0) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, nullptr, + * 0);*/ + goto _end; + } + + if (XMIToMidiExtractTracks(&ctx, 0) < 0) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, nullptr, + * 0);*/ + goto _end; + } + + for (i = 0; i < ctx.info.tracks; i++) + { + out.push_back(std::vector()); + ctx.dyn_out_cur = &out.back(); + ctx.dyn_out_cur->resize(kDestinationChunkSize); + ctx.dst = ctx.dyn_out_cur->data(); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = kDestinationChunkSize; + ctx.dstrem = kDestinationChunkSize; + + /* Header is 14 bytes long and add the rest as well */ + XMIToMidiWrite1(&ctx, 'M'); + XMIToMidiWrite1(&ctx, 'T'); + XMIToMidiWrite1(&ctx, 'h'); + XMIToMidiWrite1(&ctx, 'd'); + + XMIToMidiWrite4(&ctx, 6); + + XMIToMidiWrite2(&ctx, ctx.info.type); + XMIToMidiWrite2(&ctx, 1); + XMIToMidiWrite2(&ctx, ctx.timing[i]); /* write divisions from track0 */ + + XMIToMidiConvertListToMidiTrack(&ctx, ctx.events[i]); + ctx.dyn_out_cur->resize(ctx.dstsize - ctx.dstrem); + } + + ret = 0; + +_end: /* cleanup */ + if (ret < 0) + { + out.clear(); + } + if (ctx.events) + { + for (i = 0; i < ctx.info.tracks; i++) + XMIToMidiDeleteEventList(ctx.events[i]); + free(ctx.events); + } + free(ctx.timing); + + return (ret); +} + +static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist) +{ + XMIToMidiEvent *event; + XMIToMidiEvent *next; + + next = mlist; + + while ((event = next) != nullptr) + { + next = event->next; + free(event->buffer); + free(event); + } +} + +/* Sets current to the new event and updates list */ +static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time) +{ + if (!ctx->list) + { + ctx->list = ctx->current = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + ctx->current->time = (time < 0) ? 0 : time; + return; + } + + if (time < 0) + { + XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + event->next = ctx->list; + ctx->list = ctx->current = event; + return; + } + + if (ctx->current->time > time) + ctx->current = ctx->list; + + while (ctx->current->next) + { + if (ctx->current->next->time > time) + { + XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + event->next = ctx->current->next; + ctx->current->next = event; + ctx->current = event; + ctx->current->time = time; + return; + } + + ctx->current = ctx->current->next; + } + + ctx->current->next = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + ctx->current = ctx->current->next; + ctx->current->time = time; +} + +/* Conventional Variable Length Quantity */ +static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant) +{ + int i; + uint32_t data; + + *quant = 0; + for (i = 0; i < 4; i++) + { + if (ctx->src_ptr + 1 >= ctx->src + ctx->srcsize) + break; + data = XMIToMidiRead1(ctx); + *quant <<= 7; + *quant |= data & 0x7F; + + if (!(data & 0x80)) + { + i++; + break; + } + } + return (i); +} + +/* XMIDI Delta Variable Length Quantity */ +static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant) +{ + int i; + int32_t data; + + *quant = 0; + for (i = 0; XMIToMidiGetSourcePosition(ctx) != XMIToMidiGetSourceSize(ctx); ++i) + { + data = XMIToMidiRead1(ctx); + if (data & 0x80) + { + XMIToMidiSkipSource(ctx, -1); + break; + } + *quant += data; + } + return (i); +} + +static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value) +{ + int32_t buffer; + int i = 1, j; + buffer = value & 0x7F; + while (value >>= 7) + { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + i++; + } + for (j = 0; j < i; j++) + { + XMIToMidiWrite1(ctx, buffer & 0xFF); + buffer >>= 8; + } + + return (i); +} + +/* Converts Events + * + * Source is at the first data byte + * size 1 is single data byte + * size 2 is dual data byte + * size 3 is XMI Note on + * Returns bytes converted */ +static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, + const int size) +{ + uint32_t delta = 0; + int32_t data; + XMIToMidiEvent *prev; + int i; + + data = XMIToMidiRead1(ctx); + + /*HACK!*/ + if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) + { + data = 32; /*Change XMI 114 controller into XG bank*/ + } + + /* Bank changes are handled here */ + if ((status >> 4) == 0xB && data == 0) + { + data = XMIToMidiRead1(ctx); + + ctx->bank127[status & 0xF] = 0; + + if (ctx->convert_type == kXMIConvertMt32ToGm || ctx->convert_type == kXMIConvertMt32ToGs || + ctx->convert_type == kXMIConvertMt32ToGs127 || + (ctx->convert_type == kXMIConvertMt32ToGs127Drum && (status & 0xF) == 9)) + return (2); + + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = status; + ctx->current->data[0] = 0; + ctx->current->data[1] = data == 127 ? 0 : data; /*HACK:*/ + + if (ctx->convert_type == kXMIConvertGs127ToGs && data == 127) + ctx->bank127[status & 0xF] = 1; + + return (2); + } + + /* Handling for patch change mt32 conversion, probably should go elsewhere + */ + if ((status >> 4) == 0xC && (status & 0xF) != 9 && ctx->convert_type != kXMINoConversion) + { + if (ctx->convert_type == kXMIConvertMt32ToGm) + { + data = Mt32ToGmMap[data]; + } + else if ((ctx->convert_type == kXMIConvertGs127ToGs && ctx->bank127[status & 0xF]) || + ctx->convert_type == kXMIConvertMt32ToGs || ctx->convert_type == kXMIConvertMt32ToGs127Drum) + { + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = 0xB0 | (status & 0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = Mt32ToGsMap[data * 2 + 1]; + + data = Mt32ToGsMap[data * 2]; + } + else if (ctx->convert_type == kXMIConvertMt32ToGs127) + { + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = 0xB0 | (status & 0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + } + /* Drum track handling */ + else if ((status >> 4) == 0xC && (status & 0xF) == 9 && + (ctx->convert_type == kXMIConvertMt32ToGs127Drum || ctx->convert_type == kXMIConvertMt32ToGs127)) + { + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = status; + + ctx->current->data[0] = data; + + if (size == 1) + return (1); + + ctx->current->data[1] = XMIToMidiRead1(ctx); + + if (size == 2) + return (2); + + /* XMI Note On handling */ + prev = ctx->current; + i = XMIToMidiGetVlq(ctx, &delta); + XMIToMidiCreateNewEvent(ctx, time + delta * 3); + + ctx->current->status = status; + ctx->current->data[0] = data; + ctx->current->data[1] = 0; + ctx->current = prev; + + return (i + 2); +} + +/* Simple routine to convert system messages */ +static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, + const uint8_t status) +{ + int32_t i = 0; + + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = status; + + /* Handling of Meta events */ + if (status == 0xFF) + { + ctx->current->data[0] = XMIToMidiRead1(ctx); + i++; + } + + i += XMIToMidiGetVlq(ctx, &ctx->current->len); + + if (!ctx->current->len) + return (i); + + ctx->current->buffer = (uint8_t *)malloc(sizeof(uint8_t) * ctx->current->len); + XMIToMidiCopy(ctx, (char *)ctx->current->buffer, ctx->current->len); + + return (i + ctx->current->len); +} + +/* XMIDI and Midi to List + * Returns XMIDI PPQN */ +static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn) +{ + int32_t time = 0; + uint32_t data; + int32_t end = 0; + int32_t tempo = 500000; + int32_t tempo_set = 0; + uint32_t status = 0; + uint32_t file_size = XMIToMidiGetSourceSize(ctx); + uint32_t begin = XMIToMidiGetSourcePosition(ctx); + + /* Set Drum track to correct setting if required */ + if (ctx->convert_type == kXMIConvertMt32ToGs127) + { + XMIToMidiCreateNewEvent(ctx, 0); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + while (!end && XMIToMidiGetSourcePosition(ctx) < file_size) + { + uint32_t offset = XMIToMidiGetSourcePosition(ctx) - begin; + + /* search for branch to this offset */ + for (unsigned i = 0, n = rbrn->count; i < n; ++i) + { + if (offset == rbrn->offset[i]) + { + unsigned id = rbrn->id[i]; + + XMIToMidiCreateNewEvent(ctx, time); + + uint8_t *marker = (uint8_t *)malloc(sizeof(uint8_t) * 8); + memcpy(marker, ":XBRN:", 6); + const char hex[] = "0123456789ABCDEF"; + marker[6] = hex[id >> 4]; + marker[7] = hex[id & 15]; + + ctx->current->status = 0xFF; + ctx->current->data[0] = 0x06; + ctx->current->len = 8; + + ctx->current->buffer = marker; + } + } + + XMIToMidiGetVlq2(ctx, &data); + time += data * 3; + + status = XMIToMidiRead1(ctx); + + switch (status >> 4) + { + case kXMIStatusNoteOn: + XMIToMidiConvertEvent(ctx, time, status, 3); + break; + + /* 2 byte data */ + case kXMIStatusNoteOff: + case kXMIStatusAftertouch: + case kXMIStatusController: + case kXMIStatusPitchWheel: + XMIToMidiConvertEvent(ctx, time, status, 2); + break; + + /* 1 byte data */ + case kXMIStatusProgramChange: + case kXMIStatusPressure: + XMIToMidiConvertEvent(ctx, time, status, 1); + break; + + case kXMIStatusSysex: + if (status == 0xFF) + { + int32_t pos = XMIToMidiGetSourcePosition(ctx); + uint32_t dat = XMIToMidiRead1(ctx); + + if (dat == 0x2F) /* End */ + end = 1; + else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ + { + XMIToMidiSkipSource(ctx, 1); + tempo = XMIToMidiRead1(ctx) << 16; + tempo += XMIToMidiRead1(ctx) << 8; + tempo += XMIToMidiRead1(ctx); + tempo *= 3; + tempo_set = 1; + } + else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ + { + XMIToMidiGetVlq(ctx, &dat); + XMIToMidiSkipSource(ctx, dat); + break; + } + + XMIToMidiSeekSource(ctx, pos); + } + XMIToMidiConvertSystemMessage(ctx, time, status); + break; + + default: + break; + } + } + return ((tempo * 3) / 25000); +} + +/* Converts and event list to a MidiTrack + * Returns bytes of the array + * buf can be nullptr */ +static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist) +{ + int32_t time = 0; + XMIToMidiEvent *event; + uint32_t delta; + uint8_t last_status = 0; + uint32_t i = 8; + uint32_t j; + uint32_t size_pos, cur_pos; + int end = 0; + + XMIToMidiWrite1(ctx, 'M'); + XMIToMidiWrite1(ctx, 'T'); + XMIToMidiWrite1(ctx, 'r'); + XMIToMidiWrite1(ctx, 'k'); + + size_pos = XMIToMidiGetDestinationPosition(ctx); + XMIToMidiSkipDestination(ctx, 4); + + for (event = mlist; event && !end; event = event->next) + { + delta = (event->time - time); + time = event->time; + + i += XMIToMidiPutVlq(ctx, delta); + + if ((event->status != last_status) || (event->status >= 0xF0)) + { + XMIToMidiWrite1(ctx, event->status); + i++; + } + + last_status = event->status; + + switch (event->status >> 4) + { + /* 2 bytes data + * Note off, Note on, Aftertouch, Controller and Pitch Wheel */ + case 0x8: + case 0x9: + case 0xA: + case 0xB: + case 0xE: + XMIToMidiWrite1(ctx, event->data[0]); + XMIToMidiWrite1(ctx, event->data[1]); + i += 2; + break; + + /* 1 bytes data + * Program Change and Channel Pressure */ + case 0xC: + case 0xD: + XMIToMidiWrite1(ctx, event->data[0]); + i++; + break; + + /* Variable length + * SysEx */ + case 0xF: + if (event->status == 0xFF) + { + if (event->data[0] == 0x2f) + end = 1; + XMIToMidiWrite1(ctx, event->data[0]); + i++; + } + i += XMIToMidiPutVlq(ctx, event->len); + if (event->len) + { + for (j = 0; j < event->len; j++) + { + XMIToMidiWrite1(ctx, event->buffer[j]); + i++; + } + } + break; + + /* Never occur */ + default: + /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ + break; + } + } + + cur_pos = XMIToMidiGetDestinationPosition(ctx); + XMIToMidiSeekDestination(ctx, size_pos); + XMIToMidiWrite4(ctx, i - 8); + XMIToMidiSeekDestination(ctx, cur_pos); + + return (i); +} + +/* Assumes correct xmidi */ +static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx) +{ + uint32_t num = 0; + signed short ppqn; + uint32_t len = 0; + int32_t begin; + char buf[32]; + uint32_t branch[128]; + + /* clear branch points */ + for (unsigned i = 0; i < 128; ++i) + branch[i] = ~0u; + + while (XMIToMidiGetSourcePosition(ctx) < XMIToMidiGetSourceSize(ctx) && num != ctx->info.tracks) + { + /* Read first 4 bytes of name */ + XMIToMidiCopy(ctx, buf, 4); + len = XMIToMidiRead4(ctx); + + /* Skip the FORM entries */ + if (!memcmp(buf, "FORM", 4)) + { + XMIToMidiSkipSource(ctx, 4); + XMIToMidiCopy(ctx, buf, 4); + len = XMIToMidiRead4(ctx); + } + + if (!memcmp(buf, "RBRN", 4)) + { + begin = XMIToMidiGetSourcePosition(ctx); + uint32_t count; + + if (len < 2) + { + /* insufficient data */ + goto rbrn_nodata; + } + + count = XMIToMidiRead2(ctx); + if (len - 2 < 6 * count) + { + /* insufficient data */ + goto rbrn_nodata; + } + + for (uint32_t i = 0; i < count; ++i) + { + /* read branch point as byte offset */ + uint32_t ctlvalue = XMIToMidiRead2(ctx); + uint32_t evtoffset = XMIToMidiRead4LittleEndian(ctx); + if (ctlvalue < 128) + branch[ctlvalue] = evtoffset; + } + + rbrn_nodata: + XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); + continue; + } + + if (memcmp(buf, "EVNT", 4)) + { + XMIToMidiSkipSource(ctx, (len + 1) & ~1); + continue; + } + + ctx->list = nullptr; + begin = XMIToMidiGetSourcePosition(ctx); + + /* Rearrange branches as structure */ + XMIToMidiBranch rbrn; + rbrn.count = 0; + for (unsigned i = 0; i < 128; ++i) + { + if (branch[i] != ~0u) + { + unsigned index = rbrn.count; + rbrn.id[index] = i; + rbrn.offset[index] = branch[i]; + rbrn.count = index + 1; + } + } + + /* Convert it */ + if ((ppqn = XMIToMidiConvertFiletoList(ctx, &rbrn)) == 0) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, nullptr, + * 0);*/ + break; + } + ctx->timing[num] = ppqn; + ctx->events[num] = ctx->list; + + /* Increment Counter */ + num++; + + /* go to start of next track */ + XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); + + /* clear branch points */ + for (unsigned i = 0; i < 128; ++i) + branch[i] = ~0u; + } + + /* Return how many were converted */ + return (num); +} + +static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx) +{ + uint32_t i; + uint32_t start; + uint32_t len; + uint32_t chunk_len; + uint32_t file_size; + char buf[32]; + + file_size = XMIToMidiGetSourceSize(ctx); + if (XMIToMidiGetSourcePosition(ctx) + 8 > file_size) + { + badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too + short)", 0);*/ + return (-1); + } + + /* Read first 4 bytes of header */ + XMIToMidiCopy(ctx, buf, 4); + + /* Could be XMIDI */ + if (!memcmp(buf, "FORM", 4)) + { + /* Read length of */ + len = XMIToMidiRead4(ctx); + + start = XMIToMidiGetSourcePosition(ctx); + if (start + 4 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + /* XDIRless XMIDI, we can handle them here. */ + if (!memcmp(buf, "XMID", 4)) + { + /*_WM_DEBUG_MSG("Warning: XMIDI without XDIR");*/ + ctx->info.tracks = 1; + } + /* Not an XMIDI that we recognise */ + else if (memcmp(buf, "XDIR", 4)) + { + goto badfile; + } + else + { /* Seems Valid */ + ctx->info.tracks = 0; + + for (i = 4; i < len; i++) + { + /* check too short files */ + if (XMIToMidiGetSourcePosition(ctx) + 10 > file_size) + break; + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + /* Read length of chunk */ + chunk_len = XMIToMidiRead4(ctx); + + /* Add eight bytes */ + i += 8; + + if (memcmp(buf, "INFO", 4)) + { + /* Must align */ + XMIToMidiSkipSource(ctx, (chunk_len + 1) & ~1); + i += (chunk_len + 1) & ~1; + continue; + } + + /* Must be at least 2 bytes long */ + if (chunk_len < 2) + break; + + ctx->info.tracks = XMIToMidiRead2(ctx); + break; + } + + /* Didn't get to fill the header */ + if (ctx->info.tracks == 0) + { + goto badfile; + } + + /* Ok now to start part 2 + * Goto the right place */ + XMIToMidiSeekSource(ctx, start + ((len + 1) & ~1)); + if (XMIToMidiGetSourcePosition(ctx) + 12 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + if (memcmp(buf, "CAT ", 4)) + { + /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found + \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ + return (-1); + } + + /* Now read length of this track */ + XMIToMidiRead4(ctx); + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + if (memcmp(buf, "XMID", 4)) + { + /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found + \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ + return (-1); + } + + /* Valid XMID */ + ctx->datastart = XMIToMidiGetSourcePosition(ctx); + return (0); + } + } + + return (-1); +} + +static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber) +{ + uint32_t i; + + ctx->events = (XMIToMidiEvent **)calloc(ctx->info.tracks, sizeof(XMIToMidiEvent *)); + ctx->timing = (int16_t *)calloc(ctx->info.tracks, sizeof(int16_t)); + /* type-2 for multi-tracks, type-0 otherwise */ + ctx->info.type = (ctx->info.tracks > 1 && (dstTrackNumber < 0 || ctx->info.tracks >= dstTrackNumber)) ? 2 : 0; + + XMIToMidiSeekSource(ctx, ctx->datastart); + i = XMIToMidiExtractTracksFromXMI(ctx); + + if (i != ctx->info.tracks) + { + /*_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from + XMIDI", ctx->info.tracks, i);*/ + return (-1); + } + + return (0); +} +#endif + +/*! Raw MIDI event hook */ +typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, + size_t len); +/*! PCM render */ +typedef void (*PcmRender)(void *userdata, uint8_t *stream, size_t length); +/*! Library internal debug messages */ +typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); +/*! Loop Start event hook */ +typedef void (*LoopStartHook)(void *userdata); +/*! Loop Start event hook */ +typedef void (*LoopEndHook)(void *userdata); +typedef void (*SongStartHook)(void *userdata); + +/*! Note-On MIDI event */ +typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); +/*! Note-Off MIDI event */ +typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); +/*! Note-Off MIDI event with a velocity */ +typedef void (*RtNoteOffVel)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); +/*! Note aftertouch MIDI event */ +typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); +/*! Channel aftertouch MIDI event */ +typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); +/*! Controller change MIDI event */ +typedef void (*RtControllerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); +/*! Patch change MIDI event */ +typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); +/*! Pitch bend MIDI event */ +typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); +/*! System Exclusive MIDI event */ +typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); +/*! Meta event hook */ +typedef void (*MetaEventHook)(void *userdata, uint8_t type, const uint8_t *data, size_t len); +/*! Device Switch MIDI event */ +typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); +/*! Get the channels offset for current MIDI device */ +typedef size_t (*RtCurrentDevice)(void *userdata, size_t track); +/*! [Non-Standard] Pass raw OPL3 data to the chip + */ +typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); + +/** + \brief Real-Time MIDI interface between Sequencer and the Synthesizer + */ +struct MidiRealTimeInterface +{ + /*! MIDI event hook which catches all MIDI events */ + RawEventHook onEvent; + /*! User data which will be passed through On-Event hook */ + void *onEvent_userdata; + + /*! PCM render hook which catches passing of loop start point */ + PcmRender onPcmRender; + /*! User data which will be passed through On-PCM-render hook */ + void *onPcmRender_userdata; + + /*! Sample rate */ + uint32_t pcmSampleRate; + + /*! Size of one sample in bytes */ + uint32_t pcmFrameSize; + + /*! Debug message hook */ + DebugMessageHook onDebugMessage; + /*! User data which will be passed through Debug Message hook */ + void *onDebugMessage_userdata; + + /*! Loop start hook which catches passing of loop start point */ + LoopStartHook onloopStart; + /*! User data which will be passed through On-LoopStart hook */ + void *onloopStart_userdata; + + /*! Loop start hook which catches passing of loop start point */ + LoopEndHook onloopEnd; + /*! User data which will be passed through On-LoopStart hook */ + void *onloopEnd_userdata; + + /*! Song start hook which is calling when starting playing song at begin + */ + SongStartHook onSongStart; + /*! User data which will be passed through On-SongStart hook */ + void *onSongStart_userdata; + + /*! MIDI Run Time event calls user data */ + void *rtUserData; + + /*************************************************** + * Standard MIDI events. All of them are required! * + ***************************************************/ + + /*! Note-On MIDI event hook */ + RtNoteOn rt_noteOn; + /*! Note-Off MIDI event hook */ + RtNoteOff rt_noteOff; + + /*! Note-Off MIDI event hook with a velocity */ + RtNoteOffVel rt_noteOffVel; + + /*! Note aftertouch MIDI event hook */ + RtNoteAfterTouch rt_noteAfterTouch; + + /*! Channel aftertouch MIDI event hook */ + RtChannelAfterTouch rt_channelAfterTouch; + + /*! Controller change MIDI event hook */ + RtControllerChange rt_controllerChange; + + /*! Patch change MIDI event hook */ + RtPatchChange rt_patchChange; + + /*! Pitch bend MIDI event hook */ + RtPitchBend rt_pitchBend; + + /*! System Exclusive MIDI event hook */ + RtSysEx rt_systemExclusive; + + /******************* + * Optional events * + *******************/ + + /*! Meta event hook which catches all meta events */ + MetaEventHook rt_metaEvent; + + /*! Device Switch MIDI event hook */ + RtDeviceSwitch rt_deviceSwitch; + + /*! Get the channels offset for current MIDI device hook. Returms + * multiple to 16 value. */ + RtCurrentDevice rt_currentDevice; + + /****************************************** + * NonStandard events. There are optional * + ******************************************/ + /*! [Non-Standard] Pass raw OPL3 data to the chip hook */ + RtRawOPL rt_rawOPL; +}; + +// MidiFraction is a stripped down version of +// Bisqwit's Fraction class with the following +// copyright: +/* + * Fraction number handling. + * Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/) + * + * The license of this file is in Public Domain: + * https://bisqwit.iki.fi/src/index.html + * + * "... and orphan source code files are copyrighted public domain." + */ + +class MidiFraction +{ + uint64_t num1_, num2_; + void Optim(); + + public: + MidiFraction() : num1_(0), num2_(1) + { + } + MidiFraction(uint64_t value) : num1_(value), num2_(1) + { + } + MidiFraction(uint64_t n, uint64_t d) : num1_(n), num2_(d) + { + } + inline double Value() const + { + return Nom() / (double)Denom(); + } + MidiFraction &operator*=(const MidiFraction &b) + { + num1_ *= b.Nom(); + num2_ *= b.Denom(); + Optim(); + return *this; + } + MidiFraction operator*(const MidiFraction &b) const + { + MidiFraction tmp(*this); + tmp *= b; + return tmp; + } + const uint64_t &Nom() const + { + return num1_; + } + const uint64_t &Denom() const + { + return num2_; + } +}; + +void MidiFraction::Optim() +{ + /* Euclidean algorithm */ + uint64_t n1, n2, nn1, nn2; + + nn1 = num1_; + nn2 = num2_; + + if (nn1 < nn2) + n1 = num1_, n2 = num2_; + else + n1 = num2_, n2 = num1_; + + if (!num1_) + { + num2_ = 1; + return; + } + for (;;) + { + uint64_t tmp = n2 % n1; + if (!tmp) + break; + n2 = n1; + n1 = tmp; + } + num1_ /= n1; + num2_ /= n1; +} + +MidiFraction operator*(const uint64_t bla, const MidiFraction &b) +{ + return MidiFraction(bla) * b; +} + +class MidiSequencer +{ + /** + * @brief MIDI Event utility container + */ + struct MidiEvent + { + /** + * @brief Main MIDI event types + */ + enum Types + { + //! Unknown event + kUnknown = 0x00, + //! Note-Off event + kNoteOff = 0x08, // size == 2 + //! Note-On event + kNoteOn = 0x09, // size == 2 + //! Note After-Touch event + kNoteTouch = 0x0A, // size == 2 + //! Controller change event + kControlChange = 0x0B, // size == 2 + //! Patch change event + kPatchChange = 0x0C, // size == 1 + //! Channel After-Touch event + kChannelAftertouch = 0x0D, // size == 1 + //! Pitch-bend change event + kPitchWheel = 0x0E, // size == 2 + + //! System Exclusive message, type 1 + kSysex = 0xF0, // size == len + //! Sys Com Song Position Pntr [LSB, MSB] + kSysComSongPositionPointer = 0xF2, // size == 2 + //! Sys Com Song Select(Song #) [0-127] + kSysComSongSelect = 0xF3, // size == 1 + //! System Exclusive message, type 2 + kSysex2 = 0xF7, // size == len + //! Special event + kSpecial = 0xFF + }; + /** + * @brief Special MIDI event sub-types + */ + enum SubTypes + { + //! Sequension number + kSequensionNumber = 0x00, // size == 2 + //! Text label + kText = 0x01, // size == len + //! Copyright notice + kCopyright = 0x02, // size == len + //! Sequence track title + kSequenceTrackTitle = 0x03, // size == len + //! Instrument title + kInstrumentTitle = 0x04, // size == len + //! Lyrics text fragment + kLyrics = 0x05, // size == len + //! MIDI Marker + kMarker = 0x06, // size == len + //! Cue Point + kCuePoint = 0x07, // size == len + //! [Non-Standard] Device Switch + kDeviceSwitch = 0x09, // size == len + //! MIDI Channel prefix + kMidiChannelPrefix = 0x20, // size == 1 + + //! End of Track event + kEndTrack = 0x2F, // size == 0 + //! Tempo change event + kTempoChange = 0x51, // size == 3 + //! SMPTE offset + kSmpteOffset = 0x54, // size == 5 + //! Time signature + kTimeSignature = 0x55, // size == 4 + //! Key signature + kKeySignature = 0x59, // size == 2 + //! Sequencer specs + kSequencerSpec = 0x7F, // size == len + + /* Non-standard, internal ADLMIDI usage only */ + //! [Non-Standard] Loop Start point + kLoopStart = 0xE1, // size == 0 + //! [Non-Standard] Loop End point + kLoopEnd = 0xE2, // size == 0 + //! [Non-Standard] Raw OPL data + kRawOPL = 0xE3, // size == 0 + + //! [Non-Standard] Loop Start point with support of multi-loops + kLoopStackBegin = 0xE4, // size == 1 + //! [Non-Standard] Loop End point with + //! support of multi-loops + kLoopStackEnd = 0xE5, // size == 0 + //! [Non-Standard] Loop End point with + //! support of multi-loops + kLoopStackBreak = 0xE6, // size == 0 + //! [Non-Standard] Callback Trigger + kCallbackTrigger = 0xE7, // size == 1 + + // Built-in hooks + kSongBeginHook = 0x101 + }; + //! Main type of event + uint_fast16_t type = kUnknown; + //! Sub-type of the event + uint_fast16_t sub_type = kUnknown; + //! Targeted MIDI channel + uint_fast16_t channel = 0; + //! Is valid event + uint_fast16_t is_valid = 1; + //! Reserved 5 bytes padding + uint_fast16_t padding[4]; + //! Absolute tick position (Used for the tempo calculation only) + uint64_t absolute_tick_position = 0; + //! Raw data of this event + std::vector data; + }; + + /** + * @brief A track position event contains a chain of MIDI events until next + * delay value + * + * Created with purpose to sort events by type in the same position + * (for example, to keep controllers always first than note on events or + * lower than note-off events) + */ + class MidiTrackRow + { + public: + MidiTrackRow(); + //! Clear MIDI row data + void Clear(); + //! Absolute time position in seconds + double time_; + //! Delay to next event in ticks + uint64_t delay_; + //! Absolute position in ticks + uint64_t absolute_position_; + //! Delay to next event in seconds + double time_delay_; + //! List of MIDI events in the current row + std::vector events_; + /** + * @brief Sort events in this position + * @param note_states Buffer of currently pressed/released note keys in + * the track + */ + void SortEvents(bool *note_states = nullptr); + }; + + /** + * @brief Tempo change point entry. Used in the MIDI data building function + * only. + */ + struct TempoChangePoint + { + uint64_t absolute_position; + MidiFraction tempo; + }; + + /** + * @brief Song position context + */ + struct Position + { + //! Was track began playing + bool began = false; + //! Reserved + char padding[7] = {0, 0, 0, 0, 0, 0, 0}; + //! Waiting time before next event in seconds + double wait = 0.0; + //! Absolute time position on the track in seconds + double absolute_time_position = 0.0; + //! Track information + struct TrackInfo + { + //! Delay to next event in a track + uint64_t delay = 0; + //! Last handled event type + int32_t lastHandledEvent = 0; + //! Reserved + char padding2[4]; + //! MIDI Events queue position iterator + std::list::iterator pos; + }; + std::vector track; + }; + + //! MIDI Output interface context + const MidiRealTimeInterface *midi_output_interface_; + + /** + * @brief Prepare internal events storage for track data building + * @param track_count Count of tracks + */ + void BuildSMFSetupReset(size_t track_count); + + /** + * @brief Build MIDI track data from the raw track data storage + * @return true if everything successfully processed, or false on any error + */ + bool BuildSMFTrackData(const std::vector> &track_data); + + /** + * @brief Build the time line from off loaded events + * @param tempos Pre-collected list of tempo events + * @param loop_start_ticks Global loop start tick (give zero if no global + * loop presented) + * @param loop_end_ticks Global loop end tick (give zero if no global loop + * presented) + */ + void BuildTimeLine(const std::vector &tempos, uint64_t loop_start_ticks = 0, + uint64_t loop_end_ticks = 0); + + /** + * @brief Parse one event from raw MIDI track stream + * @param [_inout] ptr pointer to pointer to current position on the raw + * data track + * @param [_in] end address to end of raw track data, needed to validate + * position and size + * @param [_inout] status status of the track processing + * @return Parsed MIDI event entry + */ + MidiEvent ParseEvent(const uint8_t **ptr, const uint8_t *end, int &status); + + /** + * @brief Process MIDI events on the current tick moment + * @param is_seek is a seeking process + * @return returns false on reaching end of the song + */ + bool ProcessEvents(bool is_seek = false); + + /** + * @brief Handle one event from the chain + * @param tk MIDI track + * @param evt MIDI event entry + * @param status Recent event type, -1 returned when end of track event was + * handled. + */ + void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status); + + public: + /** + * @brief MIDI marker entry + */ + struct MidiMarkerEntry + { + //! Label + std::string label; + //! Position time in seconds + double position_time; + //! Position time in MIDI ticks + uint64_t position_ticks; + }; + + /** + * @brief The FileFormat enum + */ + enum FileFormat + { + //! MIDI format + kFormatMidi, +#if EDGE_IMF_SUPPORT + //! Id-Software Music File + kFormatIMF, +#endif + //! EA-MUS format + kFormatRSXX, +#if EDGE_XMI_SUPPORT + //! AIL's XMIDI format (act same as MIDI, but with exceptions) + kFormatXMidi +#endif + }; + + /** + * @brief Format of loop points implemented by CC events + */ + enum LoopFormat + { + kLoopDefault, + kLoopRpgMaker = 1, + kLoopEMidi, + kLoopHmi + }; + + private: + //! Music file format type. MIDI is default. + FileFormat midi_format_; + //! SMF format identifier. + unsigned midi_smf_format_; + //! Loop points format + LoopFormat midi_loop_format_; + + //! Current position + Position midi_current_position_; + //! Track begin position + Position midi_track_begin_position_; + //! Loop start point + Position midi_loop_begin_position_; + + //! Is looping enabled or not + bool midi_loop_enabled_; + //! Don't process loop: trigger hooks only if they are set + bool midi_loop_hooks_only_; + + //! Full song length in seconds + double midi_full_song_time_length_; + //! Delay after song playd before rejecting the output stream requests + double midi_post_song_wait_delay_; + + //! Global loop start time + double midi_loop_start_time_; + //! Global loop end time + double midi_loop_end_time_; + + //! Pre-processed track data storage + std::vector> midi_track_data_; + + //! Title of music + std::string midi_music_title_; + //! Copyright notice of music + std::string midi_music_copyright_; + //! List of track titles + std::vector midi_music_track_titles_; + //! List of MIDI markers + std::vector midi_music_markers_; + + //! Time of one tick + MidiFraction midi_individual_tick_delta_; + //! Current tempo + MidiFraction midi_tempo_; + + //! Tempo multiplier factor + double midi_tempo_multiplier_; + //! Is song at end + bool midi_at_end_; + + //! Set the number of loops limit. Lesser than 0 - loop infinite + int midi_loop_count_; + + //! The number of track of multi-track file (for exmaple, XMI) to load + int midi_load_track_number_; + + //! The XMI-specific list of raw songs, converted into SMF format + std::vector> midi_raw_songs_data_; + + /** + * @brief Loop stack entry + */ + struct LoopStackEntry + { + //! is infinite loop + bool infinity = false; + //! Count of loops left to break. <0 - infinite loop + int loops = 0; + //! Start position snapshot to return back + Position start_position; + //! Loop start tick + uint64_t start = 0; + //! Loop end tick + uint64_t end = 0; + }; + + class LoopState + { + public: + //! Loop start has reached + bool caught_start_; + //! Loop end has reached, reset on handling + bool caught_end_; + + //! Loop start has reached + bool caught_stack_start_; + //! Loop next has reached, reset on handling + bool caught_stack_end_; + //! Loop break has reached, reset on handling + bool caught_stack_break_; + //! Skip next stack loop start event handling + bool skip_stack_start_; + + //! Are loop points invalid? + bool invalid_loop_; /*Loop points are invalid (loopStart after loopEnd + or loopStart and loopEnd are on same place)*/ + + //! Is look got temporarily broken because of post-end seek? + bool temporary_broken_; + + //! How much times the loop should start repeat? For example, if you + //! want to loop song twice, set value 1 + int loops_count_; + + //! how many loops left until finish the song + int loops_left_; + + //! Stack of nested loops + std::vector stack_; + //! Current level on the loop stack (<0 - out of loop, 0++ - the index + //! in the loop stack) + int stack_level_; + + /** + * @brief Reset loop state to initial + */ + void Reset() + { + caught_start_ = false; + caught_end_ = false; + caught_stack_start_ = false; + caught_stack_end_ = false; + caught_stack_break_ = false; + skip_stack_start_ = false; + loops_left_ = loops_count_; + } + + void FullReset() + { + loops_count_ = -1; + Reset(); + invalid_loop_ = false; + temporary_broken_ = false; + stack_.clear(); + stack_level_ = -1; + } + + bool IsStackEnd() + { + if (caught_stack_end_ && (stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) + { + const LoopStackEntry &e = stack_[(size_t)(stack_level_)]; + if (e.infinity || (!e.infinity && e.loops > 0)) + return true; + } + return false; + } + + void StackUp(int count = 1) + { + stack_level_ += count; + } + + void StackDown(int count = 1) + { + stack_level_ -= count; + } + + LoopStackEntry &GetCurrentStack() + { + if ((stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) + return stack_[(size_t)(stack_level_)]; + if (stack_.empty()) + { + LoopStackEntry d; + d.loops = 0; + d.infinity = 0; + d.start = 0; + d.end = 0; + stack_.push_back(d); + } + return stack_[0]; + } + }; + + LoopState midi_loop_; + + //! Whether the nth track has playback disabled + std::vector midi_track_disabled_; + //! Index of solo track, or max for disabled + size_t midi_track_solo_; + //! MIDI channel disable (exception for extra port-prefix-based channels) + bool m_channelDisable[16]; + + /** + * @brief Handler of callback trigger events + * @param userdata Pointer to user data (usually, context of something) + * @param trigger Value of the event which triggered this callback. + * @param track Identifier of the track which triggered this callback. + */ + typedef void (*TriggerHandler)(void *userdata, unsigned trigger, size_t track); + + //! Handler of callback trigger events + TriggerHandler midi_trigger_handler_; + //! User data of callback trigger events + void *midi_trigger_userdata_; + + //! File parsing errors string (adding into midi_error_string_ on aborting + //! of the process) + std::string midi_parsing_errors_string_; + //! Common error string + std::string midi_error_string_; + + class SequencerTime + { + public: + //! Time buffer + double time_rest_; + //! Sample rate + uint32_t sample_rate_; + //! Size of one frame in bytes + uint32_t frame_size_; + //! Minimum possible delay, granuality + double minimum_delay_; + //! Last delay + double delay_; + + void Init() + { + sample_rate_ = 44100; + frame_size_ = 2; + Reset(); + } + + void Reset() + { + time_rest_ = 0.0; + minimum_delay_ = 1.0 / (double)(sample_rate_); + delay_ = 0.0; + } + }; + + SequencerTime midi_time_; + + public: + MidiSequencer(); + virtual ~MidiSequencer(); + + /** + * @brief Sets the RT interface + * @param intrf Pre-Initialized interface structure (pointer will be taken) + */ + void SetInterface(const MidiRealTimeInterface *intrf); + + /** + * @brief Runs ticking in a sync with audio streaming. Use this together + * with onPcmRender hook to easily play MIDI. + * @param stream pointer to the output PCM stream + * @param length length of the buffer in bytes + * @return Count of recorded data in bytes + */ + int PlayStream(uint8_t *stream, size_t length); + + /** + * @brief Returns file format type of currently loaded file + * @return File format type enumeration + */ + FileFormat GetFormat(); + + /** + * @brief Returns the number of tracks + * @return Track count + */ + size_t GetTrackCount() const; + + /** + * @brief Sets whether a track is playing + * @param track Track identifier + * @param enable Whether to enable track playback + * @return true on success, false if there was no such track + */ + bool SetTrackEnabled(size_t track, bool enable); + + /** + * @brief Disable/enable a channel is sounding + * @param channel Channel number from 0 to 15 + * @param enable Enable the channel playback + * @return true on success, false if there was no such channel + */ + bool SetChannelEnabled(size_t channel, bool enable); + + /** + * @brief Enables or disables solo on a track + * @param track Identifier of solo track, or max to disable + */ + void SetSoloTrack(size_t track); + + /** + * @brief Set the song number of a multi-song file (such as XMI) + * @param trackNumber Identifier of the song to load (or -1 to mix all songs + * as one song) + */ + void SetSongNum(int track); + + /** + * @brief Retrive the number of songs in a currently opened file + * @return Number of songs in the file. If 1 or less, means, the file has + * only one song inside. + */ + int GetSongsCount(); + + /** + * @brief Defines a handler for callback trigger events + * @param handler Handler to invoke from the sequencer when triggered, or + * nullptr. + * @param userdata Instance of the library + */ + void SetTriggerHandler(TriggerHandler handler, void *userdata); + + /** + * @brief Get string that describes reason of error + * @return Error string + */ + const std::string &GetErrorString(); + + /** + * @brief Check is loop enabled + * @return true if loop enabled + */ + bool GetLoopEnabled(); + + /** + * @brief Switch loop on/off + * @param enabled Enable loop + */ + void SetLoopEnabled(bool enabled); + + /** + * @brief Get the number of loops set + * @return number of loops or -1 if loop infinite + */ + int GetLoopsCount(); + + /** + * @brief How many times song should loop + * @param loops count or -1 to loop infinite + */ + void SetLoopsCount(int loops); + + /** + * @brief Switch loop hooks-only mode on/off + * @param enabled Don't loop: trigger hooks only without loop + */ + void SetLoopHooksOnly(bool enabled); + + /** + * @brief Get music title + * @return music title string + */ + const std::string &GetMusicTitle(); + + /** + * @brief Get music copyright notice + * @return music copyright notice string + */ + const std::string &GetMusicCopyright(); + + /** + * @brief Get list of track titles + * @return array of track title strings + */ + const std::vector &GetTrackTitles(); + + /** + * @brief Get list of MIDI markers + * @return Array of MIDI marker structures + */ + const std::vector &GetMarkers(); + + /** + * @brief Is position of song at end + * @return true if end of song was reached + */ + bool PositionAtEnd(); + + /** + * @brief Load MIDI file from a memory block + * @param data Pointer to memory block with MIDI data + * @param size Size of source memory block + * @param rate For IMF formats, the proper playback rate in Hz + * @return true if file successfully opened, false on any error + */ + bool LoadMidi(const uint8_t *data, size_t size, uint16_t rate = 0); + + /** + * @brief Load MIDI file by using FileAndMemReader interface + * @param mfr mem_file_c with opened source file + * @param rate For IMF formats, the proper playback rate in Hz + * @return true if file successfully opened, false on any error + */ + bool LoadMidi(epi::MemFile *mfr, uint16_t rate); + + /** + * @brief Periodic tick handler. + * @param s seconds since last call + * @param granularity don't expect intervals smaller than this, in seconds + * @return desired number of seconds until next call + */ + double Tick(double s, double granularity); + + /** + * @brief Change current position to specified time position in seconds + * @param granularity don't expect intervals smaller than this, in seconds + * @param seconds Absolute time position in seconds + * @return desired number of seconds until next call of Tick() + */ + double Seek(double seconds, const double granularity); + + /** + * @brief Gives current time position in seconds + * @return Current time position in seconds + */ + double Tell(); + + /** + * @brief Gives time length of current song in seconds + * @return Time length of current song in seconds + */ + double TimeLength(); + + /** + * @brief Gives loop start time position in seconds + * @return Loop start time position in seconds or -1 if song has no loop + * points + */ + double GetLoopStart(); + + /** + * @brief Gives loop end time position in seconds + * @return Loop end time position in seconds or -1 if song has no loop + * points + */ + double GetLoopEnd(); + + /** + * @brief Return to begin of current song + */ + void Rewind(); + + /** + * @brief Get current tempor multiplier value + * @return + */ + double GetTempoMultiplier(); + + /** + * @brief Set tempo multiplier + * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - + * slower + */ + void SetTempo(double tempo); + + private: +#if EDGE_IMF_SUPPORT + /** + * @brief Load file as Id-software-Music-File (Wolfenstein) + * @param mfr mem_file_c with opened source file + * @param rate For IMF formats, the proper playback rate in Hz + * @return true on successful load + */ + bool ParseIMF(epi::MemFile *mfr, uint16_t rate); +#endif + /** + * @brief Load file as EA MUS + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseRSXX(epi::MemFile *mfr); + + /** + * @brief Load file as GMD/MUS files (ScummVM) + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseGMF(epi::MemFile *mfr); + + /** + * @brief Load file as Standard MIDI file + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseSMF(epi::MemFile *mfr); + + /** + * @brief Load file as RIFF MIDI + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseRMI(epi::MemFile *mfr); +#if EDGE_MUS_SUPPORT + /** + * @brief Load file as DMX MUS file (Doom) + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseMUS(epi::MemFile *mfr); +#endif +#if EDGE_XMI_SUPPORT + /** + * @brief Load file as AIL eXtended MIdi + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseXMI(epi::MemFile *mfr); +#endif +}; + /** * @brief Utility function to read Big-Endian integer from raw binary data * @param buffer Pointer to raw binary buffer @@ -383,7 +3260,7 @@ void MidiSequencer::SetSoloTrack(size_t track) void MidiSequencer::SetSongNum(int track) { midi_load_track_number_ = track; - +#if EDGE_XMI_SUPPORT if (!midi_raw_songs_data_.empty() && midi_format_ == kFormatXMidi) // Reload the song { if (midi_load_track_number_ >= (int)midi_raw_songs_data_.size()) @@ -407,6 +3284,7 @@ void MidiSequencer::SetSongNum(int track) midi_format_ = kFormatXMidi; } +#endif } int MidiSequencer::GetSongsCount() @@ -1478,7 +4356,7 @@ MidiSequencer::MidiEvent MidiSequencer::ParseEvent(const uint8_t **pptr, const u break; } } - +#if EDGE_XMI_SUPPORT if (midi_format_ == kFormatXMidi) { switch (evt.data[0]) @@ -1520,6 +4398,7 @@ MidiSequencer::MidiEvent MidiSequencer::ParseEvent(const uint8_t **pptr, const u break; } } +#endif } return evt; @@ -1969,7 +4848,7 @@ static bool detectRSXX(const char *head, epi::MemFile *mfr) mfr->Seek(0, epi::File::kSeekpointStart); return ret; } - +#if EDGE_IMF_SUPPORT /** * @brief Detect the Id-software Music File format * @param head Header part @@ -2004,7 +4883,7 @@ static bool detectIMF(const char *head, epi::MemFile *mfr) return (sum1 > sum2); } - +#endif bool MidiSequencer::LoadMidi(epi::MemFile *mfr, uint16_t rate) { size_t fsize = 0; @@ -2050,25 +4929,27 @@ bool MidiSequencer::LoadMidi(epi::MemFile *mfr, uint16_t rate) mfr->Seek(0, epi::File::kSeekpointStart); return ParseGMF(mfr); } - +#if EDGE_MUS_SUPPORT if (memcmp(headerBuf, "MUS\x1A", 4) == 0) { mfr->Seek(0, epi::File::kSeekpointStart); return ParseMUS(mfr); } - +#endif +#if EDGE_XMI_SUPPORT if ((memcmp(headerBuf, "FORM", 4) == 0) && (memcmp(headerBuf + 8, "XDIR", 4) == 0)) { mfr->Seek(0, epi::File::kSeekpointStart); return ParseXMI(mfr); } - +#endif +#if EDGE_IMF_SUPPORT if (detectIMF(headerBuf, mfr)) { mfr->Seek(0, epi::File::kSeekpointStart); return ParseIMF(mfr, rate); } - +#endif if (detectRSXX(headerBuf, mfr)) { mfr->Seek(0, epi::File::kSeekpointStart); @@ -2079,7 +4960,7 @@ bool MidiSequencer::LoadMidi(epi::MemFile *mfr, uint16_t rate) delete mfr; return false; } - +#if EDGE_IMF_SUPPORT bool MidiSequencer::ParseIMF(epi::MemFile *mfr, uint16_t rate) { const size_t deltaTicks = 1; @@ -2186,7 +5067,7 @@ bool MidiSequencer::ParseIMF(epi::MemFile *mfr, uint16_t rate) return true; } - +#endif bool MidiSequencer::ParseRSXX(epi::MemFile *mfr) { const size_t headerSize = 14; @@ -2485,7 +5366,7 @@ bool MidiSequencer::ParseRMI(epi::MemFile *mfr) mfr->Seek(6l, epi::File::kSeekpointCurrent); return ParseSMF(mfr); } - +#if EDGE_MUS_SUPPORT bool MidiSequencer::ParseMUS(epi::MemFile *mfr) { const size_t headerSize = 14; @@ -2551,7 +5432,8 @@ bool MidiSequencer::ParseMUS(epi::MemFile *mfr) return ParseSMF(mfr); } - +#endif +#if EDGE_XMI_SUPPORT bool MidiSequencer::ParseXMI(epi::MemFile *mfr) { const size_t headerSize = 14; @@ -2636,4 +5518,5 @@ bool MidiSequencer::ParseXMI(epi::MemFile *mfr) ret = ParseSMF(mfr); return ret; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/source_files/edge/s_music.cc b/source_files/edge/s_music.cc index 822b164d3..6386c2f97 100644 --- a/source_files/edge/s_music.cc +++ b/source_files/edge/s_music.cc @@ -32,15 +32,29 @@ #include "i_system.h" #include "m_misc.h" #include "s_emidi.h" +#if EDGE_FLAC_SUPPORT #include "s_flac.h" +#endif #include "s_tsf.h" #include "s_fmm.h" +#if EDGE_TRACKER_SUPPORT #include "s_ibxm.h" +#endif +#if EDGE_MP3_SUPPORT #include "s_mp3.h" +#endif +#if EDGE_OGG_SUPPORT #include "s_ogg.h" +#endif +#if EDGE_IMF_SUPPORT #include "s_imf.h" +#endif +#if EDGE_RAD_SUPPORT #include "s_rad.h" +#endif +#if EDGE_SID_SUPPORT #include "s_sid.h" +#endif #include "s_sound.h" #include "snd_types.h" #include "w_files.h" @@ -150,6 +164,7 @@ void ChangeMusic(int entry_number, bool loop) SoundFormat fmt = kSoundUnknown; +#if EDGE_IMF_SUPPORT // IMF Music is the outlier in that it must be predefined in DDFPLAY with // the appropriate IMF frequency, as there is no way of determining this // from file information alone @@ -168,49 +183,68 @@ void ChangeMusic(int entry_number, bool loop) fmt = SoundFilenameToFormat(play->info_); } } - +#else + if (play->infotype_ == kDDFMusicDataLump) + { + // lumps must use auto-detection based on their contents + fmt = DetectSoundFormat(data, length); + } + else + { + // for FILE and PACK, use the file extension + fmt = SoundFilenameToFormat(play->info_); + } +#endif // NOTE: players are responsible for freeing 'data' switch (fmt) { +#if EDGE_OGG_SUPPORT case kSoundOGG: delete F; music_player = PlayOGGMusic(data, length, loop); break; - +#endif +#if EDGE_MP3_SUPPORT case kSoundMP3: delete F; music_player = PlayMP3Music(data, length, loop); break; - +#endif +#if EDGE_FLAC_SUPPORT case kSoundFLAC: delete F; music_player = PlayFLACMusic(data, length, loop); break; - +#endif +#if EDGE_TRACKER_SUPPORT case kSoundIBXM: delete F; music_player = PlayIBXMMusic(data, length, loop); break; - +#endif +#if EDGE_RAD_SUPPORT case kSoundRAD: delete F; music_player = PlayRADMusic(data, length, loop); break; - +#endif +#if EDGE_SID_SUPPORT case kSoundSID: delete F; music_player = PlaySIDMusic(data, length, loop); break; - +#endif +#if EDGE_IMF_SUPPORT case kSoundIMF: delete F; music_player = PlayIMFMusic(data, length, loop, play->type_); break; - +#endif case kSoundMIDI: +#if EDGE_MUS_SUPPORT case kSoundMUS: - case kSoundWAV: // RIFF MIDI has the same header as WAV +#endif delete F; if (var_midi_player == 0) { diff --git a/source_files/edge/s_tsf.cc b/source_files/edge/s_tsf.cc index 5dc6d2d33..292d280a1 100644 --- a/source_files/edge/s_tsf.cc +++ b/source_files/edge/s_tsf.cc @@ -27,15 +27,15 @@ #include "i_movie.h" #include "i_system.h" #include "m_misc.h" +#include "epi_str_compare.h" +#include "epi_str_util.h" +#include "s_blit.h" // clang-format off #define MidiFraction TSFFraction #define MidiSequencer TSFSequencer typedef struct MidiRealTimeInterface TSFInterface; -#include "midi_sequencer_impl.hpp" +#include "s_midi.h" // clang-format on -#include "epi_str_compare.h" -#include "epi_str_util.h" -#include "s_blit.h" #include "s_music.h" #include "tsf.h" diff --git a/source_files/edge/s_wav.cc b/source_files/edge/s_wav.cc index 88c3346c9..1113a8234 100644 --- a/source_files/edge/s_wav.cc +++ b/source_files/edge/s_wav.cc @@ -28,181 +28,10 @@ #include "snd_gather.h" #include "w_wad.h" -// The following structs and PC Speaker Conversion routine are adapted from the -// SLADE codebase, specifically -// https://github.com/sirjuddington/SLADE/blob/master/src/MainEditor/Conversions.cpp - -// ----------------------------------------------------------------------------- -// Converts Doom PC speaker sound data [in] to wav format, written to [out]. -// -// This code is partly adapted from info found on: -// http://www.shikadi.net/moddingwiki/AudioT_Format and -// http://www.shikadi.net/moddingwiki/Inverse_Frequency_Sound_format -// ----------------------------------------------------------------------------- - -// Some structs for wav conversion -struct WavChunk -{ - char id[4]; - uint32_t size; -}; - -struct WavFormatChunk -{ - WavChunk header; - uint16_t tag; - uint16_t channels; - uint32_t samplerate; - uint32_t datarate; - uint16_t blocksize; - uint16_t bps; -}; - -// For speaker sound conversion -struct SpeakerSoundHeader -{ - uint16_t zero; - uint16_t samples; -}; - -uint8_t *ConvertPCSpeakerSound(const uint8_t *data, int *length) -{ - static const double ORIG_RATE = 140.0; - static const int FACTOR = 315; // 315*140 = 44100 - static const double FREQ = 1193181.0; - static const double RATE = (ORIG_RATE * FACTOR); - static const int PC_VOLUME = 20; - static const uint16_t counters[128] = { - 0, 6818, 6628, 6449, 6279, 6087, 5906, 5736, 5575, 5423, 5279, 5120, 4971, 4830, 4697, 4554, - 4435, 4307, 4186, 4058, 3950, 3836, 3728, 3615, 3519, 3418, 3323, 3224, 3131, 3043, 2960, 2875, - 2794, 2711, 2633, 2560, 2485, 2415, 2348, 2281, 2213, 2153, 2089, 2032, 1975, 1918, 1864, 1810, - 1757, 1709, 1659, 1612, 1565, 1521, 1478, 1435, 1395, 1355, 1316, 1280, 1242, 1207, 1173, 1140, - 1107, 1075, 1045, 1015, 986, 959, 931, 905, 879, 854, 829, 806, 783, 760, 739, 718, - 697, 677, 658, 640, 621, 604, 586, 570, 553, 538, 522, 507, 493, 479, 465, 452, - 439, 427, 415, 403, 391, 380, 369, 359, 348, 339, 329, 319, 310, 302, 293, 285, - 276, 269, 261, 253, 246, 239, 232, 226, 219, 213, 207, 201, 195, 190, 184, 179}; - - // --- Read Doom sound --- - - if (*length < 4) - { - LogWarning("Invalid PC Speaker Sound\n"); - return nullptr; - } - - SpeakerSoundHeader header; - memcpy(&header, data, 4); - size_t numsamples; - - // Format checks - if (header.zero != 0) // Check for magic number - { - LogWarning("Invalid Doom PC Speaker Sound\n"); - return nullptr; - } - if (header.samples > (*length - 4) || header.samples < 4) // Check for sane values - { - LogWarning("Invalid Doom PC Speaker Sound\n"); - return nullptr; - } - numsamples = header.samples; - - // Read samples - std::vector osamples(numsamples); - std::vector nsamples(numsamples * FACTOR); - memcpy(osamples.data(), data + sizeof(SpeakerSoundHeader), numsamples); - - int sign = -1; - uint32_t phase_tic = 0; - - // Convert counter values to sample values - for (size_t s = 0; s < numsamples; ++s) - { - if (osamples[s] > 127) - { - LogWarning("Invalid PC Speaker counter value: %d > 127", osamples[s]); - return nullptr; - } - if (osamples[s] > 0) - { - // First, convert counter value to frequency in Hz - // double f = FREQ / (double)counters[osamples[s]]; - uint32_t tone = counters[osamples[s]]; - uint32_t phase_length = (tone * RATE) / (2 * FREQ); - - // Then write a bunch of samples. - for (int i = 0; i < FACTOR; ++i) - { - // Finally, convert frequency into sample value - int pos = (s * FACTOR) + i; - nsamples[pos] = 128 + sign * PC_VOLUME; - if (phase_tic++ >= phase_length) - { - sign = -sign; - phase_tic = 0; - } - } - } - else - { - memset(nsamples.data() + size_t(s * FACTOR), 128, FACTOR); - phase_tic = 0; - } - } - - // --- Write WAV --- - - WavChunk whdr, wdhdr; - WavFormatChunk fmtchunk; - - // Setup data header - char did[4] = {'d', 'a', 't', 'a'}; - memcpy(&wdhdr.id, &did, 4); - wdhdr.size = numsamples * FACTOR; - - // Setup fmt chunk - char fid[4] = {'f', 'm', 't', ' '}; - memcpy(&fmtchunk.header.id, &fid, 4); - fmtchunk.header.size = 16; - fmtchunk.tag = 1; - fmtchunk.channels = 1; - fmtchunk.samplerate = RATE; - fmtchunk.datarate = RATE; - fmtchunk.blocksize = 1; - fmtchunk.bps = 8; - - // Setup main header - char wid[4] = {'R', 'I', 'F', 'F'}; - memcpy(&whdr.id, &wid, 4); - whdr.size = wdhdr.size + fmtchunk.header.size + 20; - - // Write chunks - - *length = 20 + sizeof(WavFormatChunk) + (numsamples * FACTOR); - int write_counter = 0; - uint8_t *new_data = new uint8_t[*length]; - memcpy(new_data + write_counter, &whdr, 8); - write_counter += 8; - char wave[4] = {'W', 'A', 'V', 'E'}; - memcpy(new_data + write_counter, wave, 4); - write_counter += 4; - memcpy(new_data + write_counter, &fmtchunk, sizeof(WavFormatChunk)); - write_counter += sizeof(WavFormatChunk); - memcpy(new_data + write_counter, &wdhdr, 8); - write_counter += 8; - memcpy(new_data + write_counter, nsamples.data(), numsamples * FACTOR); - write_counter += numsamples * FACTOR; - - return new_data; -} - -bool LoadWAVSound(SoundData *buf, uint8_t *data, int length, bool pc_speaker) +bool LoadWAVSound(SoundData *buf, uint8_t *data, int length) { drwav wav; - if (pc_speaker) - data = ConvertPCSpeakerSound(data, &length); - if (!drwav_init_memory(&wav, data, length, nullptr)) { LogWarning("Failed to load WAV sound (corrupt wav?)\n"); diff --git a/source_files/edge/s_wav.h b/source_files/edge/s_wav.h index 893be2174..736b36a6d 100644 --- a/source_files/edge/s_wav.h +++ b/source_files/edge/s_wav.h @@ -20,7 +20,7 @@ #include "snd_data.h" -bool LoadWAVSound(SoundData *buf, uint8_t *data, int length, bool pc_speaker); +bool LoadWAVSound(SoundData *buf, uint8_t *data, int length); //--- editor settings --- // vi:ts=4:sw=4:noexpandtab diff --git a/source_files/edge/snd_types.cc b/source_files/edge/snd_types.cc index 5ea51b9c1..a4d3c7e65 100644 --- a/source_files/edge/snd_types.cc +++ b/source_files/edge/snd_types.cc @@ -21,55 +21,60 @@ #include "epi.h" #include "epi_filesystem.h" #include "epi_str_util.h" +#if EDGE_TRACKER_SUPPORT #include "s_ibxm.h" +#endif SoundFormat DetectSoundFormat(uint8_t *data, int song_len) { // Start by trying the simple reliable header checks - +#if EDGE_WAV_SUPPORT if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F') { return kSoundWAV; } - +#endif +#if EDGE_FLAC_SUPPORT if (data[0] == 'f' && data[1] == 'L' && data[2] == 'a' && data[3] == 'C') { return kSoundFLAC; } - +#endif +#if EDGE_OGG_SUPPORT if (data[0] == 'O' && data[1] == 'g' && data[2] == 'g' && data[3] == 'S') { return kSoundOGG; } - +#endif +#if EDGE_SID_SUPPORT if ((data[0] == 'P' || data[0] == 'R') && data[1] == 'S' && data[2] == 'I' && data[3] == 'D') { return kSoundSID; } - +#endif +#if EDGE_MUS_SUPPORT if (data[0] == 'M' && data[1] == 'U' && data[2] == 'S') { return kSoundMUS; } - +#endif if (data[0] == 'M' && data[1] == 'T' && data[2] == 'h' && data[3] == 'd') { return kSoundMIDI; } - +#if EDGE_XMI_SUPPORT // XMI MIDI if (song_len > 12 && data[0] == 'F' && data[1] == 'O' && data[2] == 'R' && data[3] == 'M' && data[8] == 'X' && data[9] == 'D' && data[10] == 'I' && data[11] == 'R') { return kSoundMIDI; } - +#endif // GMF MIDI if (data[0] == 'G' && data[1] == 'M' && data[2] == 'F' && data[3] == '\x1') { return kSoundMIDI; } - // Electronic Arts MIDI if (song_len > data[0] && data[0] >= 0x5D) { @@ -78,7 +83,7 @@ SoundFormat DetectSoundFormat(uint8_t *data, int song_len) data[offset + 4] == '}' && data[offset + 5] == 'u') return kSoundMIDI; } - +#if EDGE_RAD_SUPPORT // Reality Adlib Tracker 2 if (song_len > 16) { @@ -95,29 +100,30 @@ SoundFormat DetectSoundFormat(uint8_t *data, int song_len) if (is_rad) return kSoundRAD; } - +#endif // Moving on to more specialized or less reliable detections - +#if EDGE_TRACKER_SUPPORT if (CheckIBXMFormat(data, song_len)) { return kSoundIBXM; } - +#endif +#if EDGE_MP3_SUPPORT if ((data[0] == 'I' && data[1] == 'D' && data[2] == '3') || (data[0] == 0xFF && ((data[1] >> 4 & 0xF) == 0xF))) { return kSoundMP3; } - +#endif +#if EDGE_DOOM_SFX_SUPPORT if (data[0] == 0x3) { return kSoundDoom; } - if (data[0] == 0x0) { return kSoundPCSpeaker; } - +#endif return kSoundUnknown; } @@ -126,35 +132,45 @@ SoundFormat SoundFilenameToFormat(std::string_view filename) std::string ext = epi::GetExtension(filename); epi::StringLowerASCII(ext); - +#if EDGE_WAV_SUPPORT if (ext == ".wav" || ext == ".wave") return kSoundWAV; - +#endif +#if EDGE_FLAC_SUPPORT if (ext == ".flac") return kSoundFLAC; - +#endif +#if EDGE_OGG_SUPPORT if (ext == ".ogg") return kSoundOGG; - +#endif +#if EDGE_MP3_SUPPORT if (ext == ".mp3") return kSoundMP3; - +#endif +#if EDGE_SID_SUPPORT if (ext == ".sid" || ext == ".psid") return kSoundSID; - - // Test MUS vs EA-MIDI MUS ? +#endif +#if EDGE_MUS_SUPPORT if (ext == ".mus") return kSoundMUS; - - if (ext == ".mid" || ext == ".midi" || ext == ".xmi" || ext == ".rmi" || ext == ".rmid") +#endif + if (ext == ".mid" || ext == ".midi") return kSoundMIDI; - +#if EDGE_XMI_SUPPORT + if (ext == ".xmi") + return kSoundMIDI; +#endif +#if EDGE_TRACKER_SUPPORT if (ext == ".mod" || ext == ".s3m" || ext == ".xm") return kSoundIBXM; - +#endif +#if EDGE_RAD_SUPPORT if (ext == ".rad") return kSoundRAD; - +#endif +#if EDGE_DOOM_SFX_SUPPORT // Not sure if these will ever be encountered in the wild, but according to // the VGMPF Wiki they are valid DMX file extensions if (ext == ".dsp" || ext == ".pcs" || ext == ".gsp" || ext == ".gsw") @@ -165,7 +181,7 @@ SoundFormat SoundFilenameToFormat(std::string_view filename) // unconditional which is why I didn't throw it up there if (ext == ".lmp") return kSoundPCSpeaker; - +#endif return kSoundUnknown; } diff --git a/source_files/edge/snd_types.h b/source_files/edge/snd_types.h index 4ca604547..480d064ab 100644 --- a/source_files/edge/snd_types.h +++ b/source_files/edge/snd_types.h @@ -25,18 +25,38 @@ enum SoundFormat { kSoundUnknown = 0, +#if EDGE_WAV_SUPPORT kSoundWAV, +#endif +#if EDGE_FLAC_SUPPORT kSoundFLAC, +#endif +#if EDGE_OGG_SUPPORT kSoundOGG, +#endif +#if EDGE_MP3_SUPPORT kSoundMP3, +#endif +#if EDGE_TRACKER_SUPPORT kSoundIBXM, +#endif +#if EDGE_SID_SUPPORT kSoundSID, +#endif +#if EDGE_RAD_SUPPORT kSoundRAD, +#endif +#if EDGE_MUS_SUPPORT kSoundMUS, +#endif kSoundMIDI, +#if EDGE_IMF_SUPPORT kSoundIMF, // Used with DDFPLAY; not in auto-detection +#endif +#if EDGE_DOOM_SFX_SUPPORT kSoundDoom, kSoundPCSpeaker +#endif }; // determine sound format from the file.