mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
b5d21be9c5
* temp * temp * before cleaning up * cleanup merge * fix warnings * merge fix * clang format
410 lines
9 KiB
C++
410 lines
9 KiB
C++
// Copyright: 2021 - 2022, Ziemas
|
|
// SPDX-License-Identifier: ISC
|
|
#include "midi_handler.h"
|
|
|
|
#include "ame_handler.h"
|
|
|
|
#include <third-party/fmt/core.h>
|
|
|
|
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<ame_handler*> 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<size_t, u32> 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<MusicBank*>(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<midi_voice>(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];
|
|
(void)pitch;
|
|
(void)channel;
|
|
// 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("invalid status {}", m_status));
|
|
return;
|
|
}
|
|
|
|
new_delta();
|
|
}
|
|
}
|
|
|
|
} // namespace snd
|