Initial JACK transport support. Can be switched on with the "Sync" button in the toolbar. Can de-sync easily. Best is to stop, seek, then start.
This commit is contained in:
parent
f94a218fe8
commit
6172e8a7ec
34
jack.cpp
34
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,
|
||||
|
|
10
jack.hpp
10
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);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
|
|
28
jacker.glade
28
jacker.glade
|
@ -92,8 +92,8 @@
|
|||
<child>
|
||||
<object class="GtkImageMenuItem" id="menuitem_song">
|
||||
<property name="visible">True</property>
|
||||
<property name="related_action">show_song_action</property>
|
||||
<property name="use_action_appearance">True</property>
|
||||
<property name="related_action">show_song_action</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
|
@ -101,8 +101,8 @@
|
|||
<child>
|
||||
<object class="GtkImageMenuItem" id="menuitem_pattern">
|
||||
<property name="visible">True</property>
|
||||
<property name="related_action">show_pattern_action</property>
|
||||
<property name="use_action_appearance">True</property>
|
||||
<property name="related_action">show_pattern_action</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
|
@ -146,8 +146,8 @@
|
|||
<child>
|
||||
<object class="GtkToolButton" id="toolbutton1">
|
||||
<property name="visible">True</property>
|
||||
<property name="related_action">play_action</property>
|
||||
<property name="use_action_appearance">True</property>
|
||||
<property name="related_action">play_action</property>
|
||||
<property name="label" translatable="yes">toolbutton1</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
|
@ -159,8 +159,8 @@
|
|||
<child>
|
||||
<object class="GtkToolButton" id="toolbutton2">
|
||||
<property name="visible">True</property>
|
||||
<property name="related_action">stop_action</property>
|
||||
<property name="use_action_appearance">True</property>
|
||||
<property name="related_action">stop_action</property>
|
||||
<property name="label" translatable="yes">toolbutton2</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
|
@ -310,6 +310,19 @@
|
|||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="toolbutton7">
|
||||
<property name="visible">True</property>
|
||||
<property name="use_action_appearance">True</property>
|
||||
<property name="related_action">sync_action</property>
|
||||
<property name="label" translatable="yes">toolbutton7</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -702,8 +715,8 @@
|
|||
<child>
|
||||
<object class="GtkImageMenuItem" id="menuitem5">
|
||||
<property name="visible">True</property>
|
||||
<property name="related_action">add_track_action</property>
|
||||
<property name="use_action_appearance">True</property>
|
||||
<property name="related_action">add_track_action</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
|
@ -749,4 +762,9 @@
|
|||
<property name="stock_id">gtk-quit</property>
|
||||
</object>
|
||||
<object class="GtkAccelGroup" id="accel_group"/>
|
||||
<object class="GtkToggleAction" id="sync_action">
|
||||
<property name="label">Sync</property>
|
||||
<property name="short_label">Sync</property>
|
||||
<property name="tooltip">Sync to JACK Transport</property>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
178
main.cpp
178
main.cpp
|
@ -17,6 +17,7 @@
|
|||
#include "player.hpp"
|
||||
|
||||
#include "jsong.hpp"
|
||||
#include "ring_buffer.hpp"
|
||||
|
||||
namespace Jacker {
|
||||
|
||||
|
@ -30,6 +31,18 @@ const char AccelPathOpen[] = "<Jacker>/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<ThreadMessage> thread_messages;
|
||||
|
||||
Jack::MIDIPort *midi_omni_out;
|
||||
typedef std::vector<Jack::MIDIPort *> 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<Gtk::Adjustment> fpb_range;
|
||||
|
||||
Glib::RefPtr<Gtk::AccelGroup> accel_group;
|
||||
Glib::RefPtr<Gtk::ToggleAction> sync_action;
|
||||
|
||||
Gtk::Window* window;
|
||||
PatternView *pattern_view;
|
||||
|
@ -324,6 +480,23 @@ public:
|
|||
return adjustment;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Glib::RefPtr<Gtk::ToggleAction> connect_toggle_action(const std::string &name,
|
||||
const T &signal,
|
||||
const Glib::ustring& accel_path="") {
|
||||
Glib::RefPtr<Gtk::ToggleAction> action =
|
||||
Glib::RefPtr<Gtk::ToggleAction>::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<typename T>
|
||||
void connect_action(const std::string &name,
|
||||
const T &signal,
|
||||
|
@ -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() {
|
||||
|
|
|
@ -117,7 +117,6 @@ protected:
|
|||
|
||||
volatile int read_position; // last read position, in frames
|
||||
volatile bool playing;
|
||||
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
|
|
Loading…
Reference in New Issue