midi: add generator
This adds both a midi generator and a midi type 1 simulator. While I would have preferred to use an existing tool for this, I found that timidity++ does not emit pitch wheel events correctly, and I don't know of another widely-distributed tool that does midi-to-midi format conversion. The c++ and python versions were co-developed. I wrote one to test the other. There is more cleanup to do, but `roundtrip.cpp` produces a valid type 0 midi file given a type 1 or type 0 midi file as input.
This commit is contained in:
parent
0c34b0634a
commit
86d12c37ed
@ -12,7 +12,7 @@ include $(LIB)/m68k/common.mk
|
||||
sox \
|
||||
-r 44100 -e signed-integer -b 16 -c 1 -n -B \
|
||||
$@.raw \
|
||||
synth 100s $* 440 vol -10dB
|
||||
synth 101s $* 441 vol -10dB
|
||||
mv $@.raw $@
|
||||
|
||||
%.pcm.o: %.pcm
|
||||
|
@ -7,8 +7,8 @@
|
||||
#include "../common/copy.hpp"
|
||||
|
||||
extern void * _sine_start __asm("_binary_sine_44100_s16be_1ch_100sample_pcm_start");
|
||||
//extern void * _midi_start __asm("_binary_midi_test_c_major_scale_mid_start");
|
||||
extern void * _midi_start __asm("_binary_f2_mid_start");
|
||||
extern void * _midi_start __asm("_binary_midi_test_c_major_scale_mid_start");
|
||||
//extern void * _midi_start __asm("_binary_f2_mid_start");
|
||||
|
||||
uint16_t
|
||||
midi_note_to_oct_fns(const int8_t midi_note)
|
||||
@ -75,7 +75,7 @@ void error()
|
||||
// start address (bytes)
|
||||
slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(sine_start); // kx kb sbctl[1:0] ssctl[1:0] lpctl[1:0] 8b sa[19:0]
|
||||
slot.LSA = 0; // loop start address (samples)
|
||||
slot.LEA = 99; // loop end address (samples)
|
||||
slot.LEA = 100; // loop end address (samples)
|
||||
slot.EG = EG__EGHOLD; // d2r d1r ho ar krs dl rr
|
||||
slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl
|
||||
//slot.PITCH = PITCH__OCT(0) | PITCH__FNS(0); // oct fns
|
||||
@ -89,7 +89,7 @@ void error()
|
||||
// start address (bytes)
|
||||
slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(sine_start); // kx kb sbctl[1:0] ssctl[1:0] lpctl[1:0] 8b sa[19:0]
|
||||
slot.LSA = 0; // loop start address (samples)
|
||||
slot.LEA = 99; // loop end address (samples)
|
||||
slot.LEA = 100; // loop end address (samples)
|
||||
slot.EG = EG__EGHOLD; // d2r d1r ho ar krs dl rr
|
||||
slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl
|
||||
slot.PITCH = PITCH__OCT(1) | PITCH__FNS(0); // oct fns
|
||||
@ -133,7 +133,7 @@ static struct vs voice_slot[16][128];
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("unroll-loops")
|
||||
int8_t alloc_slot()
|
||||
static inline int8_t alloc_slot()
|
||||
{
|
||||
for (int i = 0; i < 32; i++) {
|
||||
uint32_t bit = (1 << i);
|
||||
@ -146,7 +146,7 @@ int8_t alloc_slot()
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void free_slot(int8_t i)
|
||||
static inline void free_slot(int8_t i)
|
||||
{
|
||||
slot_alloc &= ~(1 << i);
|
||||
}
|
||||
@ -203,16 +203,14 @@ void midi_step()
|
||||
|
||||
scsp.ram.u32[3] = midi_event.data.note_on.note;
|
||||
|
||||
//slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(sine_start);
|
||||
slot.LOOP = LOOP__KYONB | LOOP__LPCTL__NORMAL | SAH__SA(sine_start);
|
||||
slot.SAL = SAL__SA(sine_start);
|
||||
slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(sine_start);
|
||||
slot.LSA = 0;
|
||||
slot.LEA = 99;
|
||||
slot.EG = EG__EGHOLD; //EG__AR(0x0f) | EG__D1R(0x4) | EG__D2R(0x4) | EG__RR(0x1f);
|
||||
slot.LEA = 100;
|
||||
slot.EG = EG__AR(0x1f) | EG__D1R(0x0) | EG__D2R(0x0) | EG__RR(0x1f);
|
||||
slot.FM = 0;
|
||||
slot.PITCH = midi_note_to_oct_fns(midi_event.data.note_on.note);
|
||||
slot.LFO = 0;
|
||||
slot.MIXER = MIXER__DISDL(0b110);
|
||||
slot.MIXER = MIXER__DISDL(0b101);
|
||||
|
||||
if (v.count == 1)
|
||||
kyonex = 1;
|
||||
@ -230,7 +228,7 @@ void midi_step()
|
||||
v.slot_ix = -1;
|
||||
slot.LOOP = 0;
|
||||
scsp.reg.slot[0].SA |= SA__KYONEX;
|
||||
kyonex = 1;
|
||||
//kyonex = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -67,12 +67,6 @@ int parse(uint8_t const * start)
|
||||
std::cout << "header.format: " << midi::strings::header_format(header.format) << '\n';
|
||||
std::cout << "header.ntrks: " << header.ntrks << '\n';
|
||||
|
||||
//while header.n
|
||||
// while header.ntrks:
|
||||
//
|
||||
// for event in events:
|
||||
// ev
|
||||
|
||||
for (int32_t i = 0; i < header.ntrks; i++) {
|
||||
std::cout << "track[" << i << "]:\n";
|
||||
|
||||
@ -132,7 +126,7 @@ int main(int argc, char *argv[])
|
||||
std::cerr << argv[1] << '\n';
|
||||
|
||||
std::ifstream ifs;
|
||||
ifs.open(argv[1], std::ios::binary | std::ios::ate);
|
||||
ifs.open(argv[1], std::ios::in | std::ios::binary | std::ios::trunc);
|
||||
if (!ifs.is_open()) {
|
||||
std::cerr << "ifstream\n";
|
||||
return -1;
|
||||
@ -145,6 +139,7 @@ int main(int argc, char *argv[])
|
||||
std::cerr << "read\n";
|
||||
return -1;
|
||||
}
|
||||
ifs.close();
|
||||
|
||||
parse(start);
|
||||
|
||||
|
23
midi/dump.py
Normal file
23
midi/dump.py
Normal file
@ -0,0 +1,23 @@
|
||||
import sys
|
||||
|
||||
from parser import *
|
||||
|
||||
def dump_file(buf):
|
||||
buf, header = parse_header(buf)
|
||||
print(header)
|
||||
assert header.ntrks > 0
|
||||
tracks = []
|
||||
for track_num in range(header.ntrks):
|
||||
buf, track = parse_track(buf)
|
||||
tracks.append(track)
|
||||
print(f"track {track_num}:")
|
||||
for i, event in enumerate(track.events):
|
||||
print(' ' + repr(event))
|
||||
|
||||
print("remaining data:", len(buf))
|
||||
|
||||
import sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
b = memoryview(f.read())
|
||||
|
||||
dump_file(b)
|
208
midi/generate.cpp
Normal file
208
midi/generate.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
#include "midi.hpp"
|
||||
#include "generate.hpp"
|
||||
|
||||
namespace midi {
|
||||
namespace generate {
|
||||
|
||||
constexpr inline buf_t
|
||||
int_variable_length(buf_t buf, const uint32_t n) noexcept
|
||||
{
|
||||
int nonzero = 0;
|
||||
for (int i = 3; i > 0; i--) {
|
||||
uint8_t nib = (n >> (7 * i)) & 0x7f;
|
||||
if (nib != 0) nonzero = 1;
|
||||
if (nonzero) {
|
||||
buf[0] = 0x80 | nib;
|
||||
buf++;
|
||||
}
|
||||
}
|
||||
buf[0] = n & 0x7f;
|
||||
buf++;
|
||||
return buf;
|
||||
}
|
||||
|
||||
// note: there are no alignment requirements for the location of a
|
||||
// fixed-length integer inside a midi file--reinterpret_cast+byteswap
|
||||
// would be faster if it weren't that this is possibly-unaligned
|
||||
// access.
|
||||
|
||||
constexpr inline buf_t
|
||||
int_fixed_length16(buf_t buf, const uint16_t n) noexcept
|
||||
{
|
||||
buf[0] = (n >> 8) & 0xff;
|
||||
buf[1] = (n >> 0) & 0xff;
|
||||
return buf + (sizeof (uint16_t));
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
int_fixed_length32(buf_t buf, const uint32_t n) noexcept
|
||||
{
|
||||
buf[0] = (n >> 24) & 0xff;
|
||||
buf[1] = (n >> 16) & 0xff;
|
||||
buf[2] = (n >> 8 ) & 0xff;
|
||||
buf[3] = (n >> 0 ) & 0xff;
|
||||
return buf + (sizeof (uint32_t));
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
header_chunk_type(buf_t buf) noexcept
|
||||
{
|
||||
buf[0] = 'M';
|
||||
buf[1] = 'T';
|
||||
buf[2] = 'h';
|
||||
buf[3] = 'd';
|
||||
return buf + 4;
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
division(buf_t buf, const division_t& division) noexcept
|
||||
{
|
||||
if (division.type == division_t::type_t::metrical) {
|
||||
return int_fixed_length16(buf, division.metrical.ticks_per_quarter_note & 0x7fff);
|
||||
} else { // time_code
|
||||
buf[0] = division.time_code.smpte | 0x80;
|
||||
buf[1] = division.time_code.ticks_per_frame;
|
||||
return buf + 2;
|
||||
}
|
||||
}
|
||||
|
||||
buf_t
|
||||
header(buf_t buf, const header_t& header) noexcept
|
||||
{
|
||||
buf = header_chunk_type(buf);
|
||||
buf = int_fixed_length32(buf, 6);
|
||||
|
||||
uint16_t format;
|
||||
switch (header.format) {
|
||||
case header_t::format_t::_0: format = 0; break;
|
||||
case header_t::format_t::_1: format = 1; break;
|
||||
case header_t::format_t::_2: format = 2; break;
|
||||
default: assert(false);
|
||||
}
|
||||
buf = int_fixed_length16(buf, format);
|
||||
|
||||
buf = int_fixed_length16(buf, header.ntrks);
|
||||
|
||||
buf = division(buf, header.division);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
constexpr inline buf_t
|
||||
track_chunk_type(buf_t buf) noexcept
|
||||
{
|
||||
buf[0] = 'M';
|
||||
buf[1] = 'T';
|
||||
buf[2] = 'r';
|
||||
buf[3] = 'k';
|
||||
return buf + 4;
|
||||
}
|
||||
|
||||
buf_t
|
||||
track(buf_t buf, const track_t& track) noexcept
|
||||
{
|
||||
buf = track_chunk_type(buf);
|
||||
buf = int_fixed_length32(buf, track.length);
|
||||
return buf;
|
||||
}
|
||||
|
||||
constexpr inline uint8_t
|
||||
midi_status_byte(const midi_event_t::type_t midi_event_type) noexcept
|
||||
{
|
||||
switch (midi_event_type) {
|
||||
case midi_event_t::type_t::note_off: return 0x80;
|
||||
case midi_event_t::type_t::note_on: return 0x90;
|
||||
case midi_event_t::type_t::polyphonic_key_pressure: return 0xa0;
|
||||
case midi_event_t::type_t::channel_mode: return 0xb0;
|
||||
case midi_event_t::type_t::control_change: return 0xb0;
|
||||
case midi_event_t::type_t::program_change: return 0xc0;
|
||||
case midi_event_t::type_t::channel_pressure: return 0xd0;
|
||||
case midi_event_t::type_t::pitch_bend_change: return 0xe0;
|
||||
default: assert(false);
|
||||
};
|
||||
}
|
||||
|
||||
constexpr inline int32_t
|
||||
midi_event_message_length(const midi_event_t::type_t type) noexcept
|
||||
{
|
||||
if (type == midi_event_t::type_t::program_change || type == midi_event_t::type_t::channel_pressure)
|
||||
return 1;
|
||||
else
|
||||
return 2;
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
midi_event(buf_t buf, const midi_event_t& event, uint8_t& running_status) noexcept
|
||||
{
|
||||
uint8_t status = midi_status_byte(event.type) | event.data.note_off.channel;
|
||||
if (running_status != status) {
|
||||
running_status = status;
|
||||
buf[0] = status;
|
||||
buf++;
|
||||
}
|
||||
|
||||
// it doesn't matter which union is used, as long as it is one with
|
||||
// 3 items. note_off is used throughout, even though it could
|
||||
// represent a type other than note_off.
|
||||
|
||||
buf[0] = event.data.note_off.note;
|
||||
// possibly-initializing the third field on a 2-field midi_event
|
||||
// does not matter, as the caller should ignore it anyway. This
|
||||
// harmless extraneous initialization means a branch is not needed.
|
||||
buf[1] = event.data.note_off.velocity;
|
||||
return buf + midi_event_message_length(event.type);
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
sysex_event(buf_t buf, const sysex_event_t& event) noexcept
|
||||
{
|
||||
if not consteval {
|
||||
assert(false); // not implemented
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
meta_event(buf_t buf, const meta_event_t& meta) noexcept
|
||||
{
|
||||
buf[0] = 0xff;
|
||||
buf[1] = meta.type;
|
||||
buf = int_variable_length(buf + 2, meta.length);
|
||||
for (uint32_t i = 0; i < meta.length; i++) {
|
||||
buf[i] = meta.data[i];
|
||||
}
|
||||
|
||||
return buf + meta.length;
|
||||
}
|
||||
|
||||
constexpr inline buf_t
|
||||
event(buf_t buf, const event_t& event, uint8_t& running_status) noexcept
|
||||
{
|
||||
switch (event.type) {
|
||||
case event_t::type_t::midi:
|
||||
return midi_event(buf, event.event.midi, running_status);
|
||||
case event_t::type_t::sysex:
|
||||
running_status = 0;
|
||||
return sysex_event(buf, event.event.sysex);
|
||||
case event_t::type_t::meta:
|
||||
running_status = 0;
|
||||
return meta_event(buf, event.event.meta);
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
buf_t
|
||||
mtrk_event(buf_t buf, const mtrk_event_t& _event, uint8_t& running_status) noexcept
|
||||
{
|
||||
buf = int_variable_length(buf, _event.delta_time);
|
||||
buf = event(buf, _event.event, running_status);
|
||||
return buf;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
22
midi/generate.hpp
Normal file
22
midi/generate.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "midi.hpp"
|
||||
|
||||
namespace midi {
|
||||
namespace generate {
|
||||
|
||||
using buf_t = uint8_t *;
|
||||
|
||||
buf_t
|
||||
header(buf_t buf, const header_t& header) noexcept;
|
||||
|
||||
buf_t
|
||||
track(buf_t buf, const track_t& track) noexcept;
|
||||
|
||||
buf_t
|
||||
mtrk_event(buf_t buf, const mtrk_event_t& event, uint8_t& running_status) noexcept;
|
||||
|
||||
}
|
||||
}
|
58
midi/iterator.cpp
Normal file
58
midi/iterator.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
|
||||
#include "midi.hpp"
|
||||
#include "parse.hpp"
|
||||
|
||||
struct mtrk_iterator {
|
||||
using difference_type = int32_t;
|
||||
using element_type = midi::mtrk_event_t;
|
||||
using pointer = element_type *;
|
||||
using reference = element_type &;
|
||||
|
||||
uint8_t const * track_start;
|
||||
midi::track_t track;
|
||||
uint8_t running_status;
|
||||
uint8_t const * buf;
|
||||
uint8_t const * next_buf;
|
||||
midi::mtrk_event_t mtrk_event;
|
||||
|
||||
mtrk_iterator() = delete;
|
||||
mtrk_iterator(const midi::track_t& track,
|
||||
uint8_t const * const track_start)
|
||||
: track_start(track_start)
|
||||
, track(track)
|
||||
, running_status(0)
|
||||
, buf(track_start)
|
||||
, next_buf(track_start)
|
||||
{
|
||||
}
|
||||
|
||||
mtrk_iterator& operator=(mtrk_iterator&&) = default;
|
||||
constexpr mtrk_iterator(const mtrk_iterator&) = default;
|
||||
|
||||
reference operator*() {
|
||||
auto mtrk_event_o = midi::parse::mtrk_event(buf, running_status);
|
||||
std::tie(next_buf, mtrk_event) = *mtrk_event_o;
|
||||
return mtrk_event;
|
||||
}
|
||||
|
||||
mtrk_iterator operator++() {
|
||||
assert(buf != next_buf);
|
||||
buf = next_buf;
|
||||
return *this;
|
||||
}
|
||||
|
||||
mtrk_iterator operator++(int) {
|
||||
mtrk_iterator tmp = *this;
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool at_end() {
|
||||
return buf - track_start >= track.length;
|
||||
}
|
||||
|
||||
};
|
@ -102,6 +102,7 @@ struct midi_event_t {
|
||||
struct sysex_event_t {
|
||||
const uint8_t * data;
|
||||
uint32_t length;
|
||||
uint8_t tag;
|
||||
};
|
||||
|
||||
struct meta_event_t {
|
||||
@ -128,4 +129,8 @@ struct mtrk_event_t {
|
||||
event_t event;
|
||||
};
|
||||
|
||||
struct track_t {
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
} // midi
|
||||
|
157
midi/parse.cpp
157
midi/parse.cpp
@ -8,7 +8,7 @@
|
||||
namespace midi {
|
||||
namespace parse {
|
||||
|
||||
static constexpr inline std::optional<std::tuple<buf_t, uint32_t>>
|
||||
constexpr inline std::optional<std::tuple<buf_t, uint32_t>>
|
||||
int_variable_length(buf_t buf)
|
||||
{
|
||||
uint32_t n = 0;
|
||||
@ -24,31 +24,26 @@ int_variable_length(buf_t buf)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static inline std::tuple<buf_t, uint16_t>
|
||||
// note: there are no alignment requirements for the location of a
|
||||
// fixed-length integer inside a midi file--reinterpret_cast+byteswap
|
||||
// would be faster if it weren't that this is possibly-unaligned
|
||||
// access.
|
||||
|
||||
constexpr inline std::tuple<buf_t, uint16_t>
|
||||
int_fixed_length16(buf_t buf)
|
||||
{
|
||||
uint16_t n;
|
||||
if (0) {// constexpr (std::endian::native == std::endian::big) {
|
||||
n = *reinterpret_cast<const uint16_t*>(buf);
|
||||
} else {
|
||||
n = (buf[0] << 8 | buf[1] << 0);
|
||||
}
|
||||
uint16_t n = (buf[0] << 8 | buf[1] << 0);
|
||||
return {buf + (sizeof (uint16_t)), n};
|
||||
}
|
||||
|
||||
static inline std::tuple<buf_t, uint32_t>
|
||||
constexpr inline std::tuple<buf_t, uint32_t>
|
||||
int_fixed_length32(buf_t buf)
|
||||
{
|
||||
uint32_t n;
|
||||
if (0) {//constexpr (std::endian::native == std::endian::big) {
|
||||
n = *reinterpret_cast<const uint32_t*>(buf);
|
||||
} else {
|
||||
n = (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0);
|
||||
}
|
||||
uint32_t n = (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0);
|
||||
return {buf + (sizeof (uint32_t)), n};
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<buf_t>
|
||||
constexpr inline std::optional<buf_t>
|
||||
header_chunk_type(buf_t buf)
|
||||
{
|
||||
if ( buf[0] == 'M'
|
||||
@ -60,13 +55,13 @@ header_chunk_type(buf_t buf)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static inline std::tuple<buf_t, division_t>
|
||||
constexpr inline std::tuple<buf_t, division_t>
|
||||
division(buf_t buf)
|
||||
{
|
||||
uint16_t n;
|
||||
std::tie(buf, n) = int_fixed_length16(buf);
|
||||
if ((n & (1 << 15)) != 0) {
|
||||
int8_t smpte = ((n >> 8) & 0x7f) - 0x80; // sign-extend
|
||||
int8_t smpte = (n >> 8);
|
||||
uint8_t ticks_per_frame = n & 0xff;
|
||||
return { buf, { .type = division_t::type_t::time_code,
|
||||
.time_code = { smpte, ticks_per_frame }
|
||||
@ -103,28 +98,53 @@ header(buf_t buf)
|
||||
return {{buf, {format, ntrks, division}}};
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<std::tuple<buf_t, midi_event_t::type_t>>
|
||||
midi_event_type(buf_t buf)
|
||||
constexpr inline std::optional<buf_t>
|
||||
track_chunk_type(buf_t buf)
|
||||
{
|
||||
uint8_t n = buf[0] & 0xf0;
|
||||
if ( buf[0] == 'M'
|
||||
&& buf[1] == 'T'
|
||||
&& buf[2] == 'r'
|
||||
&& buf[3] == 'k')
|
||||
return {buf + 4};
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::tuple<buf_t, track_t>>
|
||||
track(buf_t buf)
|
||||
{
|
||||
auto buf_o = track_chunk_type(buf);
|
||||
if (!buf_o) return std::nullopt;
|
||||
buf = *buf_o;
|
||||
|
||||
uint32_t length;
|
||||
std::tie(buf, length) = int_fixed_length32(buf);
|
||||
|
||||
return {{buf, {length}}};
|
||||
}
|
||||
|
||||
constexpr inline std::optional<midi_event_t::type_t>
|
||||
midi_event_type(uint8_t status, uint8_t param0)
|
||||
{
|
||||
uint8_t n = status & 0xf0;
|
||||
// do not increment buf; the caller needs to parse channel
|
||||
switch (n) {
|
||||
case 0x80: return {{buf, midi_event_t::type_t::note_off}};
|
||||
case 0x90: return {{buf, midi_event_t::type_t::note_on}};
|
||||
case 0xa0: return {{buf, midi_event_t::type_t::polyphonic_key_pressure}};
|
||||
case 0x80: return {midi_event_t::type_t::note_off};
|
||||
case 0x90: return {midi_event_t::type_t::note_on};
|
||||
case 0xa0: return {midi_event_t::type_t::polyphonic_key_pressure};
|
||||
case 0xb0:
|
||||
if (buf[0] >= 121 && buf[0] <= 127)
|
||||
return {{buf, midi_event_t::type_t::channel_mode}};
|
||||
if (param0 >= 121 && param0 <= 127)
|
||||
return {midi_event_t::type_t::channel_mode};
|
||||
else
|
||||
return {{buf, midi_event_t::type_t::control_change}};
|
||||
case 0xc0: return {{buf, midi_event_t::type_t::program_change}};
|
||||
case 0xd0: return {{buf, midi_event_t::type_t::channel_pressure}};
|
||||
case 0xe0: return {{buf, midi_event_t::type_t::pitch_bend_change}};
|
||||
return {midi_event_t::type_t::control_change};
|
||||
case 0xc0: return {midi_event_t::type_t::program_change};
|
||||
case 0xd0: return {midi_event_t::type_t::channel_pressure};
|
||||
case 0xe0: return {midi_event_t::type_t::pitch_bend_change};
|
||||
default: return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr inline int32_t
|
||||
constexpr inline int32_t
|
||||
midi_event_message_length(midi_event_t::type_t type)
|
||||
{
|
||||
if (type == midi_event_t::type_t::program_change || type == midi_event_t::type_t::channel_pressure)
|
||||
@ -133,31 +153,44 @@ midi_event_message_length(midi_event_t::type_t type)
|
||||
return 2;
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<std::tuple<buf_t, midi_event_t>>
|
||||
midi_event(buf_t buf)
|
||||
constexpr inline std::optional<std::tuple<buf_t, midi_event_t>>
|
||||
midi_event(buf_t buf, uint8_t& running_status)
|
||||
{
|
||||
if ((buf[0] & 0x80) == 0) {
|
||||
// invalid running_status
|
||||
if ((running_status & 0x80) == 0) return std::nullopt;
|
||||
} else {
|
||||
// this is a status change
|
||||
running_status = buf[0];
|
||||
buf++;
|
||||
}
|
||||
|
||||
midi_event_t event;
|
||||
auto type_o = midi_event_type(running_status, buf[0]);
|
||||
if (!type_o) return std::nullopt;
|
||||
event.type = *type_o;
|
||||
event.data.note_off.channel = running_status & 0x0f;
|
||||
|
||||
// it doesn't matter which union is used, as long as it is one with
|
||||
// 3 items. note_off is used throughout, even though it could
|
||||
// represent a type other than note_off.
|
||||
midi_event_t event;
|
||||
auto type_o = midi_event_type(buf); // does not increment buf
|
||||
if (!type_o) return std::nullopt;
|
||||
std::tie(buf, event.type) = *type_o;
|
||||
event.data.note_off.channel = buf[0] & 0x0f;
|
||||
event.data.note_off.note = buf[1];
|
||||
event.data.note_off.note = buf[0];
|
||||
// possibly-initializing the third field on a 2-field midi_event
|
||||
// does not matter, as the caller should ignore it anyway. This
|
||||
// harmless extraneous initialization means a branch is not needed.
|
||||
event.data.note_off.velocity = buf[2];
|
||||
buf += 1 + midi_event_message_length(event.type);
|
||||
event.data.note_off.velocity = buf[1];
|
||||
buf += midi_event_message_length(event.type);
|
||||
|
||||
// this does not validate that buf[1]/buf[2] are <= 0x7f
|
||||
return {{buf, event}};
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<std::tuple<buf_t, sysex_event_t>>
|
||||
constexpr inline std::optional<std::tuple<buf_t, sysex_event_t>>
|
||||
sysex_event(buf_t buf)
|
||||
{
|
||||
// not implemented
|
||||
return std::nullopt;
|
||||
/*
|
||||
if (buf[0] != 0xf0 && buf[0] != 0xf7) return std::nullopt;
|
||||
buf++;
|
||||
auto length_o = int_variable_length(buf);
|
||||
@ -165,9 +198,10 @@ sysex_event(buf_t buf)
|
||||
uint32_t length;
|
||||
std::tie(buf, length) = *length_o;
|
||||
return {{buf + length, {buf, length}}};
|
||||
*/
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<std::tuple<buf_t, meta_event_t>>
|
||||
constexpr inline std::optional<std::tuple<buf_t, meta_event_t>>
|
||||
meta_event(buf_t buf)
|
||||
{
|
||||
if (buf[0] != 0xff) return std::nullopt;
|
||||
@ -180,18 +214,20 @@ meta_event(buf_t buf)
|
||||
return {{buf + length, {buf, length, type}}};
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<std::tuple<buf_t, event_t>>
|
||||
event(buf_t buf)
|
||||
constexpr inline std::optional<std::tuple<buf_t, event_t>>
|
||||
event(buf_t buf, uint8_t& running_status)
|
||||
{
|
||||
if (auto midi_o = midi_event(buf)) {
|
||||
if (auto midi_o = midi_event(buf, running_status)) {
|
||||
midi_event_t midi;
|
||||
std::tie(buf, midi) = *midi_o;
|
||||
return {{buf, {event_t::type_t::midi, {.midi = midi}}}};
|
||||
} else if (auto meta_o = meta_event(buf)) {
|
||||
running_status = 0;
|
||||
meta_event_t meta;
|
||||
std::tie(buf, meta) = *meta_o;
|
||||
return {{buf, {event_t::type_t::meta, {.meta = meta}}}};
|
||||
} else if (auto sysex_o = sysex_event(buf)) {
|
||||
running_status = 0;
|
||||
sysex_event_t sysex;
|
||||
std::tie(buf, sysex) = *sysex_o;
|
||||
return {{buf, {event_t::type_t::sysex, {.sysex = sysex}}}};
|
||||
@ -201,44 +237,19 @@ event(buf_t buf)
|
||||
}
|
||||
|
||||
std::optional<std::tuple<buf_t, mtrk_event_t>>
|
||||
mtrk_event(buf_t buf)
|
||||
mtrk_event(buf_t buf, uint8_t& running_status)
|
||||
{
|
||||
auto delta_time_o = int_variable_length(buf);
|
||||
if (!delta_time_o) return std::nullopt;
|
||||
uint32_t delta_time;
|
||||
std::tie(buf, delta_time) = *delta_time_o;
|
||||
|
||||
auto event_o = event(buf);
|
||||
auto event_o = event(buf, running_status);
|
||||
if (!event_o) return std::nullopt;
|
||||
event_t event;
|
||||
std::tie(buf, event) = *event_o;
|
||||
return {{buf, {delta_time, event}}};
|
||||
}
|
||||
|
||||
static constexpr inline std::optional<buf_t>
|
||||
track_chunk_type(buf_t buf)
|
||||
{
|
||||
if ( buf[0] == 'M'
|
||||
&& buf[1] == 'T'
|
||||
&& buf[2] == 'r'
|
||||
&& buf[3] == 'k')
|
||||
return {buf + 4};
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::tuple<buf_t, uint32_t>>
|
||||
track(buf_t buf)
|
||||
{
|
||||
auto buf_o = track_chunk_type(buf);
|
||||
if (!buf_o) return std::nullopt;
|
||||
buf = *buf_o;
|
||||
|
||||
uint32_t length;
|
||||
std::tie(buf, length) = int_fixed_length32(buf);
|
||||
|
||||
return {{buf, length}};
|
||||
}
|
||||
|
||||
} // namespace parse
|
||||
} // namespace midi
|
||||
|
@ -14,11 +14,11 @@ using buf_t = uint8_t const *;
|
||||
std::optional<std::tuple<buf_t, header_t>>
|
||||
header(buf_t buf);
|
||||
|
||||
std::optional<std::tuple<buf_t, uint32_t>>
|
||||
std::optional<std::tuple<buf_t, track_t>>
|
||||
track(buf_t buf);
|
||||
|
||||
std::optional<std::tuple<buf_t, mtrk_event_t>>
|
||||
mtrk_event(buf_t buf);
|
||||
mtrk_event(buf_t buf, uint8_t& running_status);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class ChannelMode:
|
||||
|
||||
@dataclass
|
||||
class MIDIEvent:
|
||||
event = Union[
|
||||
event: Union[
|
||||
NoteOff,
|
||||
NoteOn,
|
||||
PolyphonicKeyPressure,
|
||||
@ -299,9 +299,11 @@ def parse_meta_event(buf):
|
||||
return None
|
||||
type_n = buf[1]
|
||||
type, value_parser = meta_nums[type_n]
|
||||
#print(len(buf[2:]))
|
||||
buf, length = parse_variable_length(buf[2:])
|
||||
#print(len(buf[0:]))
|
||||
data = buf[:length]
|
||||
print("meta", length, bytes(data))
|
||||
#print("meta", type_n, length, bytes(data))
|
||||
buf = buf[length:]
|
||||
return buf, MetaEvent(type, value_parser(data))
|
||||
|
||||
@ -316,41 +318,46 @@ midi_messages = dict([
|
||||
# ChannelMode handled specially
|
||||
])
|
||||
|
||||
running_status = None
|
||||
|
||||
def parse_midi_event(buf):
|
||||
message_type = buf[0] & 0xf0
|
||||
global running_status
|
||||
if buf[0] & 0x80 == 0:
|
||||
assert running_status is not None, buf[0]
|
||||
else:
|
||||
running_status = buf[0]
|
||||
buf = buf[1:]
|
||||
message_type = running_status & 0xf0
|
||||
channel = running_status & 0x0f
|
||||
#assert message_type != 0xf0, hex(message_type)
|
||||
if message_type not in midi_messages:
|
||||
return None
|
||||
channel = buf[0] & 0x0f
|
||||
message_length, cls = midi_messages[message_type]
|
||||
buf = buf[1:]
|
||||
|
||||
data = buf[:message_length]
|
||||
# handle channel mode specially
|
||||
if cls is ControlChange and data[0] >= 121 and data[0] <= 127:
|
||||
# 0xb0 is overloaded for both control change and channel mode
|
||||
cls = ChannelMode
|
||||
message = cls(channel, *data)
|
||||
message = MIDIEvent(cls(channel, *data))
|
||||
buf = buf[message_length:]
|
||||
return buf, message
|
||||
|
||||
def parse_event(buf):
|
||||
while True:
|
||||
b = buf[0]
|
||||
if (sysex := parse_sysex_event(buf)) is not None:
|
||||
if (midi := parse_midi_event(buf)) is not None:
|
||||
buf, midi = midi
|
||||
return buf, midi
|
||||
elif (sysex := parse_sysex_event(buf)) is not None:
|
||||
running_status = None
|
||||
buf, sysex = sysex
|
||||
return buf, sysex
|
||||
elif (meta := parse_meta_event(buf)) is not None:
|
||||
running_status = None
|
||||
buf, meta = meta
|
||||
return buf, meta
|
||||
elif (midi := parse_midi_event(buf)) is not None:
|
||||
buf, midi = midi
|
||||
return buf, midi
|
||||
else:
|
||||
print(hex(buf[0]), file=sys.stderr)
|
||||
buf = buf[1:]
|
||||
while (buf[0] & 0x80) == 0:
|
||||
print(hex(buf[0]), file=sys.stderr)
|
||||
buf = buf[1:]
|
||||
assert False, ' '.join([hex(i)[2:] for i in buf[0:40]])
|
||||
return None
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
@ -374,48 +381,16 @@ class Track:
|
||||
events: list[Event]
|
||||
|
||||
def parse_track(buf):
|
||||
head_buf = buf
|
||||
buf, _ = parse_track_chunk_type(buf)
|
||||
buf, length = parse_uint32(buf)
|
||||
offset = len(buf)
|
||||
events = []
|
||||
while (offset - len(buf)) < length:
|
||||
try:
|
||||
buf, event = parse_mtrk_event(buf)
|
||||
except:
|
||||
print('len', len(head_buf) - len(buf), length)
|
||||
raise
|
||||
events.append(event)
|
||||
return buf, Track(events)
|
||||
|
||||
_slots = set()
|
||||
|
||||
def simulate_note(ix, ev):
|
||||
if type(ev.event) is NoteOn:
|
||||
print(repr(ev.event))
|
||||
|
||||
_slots.add((ev.event.channel, ev.event.note))
|
||||
assert len(_slots) <= 32, (hex(ix))
|
||||
if type(ev.event) is NoteOff:
|
||||
print(repr(ev.event))
|
||||
try:
|
||||
_slots.remove((ev.event.channel, ev.event.note))
|
||||
except:
|
||||
print("ix", hex(ix))
|
||||
raise
|
||||
|
||||
def parse_file(buf):
|
||||
buf, header = parse_header(buf)
|
||||
#print(header)
|
||||
assert header.ntrks > 0
|
||||
tracks = []
|
||||
for track_num in range(header.ntrks):
|
||||
buf, track = parse_track(buf)
|
||||
tracks.append(track)
|
||||
#print(f"track {track_num}:")
|
||||
for i, event in enumerate(track.events):
|
||||
#simulate_note(i, event)
|
||||
print(event)
|
||||
|
||||
print("remaining data:", len(buf))
|
||||
|
||||
import sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
b = memoryview(f.read())
|
||||
|
||||
parse_file(b)
|
||||
|
223
midi/roundtrip.cpp
Normal file
223
midi/roundtrip.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
#include "strings.hpp"
|
||||
#include "parse.hpp"
|
||||
#include "generate.hpp"
|
||||
|
||||
#include "iterator.cpp"
|
||||
|
||||
static char output_buf[4096];
|
||||
|
||||
inline uint32_t output_mtrk_event(const midi::mtrk_event_t& mtrk_event, uint8_t& running_status,
|
||||
std::ofstream& ofs, const bool write)
|
||||
{
|
||||
uint8_t * output_buf_start = reinterpret_cast<uint8_t *>(&output_buf[0]);
|
||||
const uint8_t * output_buf_end = midi::generate::mtrk_event(output_buf_start, mtrk_event, running_status);
|
||||
const uint32_t event_length = output_buf_end - output_buf_start;
|
||||
|
||||
std::cerr << "event_length " << event_length << '\n';
|
||||
|
||||
if (write) ofs.write(output_buf, event_length);
|
||||
|
||||
return event_length;
|
||||
}
|
||||
|
||||
inline void output_track(const midi::track_t& track, std::ofstream& ofs)
|
||||
{
|
||||
uint8_t * output_buf_start = reinterpret_cast<uint8_t *>(&output_buf[0]);
|
||||
const uint8_t * output_buf_end = midi::generate::track(output_buf_start, track);
|
||||
const uint32_t track_length = output_buf_end - output_buf_start;
|
||||
|
||||
ofs.write(output_buf, track_length);
|
||||
}
|
||||
|
||||
inline void output_header(const midi::header_t& header, std::ofstream& ofs)
|
||||
{
|
||||
uint8_t * output_buf_start = reinterpret_cast<uint8_t *>(&output_buf[0]);
|
||||
const uint8_t * output_buf_end = midi::generate::header(output_buf_start, header);
|
||||
const uint32_t header_length = output_buf_end - output_buf_start;
|
||||
|
||||
ofs.write(output_buf, header_length);
|
||||
}
|
||||
|
||||
constexpr inline bool
|
||||
init_track_iterators(const midi::header_t& header, mtrk_iterator * its, uint8_t const * buf)
|
||||
{
|
||||
for (int32_t i = 0; i < header.ntrks; i++) {
|
||||
auto track_o = midi::parse::track(buf);
|
||||
if (!track_o) {
|
||||
std::cerr << "invalid track\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
midi::track_t track;
|
||||
std::tie(buf, track) = *track_o;
|
||||
|
||||
std::cout << "track[" << i << "] track.length: " << track.length << '\n';
|
||||
|
||||
uint8_t const * const track_start = buf;
|
||||
its[i] = mtrk_iterator(track, track_start);
|
||||
|
||||
buf += track.length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::optional<uint32_t>
|
||||
simulate_delta_time(const midi::header_t& header, uint8_t const * buf,
|
||||
std::ofstream& ofs, const bool write)
|
||||
{
|
||||
//
|
||||
// simulate delta_time
|
||||
//
|
||||
using mtrk_storage = std::aligned_storage_t<sizeof(mtrk_iterator), alignof(mtrk_iterator)>;
|
||||
mtrk_storage its_storage[header.ntrks];
|
||||
auto &its = reinterpret_cast<mtrk_iterator (&)[header.ntrks]>(its_storage);
|
||||
|
||||
if (!init_track_iterators(header, its, buf))
|
||||
return std::nullopt;
|
||||
uint32_t track_times[header.ntrks] = {0};
|
||||
|
||||
uint32_t global_time = 0;
|
||||
uint32_t last_global_time = 0;
|
||||
int32_t complete = 0;
|
||||
uint32_t total_length = 0;
|
||||
uint8_t output_running_status = 0;
|
||||
do {
|
||||
complete = 0;
|
||||
for (uint32_t i = 0; i < header.ntrks; i++) {
|
||||
mtrk_iterator& it = its[i];
|
||||
uint32_t& track_time = track_times[i];
|
||||
|
||||
while (!it.at_end()) {
|
||||
const midi::mtrk_event_t& mtrk_event = *it;
|
||||
if (track_time + mtrk_event.delta_time <= global_time) {
|
||||
track_time += mtrk_event.delta_time;
|
||||
++it;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mtrk_event.event.type == midi::event_t::type_t::meta
|
||||
&& mtrk_event.event.event.meta.type == 0x2f) {
|
||||
// suppress end of track
|
||||
std::cout << "suppress EOT\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
total_length += output_mtrk_event({
|
||||
global_time - last_global_time,
|
||||
mtrk_event.event
|
||||
}, output_running_status, ofs, write);
|
||||
last_global_time = global_time;
|
||||
}
|
||||
if (it.at_end()) ++complete;
|
||||
}
|
||||
// increment global time to the next time step
|
||||
++global_time;
|
||||
} while (complete != header.ntrks);
|
||||
|
||||
|
||||
midi::mtrk_event_t mtrk_event_eot = {
|
||||
0,
|
||||
{ // event_t
|
||||
.type = midi::event_t::type_t::meta,
|
||||
.event = {
|
||||
.meta = { nullptr, 0, 0x2f } // EndOfTrack
|
||||
}
|
||||
}
|
||||
};
|
||||
// emit final EndOfTrack
|
||||
total_length += output_mtrk_event(mtrk_event_eot, output_running_status, ofs, write);
|
||||
last_global_time = global_time;
|
||||
|
||||
return total_length;
|
||||
}
|
||||
|
||||
int roundtrip(uint8_t const * start, std::ofstream& ofs)
|
||||
{
|
||||
|
||||
uint8_t const * buf = &start[0];
|
||||
auto header_o = midi::parse::header(buf);
|
||||
if (!header_o) {
|
||||
std::cerr << "invalid header\n";
|
||||
return -1;
|
||||
}
|
||||
midi::header_t header;
|
||||
std::tie(buf, header) = *header_o;
|
||||
|
||||
std::cout << "header.format: " << midi::strings::header_format(header.format) << '\n';
|
||||
std::cout << "header.ntrks: " << header.ntrks << '\n';
|
||||
|
||||
//
|
||||
// write output header
|
||||
//
|
||||
output_header({
|
||||
.format = midi::header_t::format_t::_0,
|
||||
.ntrks = 1,
|
||||
.division = header.division,
|
||||
}, ofs);
|
||||
|
||||
//
|
||||
// first round of simulation: calculate track length (in bytes)
|
||||
//
|
||||
auto track_length_o = simulate_delta_time(header, buf, ofs, false);
|
||||
if (!track_length_o) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
output_track({ *track_length_o }, ofs);
|
||||
|
||||
//
|
||||
// second round of simulation: write the actual track
|
||||
//
|
||||
auto _o = simulate_delta_time(header, buf, ofs, true);
|
||||
if (!_o) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2) {
|
||||
std::cerr << "argc < 3\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cerr << argv[1] << '\n';
|
||||
|
||||
std::ifstream ifs;
|
||||
ifs.open(argv[1], std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (!ifs.is_open()) {
|
||||
std::cerr << "ifstream: " << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto size = static_cast<int32_t>(ifs.tellg());
|
||||
uint8_t start[size];
|
||||
ifs.seekg(0);
|
||||
if (!ifs.read(reinterpret_cast<char*>(&start[0]), size)) {
|
||||
std::cerr << "read\n";
|
||||
return -1;
|
||||
}
|
||||
ifs.close();
|
||||
|
||||
std::ofstream ofs;
|
||||
ofs.open(argv[2], std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
if (!ofs.is_open()) {
|
||||
std::cerr << "ofstream\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
roundtrip(start, ofs);
|
||||
|
||||
ofs.close();
|
||||
|
||||
return 0;
|
||||
}
|
74
midi/simulate.py
Normal file
74
midi/simulate.py
Normal file
@ -0,0 +1,74 @@
|
||||
from itertools import chain
|
||||
from parser import *
|
||||
|
||||
"""
|
||||
_slots = set()
|
||||
|
||||
def simulate_note(ix, ev):
|
||||
if type(ev.event) is NoteOn:
|
||||
print(repr(ev.event))
|
||||
|
||||
_slots.add((ev.event.channel, ev.event.note))
|
||||
assert len(_slots) <= 32, (hex(ix))
|
||||
if type(ev.event) is NoteOff:
|
||||
print(repr(ev.event))
|
||||
try:
|
||||
_slots.remove((ev.event.channel, ev.event.note))
|
||||
except:
|
||||
print("ix", hex(ix))
|
||||
raise
|
||||
"""
|
||||
|
||||
def linearize_track(track):
|
||||
time = 0
|
||||
for i, event in enumerate(track.events):
|
||||
time += event.delta_time
|
||||
yield time, i, event
|
||||
|
||||
l = {
|
||||
NoteOff: 0,
|
||||
NoteOn: 1,
|
||||
PolyphonicKeyPressure: 2,
|
||||
ControlChange: 3,
|
||||
ProgramChange: 4,
|
||||
ChannelPressure: 5,
|
||||
PitchBendChange: 6,
|
||||
ChannelMode: 7,
|
||||
}
|
||||
|
||||
def sort_key(global_time, i, event):
|
||||
channel = event.event.event.channel if type(event.event) is MIDIEvent else 99
|
||||
priority = -l[type(event.event.event)] if type(event.event) is MIDIEvent else -99
|
||||
return global_time, priority, channel
|
||||
|
||||
def linearize_events(tracks):
|
||||
global_time = 0
|
||||
linearized = list(chain.from_iterable(map(linearize_track, tracks)))
|
||||
linear_sort = sorted(linearized, key=lambda args: sort_key(*args))
|
||||
for abs_time, i, event in linear_sort:
|
||||
if type(event.event) is MetaEvent and event.event.type is MetaType.EndOfTrack:
|
||||
continue
|
||||
inner_event = event.event
|
||||
delta_time = abs_time - global_time
|
||||
print(Event(delta_time, inner_event))
|
||||
global_time = abs_time
|
||||
print(Event(delta_time=0, event=MetaEvent(type=MetaType.EndOfTrack, value=None)))
|
||||
|
||||
def dump_file(buf):
|
||||
buf, header = parse_header(buf)
|
||||
print(header)
|
||||
assert header.ntrks > 0
|
||||
tracks = []
|
||||
for track_num in range(header.ntrks):
|
||||
buf, track = parse_track(buf)
|
||||
tracks.append(track)
|
||||
print("remaining data:", len(buf))
|
||||
assert len(buf) == 0, bytes(buf)
|
||||
|
||||
linearize_events(tracks)
|
||||
|
||||
import sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
b = memoryview(f.read())
|
||||
|
||||
dump_file(b)
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
|
||||
#include "midi.hpp"
|
||||
|
||||
@ -14,8 +15,8 @@ header_format(header_t::format_t format)
|
||||
case header_t::format_t::_0: return "0";
|
||||
case header_t::format_t::_1: return "1";
|
||||
case header_t::format_t::_2: return "2";
|
||||
default: assert(false);
|
||||
}
|
||||
while (1);
|
||||
}
|
||||
|
||||
}
|
||||
|
86
midi/test_generate.cpp
Normal file
86
midi/test_generate.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include "midi.hpp"
|
||||
#include "generate.cpp"
|
||||
|
||||
namespace midi {
|
||||
namespace test {
|
||||
|
||||
struct buf_n_t {
|
||||
uint32_t n;
|
||||
uint8_t buf[4];
|
||||
int32_t len;
|
||||
};
|
||||
|
||||
void int_variable_length()
|
||||
{
|
||||
static buf_n_t tests[] = {
|
||||
{0x0000'0000, {0x00}, 1},
|
||||
{0x0000'0040, {0x40}, 1},
|
||||
{0x0000'007f, {0x7f}, 1},
|
||||
|
||||
{0x0000'0080, {0x81, 0x00}, 2},
|
||||
{0x0000'2000, {0xc0, 0x00}, 2},
|
||||
{0x0000'3fff, {0xff, 0x7f}, 2},
|
||||
|
||||
{0x0000'4000, {0x81, 0x80, 0x00}, 3},
|
||||
{0x0010'0000, {0xc0, 0x80, 0x00}, 3},
|
||||
{0x001f'ffff, {0xff, 0xff, 0x7f}, 3},
|
||||
|
||||
{0x0020'0000, {0x81, 0x80, 0x80, 0x00}, 4},
|
||||
{0x0800'0000, {0xc0, 0x80, 0x80, 0x00}, 4},
|
||||
{0x0fff'ffff, {0xff, 0xff, 0xff, 0x7f}, 4},
|
||||
};
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
uint8_t buf1[4];
|
||||
buf_n_t& test = tests[i];
|
||||
uint8_t * buf2 = generate::int_variable_length(&buf1[0], test.n);
|
||||
assert(buf2 - buf1 == test.len);
|
||||
for (int j = 0; j < test.len; j++)
|
||||
assert(buf1[j] == test.buf[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void header()
|
||||
{
|
||||
header_t header = {
|
||||
.format = header_t::format_t::_2,
|
||||
.ntrks = 1,
|
||||
.division = {
|
||||
.type = division_t::type_t::time_code,
|
||||
.time_code = {
|
||||
.smpte = -30,
|
||||
.ticks_per_frame = 0x50
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
uint8_t buf[64] = {0xee};
|
||||
uint8_t * res = generate::header(buf, header);
|
||||
uint8_t expect[] = {
|
||||
0x4d, 0x54, 0x68, 0x64, // chunk type
|
||||
0x00, 0x00, 0x00, 0x06, // length
|
||||
0x00, 0x02, // format
|
||||
0x00, 0x01, // ntrks
|
||||
0xe2, 0x50, // division
|
||||
};
|
||||
assert(res - buf == 14);
|
||||
for (int i = 0; i < 14; i++)
|
||||
assert(expect[i] == buf[i]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
using namespace midi::test;
|
||||
|
||||
int_variable_length();
|
||||
header();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user