Skip to content

Commit

Permalink
Version 4: New (improved?) velocity calculation. See wiki https://git…
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathan Kopp committed Mar 5, 2020
1 parent 847b6c0 commit 29dc314
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 37 deletions.
27 changes: 20 additions & 7 deletions linnstrument-firmware.ino
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ For any questions about this, contact Roger Linn Design at support@rogerlinndesi
/******************************************** CONSTANTS ******************************************/

const char* OSVersion = "222.";
const char* OSVersionBuild = ".K03";
const char* OSVersionBuild = ".K04";

// SPI addresses
#define SPI_LEDS 10 // Arduino pin for LED control over SPI
Expand Down Expand Up @@ -242,6 +242,7 @@ const unsigned short ccFaderDefaults[8] = {1, 2, 3, 4, 5, 6, 7, 8};
#define VELOCITY_SCALE_LOW 43
#define VELOCITY_SCALE_MEDIUM 41
#define VELOCITY_SCALE_HIGH 40
#define VELOCITY2_MAX_SAMPLES 8 // cannot be more than 63, due to 6-bit storage of vcount

#define DEFAULT_MIN_VELOCITY 1 // default minimum velocity value
#define DEFAULT_MAX_VELOCITY 127 // default maximum velocity value
Expand Down Expand Up @@ -269,6 +270,8 @@ byte sensorCol = 0; // currently read column in touch se
byte sensorRow = 0; // currently read row in touch sensor
byte sensorSplit = 0; // the split of the currently read touch sensor

int numCellsCalculatingVelocity = 0;

// The most-recently touched cell within each channel of each split is said to have "focus",
// saved as the specific column and row for the focus cell.
// If in 1Ch/Poly mode, continuous X and Y messages are sent only from movements within the focused cell.
Expand Down Expand Up @@ -315,7 +318,8 @@ struct __attribute__ ((packed)) TouchInfo {
boolean hasUsableX(); // indicates whether the X data is usable
void clearMusicalData(); // clear the musical data
void clearSensorData(); // clears the measured sensor data
boolean isCalculatingVelocity(); // indicates whether the initial velocity is being calculated
void setCalculatingVelocity(); // tag this cell as initial velocity being calculated
void clearCalculatingVelocity(); // tag this cell as initial velocity NOT being calculated
int32_t fxdInitialReferenceX(); // initial calibrated reference X value of each cell at the start of the touch

#ifdef TESTING_SENSOR_DISABLE
Expand Down Expand Up @@ -354,26 +358,30 @@ struct __attribute__ ((packed)) TouchInfo {
byte percentRawZ:7; // percentage of Z compared to the raw offset and range
boolean shouldRefreshX:1; // indicate whether it's necessary to refresh X

byte vcount:6; // the number of times the pressure was measured to obtain a velocity
byte vcount2:2; // number of samples below max

TouchState touched:2; // touch status of all sensor cells
byte vcount:4; // the number of times the pressure was measured to obtain a velocity
boolean slideTransfer:1; // indicates whether this touch is part of a slide transfer
boolean rogueSweepX:1; // indicates whether the last X position is a rogue sweep

byte pendingReleaseCount:4; // counter before which the note release will be effective
boolean featherTouch:1; // indicates whether this is a feather touch

unsigned short pressureZ:10; // the Z value with pressure sensitivity
unsigned short previousRawZ:12; // the previous raw Z value
int :5;
boolean featherTouch:1; // indicates whether this is a feather touch
boolean isCalculatingVelocity:1;

boolean phantomSet:1; // indicates whether phantom touch coordinates are set
byte velocity:7; // velocity from 0 to 127

boolean shouldRefreshZ:1; // indicate whether it's necessary to refresh Z
byte velocityZ:7; // the Z value with velocity sensitivity
byte maxVelocityZ; // 0..127

byte noteInitialVelocity:8;
unsigned short noteInitialMaxValueZHi:10;
unsigned short previousValueZHi:10;
boolean newVelocity:1;
};
TouchInfo touchInfo[MAXCOLS][MAXROWS]; // store as much touch information instances as there are cells

Expand Down Expand Up @@ -1465,18 +1473,23 @@ inline void modeLoopPerformance() {
if (sensorCell->initialX != SHRT_MIN && // check if there was movement on the cell
abs(sensorCell->initialX - sensorCell->currentCalibratedX) > CALX_QUARTER_UNIT) {
if (calcTimeDelta(millis(), sensorCell->lastTouch) > 70 ) { // only release if it's later than 70ms after the touch to debounce some note starts
sensorCell->clearCalculatingVelocity();

handleTouchRelease();
}
}
else { // this release happened on a mostly stationary touch, reduce the debounce time
if (calcTimeDelta(millis(), sensorCell->lastTouch) > 35 ) { // only release if it's later than 35ms after the touch to debounce some note starts
sensorCell->clearCalculatingVelocity();

handleTouchRelease();
}
}
}


if (canShortCircuit) {
sensorCell->shouldRefreshData(); // immediately process this cell again without going through a full surface scan
nextSensorCell();
return;
}
}
Expand Down
63 changes: 43 additions & 20 deletions ls_handleTouches.ino
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ These routines handle the processing of new touch events, continuous updates of
released touch events
**************************************************************************************************/

