diff --git a/jack.cpp b/jack.cpp index 98fa54b..fa37646 100644 --- a/jack.cpp +++ b/jack.cpp @@ -64,6 +64,16 @@ void Client::shutdown_callback(void *arg) { client->on_shutdown(); } +int Client::sync_callback(jack_transport_state_t state, jack_position_t *pos, void *arg) { + Client *client = (Client *)arg; + /* + printf("sync_callback: %i, BBT: %i|%i|%i, BPM: %f, frame: %i\n", + state, pos->bar, pos->beat, pos->tick, (float)pos->beats_per_minute, pos->frame); + */ + assert(pos); + return client->on_sync(state, *pos)?1:0; +} + Client::Client(const char *name) { this->name = name; handle = NULL; @@ -94,6 +104,9 @@ bool Client::init() { handle_status(jack_set_sample_rate_callback( handle, &sample_rate_callback, this)); + handle_status(jack_set_sync_callback( + handle, &sync_callback, this)); + jack_on_shutdown(handle, &shutdown_callback, this); for (PortList::iterator i = ports.begin(); i != ports.end(); ++i) { @@ -125,6 +138,11 @@ void Client::shutdown() { } +TransportState Client::transport_query(Position *pos) { + assert(handle); + return jack_transport_query(handle, pos); +} + void Client::add_port(Port *port) { ports.push_back(port); if (handle) // port came after init, so post-init it @@ -137,6 +155,22 @@ void Client::remove_port(Port *port) { ports.remove(port); } +void Client::transport_locate(NFrames frame) { + assert(handle); + jack_transport_locate(handle, frame); +} + +void Client::transport_start() { + assert(handle); + jack_transport_start(handle); +} + +void Client::transport_stop() { + assert(handle); + jack_transport_stop(handle); +} + + //============================================================================= Port::Port(Client &client, diff --git a/jack.hpp b/jack.hpp index 5f498bf..90a98f5 100644 --- a/jack.hpp +++ b/jack.hpp @@ -100,6 +100,9 @@ protected: //============================================================================= +typedef jack_transport_state_t TransportState; +typedef jack_position_t Position; + class Client { friend class Port; @@ -115,9 +118,15 @@ public: bool is_created(); + TransportState transport_query(Position *pos); + void transport_locate(NFrames frame); + void transport_start(); + void transport_stop(); + virtual void on_process(NFrames size) {} virtual void on_sample_rate(NFrames nframes) {} virtual void on_shutdown() {} + virtual bool on_sync(TransportState state, const Position &pos) { return true; } protected: void add_port(Port *port); @@ -132,6 +141,7 @@ protected: static int process_callback(jack_nframes_t size, void *arg); static int sample_rate_callback(jack_nframes_t nframes, void *arg); static void shutdown_callback(void *arg); + static int sync_callback(jack_transport_state_t state, jack_position_t *pos, void *arg); }; //============================================================================= diff --git a/jacker.glade b/jacker.glade index fdc2555..b34853d 100644 --- a/jacker.glade +++ b/jacker.glade @@ -92,8 +92,8 @@ True - show_song_action True + show_song_action True True @@ -101,8 +101,8 @@ True - show_pattern_action True + show_pattern_action True True @@ -146,8 +146,8 @@ True - play_action True + play_action toolbutton1 True @@ -159,8 +159,8 @@ True - stop_action True + stop_action toolbutton2 True @@ -310,6 +310,19 @@ True + + + True + True + sync_action + toolbutton7 + True + + + False + True + + False @@ -702,8 +715,8 @@ True - add_track_action True + add_track_action True True @@ -749,4 +762,9 @@ gtk-quit + + Sync + Sync + Sync to JACK Transport + diff --git a/main.cpp b/main.cpp index 82e4e68..3003c0b 100644 --- a/main.cpp +++ b/main.cpp @@ -17,6 +17,7 @@ #include "player.hpp" #include "jsong.hpp" +#include "ring_buffer.hpp" namespace Jacker { @@ -26,10 +27,22 @@ const char AccelPathPatternView[] = "/View/Pattern"; const char AccelPathTrackView[] = "/View/Song"; const char AccelPathSave[] = "/File/Save"; const char AccelPathOpen[] = "/File/Open"; - + class JackPlayer : public Jack::Client, public Player { public: + enum ThreadMessageType { + MsgPlay = 0, + MsgStop = 1, + MsgSeek = 2, + }; + + struct ThreadMessage { + ThreadMessageType type; + int position; + }; + + RingBuffer thread_messages; Jack::MIDIPort *midi_omni_out; typedef std::vector MIDIPortArray; @@ -37,7 +50,15 @@ public: MIDIPortArray midi_ports; bool defunct; + bool enable_sync; + bool waiting_for_sync; + JackPlayer() : Jack::Client("jacker") { + + thread_messages.resize(100); + + enable_sync = false; + waiting_for_sync = false; defunct = false; midi_omni_out = new Jack::MIDIPort( *this, "omni", Jack::MIDIPort::IsOutput); @@ -57,6 +78,61 @@ public: delete midi_omni_out; } + void play() { + if (enable_sync) { + transport_start(); + return; + } + Player::play(); + } + + void seek(int frame) { + if (enable_sync) { + double samples = + (double)frame * (sample_rate * 60.0) / (double)(model->beats_per_minute * model->frames_per_beat); + transport_locate((int)(samples+0.5)); + return; + } + Player::seek(frame); + } + + void stop() { + if (enable_sync) { + transport_stop(); + return; + } + Player::stop(); + } + + void handle_thread_message(ThreadMessage &msg) { + switch(msg.type) { + case MsgPlay : { + printf("SYNC: play\n"); + Player::play(); + } break; + case MsgStop : { + printf("SYNC: stop\n"); + Player::stop(); + } break; + case MsgSeek : { + printf("SYNC: seek to %i\n", msg.position); + Player::seek(msg.position); + } break; + default: break; + } + } + + void mix() { + ThreadMessage msg; + while (!thread_messages.empty()) { + msg = thread_messages.peek(); + handle_thread_message(msg); + thread_messages.pop(); + } + + Player::mix(); + } + virtual void on_sample_rate(Jack::NFrames nframes) { set_sample_rate((int)nframes); reset(); @@ -68,7 +144,86 @@ public: midi_ports[msg.port]->write_event(offset, msg); } + int get_frame_from_position(const Jack::Position &pos) { + /* + if (pos.valid & JackPositionBBT) { + int result = model->beats_per_bar * model->frames_per_beat * (pos.bar-1); + result += model->frames_per_beat * (pos.beat-1); + result += pos.tick * model->frames_per_beat / pos.ticks_per_beat; + return result; + } + */ + return (int)(((double)model->beats_per_minute * pos.frame / (sample_rate * 60.0))+0.5) * + model->frames_per_beat; + //return -1; + } + + virtual bool on_sync(Jack::TransportState state, const Jack::Position &pos) { + if (!enable_sync) { + waiting_for_sync = false; + return true; + } + + bool r_playing = false; + + switch(state) { + case JackTransportStarting: + { + if (waiting_for_sync) { + if (thread_messages.empty()) { + printf("finished waiting for sync\n"); + waiting_for_sync = false; + return true; + } else { + //printf("waiting for sync\n"); + return false; + } + } + waiting_for_sync = true; + r_playing = true; + } break; + case JackTransportStopped: + { + waiting_for_sync = false; + r_playing = false; + } break; + default: break; + } + + int r_position = get_frame_from_position(pos); + if (r_position != get_position()) { + ThreadMessage msg; + msg.type = MsgSeek; + msg.position = r_position; + thread_messages.push(msg); + } + + if (r_playing != is_playing()) { + ThreadMessage msg; + msg.type = (r_playing?MsgPlay:MsgStop); + thread_messages.push(msg); + } + return false; + } + virtual void on_process(Jack::NFrames size) { + if (enable_sync) { + Jack::Position tpos; + memset(&tpos, 0, sizeof(tpos)); + Jack::TransportState tstate = transport_query(&tpos); + switch(tstate) { + case JackTransportStopped: { + if (is_playing() && thread_messages.empty()) { + ThreadMessage msg; + msg.type = MsgStop; + thread_messages.push(msg); + } + } break; + default: + break; + } + } + midi_omni_out->clear_buffer(); for (MIDIPortArray::iterator iter = midi_ports.begin(); iter != midi_ports.end(); ++iter) { @@ -94,6 +249,7 @@ public: Glib::RefPtr fpb_range; Glib::RefPtr accel_group; + Glib::RefPtr sync_action; Gtk::Window* window; PatternView *pattern_view; @@ -323,6 +479,23 @@ public: assert(adjustment); return adjustment; } + + template + Glib::RefPtr connect_toggle_action(const std::string &name, + const T &signal, + const Glib::ustring& accel_path="") { + Glib::RefPtr action = + Glib::RefPtr::cast_static( + builder->get_object(name)); + assert(action); + if (!accel_path.empty()) { + action->set_accel_path(accel_path); + action->set_accel_group(accel_group); + action->connect_accelerator(); + } + action->signal_toggled().connect(signal); + return action; + } template void connect_action(const std::string &name, @@ -348,6 +521,10 @@ public: show_song_view(); } + void on_sync_action() { + player->enable_sync = sync_action->get_active(); + } + void init_menu() { connect_action("new_action", sigc::mem_fun(*this, &App::on_new_action)); connect_action("open_action", sigc::mem_fun(*this, &App::on_open_action), @@ -365,6 +542,7 @@ public: sigc::mem_fun(*this, &App::on_show_song_action), AccelPathTrackView); + sync_action = connect_toggle_action("sync_action", sigc::mem_fun(*this, &App::on_sync_action)); } void on_bpm_changed() { diff --git a/player.hpp b/player.hpp index 90c376e..b9f0995 100644 --- a/player.hpp +++ b/player.hpp @@ -50,7 +50,7 @@ public: protected: class Model *model; }; - + class Player { public: enum { @@ -96,7 +96,7 @@ public: bool is_playing() const; void play_event(int track, const class PatternEvent &event); - + protected: void premix(); void mix_events(MessageQueue &queue, int samples); @@ -117,7 +117,6 @@ protected: volatile int read_position; // last read position, in frames volatile bool playing; - }; //=============================================================================