Zack Buhman 86d12c37ed 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.
2023-07-01 00:15:53 +00:00

312 lines
7.3 KiB
C++

#include <cstdint>
#include <tuple>
#include "scsp.h"
#include "../math/fp.hpp"
#include "../midi/parse.hpp"
#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");
uint16_t
midi_note_to_oct_fns(const int8_t midi_note)
{
static const uint16_t _cent_to_fns[] = {
0x0,
0x3d,
0x7d,
0xc2,
0x10a,
0x157,
0x1a8,
0x1fe,
0x25a,
0x2ba,
0x321,
0x38d,
};
const int8_t a440_note = midi_note - 69;
const int8_t a440_note_d = (a440_note < 0) ? a440_note - 11 : a440_note;
const int8_t div12 = a440_note_d / static_cast<int8_t>(12);
const int8_t mod12 = a440_note % static_cast<int8_t>(12);
const uint16_t oct = div12;
const uint16_t cent = (a440_note < 0) ? 12 + mod12 : mod12;
const uint16_t fns = _cent_to_fns[cent];
return PITCH__OCT(oct) | PITCH__FNS(fns);
}
// maximum delay of 3258 days
using fp48_16 = fp<uint64_t, uint64_t, 16>;
constexpr uint8_t tactl = 0; // F/128
constexpr uint8_t tima = 0x00;
constexpr fp48_16 increment_ms{5804, 64657}; //
struct midi_state {
uint8_t const * buf;
midi::header_t header;
struct track {
uint8_t const * start;
int32_t length;
} current_track;
struct midi {
uint32_t tempo;
} midi;
fp48_16 delta_time_ms;
};
static midi_state state = {0};
[[noreturn]]
void error()
{
const uint32_t sine_start = reinterpret_cast<uint32_t>(&_sine_start);
{
scsp_slot& slot = scsp.reg.slot[0];
// 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 = 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
slot.PITCH = 0x78c2;
slot.LFO = 0; // lfof plfows
slot.MIXER = MIXER__DISDL(0b110); // disdl dipan efsdl efpan
}
{
scsp_slot& slot = scsp.reg.slot[1];
// 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 = 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
slot.LFO = 0; // lfof plfows
slot.MIXER = MIXER__DISDL(0b110); // disdl dipan efsdl efpan
}
scsp.reg.slot[0].LOOP |= LOOP__KYONEX;
while (1);
}
fp48_16 delay_ms(uint32_t delta_time)
{
return {(delta_time * state.midi.tempo) /
state.header.division.metrical.ticks_per_quarter_note};
}
static int _event = 0;
static volatile int timer_fired = 0;
extern "C"
void auto_vector_1(void) __attribute__ ((interrupt_handler));
void auto_vector_1(void)
{
// reset TIMER_A interrupt
scsp.reg.ctrl.SCIRE = INT__TIMER_A;
scsp.reg.ctrl.TIMA = TIMA__TACTL(tactl) | TIMA__TIMA(tima);
timer_fired = 1;
}
struct vs {
int8_t slot_ix;
int8_t count;
};
static int32_t slot_alloc;
static struct vs voice_slot[16][128];
#pragma GCC push_options
#pragma GCC optimize ("unroll-loops")
static inline int8_t alloc_slot()
{
for (int i = 0; i < 32; i++) {
uint32_t bit = (1 << i);
if ((slot_alloc & bit) == 0) {
slot_alloc |= bit;
return i;
}
}
return -1;
}
#pragma GCC pop_options
static inline void free_slot(int8_t i)
{
slot_alloc &= ~(1 << i);
}
void midi_step()
{
const uint32_t sine_start = reinterpret_cast<uint32_t>(&_sine_start);
// 11.2896 MHz
// 2902.4943 µs per interrupt this gives us 32768 cycles per
// interrupt, which *might* be roomy enough.
int kyonex = 0;
while (true) {
scsp.ram.u32[0] = _event;
scsp.ram.u32[1] = state.delta_time_ms.value >> 16;
if (!(state.buf - state.current_track.start < state.current_track.length))
return;
auto mtrk_event_o = midi::parse::mtrk_event(state.buf);
if (!mtrk_event_o) error();
midi::mtrk_event_t mtrk_event;
uint8_t const * buf;
std::tie(buf, mtrk_event) = *mtrk_event_o;
scsp.ram.u32[2] = delay_ms(mtrk_event.delta_time).value >> 16;
fp48_16 delta_time_ms{0};
if (mtrk_event.delta_time != 0 &&
state.delta_time_ms.value < (delta_time_ms = delay_ms(mtrk_event.delta_time)).value)
break;
else
state.buf = buf;
_event++;
switch (mtrk_event.event.type) {
case midi::event_t::type_t::midi:
{
midi::midi_event_t& midi_event = mtrk_event.event.event.midi;
switch (midi_event.type) {
case midi::midi_event_t::type_t::note_on:
{
struct vs& v = voice_slot[midi_event.data.note_on.channel][midi_event.data.note_on.note];
if (v.slot_ix == -1) {
v.slot_ix = alloc_slot();
}
v.count++;
scsp_slot& slot = scsp.reg.slot[v.slot_ix];
scsp.ram.u32[3] = midi_event.data.note_on.note;
slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(sine_start);
slot.LSA = 0;
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(0b101);
if (v.count == 1)
kyonex = 1;
}
break;
case midi::midi_event_t::type_t::note_off:
{
struct vs& v = voice_slot[midi_event.data.note_on.channel][midi_event.data.note_on.note];
if (v.slot_ix < 0) error();
v.count -= 1;
if (v.count == 0) {
free_slot(v.slot_ix);
scsp_slot& slot = scsp.reg.slot[v.slot_ix];
v.slot_ix = -1;
slot.LOOP = 0;
scsp.reg.slot[0].SA |= SA__KYONEX;
//kyonex = 1;
}
}
break;
default:
break;
}
}
break;
default:
break;
}
state.delta_time_ms -= delta_time_ms;
}
if (kyonex) {
scsp.reg.slot[0].SA |= SA__KYONEX;
}
state.delta_time_ms += increment_ms;
}
void init_midi()
{
state.buf = reinterpret_cast<uint8_t const *>(&_midi_start);
auto header_o = midi::parse::header(state.buf);
if (!header_o) error();
std::tie(state.buf, state.header) = *header_o;
auto track_o = midi::parse::track(state.buf);
if (!track_o) error();
std::tie(state.buf, state.current_track.length) = *track_o;
state.current_track.start = state.buf;
state.delta_time_ms = fp48_16{0};
state.midi.tempo = 500000; // default tempo
slot_alloc = 0;
for (int j = 0; j < 16; j++) {
for (int i = 0; i < 128; i++) {
voice_slot[j][i].slot_ix = -1;
voice_slot[j][i].count = 0;
}
}
}
void main()
{
_event = 0;
scsp.ram.u32[0] = _event;
for (long i = 0; i < 3200; i++) { asm volatile ("nop"); } // wait for (way) more than 30µs
init_midi();
// timer A is vector 1 (0b001)
scsp.reg.ctrl.SCILV2 = 0;
scsp.reg.ctrl.SCILV1 = 0;
scsp.reg.ctrl.SCILV0 = SCILV__TIMER_A;
// enable TIMER_A
scsp.reg.ctrl.SCIRE = INT__TIMER_A;
scsp.reg.ctrl.SCIEB = INT__TIMER_A;
scsp.reg.ctrl.TIMA = TIMA__TACTL(tactl) | TIMA__TIMA(tima);
asm volatile ("move.w #8192,%sr");
while (1) {
while (timer_fired == 0) {
asm volatile ("nop");
}
timer_fired = 0;
midi_step();
}
}