diff --git a/.gitmodules b/.gitmodules index 6b94a1e3..80382f51 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,11 +19,3 @@ path = vst/external/VST_SDK/VST3_SDK/vstgui4 url = https://github.com/sfztools/vstgui.git shallow = true -[submodule "clients/external/fmidi"] - path = clients/external/fmidi - url = https://github.com/jpcima/fmidi.git - shallow = true -[submodule "clients/external/fmt"] - path = clients/external/fmt - url = https://github.com/fmtlib/fmt.git - shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index daf8e57b..e71082d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ endif() option (ENABLE_LTO "Enable Link Time Optimization [default: ON]" ON) option (SFIZZ_JACK "Enable JACK stand-alone build [default: ON]" ON) +option (SFIZZ_RENDER "Enable renderer of SMF files [default: ON]" ON) option (SFIZZ_LV2 "Enable LV2 plug-in build [default: ON]" ON) option (SFIZZ_VST "Enable VST plug-in build [default: OFF]" OFF) option (SFIZZ_BENCHMARKS "Enable benchmarks build [default: OFF]" OFF) @@ -45,9 +46,7 @@ add_subdirectory (external/abseil-cpp EXCLUDE_FROM_ALL) add_subdirectory (src) # Optional targets -if (SFIZZ_JACK) - add_subdirectory (clients) -endif() +add_subdirectory (clients) if (SFIZZ_LV2) add_subdirectory (lv2) diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt index 68c4860b..322315c7 100644 --- a/clients/CMakeLists.txt +++ b/clients/CMakeLists.txt @@ -1,21 +1,24 @@ project (sfizz) -find_package(PkgConfig REQUIRED) -pkg_check_modules(JACK "jack" REQUIRED) +if (SFIZZ_JACK) + find_package(PkgConfig REQUIRED) + pkg_check_modules(JACK "jack" REQUIRED) -add_executable (sfizz_jack jack_client.cpp) -target_include_directories (sfizz_jack PRIVATE ${JACK_INCLUDE_DIRS}) -target_link_libraries (sfizz_jack PRIVATE sfizz::sfizz jack absl::flags_parse ${JACK_LIBRARIES}) -sfizz_enable_lto_if_needed (sfizz_jack) -install (TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR} -COMPONENT "jack" OPTIONAL) + add_executable (sfizz_jack jack_client.cpp) + target_include_directories (sfizz_jack PRIVATE ${JACK_INCLUDE_DIRS}) + target_link_libraries (sfizz_jack PRIVATE sfizz::sfizz jack absl::flags_parse ${JACK_LIBRARIES}) + sfizz_enable_lto_if_needed (sfizz_jack) + install (TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT "jack" OPTIONAL) +endif() -add_library(fmidi-fmt INTERFACE) -target_include_directories(fmidi-fmt INTERFACE "external/fmt/include") -target_compile_definitions(fmidi-fmt INTERFACE "FMT_HEADER_ONLY=1") -add_subdirectory(external/fmidi EXCLUDE_FROM_ALL) +if (SFIZZ_RENDER) + add_library(sfizz-fmidi STATIC "external/fmidi/sources/fmidi/fmidi_mini.cpp") + target_include_directories(sfizz-fmidi PUBLIC "external/fmidi/sources") + target_compile_definitions(sfizz-fmidi PUBLIC "FMIDI_STATIC=1" "FMIDI_DISABLE_DESCRIBE_API=1") -add_executable(sfizz_render sfizz_render.cpp) -target_link_libraries(sfizz_render sfizz::sfizz fmidi) -sfizz_enable_lto_if_needed (sfizz_render) -install (TARGETS sfizz_render DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + add_executable(sfizz_render sfizz_render.cpp) + target_link_libraries(sfizz_render PRIVATE sfizz::sfizz sfizz-fmidi sfizz-sndfile) + sfizz_enable_lto_if_needed (sfizz_render) + install (TARGETS sfizz_render DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT "render" OPTIONAL) +endif() diff --git a/clients/external/fmidi b/clients/external/fmidi deleted file mode 160000 index 46a045c7..00000000 --- a/clients/external/fmidi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 46a045c7f327d827baeddd627604907fc29ef17e diff --git a/clients/external/fmidi/LICENSE.md b/clients/external/fmidi/LICENSE.md new file mode 100644 index 00000000..44da875b --- /dev/null +++ b/clients/external/fmidi/LICENSE.md @@ -0,0 +1,23 @@ +# Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/clients/external/fmidi/sources/fmidi/fmidi.h b/clients/external/fmidi/sources/fmidi/fmidi.h new file mode 100644 index 00000000..94297f44 --- /dev/null +++ b/clients/external/fmidi/sources/fmidi/fmidi.h @@ -0,0 +1,251 @@ +// ============================================================================= +// +// The Fmidi library - a free software toolkit for MIDI file processing +// +// ============================================================================= +// Copyright Jean Pierre Cimalando 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.md or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#pragma once +#include +#include +#include +#if !defined(__cplusplus) +# include +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(_WIN32) && defined(FMIDI_BUILD) && !defined(FMIDI_STATIC) +# define FMIDI_API __declspec(dllexport) +#elif defined(_WIN32) && !defined(FMIDI_BUILD) && !defined(FMIDI_STATIC) +# define FMIDI_API __declspec(dllimport) +#elif defined(__GNUC__) +# define FMIDI_API __attribute__((visibility("default"))) +#else +# define FMIDI_API +#endif + +/////////// +// INPUT // +/////////// + +typedef struct fmidi_smf fmidi_smf_t; + +FMIDI_API fmidi_smf_t *fmidi_smf_mem_read(const uint8_t *data, size_t length); +FMIDI_API fmidi_smf_t *fmidi_smf_file_read(const char *filename); +FMIDI_API fmidi_smf_t *fmidi_smf_stream_read(FILE *stream); +FMIDI_API void fmidi_smf_free(fmidi_smf_t *smf); + +typedef struct fmidi_smf_info { + uint16_t format; + uint16_t track_count; + uint16_t delta_unit; +} fmidi_smf_info_t; + +FMIDI_API const fmidi_smf_info_t *fmidi_smf_get_info(const fmidi_smf_t *smf); +FMIDI_API double fmidi_smf_compute_duration(const fmidi_smf_t *smf); + +//////////// +// OUTPUT // +//////////// + +FMIDI_API bool fmidi_smf_mem_write(const fmidi_smf_t *smf, uint8_t **data, size_t *length); +FMIDI_API bool fmidi_smf_file_write(const fmidi_smf_t *smf, const char *filename); +FMIDI_API bool fmidi_smf_stream_write(const fmidi_smf_t *smf, FILE *stream); + +//////////////////// +// IDENTIFICATION // +//////////////////// + +typedef enum fmidi_fileformat { + fmidi_fileformat_smf, + fmidi_fileformat_xmi, +} fmidi_fileformat_t; + +FMIDI_API fmidi_fileformat_t fmidi_mem_identify(const uint8_t *data, size_t length); +FMIDI_API fmidi_fileformat_t fmidi_stream_identify(FILE *stream); + +FMIDI_API fmidi_smf_t *fmidi_auto_mem_read(const uint8_t *data, size_t length); +FMIDI_API fmidi_smf_t *fmidi_auto_file_read(const char *filename); +FMIDI_API fmidi_smf_t *fmidi_auto_stream_read(FILE *stream); + +//////////// +// EVENTS // +//////////// + +typedef enum fmidi_event_type { + fmidi_event_meta = 1, + fmidi_event_message = 2, + fmidi_event_escape = 3, + fmidi_event_xmi_timbre = 4, + fmidi_event_xmi_branch_point = 5 +} fmidi_event_type_t; + +typedef struct fmidi_event { + fmidi_event_type_t type; + uint32_t delta; + uint32_t datalen; + uint8_t data[1]; +} fmidi_event_t; + +#define fmidi_event_sizeof(datalen) \ + (offsetof(fmidi_event_t, data) + datalen) + +//////////// +// TRACKS // +//////////// + +typedef struct fmidi_track_iter { + uint16_t track; + uint32_t index; +} fmidi_track_iter_t; + +FMIDI_API void fmidi_smf_track_begin(fmidi_track_iter_t *it, uint16_t track); +FMIDI_API const fmidi_event_t *fmidi_smf_track_next( + const fmidi_smf_t *smf, fmidi_track_iter_t *it); + +///////////// +// FORMATS // +///////////// + +FMIDI_API fmidi_smf_t *fmidi_xmi_mem_read(const uint8_t *data, size_t length); +FMIDI_API fmidi_smf_t *fmidi_xmi_file_read(const char *filename); +FMIDI_API fmidi_smf_t *fmidi_xmi_stream_read(FILE *stream); + +/////////////// +// SEQUENCER // +/////////////// + +typedef struct fmidi_seq fmidi_seq_t; + +typedef struct fmidi_seq_event { + double time; + uint16_t track; + const fmidi_event_t *event; +} fmidi_seq_event_t; + +FMIDI_API fmidi_seq_t *fmidi_seq_new(const fmidi_smf_t *smf); +FMIDI_API void fmidi_seq_free(fmidi_seq_t *pl); +FMIDI_API void fmidi_seq_rewind(fmidi_seq_t *pl); +FMIDI_API bool fmidi_seq_peek_event(fmidi_seq_t *pl, fmidi_seq_event_t *plevt); +FMIDI_API bool fmidi_seq_next_event(fmidi_seq_t *pl, fmidi_seq_event_t *plevt); + +///////////// +// UTILITY // +///////////// + +typedef struct fmidi_smpte { + uint8_t code[5]; +} fmidi_smpte_t; + +FMIDI_API double fmidi_smpte_time(const fmidi_smpte_t *smpte); +FMIDI_API double fmidi_delta_time(double delta, uint16_t unit, uint32_t tempo); +FMIDI_API double fmidi_time_delta(double time, uint16_t unit, uint32_t tempo); + +//////////// +// ERRORS // +//////////// + +typedef enum fmidi_status { + fmidi_ok, + fmidi_err_format, + fmidi_err_eof, + fmidi_err_input, + fmidi_err_largefile, + fmidi_err_output +} fmidi_status_t; + +FMIDI_API fmidi_status_t fmidi_errno(); +FMIDI_API const char *fmidi_strerror(fmidi_status_t status); + +typedef struct fmidi_error_info { + fmidi_status_t code; +#if defined(FMIDI_DEBUG) + const char *file; int line; +#endif +} fmidi_error_info_t; + +FMIDI_API const fmidi_error_info_t *fmidi_errinfo(); + +//////////// +// PLAYER // +//////////// + +typedef struct fmidi_player fmidi_player_t; +FMIDI_API fmidi_player_t *fmidi_player_new(fmidi_smf_t *smf); +FMIDI_API void fmidi_player_tick(fmidi_player_t *seq, double delta); +FMIDI_API void fmidi_player_free(fmidi_player_t *seq); +FMIDI_API void fmidi_player_start(fmidi_player_t *seq); +FMIDI_API void fmidi_player_stop(fmidi_player_t *seq); +FMIDI_API void fmidi_player_rewind(fmidi_player_t *seq); +FMIDI_API bool fmidi_player_running(const fmidi_player_t *seq); +FMIDI_API double fmidi_player_current_time(const fmidi_player_t *seq); +FMIDI_API void fmidi_player_goto_time(fmidi_player_t *seq, double time); +FMIDI_API double fmidi_player_current_speed(const fmidi_player_t *seq); +FMIDI_API void fmidi_player_set_speed(fmidi_player_t *seq, double speed); +FMIDI_API void fmidi_player_event_callback( + fmidi_player_t *seq, void (*cbfn)(const fmidi_event_t *, void *), void *cbdata); +FMIDI_API void fmidi_player_finish_callback( + fmidi_player_t *seq, void (*cbfn)(void *), void *cbdata); + +////////////// +// PRINTERS // +////////////// + +#if !defined(FMIDI_DISABLE_DESCRIBE_API) +FMIDI_API void fmidi_smf_describe(const fmidi_smf_t *smf, FILE *stream); +FMIDI_API void fmidi_event_describe(const fmidi_event_t *evt, FILE *stream); +#endif + +//////////// +// LIMITS // +//////////// + +enum { fmidi_file_size_limit = 64 * 1024 * 1024 }; + +#if defined(__cplusplus) +} // extern "C" +#endif + +////////////////// +// C++ PRINTERS // +////////////////// + +#if defined(__cplusplus) && !defined(FMIDI_DISABLE_DESCRIBE_API) +# include +FMIDI_API std::ostream &operator<<(std::ostream &out, const fmidi_smf_t &smf); +FMIDI_API std::ostream &operator<<(std::ostream &out, const fmidi_event_t &evt); +#endif + +////////////// +// C++ RAII // +////////////// + +#if defined(__cplusplus) +# include + +struct fmidi_smf_deleter { + void operator()(fmidi_smf_t *x) const { fmidi_smf_free(x); } }; +struct fmidi_seq_deleter { + void operator()(fmidi_seq_t *x) const { fmidi_seq_free(x); } }; +struct fmidi_player_deleter { + void operator()(fmidi_player_t *x) const { fmidi_player_free(x); } }; + +typedef std::unique_ptr fmidi_smf_u; +typedef std::unique_ptr fmidi_seq_u; +typedef std::unique_ptr fmidi_player_u; +#endif + +//////////////// +// C++ ERRORS // +//////////////// + +#if defined(__cplusplus) +# include +FMIDI_API const std::error_category &fmidi_category(); +#endif diff --git a/clients/external/fmidi/sources/fmidi/fmidi_mini.cpp b/clients/external/fmidi/sources/fmidi/fmidi_mini.cpp new file mode 100644 index 00000000..5c9cd188 --- /dev/null +++ b/clients/external/fmidi/sources/fmidi/fmidi_mini.cpp @@ -0,0 +1,2564 @@ +// ============================================================================= +// +// The Fmidi library - a free software toolkit for MIDI file processing +// Single-file implementation, based on software revision: c513b4f +// +// ============================================================================= +// Copyright Jean Pierre Cimalando 2018-2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.md or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// ============================================================================= + + +#include "fmidi/fmidi.h" +#include + +struct fmidi_raw_track { + std::unique_ptr data; + uint32_t length; +}; + +struct fmidi_smf { + fmidi_smf_info_t info; + std::unique_ptr track; +}; + +//------------------------------------------------------------------------------ +uintptr_t fmidi_event_pad(uintptr_t size); +fmidi_event_t *fmidi_event_alloc(std::vector &buf, uint32_t datalen); +unsigned fmidi_message_sizeof(uint8_t id); + +//------------------------------------------------------------------------------ +inline uintptr_t fmidi_event_pad(uintptr_t size) +{ + uintptr_t nb = size % alignof(fmidi_event_t); + return nb ? (size + alignof(fmidi_event_t) - nb) : size; +} + +#include "fmidi/fmidi.h" + +#if !defined(FMIDI_DISABLE_DESCRIBE_API) +//------------------------------------------------------------------------------ +struct printfmt_quoted { + printfmt_quoted(const char *text, size_t length) + : text(text), length(length) {} + const char *text = nullptr; + size_t length = 0; +}; +std::ostream &operator<<(std::ostream &out, const printfmt_quoted &q); + +//------------------------------------------------------------------------------ +struct printfmt_bytes { + printfmt_bytes(const uint8_t *data, size_t size) + : data(data), size(size) {} + const uint8_t *data = nullptr; + size_t size = 0; +}; +std::ostream &operator<<(std::ostream &out, const printfmt_bytes &b); +#endif // !defined(FMIDI_DISABLE_DESCRIBE_API) + +//------------------------------------------------------------------------------ +extern thread_local fmidi_error_info_t fmidi_last_error; + +#if defined(FMIDI_DEBUG) +# define RET_FAIL(x, e) do { \ + fmidi_error_info_t &fmidi__err = fmidi_last_error; \ + fmidi__err.file = __FILE__; fmidi__err.line = __LINE__; \ + fmidi__err.code = (e); return (x); } while (0) +#else +# define RET_FAIL(x, e) \ + do { fmidi_last_error.code = (e); return (x); } while (0) +#endif + +//------------------------------------------------------------------------------ +#include +#include +#include +#include +#include + +class Writer { +public: + virtual ~Writer() {} + virtual void put(uint8_t byte) = 0; + virtual void write(const void *data, size_t size) = 0; + virtual void rwrite(const void *data, size_t size) = 0; + virtual void writeLE(const void *data, size_t size) = 0; + virtual void writeBE(const void *data, size_t size) = 0; + virtual off_t tell() const = 0; + virtual bool seek(off_t offset, int whence) = 0; +}; + +template class WriterT : public Writer { +public: + virtual ~WriterT() {} + void rwrite(const void *data, size_t size) override; + void writeLE(const void *data, size_t size) override; + void writeBE(const void *data, size_t size) override; +}; + +class Memory_Writer : public WriterT { +public: + explicit Memory_Writer(std::vector &mem) + : mem(mem), index(mem.size()) {} + void put(uint8_t byte) override; + void write(const void *data, size_t size) override; + off_t tell() const override; + bool seek(off_t offset, int whence) override; +private: + std::vector &mem; + size_t index = 0; +}; + +class Stream_Writer : public WriterT { +public: + explicit Stream_Writer(FILE *stream) + : stream(stream) {} + void put(uint8_t byte) override; + void write(const void *data, size_t size) override; + off_t tell() const override; + bool seek(off_t offset, int whence) override; +private: + FILE *stream = nullptr; +}; + +//------------------------------------------------------------------------------ +union Endian_check { + uint32_t value; + uint8_t head_byte; +}; + +//------------------------------------------------------------------------------ +template +void WriterT::rwrite(const void *data, size_t size) +{ + const uint8_t *bytes = (const uint8_t *)data; + for (size_t i = size; i-- > 0;) + static_cast(this)->put(bytes[i]); +} + +template +void WriterT::writeLE(const void *data, size_t size) +{ + switch(Endian_check{0x11223344}.head_byte) { + case 0x11: static_cast(this)->rwrite(data, size); break; + case 0x44: static_cast(this)->write(data, size); break; + default: assert(false); + } +} + +template +void WriterT::writeBE(const void *data, size_t size) +{ + switch(Endian_check{0x11223344}.head_byte) { + case 0x11: static_cast(this)->write(data, size); break; + case 0x44: static_cast(this)->rwrite(data, size); break; + default: assert(false); + } +} + +inline off_t Memory_Writer::tell() const +{ + return index; +} + +inline void Stream_Writer::put(uint8_t byte) +{ + fputc(byte, stream); +} + +inline void Stream_Writer::write(const void *data, size_t size) +{ + fwrite(data, size, 1, stream); +} + +inline bool Stream_Writer::seek(off_t offset, int whence) +{ + return fseek(stream, offset, whence) == 0; +} + +inline off_t Stream_Writer::tell() const +{ + return ftell(stream); +} + +#include +#include +#include +#include + +enum memstream_status { // make it match fmidi status codes + ms_ok, + ms_err_format, + ms_err_eof, +}; + +class memstream { +public: + memstream(const uint8_t *data, size_t length); + + size_t endpos() const; + size_t getpos() const; + memstream_status setpos(size_t off); + + memstream_status skip(size_t count); + memstream_status skipbyte(unsigned byte); + + const uint8_t *peek(size_t length); + const uint8_t *read(size_t length); + + memstream_status peekbyte(unsigned *retp); + memstream_status readbyte(unsigned *retp); + + memstream_status readintLE(uint32_t *retp, unsigned length); + memstream_status readintBE(uint32_t *retp, unsigned length); + memstream_status readvlq(uint32_t *retp); + memstream_status peekvlq(uint32_t *retp); + +private: + const uint8_t *base_ = nullptr; + size_t length_ = 0; + size_t offset_ = 0; + typedef std::tuple vlq_result; + vlq_result doreadvlq(); +}; + +//------------------------------------------------------------------------------ +inline memstream::memstream(const uint8_t *data, size_t length) + : base_(data), length_(length) { +} + +inline size_t memstream::endpos() const { + return length_; +} + +inline size_t memstream::getpos() const { + return offset_; +} +#if !defined(FMIDI_DISABLE_DESCRIBE_API) +#include +#include +#endif +#include + +double fmidi_smpte_time(const fmidi_smpte *smpte) +{ + const uint8_t *d = smpte->code; + static const double spftable[4] = { 1.0/24, 1.0/25, 1001.0/30000, 1.0/30 }; + uint8_t hh = d[0]; + double spf = spftable[(hh >> 5) & 0b11]; + hh &= 0b11111; + uint8_t mm = d[1], ss = d[2], fr = d[3], ff = d[4]; + return (fr + 0.01 * ff) * spf + ss + mm * 60 + hh * 3600; +} + +double fmidi_delta_time(double delta, uint16_t unit, uint32_t tempo) +{ + if (unit & (1 << 15)) { + unsigned tpf = unit & 0xff; // delta units per frame + unsigned fps = -(int8_t)(unit >> 8); // frames per second + return delta / (tpf * fps); + } + else { + unsigned dpqn = unit; // delta units per 1/4 note + double tpqn = 1e-6 * tempo; // 1/4 note duration + return delta * tpqn / dpqn; + } +} + +double fmidi_time_delta(double time, uint16_t unit, uint32_t tempo) +{ + if (unit & (1 << 15)) { + unsigned tpf = unit & 0xff; // delta units per frame + unsigned fps = -(int8_t)(unit >> 8); // frames per second + return time * (tpf * fps); + } + else { + unsigned dpqn = unit; // delta units per 1/4 note + double tpqn = 1e-6 * tempo; // 1/4 note duration + return time * dpqn / tpqn; + } +} + +//------------------------------------------------------------------------------ +fmidi_event_t *fmidi_event_alloc(std::vector &buf, uint32_t datalen) +{ + size_t pos = buf.size(); + size_t evsize = fmidi_event_sizeof(datalen); + size_t padsize = fmidi_event_pad(evsize); + buf.resize(buf.size() + padsize); + fmidi_event_t *event = (fmidi_event_t *)&buf[pos]; + return event; +} + +unsigned fmidi_message_sizeof(uint8_t id) +{ + if ((id >> 7) == 0) { + return 0; + } + else if ((id >> 4) != 0b1111) { + static const uint8_t sizetable[8] = { + 3, 3, 3, 3, 2, 2, 3 }; + return sizetable[(id >> 4) & 0b111]; + } + else { + static const uint8_t sizetable[16] = { + 0, 2, 3, 2, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1 }; + return sizetable[id & 0b1111]; + } +} + +//------------------------------------------------------------------------------ +class fmidi_category_t : public std::error_category { +public: + const char *name() const noexcept override + { return "fmidi"; } + std::string message(int condition) const override + { return fmidi_strerror((fmidi_status_t)condition); } +}; + +static fmidi_category_t the_category; + +const std::error_category &fmidi_category() { + return the_category; +}; + +//------------------------------------------------------------------------------ +#if !defined(FMIDI_DISABLE_DESCRIBE_API) +template +static bool fmidi_repr_meta(OutputStreamRef out, const uint8_t *data, uint32_t len) +{ + if (len <= 0) + return false; + + unsigned tag = *data++; + --len; + + printfmt_quoted qtext{(const char *)data, len}; + + switch (tag) { + default: + fmt::print(out, "(meta/unknown :tag #x{:02x})", tag); + return true; + case 0x00: { // sequence number + if (len < 2) return false; + unsigned number = (data[0] << 8) | data[1]; + fmt::print(out, "(meta/seq-number {})", number); + return true; + } + case 0x01: + fmt::print(out, "(meta/text {})", qtext); + return true; + case 0x02: + fmt::print(out, "(meta/copyright {})", qtext); + return true; + case 0x03: + fmt::print(out, "(meta/track {})", qtext); + return true; + case 0x04: + fmt::print(out, "(meta/instrument {})", qtext); + return true; + case 0x05: + fmt::print(out, "(meta/lyric {})", qtext); + return true; + case 0x06: + fmt::print(out, "(meta/marker {})", qtext); + return true; + case 0x07: + fmt::print(out, "(meta/cue-point {})", qtext); + return true; + case 0x09: + fmt::print(out, "(meta/device-name {})", qtext); + return true; + case 0x20: + if (len < 1) return false; + fmt::print(out, "(meta/channel-prefix {})", data[0]); + return true; + case 0x21: + if (len < 1) return false; + fmt::print(out, "(meta/port {})", data[0]); + return true; + case 0x2f: + case 0x3f: + fmt::print(out, "(meta/end)"); + return true; + case 0x51: { + if (len < 3) return false; + unsigned t = (data[0] << 16) | (data[1] << 8) | data[2]; + fmt::print(out, "(meta/tempo {} #|{} bpm|#)", t, 60. / (t * 1e-6)); + return true; + } + case 0x54: { + if (len < 5) return false; + static const char *fpstable[] = {"24", "25", "30000/1001", "30"}; + uint8_t hh = data[0]; + const char *fps = fpstable[(hh >> 5) & 0b11]; + fmt::print( + out, "(meta/offset {:02d} {:02d} {:02d} {:02d} {:02d}/100 :frames/second {})", + hh & 0b11111, data[1], data[2], data[3], data[4], fps); + return true; + } + case 0x58: + if (len < 4) return false; + fmt::print(out, "(meta/time-sig {} {} {} {})", + data[0], data[1], data[2], data[3]); + return true; + case 0x59: { + if (len < 2) return false; + fmt::print(out, "(meta/key-sig {} :{})", + (int8_t)data[0], data[1] ? "minor" : "major"); + return true; + } + case 0x7f: + fmt::print(out, "(meta/sequencer-specific {})", printfmt_bytes{data, len}); + return true; + } + + return false; +} + +template +static bool fmidi_repr_midi(OutputStreamRef out, const uint8_t *data, uint32_t len) +{ + if (len <= 0) + return false; + + unsigned status = *data++; + --len; + + auto b7 = [data](unsigned i) + { return data[i] & 0x7f; }; + auto b14 = [data](unsigned i) + { return (data[i] & 0x7f) | (data[i + 1] & 0x7f) << 7; }; + + if (status >> 4 == 0xf) { + unsigned op = status & 0xf; + + switch (op) { + case 0b0000: + fmt::print(out, "(sysex #xf0 {})", printfmt_bytes{data, len}); + return true; + case 0b0001: { + if (len < 1) return false; + unsigned tc = b7(0); + fmt::print(out, "(time-code {} {})", tc >> 4, tc & 0b1111); + return true; + } + case 0b0010: + if (len < 2) return false; + fmt::print(out, "(song-position {})", b14(0)); + return true; + case 0b0011: + if (len < 1) return {}; + fmt::print(out, "(song-select {})", b7(0)); + return true; + case 0b0110: + fmt::print(out, "(tune-request)"); + return true; + case 0b1000: + fmt::print(out, "(timing-clock)"); + return true; + case 0b1010: + fmt::print(out, "(start)"); + return true; + case 0b1011: + fmt::print(out, "(continue)"); + return true; + case 0b1100: + fmt::print(out, "(stop)"); + return true; + case 0b1110: + fmt::print(out, "(active-sensing)"); + return true; + case 0b1111: + fmt::print(out, "(reset)"); + return true; + } + } + else { + unsigned op = status >> 4; + unsigned ch = status & 0xf; + + switch (op) { + case 0b1000: + if (len < 2) return false; + fmt::print(out, "(note-off {} :velocity {} :channel {})", b7(0), b7(1), ch); + return true; + case 0b1001: + if (len < 2) return false; + fmt::print(out, "(note-on {} :velocity {} :channel {})", b7(0), b7(1), ch); + return true; + case 0b1010: + if (len < 2) return false; + fmt::print(out, "(poly-aftertouch {} :pressure {} :channel {})", b7(0), b7(1), ch); + return true; + case 0b1011: + if (len < 2) return false; + fmt::print(out, "(control #x{:02x} {} :channel {})", b7(0), b7(1), ch); + return true; + case 0b1100: + if (len < 1) return false; + fmt::print(out, "(program {} :channel {})", b7(0), ch); + return true; + case 0b1101: + if (len < 1) return false; + fmt::print(out, "(aftertouch :pressure {} :channel {})", b7(0), ch); + return true; + case 0b1110: + if (len < 2) return false; + fmt::print(out, "(pitch-bend {} :channel {})", b14(0), ch); + return true; + } + } + + return false; +} + +static bool fmidi_identify_sysex(const uint8_t *msg, size_t len, std::string &text) +{ + if (len < 4 || msg[0] != 0xf0 || msg[len - 1] != 0xf7) + return false; + + unsigned manufacturer = msg[1]; + unsigned deviceid = msg[2]; + + switch (manufacturer) { + case 0x7e: // universal non-realtime + if (len >= 6) { + switch ((msg[3] << 8) | msg[4]) { + case 0x0901: text = "GM system on"; return true; + case 0x0902: text = "GM system off"; return true; + } + } + break; + case 0x7f: // universal realtime + if (len >= 6) { + switch ((msg[3] << 8) | msg[4]) { + case 0x0401: text = "GM master volume"; return true; + case 0x0402: text = "GM master balance"; return true; + } + } + break; + case 0x41: // Roland + if (len >= 9) { + unsigned model = msg[3]; + unsigned mode = msg[4]; + unsigned address = (msg[5] << 16) | (msg[6] << 8) | msg[7]; + if (mode == 0x12) { // send + switch ((model << 24) | address) { + case (0x42u << 24) | 0x00007fu: text = "GS system mode set"; return true; + case (0x42u << 24) | 0x40007fu: text = "GS mode set"; return true; + default: text = fmt::format("GS parameter #x{:06x}", address); return true; + } + } + } + break; + case 0x43: // Yamaha + if (len >= 5) { + unsigned model = msg[3]; + switch((model << 8) | (deviceid & 0xf0)) + { + case (0x4c << 8) | 0x10: // XG + if (len >= 8) { + unsigned address = (msg[4] << 16) | (msg[5] << 8) | msg[6]; + switch (address) { + case 0x00007e: text = "XG system on"; return true; + default: text = fmt::format("XG parameter #x{:06x}", address); return true; + } + break; + } + } + } + break; + } + + return false; +} + +template +static void fmidi_repr_smf(OutputStreamRef out, const fmidi_smf_t &smf) +{ + const fmidi_smf_info_t *info = fmidi_smf_get_info(&smf); + fmt::print(out, "(midi-file"); + fmt::print(out, "\n :format {}", info->format); + + unsigned unit = info->delta_unit; + if (unit & (1 << 15)) + fmt::print(out, "\n :delta-unit (smpte-based :units/frame {} :frames/second {})", + unit & 0xff, -(int8_t)(unit >> 8)); + else + fmt::print(out, "\n :delta-unit (tempo-based :units/beat {})", unit); + + fmt::print(out, "\n :tracks" + "\n (", unit); + + struct RPN_Info { + unsigned lsb = 127, msb = 127; + bool nrpn = false; + }; + RPN_Info channel_rpn[16]; + + std::string strbuf; + strbuf.reserve(256); + + for (unsigned i = 0, n = info->track_count; i < n; ++i) { + fmidi_track_iter_t it; + fmidi_smf_track_begin(&it, i); + if (i > 0) + fmt::print(out, "\n "); + fmt::print(out, "(;;--- track {} ---;;", i); + while (const fmidi_event_t *evt = fmidi_smf_track_next(&smf, &it)) { + RPN_Info *rpn = nullptr; + + const uint8_t *data = evt->data; + uint32_t datalen = evt->datalen; + + if (evt->type == fmidi_event_message) { + unsigned status = data[0]; + unsigned channel = status & 0x0f; + // controllers + if (datalen == 3 && (status & 0xf0) == 0xb0) { + unsigned ctl = data[1] & 0x7f; + switch (ctl) { + case 0x62: case 0x64: // (N)RPN LSB + rpn = &channel_rpn[channel]; + rpn->lsb = data[2] & 0x7f, rpn->nrpn = ctl == 0x62; + break; + case 0x63: case 0x65: // (N)RPN MSB + rpn = &channel_rpn[channel]; + rpn->msb = data[2] & 0x7f, rpn->nrpn = ctl == 0x63; + break; + case 0x06: case 0x26: // Data Entry MSB, LSB + rpn = &channel_rpn[channel]; + break; + } + } + } + + fmt::print(out, "\n (:delta {:<5} {}", evt->delta, *evt); + if (rpn) + fmt::print(out, " #|{}RPN #x{:02x} #x{:02x}|#", + rpn->nrpn ? "N" : "", rpn->msb, rpn->lsb); + else if (fmidi_identify_sysex(data, datalen, strbuf)) + fmt::print(out, " #|{}|#", strbuf); + fmt::print(out, ")"); + } + fmt::print(out, ")"); + } + fmt::print(out, "))\n"); +} + +std::ostream &operator<<(std::ostream &out, const fmidi_smf_t &smf) +{ + fmidi_repr_smf(out, smf); + return out; +} + +void fmidi_smf_describe(const fmidi_smf_t *smf, FILE *stream) +{ + fmidi_repr_smf(stream, *smf); +} + +template +static void fmidi_repr_event(OutputStreamRef out, const fmidi_event_t &evt) +{ + const uint8_t *data = evt.data; + uint32_t len = evt.datalen; + + switch (evt.type) { + case fmidi_event_meta: { + if (!fmidi_repr_meta(out, data, len)) + fmt::print(out, "(meta/unknown)"); + break; + } + case fmidi_event_message: { + if (!fmidi_repr_midi(out, data, len)) + fmt::print(out, "(unknown)"); + break; + } + case fmidi_event_escape: { + fmt::print(out, "(raw {})", printfmt_bytes{data, len}); + break; + } + case fmidi_event_xmi_timbre: { + fmt::print(out, "(xmi/timbre :patch {} :bank {})", evt.data[0], evt.data[1]); + break; + } + case fmidi_event_xmi_branch_point: { + fmt::print(out, "(xmi/branch-point {})", evt.data[0]); + break; + } + } +} + +std::ostream &operator<<(std::ostream &out, const fmidi_event_t &evt) +{ + fmidi_repr_event(out, evt); + return out; +} + +void fmidi_event_describe(const fmidi_event_t *evt, FILE *stream) +{ + fmidi_repr_event(stream, *evt); +} + +//------------------------------------------------------------------------------ +std::ostream &operator<<(std::ostream &out, const printfmt_quoted &q) +{ + const char *text = q.text; + size_t length = q.length; + out.put('"'); + for (size_t i = 0; i < length; ++i) { + char c = text[i]; + if (c == '\\' || c == '"') out.put('\\'); + out.put(c); + } + return out.put('"'); +} + +std::ostream &operator<<(std::ostream &out, const printfmt_bytes &b) +{ + const uint8_t *data = b.data; + for (size_t i = 0, n = b.size; i < n; ++i) { + if (i > 0) out.put(' '); + fmt::print(out, "#x{:02x}", data[i]); + } + return out; +} +#endif // !defined(FMIDI_DISABLE_DESCRIBE_API) + + +thread_local fmidi_error_info_t fmidi_last_error; + +fmidi_status_t fmidi_errno() +{ + return fmidi_last_error.code; +} + +const fmidi_error_info_t *fmidi_errinfo() +{ + return &fmidi_last_error; +} + +const char *fmidi_strerror(fmidi_status_t status) +{ + switch (status) { + case fmidi_ok: return "success"; + case fmidi_err_format: return "invalid format"; + case fmidi_err_eof: return "premature end of file"; + case fmidi_err_input: return "input error"; + case fmidi_err_largefile: return "file too large"; + case fmidi_err_output: return "output error"; + } + return nullptr; +} + +//------------------------------------------------------------------------------ +void Memory_Writer::put(uint8_t byte) +{ + size_t size = mem.size(); + size_t index = this->index; + + if (index < size) + mem[index] = byte; + else + { + assert(index == size); + mem.push_back(byte); + } + + this->index = index + 1; +} + +void Memory_Writer::write(const void *data, size_t size) +{ + size_t memsize = mem.size(); + size_t index = this->index; + + const uint8_t *bytes = (const uint8_t *)data; + size_t ncopy = std::min(size, memsize - index); + std::copy(bytes, bytes + ncopy, &mem[index]); + + mem.insert(mem.end(), bytes + ncopy, bytes + size); + this->index = index + size; +} + +bool Memory_Writer::seek(off_t offset, int whence) +{ + std::make_unsigned::type uoffset(offset); + size_t size = mem.size(); + size_t index = this->index; + + switch (whence) { + case SEEK_SET: + if (uoffset > size) + return false; + this->index = uoffset; + break; + case SEEK_CUR: + if (offset >= 0) { + if (size - index < uoffset) + return false; + this->index = index + uoffset; + } + else { + if (index < uoffset) + return false; + this->index = index - uoffset; + } + break; + case SEEK_END: + if (uoffset > size) + return false; + this->index = size - uoffset; + break; + } + return true; +} + +#include "fmidi/fmidi.h" +#include +#include +#include + +struct fmidi_player_context { + fmidi_player_t *plr; + fmidi_seq_u seq; + double timepos; + double speed; + bool have_event; + fmidi_seq_event_t sqevt; + void (*cbfn)(const fmidi_event_t *, void *); + void *cbdata; + void (*finifn)(void *); + void *finidata; +}; + +struct fmidi_player { + bool running; + fmidi_player_context ctx; +}; + +fmidi_player_t *fmidi_player_new(fmidi_smf_t *smf) +{ + fmidi_player_u plr(new fmidi_player_t); + plr->running = false; + + fmidi_player_context &ctx = plr->ctx; + ctx.plr = plr.get(); + ctx.seq.reset(fmidi_seq_new(smf)); + ctx.timepos = 0; + ctx.speed = 1; + ctx.have_event = false; + ctx.cbfn = nullptr; + ctx.cbdata = nullptr; + ctx.finifn = nullptr; + ctx.finidata = nullptr; + + return plr.release(); +} + +void fmidi_player_tick(fmidi_player_t *plr, double delta) +{ + fmidi_player_context &ctx = plr->ctx; + fmidi_seq_t &seq = *ctx.seq; + void (*cbfn)(const fmidi_event_t *, void *) = ctx.cbfn; + void *cbdata = ctx.cbdata; + + double timepos = ctx.timepos; + bool have_event = ctx.have_event; + fmidi_seq_event_t &sqevt = ctx.sqevt; + + timepos += ctx.speed * delta; + + bool more = have_event || fmidi_seq_next_event(&seq, &sqevt); + if (more) { + have_event = true; + while (more && timepos > sqevt.time) { + const fmidi_event_t &event = *sqevt.event; + if (cbfn) + cbfn(&event, cbdata); + have_event = more = fmidi_seq_next_event(&seq, &sqevt); + } + } + + ctx.have_event = have_event; + ctx.timepos = timepos; + + if (!more) { + plr->running = false; + if (ctx.finifn) + ctx.finifn(ctx.finidata); + } +} + +void fmidi_player_free(fmidi_player_t *plr) +{ + delete plr; +} + +void fmidi_player_start(fmidi_player_t *plr) +{ + plr->running = true; +} + +void fmidi_player_stop(fmidi_player_t *plr) +{ + plr->running = false; +} + +void fmidi_player_rewind(fmidi_player_t *plr) +{ + fmidi_player_context &ctx = plr->ctx; + fmidi_seq_rewind(ctx.seq.get()); + ctx.timepos = 0; + ctx.have_event = false; +} + +bool fmidi_player_running(const fmidi_player_t *plr) +{ + return plr->running; +} + +double fmidi_player_current_time(const fmidi_player_t *plr) +{ + return plr->ctx.timepos; +} + +void fmidi_player_goto_time(fmidi_player_t *plr, double time) +{ + fmidi_player_context &ctx = plr->ctx; + fmidi_seq_t &seq = *ctx.seq; + + uint8_t programs[16]; + uint8_t controls[16 * 128]; + std::fill_n(programs, 16, 0); + std::fill_n(controls, 16 * 128, 255); + + fmidi_player_rewind(plr); + + for (fmidi_seq_event_t sqevt; + fmidi_seq_peek_event(&seq, &sqevt) && sqevt.time < time;) { + const fmidi_event_t &evt = *sqevt.event; + if (evt.type == fmidi_event_message) { + uint8_t status = evt.data[0]; + if (status >> 4 == 0b1100 && evt.datalen == 2) { // program change + uint8_t channel = status & 0xf; + programs[channel] = evt.data[1] & 127; + } + else if (status >> 4 == 0b1011 && evt.datalen == 3) { // control change + uint8_t channel = status & 0xf; + uint8_t id = evt.data[1] & 127; + controls[channel * 128 + id] = evt.data[2] & 127; + } + } + fmidi_seq_next_event(&seq, nullptr); + } + + ctx.timepos = time; + + if (ctx.cbfn) { + uint8_t evtbuf[fmidi_event_sizeof(3)]; + fmidi_event_t *evt = (fmidi_event_t *)evtbuf; + evt->type = fmidi_event_message; + evt->delta = 0; + + for (unsigned c = 0; c < 16; ++c) { + // all sound off + evt->datalen = 3; + evt->data[0] = (0b1011 << 4) | c; + evt->data[1] = 120; + evt->data[2] = 0; + ctx.cbfn(evt, ctx.cbdata); + // reset all controllers + evt->datalen = 3; + evt->data[0] = (0b1011 << 4) | c; + evt->data[1] = 121; + evt->data[2] = 0; + ctx.cbfn(evt, ctx.cbdata); + // program change + evt->datalen = 2; + evt->data[0] = (0b1100 << 4) | c; + evt->data[1] = programs[c]; + ctx.cbfn(evt, ctx.cbdata); + // control change + for (unsigned id = 0; id < 128; ++id) { + uint8_t val = controls[c * 128 + id]; + if (val < 128) { + evt->datalen = 3; + evt->data[0] = (0b1011 << 4) | c; + evt->data[1] = id; + evt->data[2] = val; + ctx.cbfn(evt, ctx.cbdata); + } + } + } + } +} + +double fmidi_player_current_speed(const fmidi_player_t *plr) +{ + return plr->ctx.speed; +} + +void fmidi_player_set_speed(fmidi_player_t *plr, double speed) +{ + plr->ctx.speed = speed; +} + +void fmidi_player_event_callback( + fmidi_player_t *plr, void (*cbfn)(const fmidi_event_t *, void *), void *cbdata) +{ + fmidi_player_context &ctx = plr->ctx; + ctx.cbfn = cbfn; + ctx.cbdata = cbdata; +} + +void fmidi_player_finish_callback( + fmidi_player_t *plr, void (*cbfn)(void *), void *cbdata) +{ + fmidi_player_context &ctx = plr->ctx; + ctx.finifn = cbfn; + ctx.finidata = cbdata; +} + +#include "fmidi/fmidi.h" +#include +#include + +struct fmidi_seq_timing { + fmidi_smpte startoffset; + uint32_t tempo; +}; + +struct fmidi_seq_pending_event { + const fmidi_event_t *event; + double delta; +}; + +struct fmidi_seq_track_info { + double timepos; + fmidi_track_iter_t iter; + fmidi_seq_pending_event next; + std::shared_ptr timing; +}; + +struct fmidi_seq { + const fmidi_smf_t *smf; + std::unique_ptr track; +}; + +static double fmidi_convert_delta( + const fmidi_seq_t *seq, uint16_t trkno, double delta) +{ + uint16_t unit = fmidi_smf_get_info(seq->smf)->delta_unit; + uint32_t tempo = seq->track[trkno].timing->tempo; + return fmidi_delta_time(delta, unit, tempo); +} + +fmidi_seq_t *fmidi_seq_new(const fmidi_smf_t *smf) +{ + std::unique_ptr seq(new fmidi_seq_t); + seq->smf = smf; + + const fmidi_smf_info_t *info = fmidi_smf_get_info(smf); + uint16_t format = info->format; + uint16_t ntracks = info->track_count; + + seq->track.reset(new fmidi_seq_track_info[ntracks]); + + for (unsigned i = 0; i < ntracks; ++i) { + fmidi_seq_track_info &track = seq->track[i]; + + std::shared_ptr timing; + if (format == 2 || i == 0) + timing.reset(new fmidi_seq_timing); + else + timing = seq->track[0].timing; + track.timing = timing; + } + + fmidi_seq_rewind(seq.get()); + return seq.release(); +} + +void fmidi_seq_free(fmidi_seq_t *seq) +{ + delete seq; +} + +void fmidi_seq_rewind(fmidi_seq_t *seq) +{ + const fmidi_smf_t *smf = seq->smf; + const fmidi_smf_info_t *info = fmidi_smf_get_info(smf); + uint16_t ntracks = info->track_count; + bool independent_multi_track = + ntracks > 1 && seq->track[0].timing != seq->track[1].timing; + + for (unsigned i = 0; i < ntracks; ++i) { + fmidi_seq_track_info &track = seq->track[i]; + std::shared_ptr timing = track.timing; + fmidi_smpte &startoffset = timing->startoffset; + fmidi_smf_track_begin(&track.iter, i); + track.next.event = nullptr; + memset(startoffset.code, 0, 5); + timing->tempo = 500000; + track.timepos = fmidi_smpte_time(&startoffset); + } + + for (unsigned i = 0; i < ntracks; ++i) { + fmidi_seq_track_info &track = seq->track[i]; + std::shared_ptr timing = track.timing; + fmidi_smpte &startoffset = timing->startoffset; + + const fmidi_event_t *evt; + fmidi_track_iter_t it; + fmidi_smf_track_begin(&it, i); + while ((evt = fmidi_smf_track_next(smf, &it)) && + evt->delta == 0 && evt->type == fmidi_event_meta) { + uint8_t id = evt->data[0]; + if (id == 0x54 && evt->datalen == 6) { // SMPTE offset + // disregard SMPTE offset for format 1 MIDI and similar + if (independent_multi_track) + memcpy(startoffset.code, &evt->data[1], 5); + } + if (id == 0x51 && evt->datalen == 4) { // set tempo + const uint8_t *d24 = &evt->data[1]; + timing->tempo = (d24[0] << 16) | (d24[1] << 8) | d24[2]; + } + } + track.timepos = fmidi_smpte_time(&startoffset); + } +} + +static fmidi_seq_pending_event *fmidi_seq_track_current_event( + fmidi_seq_t *seq, uint16_t trkno) +{ + const fmidi_smf_t *smf = seq->smf; + fmidi_seq_track_info &track = seq->track[trkno]; + + fmidi_seq_pending_event *pending; + if (track.next.event) + return &track.next; + + const fmidi_event_t *evt = fmidi_smf_track_next(smf, &track.iter); + if (!evt) + return nullptr; + + if (evt->type == fmidi_event_meta) { + uint8_t tag = evt->data[0]; + if (tag == 0x2f || tag == 0x3f) // end of track + return nullptr; // stop now even if the final event has delta + } + + pending = &track.next; + pending->event = evt; + pending->delta = evt->delta; + return pending; +} + +static int fmidi_seq_next_track(fmidi_seq_t *seq) +{ + const fmidi_smf_info_t *info = fmidi_smf_get_info(seq->smf); + unsigned ntracks = info->track_count; + + unsigned trkno = 0; + fmidi_seq_pending_event *pevt; + + pevt = fmidi_seq_track_current_event(seq, 0); + while (!pevt && ++trkno < ntracks) + pevt = fmidi_seq_track_current_event(seq, trkno); + + if (!pevt) + return -1; + + double nearest = fmidi_convert_delta(seq, trkno, pevt->delta) + + seq->track[trkno].timepos; + for (unsigned i = trkno + 1; i < ntracks; ++i) { + if ((pevt = fmidi_seq_track_current_event(seq, i))) { + double time = fmidi_convert_delta(seq, i, pevt->delta) + + seq->track[i].timepos; + if (time < nearest) { + trkno = i; + nearest = time; + } + } + } + + return trkno; +} + +bool fmidi_seq_peek_event(fmidi_seq_t *seq, fmidi_seq_event_t *sqevt) +{ + unsigned trkno = fmidi_seq_next_track(seq); + if ((int)trkno == -1) + return false; + + fmidi_seq_track_info &nexttrk = seq->track[trkno]; + + const fmidi_seq_pending_event *pevt = + fmidi_seq_track_current_event(seq, trkno); + if (!pevt) + return false; + + if (sqevt) { + sqevt->time = fmidi_convert_delta(seq, trkno, pevt->delta) + nexttrk.timepos; + sqevt->track = trkno; + sqevt->event = pevt->event; + } + + return true; +} + +static void fmidi_seq_track_advance_by( + fmidi_seq_t *seq, unsigned trkno, double time) +{ + const fmidi_smf_t *smf = seq->smf; + const fmidi_smf_info_t *info = fmidi_smf_get_info(smf); + uint16_t unit = info->delta_unit; + fmidi_seq_track_info &trk = seq->track[trkno]; + fmidi_seq_timing &tim = *trk.timing; + + fmidi_seq_pending_event *evt = fmidi_seq_track_current_event(seq, trkno); + if (evt) + evt->delta -= fmidi_time_delta(time, unit, tim.tempo); + trk.timepos += time; +} + +bool fmidi_seq_next_event(fmidi_seq_t *seq, fmidi_seq_event_t *sqevt) +{ + fmidi_seq_event_t pltmp; + sqevt = sqevt ? sqevt : &pltmp; + + if (!fmidi_seq_peek_event(seq, sqevt)) + return false; + + double time = sqevt->time; + unsigned trkno = sqevt->track; + const fmidi_event_t *evt = sqevt->event; + fmidi_seq_track_info &trk = seq->track[trkno]; + + const fmidi_smf_t *smf = seq->smf; + const fmidi_smf_info_t *info = fmidi_smf_get_info(smf); + unsigned ntracks = info->track_count; + double elapsed = time - trk.timepos; + + for (unsigned i = 0; i < ntracks; ++i) + if (i != trkno) + fmidi_seq_track_advance_by(seq, i, elapsed); + + if (evt->type == fmidi_event_meta) { + if (evt->data[0] == 0x51 && evt->datalen == 4) { // set tempo + const uint8_t *d24 = &evt->data[1]; + trk.timing->tempo = (d24[0] << 16) | (d24[1] << 8) | d24[2]; + } + } + + trk.timepos = time; + trk.next.event = nullptr; + return true; +} + + +#include +#include + +//////////////////////// +// FILE PATH ENCODING // +//////////////////////// + +FILE *fmidi_fopen(const char *path, const char *mode); + +/////////////// +// FILE RAII // +/////////////// + +struct FILE_deleter; +typedef std::unique_ptr unique_FILE; + +struct FILE_deleter { + void operator()(FILE *stream) const + { fclose(stream); } +}; +#if defined(_WIN32) +# include +# include +# include +#endif + +FILE *fmidi_fopen(const char *path, const char *mode) +{ +#if !defined(_WIN32) + return fopen(path, mode); +#else + auto toWideString = [](const char *utf8) -> wchar_t * { + unsigned wsize = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, nullptr, 0); + if (wsize == 0) + return nullptr; + wchar_t *wide = new wchar_t[wsize]; + wsize = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, wsize); + if (wsize == 0) { + delete[] wide; + return nullptr; + } + return wide; + }; + std::unique_ptr wpath(toWideString(path)); + if (!wpath) { + errno = EINVAL; + return nullptr; + } + std::unique_ptr wmode(toWideString(mode)); + if (!wmode) { + errno = EINVAL; + return nullptr; + } + return _wfopen(wpath.get(), wmode.get()); +#endif +} + + +memstream_status memstream::setpos(size_t off) +{ + if (off > length_) + return ms_err_eof; + offset_ = off; + return ms_ok; +} + +memstream_status memstream::skip(size_t count) +{ + if (length_ - offset_ < count) + return ms_err_eof; + offset_ += count; + return ms_ok; +} + +memstream_status memstream::skipbyte(unsigned byte) +{ + unsigned otherbyte; + memstream_status status = peekbyte(&otherbyte); + if (status) + return status; + if (byte != otherbyte) + return ms_err_format; + ++offset_; + return ms_ok; +} + +const uint8_t *memstream::peek(size_t length) +{ + if (length > length_ - offset_) + return nullptr; + return base_ + offset_; +} + +const uint8_t *memstream::read(size_t length) +{ + const uint8_t *ptr = peek(length); + if (ptr) + offset_ += length; + return ptr; +} + +memstream_status memstream::peekbyte(unsigned *retp) +{ + if (length_ <= offset_) + return ms_err_eof; + if (retp) + *retp = base_[offset_]; + return ms_ok; +} + +memstream_status memstream::readbyte(unsigned *retp) +{ + memstream_status ret = peekbyte(retp); + if (ret) + return ret; + ++offset_; + return ms_ok; +} + +memstream_status memstream::readintLE(uint32_t *retp, unsigned length) +{ + const uint8_t *ptr = read(length); + if (!ptr) + return ms_err_eof; + uint32_t ret = 0; + for (unsigned i = length; i-- > 0;) + ret = (ret << 8) | ptr[i]; + if (retp) + *retp = ret; + return ms_ok; +} + +memstream_status memstream::readintBE(uint32_t *retp, unsigned length) +{ + const uint8_t *ptr = read(length); + if (!ptr) + return ms_err_eof; + uint32_t ret = 0; + for (unsigned i = 0; i < length; ++i) + ret = (ret << 8) | ptr[i]; + if (retp) + *retp = ret; + return ms_ok; +} + +memstream_status memstream::readvlq(uint32_t *retp) +{ + memstream_status ret; + uint32_t value; + unsigned length; + std::tie(ret, value, length) = doreadvlq(); + offset_ += length; + if (retp) + *retp = value; + return ret; +} + +memstream_status memstream::peekvlq(uint32_t *retp) +{ + memstream_status ret; + uint32_t value; + unsigned length; + std::tie(ret, value, length) = doreadvlq(); + if (retp) + *retp = value; + return ret; +} + +memstream::vlq_result memstream::doreadvlq() +{ + uint32_t ret = 0; + unsigned length; + bool cont = true; + for (length = 0; cont && length < 4; ++length) { + if (offset_ + length >= length_) + return vlq_result{ms_err_eof, 0, 0}; + uint8_t byte = base_[offset_ + length]; + ret = (ret << 7) | (byte & ((1u << 7) - 1)); + cont = byte & (1u << 7); + } + if (cont) + return vlq_result{ms_err_format, 0, 0}; + return vlq_result{ms_ok, ret, length}; +} + +#include "fmidi/fmidi.h" +#include +#include +#include +#if defined(_WIN32) +# define fileno _fileno +#endif + +#define FOURCC(x) \ + (((uint8_t)(x)[0] << 24) | \ + ((uint8_t)(x)[1] << 16) | \ + ((uint8_t)(x)[2] << 8) | \ + ((uint8_t)(x)[3])) + +struct fmidi_xmi_timb { + uint32_t patch; + uint32_t bank; +}; + +struct fmidi_xmi_rbrn { + uint32_t id; + uint32_t dest; +}; + +struct fmidi_xmi_note { + uint32_t delta; + uint8_t channel; + uint8_t note; + uint8_t velo; +}; + +static bool operator<(const fmidi_xmi_note &a, const fmidi_xmi_note &b) +{ + return a.delta < b.delta; +} + +static void fmidi_xmi_emit_noteoffs( + uint32_t *pdelta, std::vector ¬eoffs, + std::vector &evbuf) +{ + uint32_t delta = *pdelta; + + std::sort(noteoffs.begin(), noteoffs.end()); + + size_t i = 0; + size_t n = noteoffs.size(); + + for (; i < n; ++i) { + fmidi_xmi_note xn = noteoffs[i]; + if (delta < xn.delta) + break; + + fmidi_event_t *event = fmidi_event_alloc(evbuf, 3); + event->type = fmidi_event_message; + event->delta = xn.delta; + event->datalen = 3; + + uint8_t *data = event->data; + data[0] = 0x80 | xn.channel; + data[1] = xn.note; + data[2] = xn.velo; + + delta -= xn.delta; + for (size_t k = i + 1; k < n; ++k) + noteoffs[k].delta -= xn.delta; + } + + size_t j = 0; + for (; i < n; ++i) { + fmidi_xmi_note xn = noteoffs[i]; + noteoffs[j++] = xn; + } + noteoffs.resize(j); + + *pdelta = delta; +} + +static bool fmidi_xmi_read_events( + memstream &mb, fmidi_raw_track &track, + const fmidi_xmi_timb *timb, uint32_t timb_count, + const fmidi_xmi_rbrn *rbrn, uint32_t rbrn_count) +{ + memstream_status ms; + std::vector evbuf; + evbuf.reserve(8192); + + std::vector noteoffs; + noteoffs.reserve(128); + + for (uint32_t i = 0; i < timb_count; ++i) { + fmidi_event_t *event = fmidi_event_alloc(evbuf, 2); + event->type = fmidi_event_xmi_timbre; + event->delta = 0; + event->datalen = 2; + uint8_t * data = event->data; + data[0] = timb[i].patch; + data[1] = timb[i].bank; + } + + bool eot = false; + while (!eot) { + uint32_t delta = 0; + unsigned status = 0; + + size_t branch = ~(size_t)0; + for (uint32_t i = 0; i < rbrn_count && branch == ~(size_t)0; ++i) { + if (rbrn[i].dest == mb.getpos()) + branch = i; + } + + while (!(status & 128)) { + if ((ms = mb.readbyte(&status))) + RET_FAIL(false, (fmidi_status)ms); + delta += (status & 128) ? 0 : status; + } + + if (branch != ~(size_t)0) { + fmidi_event_t *event = fmidi_event_alloc(evbuf, 1); + event->type = fmidi_event_xmi_branch_point; + event->delta = delta; + event->datalen = 1; + event->data[0] = rbrn[branch].id; + delta = 0; + } + + fmidi_xmi_emit_noteoffs(&delta, noteoffs, evbuf); + + if (status == 0xff) { + unsigned type; + uint32_t length; + if ((ms = mb.readbyte(&type)) || + (ms = mb.readvlq(&length))) + RET_FAIL(false, (fmidi_status)ms); + + const uint8_t *data = mb.read(length); + if (!data) + RET_FAIL(false, fmidi_err_eof); + + eot = type == 0x2F; + + if (eot) { + // emit later + } + else if (type == 0x51) { + // don't emit tempo change + } + else { + fmidi_event_t *event = fmidi_event_alloc(evbuf, length + 1); + event->type = fmidi_event_meta; + event->delta = delta; + event->datalen = length + 1; + event->data[0] = type; + memcpy(event->data + 1, data, length); + } + } + else if (status == 0xf0) { + uint32_t length; + if ((ms = mb.readvlq(&length))) + RET_FAIL(false, (fmidi_status)ms); + + const uint8_t *data = mb.read(length); + if (!data) + RET_FAIL(false, fmidi_err_eof); + + fmidi_event_t *event = fmidi_event_alloc(evbuf, length + 1); + event->type = fmidi_event_message; + event->delta = delta; + event->datalen = length + 1; + event->data[0] = 0xf0; + memcpy(event->data + 1, data, length); + } + else if (status == 0xf7) { + RET_FAIL(false, fmidi_err_format); + } + else if ((status & 0xf0) == 0x90) { + mb.setpos(mb.getpos() - 1); + const uint8_t *data = mb.read(3); + if (!data) + RET_FAIL(false, fmidi_err_eof); + + uint32_t interval; + if ((ms = mb.readvlq(&interval))) + RET_FAIL(false, (fmidi_status)ms); + + fmidi_event_t *event = fmidi_event_alloc(evbuf, 3); + event->type = fmidi_event_message; + event->delta = delta; + event->datalen = 3; + memcpy(event->data, data, 3); + + fmidi_xmi_note noteoff; + noteoff.delta = interval; + noteoff.channel = data[0] & 15; + noteoff.note = data[1]; + noteoff.velo = data[2]; + noteoffs.push_back(noteoff); + } + else { + unsigned length = fmidi_message_sizeof(status); + mb.setpos(mb.getpos() - 1); + const uint8_t *data = mb.read(length); + if (!data) + RET_FAIL(false, fmidi_err_eof); + + fmidi_event_t *event = fmidi_event_alloc(evbuf, length); + event->type = fmidi_event_message; + event->delta = delta; + event->datalen = length; + memcpy(event->data, data, length); + } + } + + { + uint32_t delta = UINT32_MAX; + fmidi_xmi_emit_noteoffs(&delta, noteoffs, evbuf); + } + + { + fmidi_event_t *event = fmidi_event_alloc(evbuf, 1); + event->type = fmidi_event_meta; + event->delta = 0; + event->datalen = 1; + event->data[0] = 0x2F; + } + + uint32_t evdatalen = track.length = evbuf.size(); + uint8_t *evdata = new uint8_t[evdatalen]; + track.data.reset(evdata); + memcpy(evdata, evbuf.data(), evdatalen); + + return true; +} + +static bool fmidi_xmi_read_track(memstream &mb, fmidi_raw_track &track) +{ + memstream_status ms; + + const uint8_t *fourcc; + if (!(fourcc = mb.read(4))) + RET_FAIL(false, fmidi_err_eof); + if (memcmp(fourcc, "FORM", 4)) + RET_FAIL(false, fmidi_err_format); + + uint32_t formsize; + if ((ms = mb.readintBE(&formsize, 4))) + RET_FAIL(false, (fmidi_status)ms); + + const uint8_t *formdata = mb.read(formsize); + if (!formdata) + RET_FAIL(false, fmidi_err_eof); + memstream mbform(formdata, formsize); + + if (!(fourcc = mbform.read(4))) + RET_FAIL(false, fmidi_err_eof); + if (memcmp(fourcc, "XMID", 4)) + RET_FAIL(false, fmidi_err_format); + + std::unique_ptr timb; + uint32_t timb_count = 0; + std::unique_ptr rbrn; + uint32_t rbrn_count = 0; + + while (mbform.getpos() < mbform.endpos()) { + if (!(fourcc = mbform.read(4))) + RET_FAIL(false, fmidi_err_eof); + + uint32_t chunksize; + if ((ms = mbform.readintBE(&chunksize, 4))) + RET_FAIL(false, (fmidi_status)ms); + + const uint8_t *chunkdata = mbform.read(chunksize); + if (!chunkdata) + RET_FAIL(false, fmidi_err_eof); + memstream mbchunk(chunkdata, chunksize); + + switch (FOURCC(fourcc)) { + case FOURCC("TIMB"): { + if ((ms = mbchunk.readintLE(&timb_count, 2))) + RET_FAIL(false, (fmidi_status)ms); + + timb.reset(new fmidi_xmi_timb[timb_count]); + for (uint32_t i = 0; i < timb_count; ++i) { + if ((ms = mbchunk.readintLE(&timb[i].patch, 1)) || + (ms = mbchunk.readintLE(&timb[i].bank, 1))) + RET_FAIL(false, (fmidi_status)ms); + } + + break; + } + case FOURCC("RBRN"): { + if ((ms = mbchunk.readintLE(&rbrn_count, 2))) + RET_FAIL(false, (fmidi_status)ms); + + rbrn.reset(new fmidi_xmi_rbrn[rbrn_count]); + for (uint32_t i = 0; i < rbrn_count; ++i) { + if ((ms = mbchunk.readintLE(&rbrn[i].id, 2)) || + (ms = mbchunk.readintLE(&rbrn[i].dest, 4))) + RET_FAIL(false, (fmidi_status)ms); + if (rbrn[i].id >= 128) + RET_FAIL(false, fmidi_err_format); + } + + break; + } + case FOURCC("EVNT"): + if (!fmidi_xmi_read_events( + mbchunk, track, + timb.get(), timb_count, rbrn.get(), rbrn_count)) + return false; + + break; + } + + if (mb.getpos() & 1) { + if ((ms = mb.skip(1))) + RET_FAIL(false, (fmidi_status)ms); + } + } + + return true; +} + +uint32_t fmidi_xmi_update_unit(fmidi_smf_t *smf) +{ + uint32_t res = 1; + const fmidi_event_t *evt; + fmidi_track_iter_t it; + fmidi_smf_track_begin(&it, 0); + bool found = false; + while (!found && (evt = fmidi_smf_track_next(smf, &it))) { + if (evt->type == fmidi_event_meta) { + uint8_t id = evt->data[0]; + if (id == 0x51 && evt->datalen == 4) { // set tempo + const uint8_t *d24 = &evt->data[1]; + uint32_t tempo = (d24[0] << 16) | (d24[1] << 8) | d24[2]; + res = 3; + smf->info.delta_unit = tempo * res * 120 / 1000000; + found = true; + } + } + } + return res; +} + +fmidi_smf_t *fmidi_xmi_mem_read(const uint8_t *data, size_t length) +{ + const uint8_t header[] = { + 'F', 'O', 'R', 'M', 0, 0, 0, 14, + 'X', 'D', 'I', 'R', 'I', 'N', 'F', 'O', 0, 0, 0, 2 + }; + + const uint8_t *start = std::search( + data, data + length, header, header + sizeof(header)); + if (start == data + length) + RET_FAIL(nullptr, fmidi_err_format); + + length = length - (start - data); + data = start; + + // ensure padding to even size (The Lost Vikings) + std::unique_ptr padded; + if (length & 1) { + padded.reset(new uint8_t[length + 1]); + memcpy(padded.get(), data, length); + padded[length] = 0; + data = padded.get(); + length = length + 1; + } + + memstream mb(data + sizeof(header), length - sizeof(header)); + memstream_status ms; + + uint32_t ntracks; + if ((ms = mb.readintLE(&ntracks, 2))) + RET_FAIL(nullptr, (fmidi_status)ms); + if (ntracks < 1) + RET_FAIL(nullptr, fmidi_err_format); + + const uint8_t *fourcc; + if (!(fourcc = mb.read(4))) + RET_FAIL(nullptr, fmidi_err_eof); + if (memcmp(fourcc, "CAT ", 4)) + RET_FAIL(nullptr, fmidi_err_format); + + uint32_t catsize; + if ((ms = mb.readintBE(&catsize, 4))) + RET_FAIL(nullptr, (fmidi_status)ms); + if (mb.endpos() - mb.getpos() < catsize) + RET_FAIL(nullptr, fmidi_err_eof); + + if (!(fourcc = mb.read(4))) + RET_FAIL(nullptr, fmidi_err_eof); + if (memcmp(fourcc, "XMID", 4)) + RET_FAIL(nullptr, fmidi_err_format); + + fmidi_smf_u smf(new fmidi_smf); + smf->info.format = (ntracks > 1) ? 2 : 0; + smf->info.track_count = ntracks; + smf->info.delta_unit = 60; + smf->track.reset(new fmidi_raw_track[ntracks]); + + for (uint32_t i = 0; i < ntracks; ++i) { + if (!fmidi_xmi_read_track(mb, smf->track[i])) + return nullptr; + if (mb.getpos() & 1) { + if ((ms = mb.skip(1))) + RET_FAIL(nullptr, (fmidi_status)ms); + } + } + + uint32_t res = fmidi_xmi_update_unit(smf.get()); + if (res == 0) + return nullptr; + + for (uint32_t i = 0; i < ntracks; ++i) { + fmidi_track_iter_t it; + fmidi_smf_track_begin(&it, i); + fmidi_event_t *event; + while ((event = const_cast( + fmidi_smf_track_next(smf.get(), &it)))) { + event->delta *= res; + } + } + + return smf.release(); +} + +fmidi_smf_t *fmidi_xmi_file_read(const char *filename) +{ + unique_FILE fh(fmidi_fopen(filename, "rb")); + if (!fh) + RET_FAIL(nullptr, fmidi_err_input); + + fmidi_smf_t *smf = fmidi_xmi_stream_read(fh.get()); + return smf; +} + +fmidi_smf_t *fmidi_xmi_stream_read(FILE *stream) +{ + struct stat st; + size_t length; + + rewind(stream); + + if (fstat(fileno(stream), &st) != 0) + RET_FAIL(nullptr, fmidi_err_input); + + length = st.st_size; + if (length > fmidi_file_size_limit) + RET_FAIL(nullptr, fmidi_err_largefile); + + bool pad = length & 1; + std::unique_ptr buf(new uint8_t[length + pad]); + if (!fread(buf.get(), length, 1, stream)) + RET_FAIL(nullptr, fmidi_err_input); + if (pad) + buf[length] = 0; + + fmidi_smf_t *smf = fmidi_xmi_mem_read(buf.get(), length + pad); + return smf; +} + +#include "fmidi/fmidi.h" +#include +#include +#include +#include +#include +#if defined(_WIN32) +# define fileno _fileno +#endif + +const fmidi_smf_info_t *fmidi_smf_get_info(const fmidi_smf_t *smf) +{ + return &smf->info; +} + +double fmidi_smf_compute_duration(const fmidi_smf_t *smf) +{ + double duration = 0; + fmidi_seq_u seq(fmidi_seq_new(smf)); + fmidi_seq_event_t sqevt; + while (fmidi_seq_next_event(seq.get(), &sqevt)) + duration = sqevt.time; + return duration; +} + +static fmidi_event_t *fmidi_read_meta_event( + memstream &mb, std::vector &evbuf, uint32_t delta) +{ + memstream_status ms; + unsigned id; + if ((ms = mb.readbyte(&id))) + RET_FAIL(nullptr, (fmidi_status)ms); + + uint32_t datalen; + const uint8_t *data; + if (id == 0x2f || id == 0x3f) { // end of track + if (mb.skipbyte(0)) { + // omitted final null byte in some broken files + } + else { + // repeated end of track events + for (bool again = true; again;) { + size_t offset = mb.getpos(); + again = !mb.readvlq(nullptr) && !mb.skipbyte(0xff) && + (!mb.skipbyte(0x2f) || !mb.skipbyte(0x3f)); + if (!again) + mb.setpos(offset); + else + again = !mb.skipbyte(0); + } + } + datalen = 0; + data = nullptr; + } + else { + if ((ms = mb.readvlq(&datalen))) + RET_FAIL(nullptr, (fmidi_status)ms); + if (!(data = mb.read(datalen))) + RET_FAIL(nullptr, fmidi_err_eof); + } + fmidi_event_t *evt = fmidi_event_alloc(evbuf, datalen + 1); + evt->type = fmidi_event_meta; + evt->delta = delta; + evt->datalen = datalen + 1; + evt->data[0] = id; + memcpy(&evt->data[1], data, datalen); + return evt; +} + +static fmidi_event_t *fmidi_read_escape_event( + memstream &mb, std::vector &evbuf, uint32_t delta) +{ + memstream_status ms; + uint32_t datalen; + const uint8_t *data; + if ((ms = mb.readvlq(&datalen))) + RET_FAIL(nullptr, (fmidi_status)ms); + if (!(data = mb.read(datalen))) + RET_FAIL(nullptr, fmidi_err_eof); + + fmidi_event_t *evt = fmidi_event_alloc(evbuf, datalen); + evt->type = fmidi_event_escape; + evt->delta = delta; + evt->datalen = datalen; + memcpy(&evt->data[0], data, datalen); + return evt; +} + +static fmidi_event_t *fmidi_read_sysex_event( + memstream &mb, std::vector &evbuf, uint32_t delta) +{ + memstream_status ms; + fmidi_event_t *evt; + + std::vector syxbuf; + syxbuf.reserve(256); + syxbuf.push_back(0xf0); + + uint32_t partlen; + const uint8_t *part; + if ((ms = mb.readvlq(&partlen))) + RET_FAIL(nullptr, (fmidi_status)ms); + if (!(part = mb.read(partlen))) + RET_FAIL(nullptr, fmidi_err_eof); + + bool term = false; + const uint8_t *endp; + + // handle files having multiple concatenated sysex events in one + while ((endp = (const uint8_t *)memchr(part, 0xf7, partlen))) { + syxbuf.insert(syxbuf.end(), part, endp + 1); + + evt = fmidi_event_alloc(evbuf, syxbuf.size()); + evt->type = fmidi_event_message; + evt->delta = delta; + evt->datalen = syxbuf.size(); + memcpy(&evt->data[0], &syxbuf[0], syxbuf.size()); + + uint32_t reallen = endp + 1 - part; + partlen -= reallen; + part += reallen; + + if (partlen == 0) + return evt; + + if (part[0] != 0xf0) { +#if 1 + // trailing garbage, ignore +#else + // sierra: incorrect length covering part of the next event. repair + mb.setpos(mb.getpos() - partlen); +#endif + return evt; + } + ++part; + --partlen; + + syxbuf.clear(); + syxbuf.push_back(0xf0); + } + + // handle the rest in multiple parts (Casio MIDI) + while (!term) { + term = endp; + if (term && endp + 1 != part + partlen) { + // ensure no excess bytes + RET_FAIL(nullptr, fmidi_err_format); + } + syxbuf.insert(syxbuf.end(), part, part + partlen); + + if (!term) { + size_t offset = mb.getpos(); + bool havecont = false; + + uint32_t contdelta; + unsigned id; + if (!mb.readvlq(&contdelta) && !mb.readbyte(&id)) { + // raw sequence incoming? use it as next sysex part + havecont = id == 0xf7; + } + if (havecont) { + if ((ms = mb.readvlq(&partlen))) + RET_FAIL(nullptr, (fmidi_status)ms); + if (!(part = mb.read(partlen))) + RET_FAIL(nullptr, fmidi_err_eof); + endp = (const uint8_t *)memchr(part, 0xf7, partlen); + } + else { + // no next part? assume unfinished message and repair + mb.setpos(offset); + syxbuf.push_back(0xf7); + term = true; + } + } + } + + evt = fmidi_event_alloc(evbuf, syxbuf.size()); + evt->type = fmidi_event_message; + evt->delta = delta; + evt->datalen = syxbuf.size(); + memcpy(&evt->data[0], &syxbuf[0], syxbuf.size()); + return evt; +} + +static fmidi_event_t *fmidi_read_message_event( + memstream &mb, std::vector &evbuf, unsigned id, uint32_t delta) +{ + uint32_t datalen = fmidi_message_sizeof(id); + const uint8_t *data; + if (datalen <= 0) + RET_FAIL(nullptr, fmidi_err_format); + if (!(data = mb.read(datalen - 1))) + RET_FAIL(nullptr, fmidi_err_eof); + + fmidi_event_t *evt = fmidi_event_alloc(evbuf, datalen); + evt->type = fmidi_event_message; + evt->delta = delta; + evt->datalen = datalen; + evt->data[0] = id; + memcpy(&evt->data[1], data, datalen - 1); + return evt; +} + +static fmidi_event_t *fmidi_read_event( + memstream &mb, std::vector &evbuf, uint8_t *runstatus) +{ + memstream_status ms; + uint32_t delta; + unsigned id; + if ((ms = mb.readvlq(&delta))) + RET_FAIL(nullptr, (fmidi_status)ms); + if ((ms = mb.readbyte(&id))) + RET_FAIL(nullptr, (fmidi_status)ms); + + fmidi_event_t *evt; + if (id == 0xff) { + evt = fmidi_read_meta_event(mb, evbuf, delta); + } + else if (id == 0xf7) { + evt = fmidi_read_escape_event(mb, evbuf, delta); + } + else if (id == 0xf0) { + evt = fmidi_read_sysex_event(mb, evbuf, delta); + } + else { + if (id & 128) { + *runstatus = id; + } + else { + id = *runstatus; + mb.setpos(mb.getpos() - 1); + } + evt = fmidi_read_message_event(mb, evbuf, id, delta); + } + + return evt; +} + +void fmidi_smf_track_begin(fmidi_track_iter_t *it, uint16_t track) +{ + it->track = track; + it->index = 0; +} + +const fmidi_event_t *fmidi_smf_track_next( + const fmidi_smf_t *smf, fmidi_track_iter_t *it) +{ + if (it->track >= smf->info.track_count) + return nullptr; + + const fmidi_raw_track &trk = smf->track[it->track]; + const uint8_t *trkdata = trk.data.get(); + + const fmidi_event_t *evt = (const fmidi_event_t *)&trkdata[it->index]; + if ((const uint8_t *)evt == trkdata + trk.length) + return nullptr; + + it->index += fmidi_event_pad(fmidi_event_sizeof(evt->datalen)); + return evt; +} + +static bool fmidi_smf_read_contents(fmidi_smf_t *smf, memstream &mb) +{ + uint16_t ntracks = smf->info.track_count; + smf->track.reset(new fmidi_raw_track[ntracks]); + + std::vector evbuf; + evbuf.reserve(8192); + + uint8_t runstatus = 0; // status runs from track to track + + for (unsigned itrack = 0; itrack < ntracks; ++itrack) { + fmidi_raw_track &trk = smf->track[itrack]; + size_t trkoffset = mb.getpos(); + + memstream_status ms; + const uint8_t *trackmagic; + uint32_t tracklen; + + if (!(trackmagic = mb.read(4))) { + // file has less tracks than promised, repair + smf->info.track_count = ntracks = itrack; + break; + } + + if (memcmp(trackmagic, "MTrk", 4)) { + if (mb.getpos() == mb.endpos()) { + // some kind of final junk header, ignore + smf->info.track_count = ntracks = itrack; + break; + } + RET_FAIL(false, fmidi_err_format); + } + if ((ms = mb.readintBE(&tracklen, 4))) + RET_FAIL(false, (fmidi_status)ms); + + // check track length, broken in many files. disregard if invalid + bool tracklengood = !mb.skip(tracklen) && + (mb.getpos() == mb.endpos() || + ((trackmagic = mb.peek(4)) && !memcmp(trackmagic, "MTrk", 4))); + mb.setpos(trkoffset + 8); + + fmidi_event_t *evt; + size_t evoffset = mb.getpos(); + bool endoftrack = false; + evbuf.clear(); + while (!endoftrack && (evt = fmidi_read_event(mb, evbuf, &runstatus))) { + // some files use 3F instead or 2F for end of track + endoftrack = evt->type == fmidi_event_meta && + (evt->data[0] == 0x2f || evt->data[0] == 0x3f); + // fmt::print(stderr, "T{} @{:#x} {}\n", itrack, evoffset, *evt); + evoffset = mb.getpos(); + if (tracklengood && evoffset > trkoffset + 8 + tracklen) + // next track overlap + RET_FAIL(false, fmidi_err_format); + } + + if (!endoftrack) { + switch (fmidi_last_error.code) { + case fmidi_err_eof: + // truncated track? stop reading + smf->info.track_count = ntracks = itrack + 1; + break; + case fmidi_err_format: + // event with absurdly high delta time? ignore the rest of + // the track and if possible proceed to the next + mb.setpos(evoffset); + if (mb.peekvlq(nullptr) == ms_err_format) { + if (!tracklengood) + smf->info.track_count = ntracks = itrack + 1; + break; + } + return false; + default: + return false; + } + } + + if (endoftrack) { + // permit meta events coming after end of track + const uint8_t *head; + while ((head = mb.peek(2)) && head[0] == 0x00 && head[1] == 0xff) { + if (!(evt = fmidi_read_event(mb, evbuf, &runstatus))) { + if (fmidi_last_error.code == fmidi_err_eof) + smf->info.track_count = ntracks = itrack + 1; + else + return false; + } + else if (tracklengood && mb.getpos() > trkoffset + 8 + tracklen) + // next track overlap + RET_FAIL(false, fmidi_err_format); + } + } + + uint32_t evdatalen = trk.length = evbuf.size(); + uint8_t *evdata = new uint8_t[evdatalen]; + trk.data.reset(evdata); + memcpy(evdata, evbuf.data(), evdatalen); + + if (tracklengood) + mb.setpos(trkoffset + 8 + tracklen); + } + + return true; +} + +fmidi_smf_t *fmidi_smf_mem_read(const uint8_t *data, size_t length) +{ + memstream mb(data, length); + memstream_status ms; + const uint8_t *filemagic; + uint32_t headerlen; + uint32_t format; + uint32_t ntracks; + uint32_t deltaunit; + + while ((filemagic = mb.peek(4)) && memcmp(filemagic, "MThd", 4)) + mb.skip(1); + mb.skip(4); + + if (!filemagic) + RET_FAIL(nullptr, fmidi_err_format); + + if ((ms = mb.readintBE(&headerlen, 4)) || + (ms = mb.readintBE(&format, 2)) || + (ms = mb.readintBE(&ntracks, 2)) || + (ms = mb.readintBE(&deltaunit, 2))) + RET_FAIL(nullptr, (fmidi_status)ms); + + if (ntracks < 1 || headerlen < 6) + RET_FAIL(nullptr, fmidi_err_format); + + if ((ms = mb.skip(headerlen - 6))) + RET_FAIL(nullptr, (fmidi_status)ms); + + std::unique_ptr smf(new fmidi_smf_t); + smf->info.format = format; + smf->info.track_count = ntracks; + smf->info.delta_unit = deltaunit; + + if (!fmidi_smf_read_contents(smf.get(), mb)) + return nullptr; + + return smf.release(); +} + +void fmidi_smf_free(fmidi_smf_t *smf) +{ + delete smf; +} + +fmidi_smf_t *fmidi_smf_file_read(const char *filename) +{ + unique_FILE fh(fmidi_fopen(filename, "rb")); + if (!fh) + RET_FAIL(nullptr, fmidi_err_input); + + fmidi_smf_t *smf = fmidi_smf_stream_read(fh.get()); + return smf; +} + +fmidi_smf_t *fmidi_smf_stream_read(FILE *stream) +{ + struct stat st; + size_t length; + + rewind(stream); + + if (fstat(fileno(stream), &st) != 0) + RET_FAIL(nullptr, fmidi_err_input); + + length = st.st_size; + if (length > fmidi_file_size_limit) + RET_FAIL(nullptr, fmidi_err_largefile); + + std::unique_ptr buf(new uint8_t[length]); + if (!fread(buf.get(), length, 1, stream)) + RET_FAIL(nullptr, fmidi_err_input); + + fmidi_smf_t *smf = fmidi_smf_mem_read(buf.get(), length); + return smf; +} + +#include "fmidi/fmidi.h" +#include + +fmidi_fileformat_t fmidi_mem_identify(const uint8_t *data, size_t length) +{ + const uint8_t smf_magic[4] = {'M', 'T', 'h', 'd'}; + if (length >= 4 && memcmp(data, smf_magic, 4) == 0) + return fmidi_fileformat_smf; + + const uint8_t rmi_magic1[4] = {'R', 'I', 'F', 'F'}; + const uint8_t rmi_magic2[8] = {'R', 'M', 'I', 'D', 'd', 'a', 't', 'a'}; + if (length >= 16 && memcmp(data, rmi_magic1, 4) == 0 && memcmp(data + 8, rmi_magic2, 8) == 0) + return fmidi_fileformat_smf; + + const uint8_t xmi_magic[20] = { + 'F', 'O', 'R', 'M', 0, 0, 0, 14, + 'X', 'D', 'I', 'R', 'I', 'N', 'F', 'O', 0, 0, 0, 2 + }; + if (length >= 20 && memcmp(data, xmi_magic, 20) == 0) + return fmidi_fileformat_xmi; + + RET_FAIL((fmidi_fileformat_t)-1, fmidi_err_format); +} + +fmidi_fileformat_t fmidi_stream_identify(FILE *stream) +{ + rewind(stream); + + uint8_t magic[32]; + size_t size = fread(magic, 1, sizeof(magic), stream); + if (ferror(stream)) + RET_FAIL((fmidi_fileformat_t)-1, fmidi_err_input); + + return fmidi_mem_identify(magic, size); +} + +fmidi_smf_t *fmidi_auto_mem_read(const uint8_t *data, size_t length) +{ + switch (fmidi_mem_identify(data, length)) { + case fmidi_fileformat_smf: + return fmidi_smf_mem_read(data, length); + case fmidi_fileformat_xmi: + return fmidi_xmi_mem_read(data, length); + default: + return nullptr; + } +} + +fmidi_smf_t *fmidi_auto_file_read(const char *filename) +{ + unique_FILE fh(fmidi_fopen(filename, "rb")); + if (!fh) + RET_FAIL(nullptr, fmidi_err_input); + + fmidi_smf_t *smf = fmidi_auto_stream_read(fh.get()); + return smf; +} + +fmidi_smf_t *fmidi_auto_stream_read(FILE *stream) +{ + switch (fmidi_stream_identify(stream)) { + case fmidi_fileformat_smf: + return fmidi_smf_stream_read(stream); + case fmidi_fileformat_xmi: + return fmidi_xmi_stream_read(stream); + default: + return nullptr; + } +} + +#include "fmidi/fmidi.h" +#include +#include + +static void write_vlq(uint32_t value, Writer &writer) +{ + unsigned shift = 28; + unsigned mask = (1u << 7) - 1; + while (shift > 0 && ((value >> shift) & mask) == 0) + shift -= 7; + while (shift > 0) { + writer.put(((value >> shift) & mask) | (1u << 7)); + shift -= 7; + } + writer.put(value & mask); +} + +static bool fmidi_smf_write(const fmidi_smf_t *smf, Writer &writer) +{ + writer.write("MThd", 4); + + const uint32_t header_size = 6; + writer.writeBE(&header_size, 4); + + const fmidi_smf_info_t *info = fmidi_smf_get_info(smf); + const uint16_t track_count = info->track_count; + writer.writeBE(&info->format, 2); + writer.writeBE(&track_count, 2); + writer.writeBE(&info->delta_unit, 2); + + for (unsigned i = 0; i < track_count; ++i) { + writer.write("MTrk", 4); + + off_t off_track_length = writer.tell(); + + uint32_t track_length = 0; + writer.writeBE(&track_length, 4); + + int running_status = -1; + + fmidi_track_iter_t iter; + fmidi_smf_track_begin(&iter, i); + + const fmidi_event_t *event; + while ((event = fmidi_smf_track_next(smf, &iter))) { + switch (event->type) { + case fmidi_event_meta: + write_vlq(event->delta, writer); + writer.put(0xff); + writer.put(event->data[0]); + write_vlq(event->datalen - 1, writer); + writer.write(event->data + 1, event->datalen - 1); + running_status = -1; + break; + case fmidi_event_message: + { + write_vlq(event->delta, writer); + uint8_t status = event->data[0]; + if (status == 0xf0) { + writer.put(0xf0); + write_vlq(event->datalen - 1, writer); + writer.write(event->data + 1, event->datalen - 1); + running_status = -1; + } + else if ((int)status == running_status) + writer.write(event->data + 1, event->datalen - 1); + else { + writer.write(event->data, event->datalen); + running_status = status; + } + break; + } + case fmidi_event_escape: + write_vlq(event->delta, writer); + writer.put(0xf7); + write_vlq(event->datalen, writer); + writer.write(event->data, event->datalen); + running_status = -1; + break; + case fmidi_event_xmi_timbre: + case fmidi_event_xmi_branch_point: + break; + } + } + + off_t off_track_end = writer.tell(); + + track_length = + std::make_unsigned::type(off_track_end) - + std::make_unsigned::type(off_track_length) - 4; + + writer.seek(off_track_length, SEEK_SET); + writer.writeBE(&track_length, 4); + writer.seek(off_track_end, SEEK_SET); + } + + return true; +} + +bool fmidi_smf_mem_write(const fmidi_smf_t *smf, uint8_t **data, size_t *length) +{ + std::vector mem; + mem.reserve(8192); + + Memory_Writer writer(mem); + if (!fmidi_smf_write(smf, writer)) + return false; + + assert(data); + assert(length); + + if (!(*data = (uint8_t *)malloc(mem.size()))) + throw std::bad_alloc(); + + memcpy(*data, mem.data(), mem.size()); + *length = mem.size(); + + return true; +} + +bool fmidi_smf_file_write(const fmidi_smf_t *smf, const char *filename) +{ + unique_FILE fh(fmidi_fopen(filename, "wb")); + if (!fh) + RET_FAIL(false, fmidi_err_output); + + return fmidi_smf_stream_write(smf, fh.get()); +} + +bool fmidi_smf_stream_write(const fmidi_smf_t *smf, FILE *stream) +{ + Stream_Writer writer(stream); + if (!fmidi_smf_write(smf, writer)) + return false; + + if (fflush(stream) != 0) + RET_FAIL(false, fmidi_err_output); + + return true; +} diff --git a/clients/external/fmt b/clients/external/fmt deleted file mode 160000 index 9bdd1596..00000000 --- a/clients/external/fmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9bdd1596cef1b57b9556f8bef32dc4a32322ef3e diff --git a/clients/sfizz_render.cpp b/clients/sfizz_render.cpp index fde7d589..4d2a3fd4 100644 --- a/clients/sfizz_render.cpp +++ b/clients/sfizz_render.cpp @@ -143,7 +143,7 @@ int main(int argc, char** argv) ERROR_IF(!synth.loadSfzFile(sfzPath), "There was an error loading the SFZ file."); LOG_INFO(synth.getNumRegions() << " regions in the SFZ."); - fmidi_smf_u midiFile { fmidi_smf_file_read(midiPath.c_str()) }; + fmidi_smf_u midiFile { fmidi_smf_file_read(midiPath.u8string().c_str()) }; ERROR_IF(!midiFile, "Can't read " << midiPath); const auto* midiInfo = fmidi_smf_get_info(midiFile.get()); @@ -155,8 +155,7 @@ int main(int argc, char** argv) LOG_INFO("-- Cutting the rendering at the last MIDI End of Track message"); } - - SndfileHandle outputFile (outputPath, SFM_WRITE, SF_FORMAT_WAV | SF_FORMAT_PCM_16, 2, sampleRate); + SndfileHandle outputFile (outputPath.u8string(), SFM_WRITE, SF_FORMAT_WAV | SF_FORMAT_PCM_16, 2, sampleRate); ERROR_IF(outputFile.error() != 0, "Error writing out the wav file: " << outputFile.strError()); auto sampleRateDouble = static_cast(sampleRate);