diff --git a/src/avtk/clipselector.cxx b/src/avtk/clipselector.cxx index 5b6fccb..48a550f 100644 --- a/src/avtk/clipselector.cxx +++ b/src/avtk/clipselector.cxx @@ -18,12 +18,13 @@ #include "clipselector.hxx" - #include #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #include "../gui.hxx" +#define RECORD_BARS_MENU_ITEM(num) { #num, 0, setRecordBarsCb, (void*)num, FL_MENU_RADIO | ((clips[clipNum].getBeatsToRecord() == num*4) ? FL_MENU_VALUE : 0) | (empty ? 0 : FL_MENU_INACTIVE) } +#define RECORD_LENGTH_MENU_ITEM(num) {#num, 0, setLengthCb, (void*)num, empty ? FL_MENU_INACTIVE : 0} extern Gui* gui; @@ -90,6 +91,34 @@ void ClipSelector::clipName(int clip, std::string name) redraw(); } +void ClipSelector::setClipBeats(int scene, int beats, bool isBeatsToRecord) +{ + clips[scene].setBeats(beats, isBeatsToRecord); + redraw(); +} + +double getCairoTextWith(cairo_t * cr, const char * str) +{ + cairo_text_extents_t ex; + cairo_text_extents(cr, str, &ex); + return ex.width; +} + +void trimStringToFit(cairo_t * cr, std::string * str, double maxWidth) +{ + double ellWidth = getCairoTextWith(cr, "…"); + double textWidth = getCairoTextWith(cr, str->c_str()); + + if(textWidth > maxWidth) { + while(textWidth + ellWidth > maxWidth){ + str->pop_back(); + textWidth = getCairoTextWith(cr, str->c_str()); + } + + *str += "…"; + } +} + void ClipSelector::setSpecial(int scene) { if ( special == -1 && scene == -1 ) { @@ -196,16 +225,38 @@ void ClipSelector::draw() cairo_line_to( cr, x+clipHeight-1, drawY + clipHeight - 2); cairo_stroke(cr); + int beatLen = 0; + + // clip bars + if(!_master) { + const char * bars = clips[i].getBarsText(); + const char * beats = clips[i].getBeatsText(); + bool toRecord = clips[i].getBeats() <= 0 && clips[i].getBeatsToRecord() > 0; // If there are BeatsToRecord, but no Beats + + if(strlen(bars)) { + if(toRecord) cairo_set_source_rgba(cr, 1.f, 0 / 255.f , 0 / 255.f, 1.f); + else cairo_set_source_rgba( cr, 255 / 255.f, 255 / 255.f, 255 / 255.f , 0.9 ); + + cairo_move_to( cr, x + clipWidth - 5 - getCairoTextWith(cr, bars), drawY + textHeight - 8); + cairo_set_font_size( cr, 11 ); + cairo_show_text( cr, bars); + + beatLen = getCairoTextWith(cr, beats); + cairo_move_to( cr, x + clipWidth - 5 - beatLen, drawY + textHeight + 7); + cairo_show_text( cr, beats); + } + } + // clip name cairo_move_to( cr, x + clipHeight + 5, drawY + textHeight ); cairo_set_source_rgba( cr, 255 / 255.f, 255 / 255.f , 255 / 255.f , 0.9 ); cairo_set_font_size( cr, 11 ); - std::string tmp = clips[i].getName().substr(0,8); - + std::string tmp = clips[i].getName(); + trimStringToFit(cr, &tmp, clipWidth - (clipHeight + 15 + beatLen)); cairo_show_text( cr, tmp.c_str() ); - // special indicator? + // special indicator if ( i == special ) { cairo_rectangle( cr, x+2, drawY, clipWidth -1, clipHeight - 3 ); cairo_set_source_rgba(cr, 0.0, 153 / 255.f, 1.0, alpha); @@ -232,7 +283,43 @@ void ClipSelector::resize(int X, int Y, int W, int H) redraw(); } +void setRecordBarsCb(Fl_Widget *w, void* data) +{ + if(!w || !data) + return; + ClipSelector *track = (ClipSelector*)w; + long bars = (long)data; + if(bars == -2) { + const char* answer = fl_input("Enter a custom number: "); + if(!answer) { + bars = -1; + fl_message("Please enter value between 1 and %i.", MAX_BARS); + } else + bars = atoi(answer); + } + + if(bars > MAX_BARS) { + bars = -1; + fl_message("Please enter value between 1 and %i.", MAX_BARS); + } + + EventLooperBarsToRecord e(track->ID, track->getLastClipNum(), bars); + writeToDspRingbuffer( &e ); +} + +void setLengthCb(Fl_Widget *w, void* data) +{ + ClipSelector *track = (ClipSelector*)w; + long beats = (long)data; + EventLooperLoopLength e(track->ID, track->getLastClipNum(), beats); + writeToDspRingbuffer( &e ); +} + +int ClipSelector::getLastClipNum() +{ + return clipNum; +} int ClipSelector::handle(int event) { @@ -247,10 +334,12 @@ int ClipSelector::handle(int event) { // calculate the clicked clip number int clipHeight = (h / numClips); - int clipNum = ( (Fl::event_y() ) - y ) / clipHeight; + clipNum = ( (Fl::event_y() ) - y ) / clipHeight; if (clipNum >= numClips) clipNum = numClips -1; // fix for clicking the lowest pixel + return clipNum; + // handle right clicks: popup menu if ( Fl::event_state(FL_BUTTON3) ) { if ( _master ) { @@ -264,18 +353,28 @@ int ClipSelector::handle(int event) } + bool empty = clips[clipNum].getState() == GridLogic::STATE_EMPTY; Fl_Menu_Item rclick_menu[] = { { "Load" }, { "Save" }, { "Special"}, { "Beats", 0, 0, 0, FL_SUBMENU | FL_MENU_DIVIDER }, - {"1 "}, - {"2"}, - {"4"}, - {"8"}, - {"16"}, - {"32"}, - {"64"}, + RECORD_LENGTH_MENU_ITEM(1), + RECORD_LENGTH_MENU_ITEM(2), + RECORD_LENGTH_MENU_ITEM(4), + RECORD_LENGTH_MENU_ITEM(8), + RECORD_LENGTH_MENU_ITEM(16), + RECORD_LENGTH_MENU_ITEM(32), + RECORD_LENGTH_MENU_ITEM(64), + {0}, + { "Bars to record", 0, 0, 0, FL_SUBMENU | FL_MENU_DIVIDER}, + RECORD_BARS_MENU_ITEM(1), + RECORD_BARS_MENU_ITEM(2), + RECORD_BARS_MENU_ITEM(4), + RECORD_BARS_MENU_ITEM(6), + RECORD_BARS_MENU_ITEM(8), + {"Endless", 0, setRecordBarsCb, (void*)-1, FL_MENU_DIVIDER | FL_MENU_RADIO | ((clips[clipNum].getBeatsToRecord() <= 0) ? FL_MENU_VALUE : 0) | (empty ? 0 : FL_MENU_INACTIVE)}, + {"Custom", 0, setRecordBarsCb, (void*)-2, empty ? 0 : FL_MENU_INACTIVE}, {0}, //{ "Record" }, { "Use as tempo" }, @@ -302,27 +401,6 @@ int ClipSelector::handle(int event) free(tmp); gui->selectSaveSample( ID, clipNum ); } - } else if ( strcmp(m->label(), "1 ") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,1); - writeToDspRingbuffer( &e ); - } else if ( strcmp(m->label(), "2") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,2); - writeToDspRingbuffer( &e ); - } else if ( strcmp(m->label(), "4") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,4); - writeToDspRingbuffer( &e ); - } else if ( strcmp(m->label(), "8") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,8); - writeToDspRingbuffer( &e ); - } else if ( strcmp(m->label(), "16") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,16); - writeToDspRingbuffer( &e ); - } else if ( strcmp(m->label(), "32") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,32); - writeToDspRingbuffer( &e ); - } else if ( strcmp(m->label(), "64") == 0 ) { - EventLooperLoopLength e = EventLooperLoopLength(ID, clipNum ,64); - writeToDspRingbuffer( &e ); } else if ( strcmp(m->label(), "Use as tempo") == 0 ) { EventLooperUseAsTempo e (ID, clipNum); writeToDspRingbuffer( &e ); @@ -338,6 +416,8 @@ int ClipSelector::handle(int event) // for a clip to become 0 EventGridState e( ID, clipNum, GridLogic::STATE_EMPTY ); writeToDspRingbuffer( &e ); + } else { + m->do_callback(this, m->user_data()); } } else { if ( _master ) { @@ -385,5 +465,21 @@ int ClipSelector::handle(int event) } } +void ClipState::setBeats(int numBeats, bool isBeatsToRecord) +{ + if(numBeats > 0) { + beatsText = std::to_string(numBeats); + barsText = std::to_string(numBeats/4); + if(isBeatsToRecord){ + beatsToRecord = numBeats; + beats = 0; + } else { + beats = numBeats; + if(numBeats <= 0) + beatsToRecord = -1; + } + } + else + barsText = beatsText = std::string(""); +} } // Avtk - diff --git a/src/avtk/clipselector.hxx b/src/avtk/clipselector.hxx index 001db14..67caa91 100644 --- a/src/avtk/clipselector.hxx +++ b/src/avtk/clipselector.hxx @@ -41,7 +41,8 @@ class ClipState public: ClipState() : state(GridLogic::STATE_EMPTY), - name("") + name(""), + barsText("") {} void setName(std::string n) @@ -64,16 +65,39 @@ public: return state; } + int getBeatsToRecord(){ + return beatsToRecord; + } + + int getBeats(){ + return beats; + } + + void setBeats(int numBeats, bool isBeatsToRecord); + + const char *getBeatsText() { + return beatsText.c_str(); + } + + const char *getBarsText() { + return barsText.c_str(); + } + private: GridLogic::State state; std::string name; + std::string beatsText; + std::string barsText; + + int beatsToRecord = -1; + int beats = 0; }; class ClipSelector : public Fl_Button { public: ClipSelector( int _x, int _y, int _w, int _h, - const char *_label, bool master = false); + char *_label, bool master = false); int ID; @@ -97,15 +121,19 @@ public: void setState( int clipNum, GridLogic::State s ); void clipName(int clip, std::string name); + void setClipBeats(int scene, int beats, bool isBeatsToRecord); + std::string clipName(int clip); void draw(); int handle(int event); void resize(int X, int Y, int W, int H); -}; + int getLastClipNum(); +private: + int clipNum; +}; } // Avtk #endif // AVTK_CLIP_SELECTOR_H - diff --git a/src/config.hxx b/src/config.hxx index b238204..c091e21 100644 --- a/src/config.hxx +++ b/src/config.hxx @@ -65,6 +65,7 @@ #define LUPPP_RETURN_WARNING 1 #define LUPPP_RETURN_ERROR 2 +#define MAX_BARS 64 /// debug.hxx for printing convienience #include "debug.hxx" diff --git a/src/event.hxx b/src/event.hxx index 8e7c1e3..d830cb2 100644 --- a/src/event.hxx +++ b/src/event.hxx @@ -1,4 +1,4 @@ -/* +/* * Author: Harry van Haaren 2013 * harryhaaren@gmail.com * @@ -23,7 +23,7 @@ #include /* - event.hxx + event.hxx This file provides declarations for each type of event that the engine uses. */ @@ -31,7 +31,6 @@ #include "looper.hxx" #include "gridlogic.hxx" #include "transport.hxx" - #pragma GCC diagnostic ignored "-Wunused-parameter" using namespace std; @@ -97,6 +96,7 @@ enum EVENT_TYPE { LOOPER_PROGRESS, LOOPER_LOOP_LENGTH, LOOPER_LOOP_USE_AS_TEMPO, + LOOPER_BARS_TO_RECORD, // choose how many bars to record /// Transport etc METRONOME_ACTIVE, @@ -127,6 +127,9 @@ enum EVENT_TYPE { // for keeping loop index's inside the enum EVENT_TYPE_FINAL, + + // Clip + CLIP_BEATS_CHANGED, }; /// returns the pretty name of an event @@ -267,6 +270,25 @@ public: EventGridSelectNewChosen(int t = -1, int s = -1):track(t),scene(s) {} }; +class EventLooperBarsToRecord : public EventBase +{ +public: + int type() + { + return int(LOOPER_BARS_TO_RECORD); + } + uint32_t size() + { + return sizeof(EventLooperBarsToRecord); + } + + int track; + int scene; + int bars; + EventLooperBarsToRecord(int t = -1, int s = -1, int b = -1) : track(t), scene(s), bars(b) {} +}; + + class EventQuit : public EventBase { public: @@ -605,6 +627,7 @@ public: EventGridEvent(int t, int s, bool p): track(t), scene(s), pressed(p) {} }; + class EventGridState : public EventBase { public: @@ -1151,6 +1174,26 @@ public: } }; +class EventClipBeatsChanged : public EventBase +{ +public: + int type() + { + return int(CLIP_BEATS_CHANGED); + } + uint32_t size() + { + return sizeof(EventClipBeatsChanged); + } + + int track; + int scene; + int beats; + bool isBeatsToRecord; + + EventClipBeatsChanged() {} + EventClipBeatsChanged(int t, int s, int b, bool i) : track(t), scene(s), beats(b), isBeatsToRecord(i) {} +}; + #endif // LUPPP_EVENT_H - diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index 3643ace..567f3c8 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -224,7 +224,6 @@ void handleDspEvents() break; } - case Event::LOOPER_LOAD: { if ( availableRead >= sizeof(EventLooperLoad) ) { EventLooperLoad ev; @@ -266,8 +265,14 @@ void handleDspEvents() } break; } - - + case Event::LOOPER_BARS_TO_RECORD: { + if ( availableRead >= sizeof(EventLooperBarsToRecord) ) { + EventLooperBarsToRecord ev; + jack_ringbuffer_read( rbToDsp, (char*)&ev, sizeof(EventLooperBarsToRecord) ); + jack->getLogic()->looperBarsToRecord( ev.track, ev.scene, ev.bars ); + } + break; + } case Event::LOOPER_LOOP_USE_AS_TEMPO: { if ( availableRead >= sizeof(EventLooperUseAsTempo) ) { EventLooperUseAsTempo ev; @@ -486,4 +491,3 @@ void writeToDspRingbuffer(EventBase* e) } #endif // LUPPP_EVENT_HANDLER_DSP_H - diff --git a/src/eventhandlergui.cxx b/src/eventhandlergui.cxx index e0c836f..f3a214a 100644 --- a/src/eventhandlergui.cxx +++ b/src/eventhandlergui.cxx @@ -221,7 +221,7 @@ void handleGuiEvents() delete ev.ab; } else { gui->getDiskWriter()->writeAudioBuffer(ev.track, ev.scene, ev.ab, - gui->saveBufferPath.c_str()); + gui->saveBufferPath.c_str()); gui->saveBufferPath = ""; } @@ -279,6 +279,14 @@ void handleGuiEvents() break; } + case Event::CLIP_BEATS_CHANGED: { + if ( availableRead >= sizeof(EventClipBeatsChanged) ) { + EventClipBeatsChanged ev; + jack_ringbuffer_read( rbToGui, (char*)&ev, sizeof(EventClipBeatsChanged) ); + gui->getTrack(ev.track)->getClipSelector()->setClipBeats(ev.scene, ev.beats, ev.isBeatsToRecord); + } + break; + } case Event::TRACK_SEND: { if ( availableRead >= sizeof(EventTrackSend) ) { @@ -491,4 +499,3 @@ void writeToGuiRingbuffer(EventBase* e) } #endif // LUPPP_EVENT_HANDLER_DSP_H - diff --git a/src/jacksendreturn.hxx b/src/jacksendreturn.hxx index e2fc3f6..fc6c6c8 100644 --- a/src/jacksendreturn.hxx +++ b/src/jacksendreturn.hxx @@ -43,14 +43,14 @@ public: void sendVolume(float vol); private: + int m_trackid; + AudioProcessor* m_previousProcessor; bool m_active; float m_sendvol; jack_port_t* m_sendport_l; jack_port_t* m_sendport_r; jack_port_t* m_returnport_l; jack_port_t* m_returnport_r; - int m_trackid; - AudioProcessor* m_previousProcessor; int m_counter; }; diff --git a/src/logic.cxx b/src/logic.cxx index 6c5b547..dca2374 100644 --- a/src/logic.cxx +++ b/src/logic.cxx @@ -157,6 +157,14 @@ void Logic::looperClipLenght(int t, int s, int l) } +void Logic::looperBarsToRecord(int t, int s, int b) +{ + if ( t >= 0 && t < NTRACKS ) { + jack->getLooper( t )->getClip( s )->setBarsToRecord(b); + } else { LUPPP_WARN("invalid track number %i: check controller map has \"track\" field.", t ); + } +} + void Logic::looperUseAsTempo(int t, int s) { size_t clipBeats = jack->getLooper( t )->getClip( s )->getBeats(); diff --git a/src/logic.hxx b/src/logic.hxx index 8a16c9f..c6d1a2f 100644 --- a/src/logic.hxx +++ b/src/logic.hxx @@ -20,6 +20,7 @@ #define LUPPP_LOGIC_H #include "event.hxx" +#include "avtk/clipselector.hxx" /** Logic * This class contains an interface exposing most functionality in Luppp. The @@ -63,6 +64,7 @@ public: void trackJackSend(int t, float vol); void looperUseAsTempo(int track, int scene); void looperClipLenght(int track, int scene, int lenght); + void looperBarsToRecord(int track, int scene, int bars); }; #endif // LUPPP_LOGIC_H diff --git a/src/looper.cxx b/src/looper.cxx index ad376d1..dac8439 100644 --- a/src/looper.cxx +++ b/src/looper.cxx @@ -89,7 +89,6 @@ void Looper::beat() // clips[i]->setPlayHead(iph-(iph%fpb)*playSpeed); // } - } void Looper::setRequestedBuffer(int s, AudioBuffer* ab) @@ -110,6 +109,9 @@ void Looper::process(unsigned int nframes, Buffers* buffers) // handle state of clip, and do what needs doing: // record into buffer, play from buffer, etc if ( clips[clip]->recording() ) { + if(clips[clip]->getBeatsToRecord() > 0 && clips[clip]->getBeats() >= clips[clip]->getBeatsToRecord() - 4) + clips[clip]->queuePlay(true); + if ( clips[clip]->recordSpaceAvailable() < LOOPER_SAMPLES_BEFORE_REQUEST && !clips[clip]->newBufferInTransit() ) { EventLooperClipRequestBuffer e( track, clip, clips[clip]->audioBufferSize() + LOOPER_SAMPLES_UPDATE_SIZE); @@ -195,12 +197,11 @@ void Looper::pitchShift(int count, float* input, float* output) int iTemp5 = int(fTemp4); float out=output[0]; out += (float)(((1 - fTemp3) * (((fTemp4 - iTemp5) * - fVec0[(IOTA-int((int((1 + iTemp5)) & 65535)))&65535]) + ((0 - (( - fRec0[0] + fSlow3) - iTemp5)) * fVec0[(IOTA-int((iTemp5 & 65535))) - &65535]))) + (fTemp3 * (((fRec0[0] - iTemp1) * fVec0[(IOTA-int((int( - iTemp2) & 65535)))&65535]) + ((iTemp2 - fRec0[0]) * fVec0[(IOTA-int(( - iTemp1 & 65535)))&65535])))); - + fVec0[(IOTA-int((int((1 + iTemp5)) & 65535)))&65535]) + ((0 - (( + fRec0[0] + fSlow3) - iTemp5)) * fVec0[(IOTA-int((iTemp5 & 65535))) + &65535]))) + (fTemp3 * (((fRec0[0] - iTemp1) * fVec0[(IOTA-int((int( + iTemp2) & 65535)))&65535]) + ((iTemp2 - fRec0[0]) * fVec0[(IOTA-int(( + iTemp1 & 65535)))&65535])))); output[0]=out; fRec0[1] = fRec0[0]; IOTA = IOTA+1; diff --git a/src/looperclip.cxx b/src/looperclip.cxx index 5b70f75..062faab 100644 --- a/src/looperclip.cxx +++ b/src/looperclip.cxx @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -#include "looperclip.hxx" - #include #include "config.hxx" #include "jack.hxx" @@ -51,6 +49,8 @@ void LooperClip::init() _queuePlay = false; _queueStop = false; _queueRecord= false; + _beatsToRecord= -1; + setBeats(0); if ( _buffer ) { _buffer->init(); @@ -59,8 +59,6 @@ void LooperClip::init() _playhead = 0; _recordhead = 0; - - } void LooperClip::save() @@ -116,6 +114,8 @@ void LooperClip::load( AudioBuffer* ab ) } _buffer = ab; + EventClipBeatsChanged e( track, scene, _beatsToRecord, true ); + writeToGuiRingbuffer( &e ); _playhead = 0; jack->getControllerUpdater()->setTrackSceneProgress(track, scene, 0 ); @@ -183,11 +183,27 @@ void LooperClip::setPlayHead(float ph) { if(!_recording&&_playing) { _playhead = ph; - jack->getControllerUpdater()->setTrackSceneProgress(track, scene, getProgress() ); + jack->getControllerUpdater()->setTrackSceneProgress( track, scene, getProgress() ); } } +void LooperClip::setBarsToRecord(int bars) +{ + if(!(_playing || _queuePlay || _queueStop || _loaded)) { + _beatsToRecord = bars * 4; // we set beats + EventClipBeatsChanged e( track, scene, _beatsToRecord, true); + writeToGuiRingbuffer(&e); + } +} +int LooperClip::getBeatsToRecord() +{ + return _beatsToRecord; +} + +int LooperClip::getBarsToRecord(){ + return _beatsToRecord / 4; +} void LooperClip::record(int count, float* L, float* R) { @@ -239,8 +255,12 @@ size_t LooperClip::audioBufferSize() void LooperClip::setBeats(int beats) { - if ( _buffer ) { - _buffer->setBeats( beats ); + if (_loaded || _playing || _queuePlay || _queueStop || beats == 0) { + if(_buffer) + _buffer->setBeats( beats ); + + EventClipBeatsChanged e(track, scene, beats, false); + writeToGuiRingbuffer(&e); } } @@ -275,7 +295,7 @@ void LooperClip::bar() // first update the buffer, as time has passed if ( _recording ) { // FIXME: assumes 4 beats in a bar - _buffer->setBeats( _buffer->getBeats() + 4 ); + setBeats( _buffer->getBeats() + 4 ); _buffer->setAudioFrames( jack->getTimeManager()->getFpb() * _buffer->getBeats() ); } diff --git a/src/looperclip.hxx b/src/looperclip.hxx index b6af836..e01c310 100644 --- a/src/looperclip.hxx +++ b/src/looperclip.hxx @@ -116,6 +116,11 @@ public: ///reset the play head to zero. Does nothing when recording void setPlayHead(float ph); + // set how many beats to record (up to full bar) + void setBarsToRecord(int bars); + int getBeatsToRecord(); + int getBarsToRecord(); + #ifdef BUILD_TESTS // used only in test cases void setState( bool load, bool play, bool rec, bool qPlay, bool qStop, bool qRec ); @@ -140,8 +145,9 @@ private: float _playhead; float _recordhead; + int _beatsToRecord; + AudioBuffer* _buffer; }; #endif // LUPPP_LOOPER_CLIP_H -