// Copyright: 2021 - 2022, Ziemas // SPDX-License-Identifier: ISC #include "midi_handler.h" #include "ame_handler.h" #include namespace snd { /* ** In the original 989snd, the player struct can live in different places ** depending on the type of file. ** ** For files with multiple tracks it lives in-place before the sequence data ** where the file is loaded. For single track (like sunken) it lives separetely ** ** the sequencer ticks at 240hz ** */ midi_handler::midi_handler(MIDIBlockHeader* block, voice_manager& vm, MIDISound& sound, s32 vol, s32 pan, locator& loc, u32 bank) : m_sound(sound), m_locator(loc), m_repeats(sound.Repeats), m_bank(bank), m_header(block), m_vm(vm) { if (vol == VOLUME_DONT_CHANGE) { vol = 1024; } m_vol = (vol * m_sound.Vol) >> 10; if (m_vol >= 128) { m_vol = 127; } if (pan == PAN_DONT_CHANGE || pan == PAN_RESET) { m_pan = m_sound.Pan; } else { m_pan = pan; } init_midi(); } midi_handler::midi_handler(MIDIBlockHeader* block, voice_manager& vm, MIDISound& sound, s32 vol, s32 pan, locator& loc, u32 bank, std::optional parent) : m_parent(parent), m_sound(sound), m_locator(loc), m_vol(vol), m_pan(pan), m_repeats(sound.Repeats), m_bank(bank), m_header(block), m_vm(vm) { init_midi(); } void midi_handler::init_midi() { m_seq_data_start = (u8*)((uintptr_t)m_header + (uintptr_t)m_header->DataStart); m_seq_ptr = m_seq_data_start; m_tempo = m_header->Tempo; m_ppq = m_header->PPQ; m_chanvol.fill(0x7f); m_chanpan.fill(0); } std::pair midi_handler::read_vlq(u8* value) { size_t len = 1; u32 out = *value & 0x7f; // fmt::print("starting with {:x}\n", *value); if ((*value & 0x80) != 0) { while ((*value & 0x80) != 0) { len++; value++; out = (out << 7) + (*value & 0x7f); } } return {len, out}; } void midi_handler::pause() { m_paused = true; for (auto& p : m_voices) { auto voice = p.lock(); if (voice == nullptr) { continue; } m_vm.pause(voice); } } void midi_handler::unpause() { m_paused = false; for (auto& p : m_voices) { auto voice = p.lock(); if (voice == nullptr) { continue; } m_vm.unpause(voice); } } void midi_handler::stop() { m_track_complete = true; for (auto& p : m_voices) { auto voice = p.lock(); if (voice == nullptr) { continue; } voice->key_off(); } } void midi_handler::set_vol_pan(s32 vol, s32 pan) { // TODO } void midi_handler::set_pmod(s32 mod) { // TODO } void midi_handler::mute_channel(u8 channel) { // fmt::print("{:x} ame muting channel {}\n", (u64)this, channel); m_mute_state[channel] = true; } void midi_handler::unmute_channel(u8 channel) { // fmt::print("{:x} ame unmuting channel {}\n", (u64)this, channel); m_mute_state[channel] = false; } void midi_handler::note_on() { u8 channel = m_status & 0xf; u8 note = m_seq_ptr[0]; u8 velocity = m_seq_ptr[1]; if (velocity == 0) { note_off(); return; } if (m_mute_state[channel]) { m_seq_ptr += 2; return; } // fmt::print("{:x} {}: [ch{:01x}] note on {:02x} {:02x}\n", (u64)this, m_time, channel, note, // velocity); // Key on all the applicable tones for the program auto bank = dynamic_cast(m_locator.get_bank_by_name(m_header->BankID)); auto& program = bank->programs[m_programs[channel]]; for (auto& t : program.tones) { if (note >= t.MapLow && note <= t.MapHigh) { s16 pan = m_chanpan[channel] + m_pan; if (pan >= 360) { pan -= 360; } auto voice = std::make_shared(t); voice->basevol = m_vm.make_volume_b(m_vol, (velocity * m_chanvol[channel]) / 0x7f, pan, program.d.Vol, program.d.Pan, t.Vol, t.Pan); voice->note = note; voice->channel = channel; voice->start_note = note; voice->start_fine = 0; // TODO // voice->current_pm = 0; // voice->current_pb = 0; voice->group = m_sound.VolGroup; m_vm.start_tone(voice); m_voices.emplace_front(voice); } } m_seq_ptr += 2; } void midi_handler::note_off() { u8 channel = m_status & 0xf; u8 note = m_seq_ptr[0]; // Yep, no velocity for note-offs [[maybe_unused]] u8 velocity = m_seq_ptr[1]; // fmt::print("{}: note off {:02x} {:02x} {:02x}\n", m_time, m_status, m_seq_ptr[0], // m_seq_ptr[1]); for (auto& v : m_voices) { auto voice = v.lock(); if (voice == nullptr) { continue; } if (voice->channel == channel && voice->note == note) { voice->key_off(); } } m_seq_ptr += 2; } void midi_handler::program_change() { u8 channel = m_status & 0xf; u8 program = m_seq_ptr[0]; m_programs[channel] = program; // fmt::print("{:x} {}: [ch{:01x}] program change {:02x} -> {:02x}\n", (u64)this, m_time, channel, // m_programs[channel], program); m_seq_ptr += 1; } void midi_handler::channel_pressure() { u8 channel = m_status & 0xf; u8 note = m_seq_ptr[0]; // fmt::print("{}: channel pressure {:02x} {:02x}\n", m_time, m_status, m_seq_ptr[0]); for (auto& v : m_voices) { auto voice = v.lock(); if (voice == nullptr) { continue; } if (voice->channel == channel && voice->note == note) { voice->key_off(); } } m_seq_ptr += 1; } void midi_handler::channel_pitch() { u8 channel = m_status & 0xF; u32 pitch = (m_seq_ptr[0] << 7) | m_seq_ptr[1]; fmt::print("{}: pitch ch{:01x} {:04x}\n", m_time, channel, pitch); m_seq_ptr += 2; } void midi_handler::meta_event() { // fmt::print("{}: meta event {:02x}\n", m_time, *m_seq_ptr); size_t len = m_seq_ptr[1]; if (*m_seq_ptr == 0x2f) { m_seq_ptr = m_seq_data_start; // If repeats was 0 we'll go negative, fail this test, and loop infinitely as intended m_repeats--; if (m_repeats == 0) { m_track_complete = true; } if (m_repeats < 0) { m_repeats = 0; } return; } if (*m_seq_ptr == 0x51) { m_tempo = (m_seq_ptr[2] << 16) | (m_seq_ptr[3] << 8) | (m_seq_ptr[4]); } m_seq_ptr += len + 2; } void midi_handler::system_event() { // fmt::print("{}: system event {:02x}\n", m_time, *m_seq_ptr); switch (*m_seq_ptr) { case 0x75: m_seq_ptr++; if (m_parent.has_value()) { auto [cont, ptr] = m_parent.value()->run_ame(*this, m_seq_ptr); m_seq_ptr = ptr; if (!cont) { fmt::print("{:x} track stopped by ame\n", (u64)this); m_track_complete = true; } } else { throw midi_error("MIDI tried to run AME without an AME handler"); } break; default: throw midi_error(fmt::format("Unknown system message {:02x}", *m_seq_ptr)); } } bool midi_handler::tick() { if (m_paused) { return m_track_complete; } try { m_voices.remove_if([](auto& v) { return v.expired(); }); step(); } catch (midi_error& e) { m_track_complete = true; fmt::print("MIDI Error: {}\n", e.what()); fmt::print("Sequence following: "); for (int i = 0; i < 10; i++) { fmt::print("{:x} ", m_seq_ptr[i]); } fmt::print("\n"); } return m_track_complete; } void midi_handler::new_delta() { auto [len, delta] = read_vlq(m_seq_ptr); m_seq_ptr += len; m_time += delta; m_ppt = 100 * mics_per_tick / (m_tempo / m_ppq); m_tickdelta = 100 * delta + m_tickerror; if (m_tickdelta < 0 || m_tickdelta < m_ppt / 2) { m_tickerror = m_tickdelta; m_tickdelta = 0; } if (m_tickdelta != 0) { m_tick_countdown = (m_tickdelta / 100 * m_tempo / m_ppq - 1 + mics_per_tick) / mics_per_tick; m_tickerror = m_tickdelta - m_ppt * m_tick_countdown; } } void midi_handler::step() { if (m_get_delta) { new_delta(); m_get_delta = false; } else { m_tick_countdown--; } while (!m_tick_countdown && !m_track_complete) { // running status, new events always have top bit if (*m_seq_ptr & 0x80) { m_status = *m_seq_ptr; m_seq_ptr++; } switch (m_status >> 4) { case 0x8: note_off(); break; case 0x9: note_on(); break; case 0xD: channel_pressure(); break; case 0xC: program_change(); break; case 0xE: channel_pitch(); break; case 0xF: // normal meta-event if (m_status == 0xFF) { meta_event(); break; } if (m_status == 0xF0) { system_event(); break; } [[fallthrough]]; default: throw midi_error(fmt::format("MIDI error: invalid status {}", m_status)); return; } new_delta(); } } } // namespace snd