#define VELOCITYZ_TO_PRESSUREZ(x) ((x)*4/5)
#define PRESSUREZ_TO_VELOCITYZ(x) ((x)*5/4)

extern int32_t colsInRowsAnimated[MAXROWS];
extern unsigned long touchAnimationLastMoment[MAXCOLS][MAXROWS];
extern unsigned long touchAnimationSpeed[MAXCOLS][MAXROWS];
extern signed char touchAnimationLastState[MAXCOLS][MAXROWS];
extern byte lastTouchAnimCol[2];
extern byte lastTouchAnimRow[2];

extern int numCellsCalculatingVelocity;


void cellTouched(TouchState state) {
cellTouched(sensorCol, sensorRow, state);
Expand Down Expand Up @@ -130,6 +129,8 @@ void transferFromSameRowCell(byte col) {
sensorCell->noteInitialMaxValueZHi = fromCell->noteInitialMaxValueZHi;
sensorCell->previousValueZHi = fromCell->previousValueZHi;
sensorCell->vcount = fromCell->vcount;
sensorCell->vcount2 = fromCell->vcount2;
sensorCell->maxVelocityZ = fromCell->maxVelocityZ;
noteTouchMapping[sensorSplit].changeCell(sensorCell->note, sensorCell->channel, sensorCol, sensorRow);

fromCell->lastTouch = 0;
Expand All @@ -152,6 +153,7 @@ void transferFromSameRowCell(byte col) {
fromCell->velocity = 0;
fromCell->noteInitialVelocity = 0;
// do not reset vcount!
// do not reset previousVelocityZ

signed char channel = sensorCell->channel;
if (channel > 0 && col == focus(sensorSplit, channel).col && sensorRow == focus(sensorSplit, channel).row) {
Expand Down Expand Up @@ -200,6 +202,8 @@ void transferToSameRowCell(byte col) {
toCell->noteInitialMaxValueZHi = sensorCell->noteInitialMaxValueZHi;
toCell->previousValueZHi = sensorCell->previousValueZHi;
toCell->vcount = sensorCell->vcount;
toCell->vcount2 = sensorCell->vcount2;
toCell->maxVelocityZ = sensorCell->maxVelocityZ;
noteTouchMapping[sensorSplit].changeCell(toCell->note, toCell->channel, col, sensorRow);

sensorCell->lastTouch = 0;
Expand Down Expand Up @@ -499,7 +503,8 @@ boolean handleNewTouch() {
else if (!isLowRow() || allowNewTouchOnLowRow()) {
initVelocity();
calcVelocity(sensorCell->velocityZ);
result = true;
sensorCell->setCalculatingVelocity();
result = false;
}
else {
cellTouched(untouchedCell);
Expand All @@ -509,7 +514,8 @@ boolean handleNewTouch() {
default:
initVelocity();
calcVelocity(sensorCell->velocityZ);
result = true;
sensorCell->setCalculatingVelocity();
result = false;
break;
}
}
Expand Down Expand Up @@ -711,6 +717,7 @@ void handleNonPlayingTouch() {
break;
case displayCalibration:
initVelocity();
sensorCell->clearCalculatingVelocity();
break;
case displayEditAudienceMessage:
handleEditAudienceMessageNewTouch();
Expand Down Expand Up @@ -775,34 +782,47 @@ boolean handleXYZupdate() {
VelocityState velState = calcVelocity(sensorCell->velocityZ);

// velocity calculation works in stages, handle each one
boolean newVelocity = false;
switch (velState) {
// when the velocity is being calculated, the performance loop can be short-circuited
case velocityCalculating:
return true;
sensorCell->setCalculatingVelocity();
//return true;
break;

case velocityNew:
sensorCell->clearCalculatingVelocity();

if (isPhantomTouchIndividual() || isPhantomTouchContextual()) {
cellTouched(untouchedCell);
return false;
}

// mark this as a valid new velocity and process it as such further down the method
newVelocity = true;
sensorCell->newVelocity = true;
break;

case velocityCalculated:
sensorCell->clearCalculatingVelocity();

// velocity has been calculated, no need to short-circuit anymore and we can continue
// with the main touch logic
break;
}

// If we are calculating velocity for at least one cell, then skip all the rest of this function
// FOR ALL CELLS (not just the ones that are claculating velocity). This means we don't spend
// time sending any messages. This takes the place of the previous 'shortCircuit' concept
// so that velocity calculations are fast for quick note-on messages, while still scanning all
// of the pads to improve response to multiple notes being hit simultaneously.
if(numCellsCalculatingVelocity > 0) return true;

// only continue if the active display modes require finger tracking
if (displayMode != displayNormal &&
displayMode != displayVolume &&
(displayMode != displaySplitPoint || splitButtonDown)) {
// check if this should be handled as a non-playing touch
if (newVelocity) {
if (sensorCell->newVelocity) {
sensorCell->newVelocity = false;
handleNonPlayingTouch();
performContinuousTasks();
}
Expand Down Expand Up @@ -834,7 +854,7 @@ boolean handleXYZupdate() {
}

// this cell corresponds to a playing note
if (newVelocity) {
if (sensorCell->newVelocity) {
sensorCell->lastTouch = lastTouchMoment;
sensorCell->lastMovedX = 0;
sensorCell->lastValueX = INVALID_DATA;
Expand Down Expand Up @@ -894,7 +914,8 @@ boolean handleXYZupdate() {
}

// we don't need to handle any expression in control mode
if (controlModeActive && !newVelocity) {
if (controlModeActive && !sensorCell->newVelocity) {
sensorCell->newVelocity = false;
return false;
}

Expand All @@ -917,7 +938,7 @@ boolean handleXYZupdate() {
}

// for a new note, we need to reset the previous value to avoid slewing from this cell's old value
if(newVelocity) {
if(sensorCell->newVelocity) {
sensorCell->fxdPrevTimbre = FXD_CONST_255;
}
short tempY = handleYExpression();
Expand All @@ -929,24 +950,24 @@ boolean handleXYZupdate() {
// update the low row state, but not for the low row cells themselves when there's a new velocity
// this is handled in lowRowStart, and immediately calling handleLowRowState will wrongly handle the
// low row state transitions
if ((!newVelocity || !isLowRow()) && !userFirmwareActive) {
handleLowRowState(newVelocity, valueX, valueY, valueZ);
if ((!sensorCell->newVelocity || !isLowRow()) && !userFirmwareActive) {
handleLowRowState(sensorCell->newVelocity, valueX, valueY, valueZ);
}

// the volume fader has its own operation mode
if (displayMode == displayVolume) {
if (sensorCell->isMeaningfulTouch()) {
handleVolumeNewTouch(newVelocity);
handleVolumeNewTouch(sensorCell->newVelocity);
}
}
else if (Split[sensorSplit].ccFaders && !userFirmwareActive) {
if (sensorCell->isMeaningfulTouch()) {
handleFaderTouch(newVelocity);
handleFaderTouch(sensorCell->newVelocity);
}
}
else if (Split[Global.currentPerSplit].sequencer && !userFirmwareActive) {
if (sensorCell->isMeaningfulTouch()) {
handleSequencerTouch(newVelocity);
handleSequencerTouch(sensorCell->newVelocity);
}
}
else if (handleNotes && sensorCell->hasNote()) {
Expand Down Expand Up @@ -1051,7 +1072,7 @@ boolean handleXYZupdate() {
}

// send the note on if this in a newly calculated velocity
if (newVelocity) {
if (sensorCell->newVelocity) {
if (isStrummedSplit(sensorSplit)) {
handleStrummedRowChange(true, 0);
}
Expand Down Expand Up @@ -1083,6 +1104,7 @@ boolean handleXYZupdate() {
}
}

sensorCell->newVelocity = false;
return false;
}

Expand Down Expand Up @@ -1300,8 +1322,6 @@ void sendNewNote() {
midiSendNoteOn(sensorSplit, sensorCell->note, sensorCell->velocity, sensorCell->channel);
}
else {
unsigned short valueZHiFromVelocity = VELOCITYZ_TO_PRESSUREZ(sensorCell->velocity*1016/127);
if(valueZHiFromVelocity>508 && valueZHiFromVelocity > valueZHi) valueZHi = valueZHiFromVelocity;
byte valueZ = scale1016to127(valueZHi, false);

preSendLoudness(sensorSplit, valueZ, valueZHi, sensorCell->note, sensorCell->channel, true);
Expand Down Expand Up @@ -1942,6 +1962,9 @@ void postTouchRelease() {

// reset velocity calculations
sensorCell->vcount = 0;
sensorCell->vcount2 = 0;
sensorCell->maxVelocityZ = 0;
sensorCell->newVelocity = false;

sensorCell->clearSensorData();

Expand Down
Loading

0 comments on commit 29dc314

Please sign in to comment.