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();
|
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) {
|
Client::Client(const char *name) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
handle = NULL;
|
handle = NULL;
|
||||||
|
@ -94,6 +104,9 @@ bool Client::init() {
|
||||||
handle_status(jack_set_sample_rate_callback(
|
handle_status(jack_set_sample_rate_callback(
|
||||||
handle, &sample_rate_callback, this));
|
handle, &sample_rate_callback, this));
|
||||||
|
|
||||||
|
handle_status(jack_set_sync_callback(
|
||||||
|
handle, &sync_callback, this));
|
||||||
|
|
||||||
jack_on_shutdown(handle, &shutdown_callback, this);
|
jack_on_shutdown(handle, &shutdown_callback, this);
|
||||||
|
|
||||||
for (PortList::iterator i = ports.begin(); i != ports.end(); ++i) {
|
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) {
|
void Client::add_port(Port *port) {
|
||||||
ports.push_back(port);
|
ports.push_back(port);
|
||||||
if (handle) // port came after init, so post-init it
|
if (handle) // port came after init, so post-init it
|
||||||
|
@ -137,6 +155,22 @@ void Client::remove_port(Port *port) {
|
||||||
ports.remove(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,
|
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 {
|
class Client {
|
||||||
friend class Port;
|
friend class Port;
|
||||||
|
|
||||||
|
@ -115,9 +118,15 @@ public:
|
||||||
|
|
||||||
bool is_created();
|
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_process(NFrames size) {}
|
||||||
virtual void on_sample_rate(NFrames nframes) {}
|
virtual void on_sample_rate(NFrames nframes) {}
|
||||||
virtual void on_shutdown() {}
|
virtual void on_shutdown() {}
|
||||||
|
virtual bool on_sync(TransportState state, const Position &pos) { return true; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void add_port(Port *port);
|
void add_port(Port *port);
|
||||||
|
@ -132,6 +141,7 @@ protected:
|
||||||
static int process_callback(jack_nframes_t size, void *arg);
|
static int process_callback(jack_nframes_t size, void *arg);
|
||||||
static int sample_rate_callback(jack_nframes_t nframes, void *arg);
|
static int sample_rate_callback(jack_nframes_t nframes, void *arg);
|
||||||
static void shutdown_callback(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>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="menuitem_song">
|
<object class="GtkImageMenuItem" id="menuitem_song">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="related_action">show_song_action</property>
|
|
||||||
<property name="use_action_appearance">True</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_underline">True</property>
|
||||||
<property name="use_stock">True</property>
|
<property name="use_stock">True</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -101,8 +101,8 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="menuitem_pattern">
|
<object class="GtkImageMenuItem" id="menuitem_pattern">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="related_action">show_pattern_action</property>
|
|
||||||
<property name="use_action_appearance">True</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_underline">True</property>
|
||||||
<property name="use_stock">True</property>
|
<property name="use_stock">True</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -146,8 +146,8 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkToolButton" id="toolbutton1">
|
<object class="GtkToolButton" id="toolbutton1">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="related_action">play_action</property>
|
|
||||||
<property name="use_action_appearance">True</property>
|
<property name="use_action_appearance">True</property>
|
||||||
|
<property name="related_action">play_action</property>
|
||||||
<property name="label" translatable="yes">toolbutton1</property>
|
<property name="label" translatable="yes">toolbutton1</property>
|
||||||
<property name="use_underline">True</property>
|
<property name="use_underline">True</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -159,8 +159,8 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkToolButton" id="toolbutton2">
|
<object class="GtkToolButton" id="toolbutton2">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="related_action">stop_action</property>
|
|
||||||
<property name="use_action_appearance">True</property>
|
<property name="use_action_appearance">True</property>
|
||||||
|
<property name="related_action">stop_action</property>
|
||||||
<property name="label" translatable="yes">toolbutton2</property>
|
<property name="label" translatable="yes">toolbutton2</property>
|
||||||
<property name="use_underline">True</property>
|
<property name="use_underline">True</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -310,6 +310,19 @@
|
||||||
<property name="homogeneous">True</property>
|
<property name="homogeneous">True</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</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>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
@ -702,8 +715,8 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="menuitem5">
|
<object class="GtkImageMenuItem" id="menuitem5">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="related_action">add_track_action</property>
|
|
||||||
<property name="use_action_appearance">True</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_underline">True</property>
|
||||||
<property name="use_stock">True</property>
|
<property name="use_stock">True</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -749,4 +762,9 @@
|
||||||
<property name="stock_id">gtk-quit</property>
|
<property name="stock_id">gtk-quit</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAccelGroup" id="accel_group"/>
|
<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>
|
</interface>
|
||||||
|
|
180
main.cpp
180
main.cpp
|
@ -17,6 +17,7 @@
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
|
|
||||||
#include "jsong.hpp"
|
#include "jsong.hpp"
|
||||||
|
#include "ring_buffer.hpp"
|
||||||
|
|
||||||
namespace Jacker {
|
namespace Jacker {
|
||||||
|
|
||||||
|
@ -26,10 +27,22 @@ const char AccelPathPatternView[] = "<Jacker>/View/Pattern";
|
||||||
const char AccelPathTrackView[] = "<Jacker>/View/Song";
|
const char AccelPathTrackView[] = "<Jacker>/View/Song";
|
||||||
const char AccelPathSave[] = "<Jacker>/File/Save";
|
const char AccelPathSave[] = "<Jacker>/File/Save";
|
||||||
const char AccelPathOpen[] = "<Jacker>/File/Open";
|
const char AccelPathOpen[] = "<Jacker>/File/Open";
|
||||||
|
|
||||||
class JackPlayer : public Jack::Client,
|
class JackPlayer : public Jack::Client,
|
||||||
public Player {
|
public Player {
|
||||||
public:
|
public:
|
||||||
|
enum ThreadMessageType {
|
||||||
|
MsgPlay = 0,
|
||||||
|
MsgStop = 1,
|
||||||
|
MsgSeek = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThreadMessage {
|
||||||
|
ThreadMessageType type;
|
||||||
|
int position;
|
||||||
|
};
|
||||||
|
|
||||||
|
RingBuffer<ThreadMessage> thread_messages;
|
||||||
|
|
||||||
Jack::MIDIPort *midi_omni_out;
|
Jack::MIDIPort *midi_omni_out;
|
||||||
typedef std::vector<Jack::MIDIPort *> MIDIPortArray;
|
typedef std::vector<Jack::MIDIPort *> MIDIPortArray;
|
||||||
|
@ -37,7 +50,15 @@ public:
|
||||||
MIDIPortArray midi_ports;
|
MIDIPortArray midi_ports;
|
||||||
bool defunct;
|
bool defunct;
|
||||||
|
|
||||||
|
bool enable_sync;
|
||||||
|
bool waiting_for_sync;
|
||||||
|
|
||||||
JackPlayer() : Jack::Client("jacker") {
|
JackPlayer() : Jack::Client("jacker") {
|
||||||
|
|
||||||
|
thread_messages.resize(100);
|
||||||
|
|
||||||
|
enable_sync = false;
|
||||||
|
waiting_for_sync = false;
|
||||||
defunct = false;
|
defunct = false;
|
||||||
midi_omni_out = new Jack::MIDIPort(
|
midi_omni_out = new Jack::MIDIPort(
|
||||||
*this, "omni", Jack::MIDIPort::IsOutput);
|
*this, "omni", Jack::MIDIPort::IsOutput);
|
||||||
|
@ -57,6 +78,61 @@ public:
|
||||||
delete midi_omni_out;
|
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) {
|
virtual void on_sample_rate(Jack::NFrames nframes) {
|
||||||
set_sample_rate((int)nframes);
|
set_sample_rate((int)nframes);
|
||||||
reset();
|
reset();
|
||||||
|
@ -68,7 +144,86 @@ public:
|
||||||
midi_ports[msg.port]->write_event(offset, msg);
|
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) {
|
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();
|
midi_omni_out->clear_buffer();
|
||||||
for (MIDIPortArray::iterator iter = midi_ports.begin();
|
for (MIDIPortArray::iterator iter = midi_ports.begin();
|
||||||
iter != midi_ports.end(); ++iter) {
|
iter != midi_ports.end(); ++iter) {
|
||||||
|
@ -94,6 +249,7 @@ public:
|
||||||
Glib::RefPtr<Gtk::Adjustment> fpb_range;
|
Glib::RefPtr<Gtk::Adjustment> fpb_range;
|
||||||
|
|
||||||
Glib::RefPtr<Gtk::AccelGroup> accel_group;
|
Glib::RefPtr<Gtk::AccelGroup> accel_group;
|
||||||
|
Glib::RefPtr<Gtk::ToggleAction> sync_action;
|
||||||
|
|
||||||
Gtk::Window* window;
|
Gtk::Window* window;
|
||||||
PatternView *pattern_view;
|
PatternView *pattern_view;
|
||||||
|
@ -323,6 +479,23 @@ public:
|
||||||
assert(adjustment);
|
assert(adjustment);
|
||||||
return 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>
|
template<typename T>
|
||||||
void connect_action(const std::string &name,
|
void connect_action(const std::string &name,
|
||||||
|
@ -348,6 +521,10 @@ public:
|
||||||
show_song_view();
|
show_song_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void on_sync_action() {
|
||||||
|
player->enable_sync = sync_action->get_active();
|
||||||
|
}
|
||||||
|
|
||||||
void init_menu() {
|
void init_menu() {
|
||||||
connect_action("new_action", sigc::mem_fun(*this, &App::on_new_action));
|
connect_action("new_action", sigc::mem_fun(*this, &App::on_new_action));
|
||||||
connect_action("open_action", sigc::mem_fun(*this, &App::on_open_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),
|
sigc::mem_fun(*this, &App::on_show_song_action),
|
||||||
AccelPathTrackView);
|
AccelPathTrackView);
|
||||||
|
|
||||||
|
sync_action = connect_toggle_action("sync_action", sigc::mem_fun(*this, &App::on_sync_action));
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_bpm_changed() {
|
void on_bpm_changed() {
|
||||||
|
|
|
@ -50,7 +50,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
class Model *model;
|
class Model *model;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Player {
|
class Player {
|
||||||
public:
|
public:
|
||||||
enum {
|
enum {
|
||||||
|
@ -96,7 +96,7 @@ public:
|
||||||
bool is_playing() const;
|
bool is_playing() const;
|
||||||
|
|
||||||
void play_event(int track, const class PatternEvent &event);
|
void play_event(int track, const class PatternEvent &event);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void premix();
|
void premix();
|
||||||
void mix_events(MessageQueue &queue, int samples);
|
void mix_events(MessageQueue &queue, int samples);
|
||||||
|
@ -117,7 +117,6 @@ protected:
|
||||||
|
|
||||||
volatile int read_position; // last read position, in frames
|
volatile int read_position; // last read position, in frames
|
||||||
volatile bool playing;
|
volatile bool playing;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
Loading…
Reference in New Issue