ref: 9dffd7c97f0263f5207db0a08dd4ab2d6af5b21d
dir: /LEAF/Src/leaf-midi.c/
/*
==============================================================================
leaf-midi.cpp
Created: 30 Nov 2018 11:29:16am
Author: airship
==============================================================================
*/
#if _WIN32 || _WIN64
#include "..\Inc\leaf-midi.h"
#else
#include "../Inc/leaf-midi.h"
#endif
/* Poly Handler */
static void nodeListInit(tPoly* poly)
{
for (int16_t i = 0; i < 128; i++)
{
poly->midiNodes[i].midiNote.pitch = i;
poly->midiNodes[i].midiNote.velocity = 0;
poly->midiNodes[i].midiNote.on = OFALSE;
}
}
// Initially everything is off, init as such
static void offListInit(tPoly* poly)
{
tMidiNode* prevNode = NULL;
tMidiNode* curNode = &poly->midiNodes[0];
poly->offListFirst = curNode;
for (int16_t i = 1; i < 128; i++)
{
curNode->prev = prevNode;
curNode->next = &poly->midiNodes[i];
prevNode = curNode;
curNode = &poly->midiNodes[i];
}
// Set the final node
curNode->prev = prevNode;
curNode->next = NULL;
}
static void onListInit(tPoly* poly)
{
poly->onListFirst = NULL;
}
void tPoly_init(tPoly* const poly)
{
nodeListInit(poly);
offListInit(poly);
onListInit(poly);
}
tMidiNote* tPoly_getMidiNote(tPoly* const poly, int8_t voiceIndex)
{
tMidiNote* midiNote = NULL;
tMidiNode* currNode = poly->onListFirst;
int8_t i = 0;
while (i < voiceIndex && currNode != NULL)
{
currNode = currNode->next;
i++;
}
if (currNode != NULL)
midiNote = &(currNode->midiNote);
return midiNote;
}
static void removeNoteFromOffList(tPoly* const poly, int8_t midiNoteNumber)
{
// If this has no prev, this is the first node on the OFF list
if (poly->midiNodes[midiNoteNumber].prev == NULL)
poly->offListFirst = poly->midiNodes[midiNoteNumber].next;
// Awkward handling to avoid deref null pointers
if (poly->midiNodes[midiNoteNumber].prev != NULL)
poly->midiNodes[midiNoteNumber].prev->next = poly->midiNodes[midiNoteNumber].next;
if (poly->midiNodes[midiNoteNumber].next != NULL)
poly->midiNodes[midiNoteNumber].next->prev = poly->midiNodes[midiNoteNumber].prev;
poly->midiNodes[midiNoteNumber].next = NULL;
poly->midiNodes[midiNoteNumber].prev = NULL;
}
static void prependNoteToOnList(tPoly* poly, int midiNoteNumber)
{
if (poly->onListFirst != NULL)
{
poly->midiNodes[midiNoteNumber].next = poly->onListFirst;
poly->onListFirst->prev = &poly->midiNodes[midiNoteNumber];
}
poly->onListFirst = &poly->midiNodes[midiNoteNumber];
}
// TODO: Fail gracefully on odd MIDI situations
// For example, getting a note off for an already on note and vice-versa
void tPoly_noteOn(tPoly* const poly, int midiNoteNumber, float velocity)
{
removeNoteFromOffList(poly, midiNoteNumber);
// Set the MIDI note on accordingly
poly->midiNodes[midiNoteNumber].midiNote.velocity = velocity;
poly->midiNodes[midiNoteNumber].midiNote.on = OTRUE;
prependNoteToOnList(poly, midiNoteNumber);
}
// Unfortunately similar code to removeNoteFromOffList without any clear way of factoring out to a helper function
static void removeNoteFromOnList(tPoly* const poly, int8_t midiNoteNumber)
{
// If this has no prev, this is the first node on the OFF list
if (poly->midiNodes[midiNoteNumber].prev == NULL)
poly->onListFirst = poly->midiNodes[midiNoteNumber].next;
// Awkward handling to avoid deref null pointers
if (poly->midiNodes[midiNoteNumber].prev != NULL)
poly->midiNodes[midiNoteNumber].prev->next = poly->midiNodes[midiNoteNumber].next;
if (poly->midiNodes[midiNoteNumber].next != NULL)
poly->midiNodes[midiNoteNumber].next->prev = poly->midiNodes[midiNoteNumber].prev;
poly->midiNodes[midiNoteNumber].next = NULL;
poly->midiNodes[midiNoteNumber].prev = NULL;
}
// Unfortunately similar code to prependNoteToOnList without any clear way of factoring out to a helper function
static void prependNoteToOffList(tPoly* const poly, int midiNoteNumber)
{
if (poly->offListFirst != NULL)
{
poly->midiNodes[midiNoteNumber].next = poly->offListFirst;
poly->offListFirst->prev = &poly->midiNodes[midiNoteNumber];
}
poly->offListFirst = &poly->midiNodes[midiNoteNumber];
}
void tPoly_noteOff(tPoly* const poly, int midiNoteNumber)
{
removeNoteFromOnList(poly, midiNoteNumber);
// Set the MIDI note on accordingly
poly->midiNodes[midiNoteNumber].midiNote.velocity = 0;
poly->midiNodes[midiNoteNumber].midiNote.on = OFALSE;
prependNoteToOffList(poly, midiNoteNumber);
}
// MPOLY
void tMPoly_init(tMPoly* const poly, int numVoices)
{
poly->numVoices = numVoices;
poly->lastVoiceToChange = 0;
// Arp mode stuff
poly->currentVoice = 0;
poly->maxLength = 128;
poly->currentNote = -1;
//default learned CCs and notes are just the CCs 1-128 - notes are skipped
for (int i = 0; i < 128; i++)
{
poly->notes[i][0] = 0;
poly->notes[i][1] = -1;
}
for (int i = 0; i < MPOLY_NUM_MAX_VOICES; ++i)
{
poly->voices[i][0] = -1;
poly->firstReceived[i] = 0;
poly->ramp[i] = (tRamp*) leaf_alloc(sizeof(tRamp));
tRamp_init(poly->ramp[i], 5.0f, 1);
}
poly->glideTime = 5.0f;
poly->pitchBend = 64;
poly->pitchBendAmount = 2.0f;
poly->stack = (tStack*) leaf_alloc(sizeof(tStack));
tStack_init(poly->stack);
poly->orderStack = (tStack*) leaf_alloc(sizeof(tStack));
tStack_init(poly->orderStack);
}
// Only needs to be used for pitch glide
void tMPoly_tick(tMPoly* poly)
{
for (int i = 0; i < MPOLY_NUM_MAX_VOICES; ++i)
{
tRamp_tick(poly->ramp[i]);
}
}
//instead of including in dacsend, should have a separate pitch bend ramp, that is added when the ramps are ticked and sent to DAC
void tMPoly_pitchBend(tMPoly* const poly, int pitchBend)
{
poly->pitchBend = pitchBend;
}
int tMPoly_noteOn(tMPoly* const poly, int note, uint8_t vel)
{
// if not in keymap or already on stack, dont do anything. else, add that note.
if (tStack_contains(poly->stack, note) >= 0) return -1;
else
{
tMPoly_orderedAddToStack(poly, note);
tStack_add(poly->stack, note);
int alteredVoice = -1;
oBool found = OFALSE;
for (int i = 0; i < poly->numVoices; i++)
{
if (poly->voices[i][0] < 0) // if inactive voice, give this note to voice
{
if (poly->firstReceived[i] == 0)
{
tRamp_setVal(poly->ramp[i], note); // can't be 1.0f, causes first note to be off pitch
poly->firstReceived[i] = 1;
}
else
{
tRamp_setTime(poly->ramp[i], poly->glideTime);
}
found = OTRUE;
poly->voices[i][0] = note;
poly->voices[i][1] = vel;
poly->lastVoiceToChange = i;
poly->notes[note][0] = vel;
poly->notes[note][1] = i;
tRamp_setDest(poly->ramp[i], poly->voices[i][0]);
alteredVoice = i;
break;
}
}
if (!found) //steal
{
int whichVoice, whichNote;
for (int j = tStack_getSize(poly->stack) - 1; j >= 0; j--)
{
whichNote = tStack_get(poly->stack, j);
whichVoice = poly->notes[whichNote][1];
if (whichVoice >= 0)
{
poly->lastVoiceToChange = j;
int oldNote = poly->voices[whichVoice][0];
poly->voices[whichVoice][0] = note;
poly->voices[whichVoice][1] = vel;
poly->notes[oldNote][1] = -1; //mark the stolen voice as inactive (in the second dimension of the notes array)
poly->notes[note][0] = vel;
poly->notes[note][1] = whichVoice;
tRamp_setTime(poly->ramp[whichVoice], poly->glideTime);
tRamp_setDest(poly->ramp[whichVoice], poly->voices[whichVoice][0]);
alteredVoice = whichVoice;
break;
}
}
}
return alteredVoice;
}
}
int16_t noteToTest = -1;
int tMPoly_noteOff(tMPoly* const poly, uint8_t note)
{
tStack_remove(poly->stack, note);
tStack_remove(poly->orderStack, note);
poly->notes[note][0] = 0;
poly->notes[note][1] = -1;
int deactivatedVoice = -1;
for (int i = 0; i < poly->numVoices; i++)
{
if (poly->voices[i][0] == note)
{
poly->voices[i][0] = -1;
poly->voices[i][1] = 0;
poly->lastVoiceToChange = i;
deactivatedVoice = i;
break;
}
}
/*
//monophonic handling
if ((poly->numVoices == 1) && (tStack_getSize(poly->stack) > 0))
{
int oldNote = tStack_first(poly->stack);
poly->voices[0][0] = oldNote;
poly->voices[0][1] = poly->notes[oldNote][0];
poly->lastVoiceToChange = 0;
}
*/
//grab old notes off the stack if there are notes waiting to replace the free voice
if (deactivatedVoice >= 0)
{
for (int j = 0; j < tStack_getSize(poly->stack); ++j)
{
noteToTest = tStack_get(poly->stack, j);
if (poly->notes[noteToTest][1] < 0) //if there is a stolen note waiting (marked inactive but on the stack)
{
poly->voices[deactivatedVoice][0] = noteToTest; //set the newly free voice to use the old stolen note
tRamp_setDest(poly->ramp[deactivatedVoice], poly->voices[deactivatedVoice][0]);
poly->voices[deactivatedVoice][1] = poly->notes[noteToTest][0]; // set the velocity of the voice to be the velocity of that note
poly->notes[noteToTest][1] = deactivatedVoice; //mark that it is no longer stolen and is now active
return -1;
}
}
}
return deactivatedVoice;
}
void tMPoly_orderedAddToStack(tMPoly* const poly, uint8_t noteVal)
{
uint8_t j;
int myPitch, thisPitch, nextPitch;
tStack* ns = poly->orderStack;
int whereToInsert = 0;
for (j = 0; j < ns->size; j++)
{
myPitch = noteVal;
thisPitch = ns->data[j];
nextPitch = ns->data[j+1];
if (myPitch > thisPitch)
{
if ((myPitch < nextPitch) || (nextPitch == -1))
{
whereToInsert = j+1;
break;
}
}
}
//first move notes that are already in the stack one position to the right
for (j = ns->size; j > whereToInsert; j--)
{
ns->data[j] = ns->data[(j - 1)];
}
//then, insert the new note into the front of the stack
ns->data[whereToInsert] = noteVal;
ns->size++;
}
void tMPoly_setNumVoices(tMPoly* const poly, uint8_t numVoices)
{
poly->numVoices = (numVoices > MPOLY_NUM_MAX_VOICES) ? MPOLY_NUM_MAX_VOICES : numVoices;
}
void tMPoly_setPitchGlideTime(tMPoly* const poly, float t)
{
if (poly->glideTime == t) return;
poly->glideTime = t;
for (int i = 0; i < MPOLY_NUM_MAX_VOICES; ++i)
{
tRamp_setTime(poly->ramp[i], poly->glideTime);
}
}
int tMPoly_getNumVoices(tMPoly* const poly)
{
return poly->numVoices;
}
float tMPoly_getPitch(tMPoly* const poly, uint8_t voice)
{
//float pitchBend = ((float)(poly->pitchBend - 8192) / 8192.0f) * poly->pitchBendAmount;
return tRamp_sample(poly->ramp[voice]);// + pitchBend;
return poly->voices[voice][0];
}
int tMPoly_getVelocity(tMPoly* const poly, uint8_t voice)
{
return poly->voices[voice][1];
}
int tMPoly_isOn(tMPoly* const poly, uint8_t voice)
{
return (poly->voices[voice][0] > 0) ? 1 : 0;
}