From 335571ad1733ec7f21856100fcc2f102a4ab7641 Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Fri, 31 May 2019 20:59:48 +0200 Subject: [PATCH] Add Auto Stop Recording --- CHANGELOG.md | 4 ++ src/controller/genericmidi.cxx | 9 +++ src/event.cxx | 16 +++++ src/event.hxx | 64 ++++++++++++++++++++ src/eventhandlerdsp.cxx | 12 +++- src/eventhandlergui.cxx | 11 ++++ src/gmastertrack.cxx | 104 ++++++++++++++++++++++++++------- src/gmastertrack.hxx | 5 +- src/jack.cxx | 12 ++++ src/jack.hxx | 8 +++ src/logic.cxx | 12 ++++ src/logic.hxx | 4 ++ src/looperclip.cxx | 38 ++++++++---- src/looperclip.hxx | 4 +- 14 files changed, 266 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ac572..874e335 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # recent changes +## Features: + +* Auto Stop Record of Clips after a defined number of bars + ## Improvements * Improve BPM Dial to avoid jitter diff --git a/src/controller/genericmidi.cxx b/src/controller/genericmidi.cxx index 6c2409f..45ff64b 100644 --- a/src/controller/genericmidi.cxx +++ b/src/controller/genericmidi.cxx @@ -329,6 +329,15 @@ void GenericMIDI::midi(unsigned char* midi) case Event::MASTER_VOL: jack->getLogic()->trackVolume( -1 , value ); break; + case Event::AUTO_STOP_REC_CLIP_LENGTH: + jack->getLogic()->setClipLength(value); + break; + case Event::AUTO_STOP_REC_CLIP_LENGTH_UP: + jack->getLogic()->setClipLengthUp(); + break; + case Event::AUTO_STOP_REC_CLIP_LENGTH_DOWN: + jack->getLogic()->setClipLengthDown(); + break; } } diff --git a/src/event.cxx b/src/event.cxx index 2e199b4..ac39167 100644 --- a/src/event.cxx +++ b/src/event.cxx @@ -41,6 +41,13 @@ const char* EventMetronomeVolume::prettyName = "metronome:volume"; const char* EventGridEvent::prettyName = "grid:event"; const char* EventGridLaunchScene::prettyName = "grid:launch_scene"; +const char *EventAutoStopRecClipLength::prettyName = + "auto_stop_rec:clip_length"; +const char *EventAutoStopRecClipLengthUp::prettyName = + "auto_stop_rec:clip_length_up"; +const char *EventAutoStopRecClipLengthDown::prettyName = + "auto_stop_rec:clip_length_down"; + EVENT_TYPE Event::getTypeFromName(const char* name) { for(int i = 0; i < EVENT_TYPE_FINAL; i++) { @@ -116,6 +123,15 @@ const char* Event::getPrettyName( int type ) case METRONOME_VOLUME: { return EventMetronomeVolume::prettyName; } + case AUTO_STOP_REC_CLIP_LENGTH: { + return EventAutoStopRecClipLength::prettyName; + } + case AUTO_STOP_REC_CLIP_LENGTH_UP: { + return EventAutoStopRecClipLengthUp::prettyName; + } + case AUTO_STOP_REC_CLIP_LENGTH_DOWN: { + return EventAutoStopRecClipLengthDown::prettyName; + } default: return 0; diff --git a/src/event.hxx b/src/event.hxx index b50ca64..f305f5f 100644 --- a/src/event.hxx +++ b/src/event.hxx @@ -99,6 +99,11 @@ enum EVENT_TYPE { LOOPER_LOOP_LENGTH, LOOPER_LOOP_USE_AS_TEMPO, + // Set the clip length for auto stop recording + AUTO_STOP_REC_CLIP_LENGTH, + AUTO_STOP_REC_CLIP_LENGTH_UP, + AUTO_STOP_REC_CLIP_LENGTH_DOWN, + /// Transport etc METRONOME_ACTIVE, METRONOME_VOLUME, @@ -864,6 +869,65 @@ public: EventLooperUseAsTempo(int t, int s) : track(t), scene(s) {} }; +class EventAutoStopRecClipLength : public EventBase { +public: + static const char *prettyName; + int clipLength; + + const char *name() { + return prettyName; + } + + int type() { + return int(AUTO_STOP_REC_CLIP_LENGTH); + } + + uint32_t size() { + return sizeof(EventAutoStopRecClipLength); + } + + EventAutoStopRecClipLength() {} + EventAutoStopRecClipLength(int l) : clipLength(l) {} +}; + +class EventAutoStopRecClipLengthUp : public EventBase { +public: + static const char *prettyName; + + const char *name() { + return prettyName; + } + + int type() { + return int(AUTO_STOP_REC_CLIP_LENGTH_UP); + } + + uint32_t size() { + return sizeof(EventAutoStopRecClipLengthUp); + } + + EventAutoStopRecClipLengthUp() {} +}; + +class EventAutoStopRecClipLengthDown : public EventBase { +public: + static const char *prettyName; + + const char *name() { + return prettyName; + } + + int type() { + return int(AUTO_STOP_REC_CLIP_LENGTH_DOWN); + } + + uint32_t size() { + return sizeof(EventAutoStopRecClipLengthDown); + } + + EventAutoStopRecClipLengthDown() {} +}; + class EventLooperLoad : public EventBase { public: diff --git a/src/eventhandlerdsp.cxx b/src/eventhandlerdsp.cxx index 43dc58d..1b25c62 100644 --- a/src/eventhandlerdsp.cxx +++ b/src/eventhandlerdsp.cxx @@ -272,7 +272,17 @@ void handleDspEvents() break; } - + // Set Clip Length for Auto Stop Record + case Event::AUTO_STOP_REC_CLIP_LENGTH: { + if(availableRead >= + sizeof(EventAutoStopRecClipLength)) { + EventAutoStopRecClipLength e; + jack_ringbuffer_read(rbToDsp, (char*)&e, + sizeof(EventAutoStopRecClipLength)); + jack->setClipLength(e.clipLength); + } + break; + } case Event::TIME_BPM: { if ( availableRead >= sizeof(EventTimeBPM) ) { diff --git a/src/eventhandlergui.cxx b/src/eventhandlergui.cxx index e23e171..b763624 100644 --- a/src/eventhandlergui.cxx +++ b/src/eventhandlergui.cxx @@ -124,6 +124,17 @@ void handleGuiEvents() } break; } + case Event::AUTO_STOP_REC_CLIP_LENGTH: { + if(availableRead >= sizeof(EventAutoStopRecClipLength)) { + EventAutoStopRecClipLength e; + jack_ringbuffer_read(rbToGui, + (char *)&e, + sizeof(EventAutoStopRecClipLength)); + gui->getMasterTrack()->setClipLength(e.clipLength); + + } + break; + } case Event::LOOPER_STATE: { if ( availableRead >= sizeof(EventLooperState) ) { EventLooperState ev; diff --git a/src/gmastertrack.cxx b/src/gmastertrack.cxx index 45d520b..e365fb3 100644 --- a/src/gmastertrack.cxx +++ b/src/gmastertrack.cxx @@ -19,6 +19,7 @@ #include "gmastertrack.hxx" #include +#include static void gmastertrack_tempoDial_callback(Fl_Widget *w, void *data) { @@ -188,37 +189,81 @@ static void gmastertrack_button_callback(Fl_Widget *w, void *data) } } -#define OFST 33 -GMasterTrack::GMasterTrack(int x, int y, int w, int h, const char* l ) : - Fl_Group(x, y, w, h), - title( strdup(l) ), - bg( x, y , w, h, title ), +static void gmastertrack_autoStopRec_callback(Fl_Widget *w, void *data) { + int clipLength = -1; - // with "true" master flag: launches scenes instead of clips on tracks - clipSel(x + 5, y + 26 + 102, 140, 294,"", true), + if(Fl::event_button() == FL_LEFT_MOUSE) { + Fl_Menu_Item rclick_menu[] = { { "1" }, { "2" }, { "4" }, + { "8" }, { "16" }, { "∞" }, { "Custom" }, { 0 } }; - source(x+5, y+26, 140, 100, ""), - volBox(x+5, y+422, 140, 232, ""), + Fl_Menu_Item *m = (Fl_Menu_Item *)rclick_menu->popup( + Fl::event_x(), Fl::event_y(), 0, 0, 0); - transport ( x + w * 2/4.f - 18, y + 436 + OFST * 0, 44,28, "Stop" ), - tapTempo ( x + w * 2/4.f - 18, y + 436 + OFST * 1, 44,28, "Tap" ), - metronomeButton( x + w * 2/4.f - 18, y + 436 + OFST * 2, 44,28,"Metro"), + if(!m) { + return; + } else if(strcmp(m->label(), "Custom") == 0) { + const char *answer = fl_input( + // TODO magic number + "Enter Clip Length (range 1 and 64):", 0); - tempoDial ( x + w * 2/4.f - 18, y + 436 + OFST * 3.5, 45, 38,"BPM"), - returnVol ( x + w * 2/4.f - 18, y + 436 + OFST * 5, 45, 38,"Return"), + if(answer) { + clipLength = atoi(answer); - inputVolume(x + 9,y + 26 + 4, w - 18, 30,""), + // TODO magic number + if(clipLength < 1 || clipLength > 64) { + // do not accept invalid input + clipLength = -1; + } + } + } else if(strcmp(m->label(), "∞") == 0) { + clipLength = 0; + } else { + clipLength = atoi(m->label()); + } - inputToSend (x + 10,y + 28 + 68, 40, 26,"Snd"), - inputToSendVol(x + w*0.2-15,y + 28 + 36, 30, 30,""), + } else { + clipLength = 0; + } - inputToSidechainKey (x + w*0.5-20,y + 28 + 68, 40, 26,"Key"), - inputToSidechainSignalVol(x + w*0.5-15,y + 28 + 36, 30, 30,""), + if(clipLength >= 0) { + EventAutoStopRecClipLength e = EventAutoStopRecClipLength(clipLength); + writeToDspRingbuffer(&e); + } +} - inputToMix (x + w*0.8-20,y + 28 + 68, 40, 26,"Mix"), - inputToMixVol(x + w*0.8-15,y + 28 + 36, 30, 30,""), +#define OFST 30 +GMasterTrack::GMasterTrack(int x, int y, int w, int h, const char *l) + : Fl_Group(x, y, w, h), title(strdup(l)), bg(x, y, w, h, title), - volume(x+106, y +425, 36, 216, "") + // with "true" master flag: launches scenes instead of clips on tracks + clipSel(x + 5, y + 26 + 102, 140, 294, "", true), + + source(x + 5, y + 26, 140, 100, ""), + volBox(x + 5, y + 422, 140, 232, ""), + + transport(x + w * 2 / 4.f - 18, y + 436 + OFST * 0, 44, 22, "Stop"), + tapTempo(x + w * 2 / 4.f - 18, y + 436 + OFST * 1, 44, 22, "Tap"), + metronomeButton( + x + w * 2 / 4.f - 18, y + 436 + OFST * 2, 44, 22, "Metro"), + autoStopRecButton( + x + w * 2 / 4.f - 18, y + 436 + OFST * 3, 44, 22, "∞"), + + tempoDial(x + w * 2 / 4.f - 18, y + 436 + OFST * 4, 45, 38, "BPM"), + returnVol( + x + w * 2 / 4.f - 18, y + 436 + OFST * 5.5, 45, 38, "Return"), + + inputVolume(x + 9, y + 26 + 4, w - 18, 30, ""), + + inputToSend(x + 10, y + 28 + 68, 40, 26, "Snd"), + inputToSendVol(x + w * 0.2 - 15, y + 28 + 36, 30, 30, ""), + + inputToSidechainKey(x + w * 0.5 - 20, y + 28 + 68, 40, 26, "Key"), + inputToSidechainSignalVol(x + w * 0.5 - 15, y + 28 + 36, 30, 30, ""), + + inputToMix(x + w * 0.8 - 20, y + 28 + 68, 40, 26, "Mix"), + inputToMixVol(x + w * 0.8 - 15, y + 28 + 36, 30, 30, ""), + + volume(x + 106, y + 425, 36, 216, "") { ID = privateID++; @@ -235,6 +280,8 @@ GMasterTrack::GMasterTrack(int x, int y, int w, int h, const char* l ) : metronomeButton.callback( gmastertrack_button_callback, 0 ); + autoStopRecButton.callback(gmastertrack_autoStopRec_callback, &ID); + tempoDial.callback( gmastertrack_tempoDial_callback, 0 ); inputToSend.setColor( 0, 1.0, 0 ); @@ -321,6 +368,19 @@ void GMasterTrack::metronomeEnable( bool b ) metronomeButton.value( b ); } +void +GMasterTrack::setClipLength(int l) +{ + const char *str; + if(l == 0) { + str = "∞"; + } else { + std::string tmp = std::to_string(l); + str = tmp.c_str(); + } + autoStopRecButton.copy_label(str); +} + int GMasterTrack::getBpm() { return bpm; diff --git a/src/gmastertrack.hxx b/src/gmastertrack.hxx index 2b57b81..428addb 100644 --- a/src/gmastertrack.hxx +++ b/src/gmastertrack.hxx @@ -59,6 +59,8 @@ public: void setInputToActive(int to, bool f); void metronomeEnable( bool b ); + void + setClipLength(int l); Avtk::Volume* getInputVolume(); Avtk::Volume* getVolume(); @@ -91,7 +93,8 @@ private: Avtk::Button transport; Avtk::Button tapTempo; Avtk::LightButton metronomeButton; - Avtk::Dial tempoDial; + Avtk::LightButton autoStopRecButton; + Avtk::Dial tempoDial; Avtk::Dial returnVol; diff --git a/src/jack.cxx b/src/jack.cxx index ff742b8..99e3d02 100644 --- a/src/jack.cxx +++ b/src/jack.cxx @@ -771,3 +771,15 @@ int Jack::static_timebase(jack_transport_state_t state, { return static_cast(instance)->timebase(state,nframes, pos, newPos ); } + +void +Jack::setClipLength(int l) +{ + // TODO magic numbers + if(l >= 0 && l <= 16) { + clipLength = l; + } + + EventAutoStopRecClipLength e = EventAutoStopRecClipLength(clipLength); + writeToGuiRingbuffer(&e); +} \ No newline at end of file diff --git a/src/jack.hxx b/src/jack.hxx index 1284426..037efbc 100644 --- a/src/jack.hxx +++ b/src/jack.hxx @@ -89,6 +89,10 @@ public: { return metronome; } + int getClipLength() + { + return clipLength; + } GridLogic* getGridLogic() { return gridLogic; @@ -120,6 +124,9 @@ public: void inputTo(INPUT_TO to, float v); void inputToActive(INPUT_TO to, bool a); + void + setClipLength(int l); + jack_client_t* getJackClientPointer() { return client; @@ -150,6 +157,7 @@ private: GridLogic* gridLogic; ControllerUpdater* controllerUpdater; + int clipLength; vector loopers; vector tracksendreturns; vector trackOutputs; diff --git a/src/logic.cxx b/src/logic.cxx index c99aacf..e390dff 100644 --- a/src/logic.cxx +++ b/src/logic.cxx @@ -168,3 +168,15 @@ void Logic::looperUseAsTempo(int t, int s) } } + +void Logic::setClipLength(float l) { + jack->setClipLength((int)(l * 16)); +} + +void Logic::setClipLengthUp() { + jack->setClipLength(jack->getClipLength() * 2); +} + +void Logic::setClipLengthDown() { + jack->setClipLength(jack->getClipLength() / 2); +} \ No newline at end of file diff --git a/src/logic.hxx b/src/logic.hxx index 8a16c9f..6daf627 100644 --- a/src/logic.hxx +++ b/src/logic.hxx @@ -63,6 +63,10 @@ public: void trackJackSend(int t, float vol); void looperUseAsTempo(int track, int scene); void looperClipLenght(int track, int scene, int lenght); + + void setClipLength(float f); + void setClipLengthUp(); + void setClipLengthDown(); }; #endif // LUPPP_LOGIC_H diff --git a/src/looperclip.cxx b/src/looperclip.cxx index 32b76b9..2646ba6 100644 --- a/src/looperclip.cxx +++ b/src/looperclip.cxx @@ -68,6 +68,8 @@ void LooperClip::init() _playbackSpeedChange = false; _beatsPlayed = 0; + _barsPlayed = 0; + _barsRecorded = 0; updateController(); } @@ -172,6 +174,13 @@ void LooperClip::resetPlayHead() { _playhead = 0; _beatsPlayed = 0; + _barsPlayed = 0; + + if(_playbackSpeedChange) { + _playbackSpeed = _nextPlaybackSpeed; + _playbackSpeedChange = false; + } + updateController(); } } @@ -254,25 +263,28 @@ long LooperClip::getActualAudioLength() return _buffer->getAudioFrames(); } -void LooperClip::bar() +void +LooperClip::bar() { - if(_playbackSpeedChange) { - _playbackSpeed = _nextPlaybackSpeed; - _playbackSpeedChange = false; + if(_playing) { + _barsPlayed++; + } + if(_recording) { + _barsRecorded++; } - if ( _queuePlay ) { + if(_queuePlay) { setPlaying(); - } - else if (_queueStop && _loaded) - { + } else if(_queueStop && _loaded) { setStopped(); - } - else if ( _queueRecord ) - { + } else if(_queueRecord) { setRecording(); } + if(jack->getClipLength() > 0 && + _barsRecorded == jack->getClipLength() - 1) { + queuePlay(); + } if(_recording) { // FIXME: assumes 4 beats in a bar _buffer->setBeats(_buffer->getBeats() + 4); @@ -281,7 +293,9 @@ void LooperClip::bar() } } -void LooperClip::beat() { +void +LooperClip::beat() +{ if(_playing) { _beatsPlayed++; } diff --git a/src/looperclip.hxx b/src/looperclip.hxx index 2c3be0e..9493f28 100644 --- a/src/looperclip.hxx +++ b/src/looperclip.hxx @@ -154,7 +154,9 @@ private: bool _playbackSpeedChange; unsigned int _beatsPlayed; - AudioBuffer* _buffer; + unsigned int _barsPlayed; + unsigned int _barsRecorded; + AudioBuffer *_buffer; /// Request new internal Buffer void requestNewBuffer();