Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seeker #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 83 additions & 30 deletions sources/player/player.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <gsl.hpp>
#include <stdexcept>
#include <cstdio>
#include <cstring>

Player::Player()
: quit_(false),
Expand Down Expand Up @@ -208,6 +209,7 @@ void Player::process_command_queue()

reset_current_playback();
ins.initialize();
ins.flush_events();
stop_ticking();

std::unique_lock<std::mutex> lock(*wait_mutex);
Expand Down Expand Up @@ -316,7 +318,7 @@ void Player::resume_play_list()
pl_.reset(pl);

fmidi_player_set_speed(pl, current_speed_ * 0.01);
fmidi_player_event_callback(pl, [](const fmidi_event_t *ev, void *ud) { static_cast<Player *>(ud)->play_event(*ev); }, this);
fmidi_player_event_callback(pl, [](const fmidi_event_t *ev, void *ud) { static_cast<Player *>(ud)->on_sequence_event(*ev); }, this);
fmidi_player_finish_callback(pl, [](void *ud) { static_cast<Player *>(ud)->file_finished(); }, this);

smf_ = std::move(smf);
Expand All @@ -335,54 +337,105 @@ void Player::tick(uint64_t elapsed)
fmidi_player_tick(pl, delta);
}

void Player::play_event(const fmidi_event_t &event)
void Player::on_sequence_event(const fmidi_event_t &event)
{
switch (event.type) {
case fmidi_event_message:
if (seeking_) {
if (!seeking_)
play_message(event.data, event.datalen);
else {
Seek_State &sks = *seek_state_;
sks.add_event(event.data, event.datalen);
}
else {
Midi_Instrument &ins = *ins_;
uint64_t now = uv_hrtime();
double ts = 0;
int flags = 0;
if (ts_started_)
ts = 1e-9 * (now - ts_last_);
else {
flags |= Midi_Message_Is_First;
ts_started_ = true;
}
ins.send_message(event.data, event.datalen, ts, flags);
ts_last_ = now;
}
break;
case fmidi_event_meta: {
uint8_t type = event.data[0];
if (type == 0x51 && event.datalen - 1 == 3) {
uint16_t unit = fmidi_smf_get_info(smf_.get())->delta_unit;
if (unit & (1 << 15))
current_tempo_ = 0; // not tempo-based file
else {
unsigned midi_tempo = (event.data[1] << 16) | (event.data[2] << 8) | event.data[3];
current_tempo_ = 60e6 / midi_tempo;
}
}
play_meta(event.data[0], event.data + 1, event.datalen - 1);
break;
}
default:
break;
}
}

void Player::seeker_play_message(const uint8_t *msg, uint32_t len)
void Player::play_message(const uint8_t *msg, uint32_t len)
{
Midi_Instrument &ins = *ins_;
uint64_t now = uv_hrtime();
double ts = 0;
int flags = 0;
if (ts_started_)
ts = 1e-9 * (now - ts_last_);
else {
flags |= Midi_Message_Is_First;
ts_started_ = true;
}
ins.send_message(msg, len, ts, flags);
ts_last_ = now;
}

#pragma message("TODO: should delay after the message if it is a reset")
void Player::play_meta(uint8_t type, const uint8_t *msg, uint32_t len)
{
if (type == 0x51 && len == 3) {
uint16_t unit = fmidi_smf_get_info(smf_.get())->delta_unit;
if (unit & (1 << 15))
current_tempo_ = 0; // not tempo-based file
else {
unsigned midi_tempo = (msg[0] << 16) | (msg[1] << 8) | msg[2];
current_tempo_ = 60e6 / midi_tempo;
}
}
}

///
static bool is_midi_reset_message(const uint8_t *msg, uint32_t len)
{
if (len >= 1 && msg[0] == 0xff) // GM system reset
return true;

if (len >= 4 && msg[0] == 0xf0 && msg[len - 1] == 0xf7) { // sysex resets
uint8_t manufacturer = msg[1];
uint8_t device_id = msg[2];
const uint8_t *payload = msg + 3;
uint8_t paysize = len - 4;

switch (manufacturer) {
case 0x7e: { // GM system messages
const uint8_t gm_on[] = {0x09, 0x01};
if (paysize >= 2 && !memcmp(gm_on, payload, 2))
return true;
break;
}
case 0x43: { // Yamaha XG
const uint8_t xg_on[] = {0x4c, 0x00, 0x00, 0x7e};
const uint8_t all_reset[] = {0x4c, 0x00, 0x00, 0x7f};
if ((device_id & 0xf0) == 0x10 && paysize >= 4 &&
(!memcmp(xg_on, payload, 4) || !memcmp(all_reset, payload, 4)))
return true;
break;
}
case 0x41: { // Roland GS / Roland MT-32
const uint8_t gs_on[] = {0x42, 0x12, 0x40, 0x00, 0x7f};
const uint8_t mt32_reset[] = {0x16, 0x12, 0x7f};
if ((device_id & 0xf0) == 0x10 &&
((paysize >= 5 && !memcmp(gs_on, payload, 5)) ||
(paysize >= 3 && !memcmp(mt32_reset, payload, 3))))
return true;
break;
}
}
}

return false;
}

///
void Player::seeker_play_message(const uint8_t *msg, uint32_t len)
{
play_message(msg, len);

ins.send_message(msg, len, 0, 0);
// add a delay if the message may reset the device
if (is_midi_reset_message(msg, len))
uv_sleep(100);
}

void Player::file_finished()
Expand Down
4 changes: 3 additions & 1 deletion sources/player/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ class Player {
void resume_play_list();

void tick(uint64_t elapsed);
void play_event(const fmidi_event_t &event);
void on_sequence_event(const fmidi_event_t &event);
void play_message(const uint8_t *msg, uint32_t len);
void play_meta(uint8_t type, const uint8_t *msg, uint32_t len);
void seeker_play_message(const uint8_t *msg, uint32_t len);
void file_finished();

Expand Down
20 changes: 20 additions & 0 deletions thirdparty/fmidi/sources/fmidi/fmidi_player.cc
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ void fmidi_player_goto_time(fmidi_player_t *plr, double time)
evt->data[1] = 121;
evt->data[2] = 0;
ctx.cbfn(evt, ctx.cbdata);
// volume
evt->datalen = 3;
evt->data[0] = (0b1011 << 4) | c;
evt->data[1] = 7;
evt->data[2] = 100;
ctx.cbfn(evt, ctx.cbdata);
// pan
evt->datalen = 3;
evt->data[0] = (0b1011 << 4) | c;
evt->data[1] = 10;
evt->data[2] = 64;
ctx.cbfn(evt, ctx.cbdata);
// bank select
evt->datalen = 3;
evt->data[0] = (0b1011 << 4) | c;
evt->data[1] = 0;
evt->data[2] = 0;
ctx.cbfn(evt, ctx.cbdata);
evt->data[1] = 32;
ctx.cbfn(evt, ctx.cbdata);
// program change
evt->datalen = 2;
evt->data[0] = (0b1100 << 4) | c;
Expand Down