Add records n beats feature.

This commit is contained in:
Valentin Boettcher 2018-05-07 16:12:56 +02:00
parent e00c403b19
commit afcda1c8a8
12 changed files with 282 additions and 66 deletions

View file

@ -18,12 +18,13 @@
#include "clipselector.hxx"
#include <unistd.h>
#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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
/*
/*
* Author: Harry van Haaren 2013
* harryhaaren@gmail.com
*
@ -23,7 +23,7 @@
#include <stdint.h>
/*
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

View file

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

View file

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

View file

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

View file

@ -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();

View file

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

View file

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

View file

@ -16,8 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "looperclip.hxx"
#include <stdio.h>
#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() );
}

View file

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