diff --git a/src/config.hxx b/src/config.hxx new file mode 100644 index 0000000..4e3b0fa --- /dev/null +++ b/src/config.hxx @@ -0,0 +1,8 @@ + +#ifndef LUPPP_CONFIG_H +#define LUPPP_CONFIG_H + +#define NTRACKS 8 + +#endif // LUPPP_CONFIG_H + diff --git a/src/event.hxx b/src/event.hxx index 28b25b9..99cdb9c 100644 --- a/src/event.hxx +++ b/src/event.hxx @@ -22,6 +22,8 @@ namespace Event RECORD, LOOPER_STATE, + LOOPER_LOOP_LENGTH, + METRONOME_ACTIVE, }; }; @@ -64,6 +66,18 @@ class EventLooperState : public EventBase EventLooperState(int t, Looper::State s) : track(t), state(s){} }; +class EventLooperLoopLength : public EventBase +{ + public: + int type() { return int(LOOPER_LOOP_LENGTH); } + uint32_t size() { return sizeof(EventLooperLoopLength); } + + int track; + float scale; // multiply length by this + EventLooperLoopLength(){} + EventLooperLoopLength(int t, float s) : track(t), scale(s){} +}; + class EventLoadSample : public EventBase { public: diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index d90f34c..b37622e 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -62,6 +62,12 @@ void handleDspEvents() jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventLooperState) ); jack->setLooperState( ev.track, ev.state ); } break; } + case Event::LOOPER_LOOP_LENGTH: { + if ( availableRead >= sizeof(EventLooperLoopLength) ) { + EventLooperLoopLength ev; + jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventLooperLoopLength) ); + jack->setLooperLoopLength( ev.track, ev.scale ); + } break; } default: { // just do nothing diff --git a/src/gmastertrack.hxx b/src/gmastertrack.hxx new file mode 100644 index 0000000..0c3e79f --- /dev/null +++ b/src/gmastertrack.hxx @@ -0,0 +1,134 @@ + +#ifndef LUPPP_G_MASTER_TRACK_H +#define LUPPP_G_MASTER_TRACK_H + +#include + +#include +#include +#include + +#include "avtk/avtk_dial.h" +#include "avtk/avtk_button.h" +#include "avtk/avtk_background.h" + + +#include "eventhandler.hxx" + +using namespace std; + +static void gmastertrack_button_callback(Fl_Widget *w, void *data) { + + + int track = 0; + if ( data ) + track = *(int*)data; + + cout << "Button " << track << " " << w->label() << " clicked" << endl; + + if ( strcmp( w->label() , "Rec" ) == 0 ) + { + EventLooperState e = EventLooperState(track,Looper::STATE_RECORD_QUEUED); + writeToDspRingbuffer( &e ); + } + else if ( strcmp( w->label() , "Play" ) == 0 ) + { + EventLooperState e = EventLooperState(track,Looper::STATE_PLAY_QUEUED); + writeToDspRingbuffer( &e ); + } + else if ( strcmp( w->label() , "Stop" ) == 0 ) + { + EventLooperState e = EventLooperState(track,Looper::STATE_STOP_QUEUED); + writeToDspRingbuffer( &e ); + } + else if ( strcmp( w->label() , "+" ) == 0 ) + { + EventLooperLoopLength e = EventLooperLoopLength(track, 2); + writeToDspRingbuffer( &e ); + } + else if ( strcmp( w->label() , "-" ) == 0 ) + { + EventLooperLoopLength e = EventLooperLoopLength(track, 0.5); + writeToDspRingbuffer( &e ); + } + else if ( strcmp( w->label(), "Metro" ) == 0 ) + { + Avtk::Button* b = (Avtk::Button*)w; + b->value( !b->value() ); + EventMetronomeActive e = EventMetronomeActive( b->value() ); + writeToDspRingbuffer( &e ); + } + else + { + cout << __FILE__ << __LINE__ << " Error: unknown command string" << endl; + } +} + +class GMasterTrack : public Fl_Group +{ + public: + GMasterTrack(int x, int y, int w, int h, const char* l = 0 ) : + Fl_Group(x, y, w, h), + title( strdup(l) ), + bg( x, y , w, h, title ), + /* + button1(x + 5, y + 24, 100, 18,"Rec"), + button2(x + 5, y + 44, 100, 18,"Play"), + button3(x + 5, y + 64, 100, 18,"Stop"), + button4(x + 5, y + 84, 48, 18,"-"), + button5(x +57, y + 84, 48, 18,"+"), + button6(x + 5, y +104, 18, 18,"6"), + */ + metronomeButton(x + 5,y + 24,140,30,"Metro") + /* + dial1(x+15, y +155, 24, 24, "A"), + dial2(x+45, y +155, 24, 24, "B"), + dial3(x+75, y +155, 24, 24, "C") + */ + { + ID = privateID++; + /* + button1.callback( gmastertrack_button_callback, &ID ); + button2.callback( gmastertrack_button_callback, &ID ); + button3.callback( gmastertrack_button_callback, &ID ); + button4.callback( gmastertrack_button_callback, &ID ); + button5.callback( gmastertrack_button_callback, &ID ); + button6.callback( gmastertrack_button_callback, &ID ); + */ + metronomeButton.callback( gmastertrack_button_callback, 0 ); + + end(); // close the group + } + + ~GMasterTrack() + { + free(title); + } + + + private: + int ID; + + char* title; + + Avtk::Background bg; + /* + Avtk::Button button1; + Avtk::Button button2; + Avtk::Button button3; + Avtk::Button button4; + Avtk::Button button5; + Avtk::Button button6; + */ + Avtk::LightButton metronomeButton; + /* + Avtk::Dial dial1; + Avtk::Dial dial2; + Avtk::Dial dial3; + */ + + static int privateID; +}; + +#endif // LUPPP_G_MASTER_TRACK_H + diff --git a/src/gtrack.hxx b/src/gtrack.hxx index 0030163..4aa8242 100644 --- a/src/gtrack.hxx +++ b/src/gtrack.hxx @@ -36,6 +36,16 @@ static void gtrack_button_callback(Fl_Widget *w, void *data) { EventLooperState e = EventLooperState(track,Looper::STATE_STOP_QUEUED); writeToDspRingbuffer( &e ); } + else if ( strcmp( w->label() , "+" ) == 0 ) + { + EventLooperLoopLength e = EventLooperLoopLength(track, 2); + writeToDspRingbuffer( &e ); + } + else if ( strcmp( w->label() , "-" ) == 0 ) + { + EventLooperLoopLength e = EventLooperLoopLength(track, 0.5); + writeToDspRingbuffer( &e ); + } else { cout << __FILE__ << __LINE__ << " Error: unknown command string" << endl; @@ -53,9 +63,10 @@ class GTrack : public Fl_Group button1(x + 5, y + 24, 100, 18,"Rec"), button2(x + 5, y + 44, 100, 18,"Play"), button3(x + 5, y + 64, 100, 18,"Stop"), - button4(x + 5, y + 84, 18, 18,"4"), - button5(x + 5, y +104, 18, 18,"5"), - button6(x + 5, y +124, 18, 18,"6"), + button4(x + 5, y + 84, 48, 18,"-"), + button5(x +57, y + 84, 48, 18,"+"), + + button6(x + 5, y +104, 18, 18,"6"), dial1(x+15, y +155, 24, 24, "A"), dial2(x+45, y +155, 24, 24, "B"), diff --git a/src/gui.cxx b/src/gui.cxx index ad4fad5..a256be0 100644 --- a/src/gui.cxx +++ b/src/gui.cxx @@ -6,44 +6,34 @@ // Hack, move to gtrack.cpp int GTrack::privateID = 0; +int GMasterTrack::privateID = 0; using namespace std; -static void gui_button_callback(Fl_Widget *w, void *data) -{ - Avtk::Button* b = (Avtk::Button*)w; - if ( strcmp( w->label(), "Metronome" ) == 0 ) - { - b->value( !b->value() ); - EventMetronomeActive e = EventMetronomeActive( b->value() ); - writeToDspRingbuffer( &e ); - } -} - Gui::Gui() : - window(600,280) + window(1200,280) { window.color(FL_BLACK); window.label("Luppp 5"); Avtk::Image* header = new Avtk::Image(0,0,600,36,"header.png"); - metronomeButton = new Avtk::LightButton(0,0,200,30,"Metronome"); - - for (int i = 0; i < 5; i++ ) + int i = 0; + for (; i < NTRACKS; i++ ) { stringstream s; s << "Track " << i+1; tracks.push_back( new GTrack(8 + i * 118, 40, 110, 230, s.str().c_str() ) ); } - metronomeButton->callback( gui_button_callback, 0 ); + master = new GMasterTrack(9 + i * 118, 40, 150, 230, "Master"); + /* box = new Fl_Box(655, 5, 200, 60, "BPM = 120"); box->box(FL_UP_BOX); box->labelsize(36); box->labeltype(FL_SHADOW_LABEL); - + */ window.end(); } diff --git a/src/gui.hxx b/src/gui.hxx index 35e56c0..4a06156 100644 --- a/src/gui.hxx +++ b/src/gui.hxx @@ -8,7 +8,9 @@ #include "avtk/avtk_light_button.h" +#include "config.hxx" #include "gtrack.hxx" +#include "gmastertrack.hxx" #include @@ -23,7 +25,9 @@ class Gui private: Fl_Double_Window window; Fl_Box* box; - Avtk::LightButton* metronomeButton; + + GMasterTrack* master; + vector tracks; }; diff --git a/src/jack.cxx b/src/jack.cxx index f4cbb39..06c5f4f 100644 --- a/src/jack.cxx +++ b/src/jack.cxx @@ -47,7 +47,7 @@ Jack::Jack() cerr << "Jack() error setting timebase callback" << endl; } - for(int i = 0; i < 5; i++) + for(int i = 0; i < NTRACKS; i++) { loopers.push_back( new Looper(i) ); timeManager.registerObserver( loopers.back() ); diff --git a/src/jack.hxx b/src/jack.hxx index 85b9ef8..fb3e891 100644 --- a/src/jack.hxx +++ b/src/jack.hxx @@ -17,6 +17,7 @@ #include #include +#include "config.hxx" #include "looper.hxx" #include "metronome.hxx" #include "timemanager.hxx" @@ -36,6 +37,10 @@ class Jack { loopers.at(t)->setState(s); } + void setLooperLoopLength(int t, float l) + { + loopers.at(t)->setLoopLength(l); + } Metronome* getMetronome(){return &metronome;} TimeManager* getTimeManager(){return &timeManager;} diff --git a/src/looper.cxx b/src/looper.cxx index ebf9bc2..1d1e72f 100644 --- a/src/looper.cxx +++ b/src/looper.cxx @@ -7,19 +7,11 @@ extern Jack* jack; void Looper::setState(State s) { - // before update, check if we recording, if so, print info - /* - if ( state == STATE_RECORDING ) + // ensure we're not setting eg PLAY_QUEUED, if we're already PLAYING + if ( static_cast(s) != static_cast(state) + 1) { - int newBpm = 120;// (lastWrittenSampleIndex / (44100/2) ) * 60; - - cout << "Looper " << track << " ending record: endPoint @ " << lastWrittenSampleIndex - << ". Bpm " << newBpm << " perhaps?" << endl; - - jack->getTimeManager()->setBpm( newBpm ); + cout << "new state " << s << endl; + state = s; } - */ - - // quantize?! - state = s; } + diff --git a/src/looper.hxx b/src/looper.hxx index 29b5ffa..714229b 100644 --- a/src/looper.hxx +++ b/src/looper.hxx @@ -14,17 +14,19 @@ class Looper : public Observer // for notifications { public: enum State { - STATE_PLAYING = 0x01, - STATE_PLAY_QUEUED = 0x02, - STATE_RECORDING = 0x03, - STATE_RECORD_QUEUED = 0x04, - STATE_STOPPED = 0x05, - STATE_STOP_QUEUED = 0x06, + STATE_PLAYING = 0, + STATE_PLAY_QUEUED, + STATE_RECORDING, + STATE_RECORD_QUEUED, + STATE_STOPPED, + STATE_STOP_QUEUED, }; Looper(int t) : track(t), state(STATE_STOPPED), + numBeats (4), + playedBeats(0), endPoint (0), playPoint (0), lastWrittenSampleIndex(0) @@ -34,8 +36,13 @@ class Looper : public Observer // for notifications void bar() { - //cout << "Looper " << track << " got bar()" << flush; - playPoint = 0; + // only reset if we're on the last beat of a loop + if ( playedBeats >= numBeats + 1 ) + { + //cout << "Looper " << track << " restting to 0 " << endl; + playPoint = 0; + playedBeats = 0; + } if ( state == STATE_PLAY_QUEUED ) { @@ -44,7 +51,7 @@ class Looper : public Observer // for notifications playPoint = 0; endPoint = lastWrittenSampleIndex; } - if ( state == STATE_RECORD_QUEUED ) + if ( state == STATE_RECORD_QUEUED && state != STATE_RECORDING ) { cout << " Q->Recording " << endl; state = STATE_RECORDING; @@ -52,7 +59,7 @@ class Looper : public Observer // for notifications endPoint = 0; lastWrittenSampleIndex = 0; } - if ( state == STATE_PLAY_QUEUED ) + if ( state == STATE_PLAY_QUEUED && state != STATE_STOPPED ) { cout << " Q->Stopped " << endl; state = STATE_STOPPED; @@ -60,8 +67,20 @@ class Looper : public Observer // for notifications } } + void setLoopLength(float l) + { + numBeats *= l; + + // avoid the 0 * 2 problem + if ( numBeats < 1 ) + numBeats = 1; + + cout << "Looper " << track << " loop lenght now " << numBeats << endl; + } + void beat() { + playedBeats++; //cout << "Looper " << track << " got beat()" << flush; } @@ -108,6 +127,8 @@ class Looper : public Observer // for notifications State state; int fpb; + int numBeats; + int playedBeats; int endPoint, playPoint, lastWrittenSampleIndex; float sample[44100*60]; diff --git a/src/metronome.hxx b/src/metronome.hxx index 1aabcf8..3669f70 100644 --- a/src/metronome.hxx +++ b/src/metronome.hxx @@ -18,7 +18,7 @@ class Metronome : public Observer Metronome() : playPoint (0), playBar (false), - active (true) + active (false) { // create beat and bar samples endPoint = (44100.f/441);