1
Fork 0

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:
Leonard Ritter 2010-08-05 11:38:51 +02:00
parent f94a218fe8
commit 6172e8a7ec
5 changed files with 248 additions and 9 deletions

View File

@ -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,

View File

@ -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);
};
//=============================================================================

View File

@ -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>

180
main.cpp
View File

@ -17,6 +17,7 @@
#include "player.hpp"
#include "jsong.hpp"
#include "ring_buffer.hpp"
namespace Jacker {
@ -26,10 +27,22 @@ const char AccelPathPatternView[] = "<Jacker>/View/Pattern";
const char AccelPathTrackView[] = "<Jacker>/View/Song";
const char AccelPathSave[] = "<Jacker>/File/Save";
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;
@ -323,6 +479,23 @@ public:
assert(adjustment);
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,
@ -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() {

View File

@ -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;
};
//=============================================================================