// ============================================================================= // // 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; }