Files
PianoProject/ESP32/note.cpp
Brandon Switzer fe8f7f72fc added variable
2020-03-24 11:40:14 -04:00

285 lines
9.6 KiB
C++

#include "note.h"
#include "serial.h"
#include "main.h"
Note notes[88];
int Note::scheduledLeftNotes = 0;
int Note::scheduledRightNotes = 0;
int Note::idGenerator = 0;
int Note::noteVelocityMs[127];
Note::Note()
{
id = idGenerator;
idGenerator++;
//initialize one row of vectors so program won't crash while comparing
//initialize note as off by default
for(int index = 0; index < 6; index++)
{
schedule[index].reserve(8);
schedule[index].resize(1);
}
schedule[OFF].push_back(millis());
}
void Note::prepareToSchedule(uint8_t velocity)
{
if(Setting::handleNotes)
{
calculateVolume(velocity);
if(Setting::scheduleNotes)
{
if(velocity == 0)
scheduleNote(velocity);
else if(canBeScheduled())
scheduleNote(velocity);
} else
sendMidiToProMicro(id, velocity);
} else
sendMidiToProMicro(id, velocity); //without modifying the volume
}
void Note::checkSchedule()
{
//reverse stack behavior: erases stack from the bottom
unsigned long ms = millis();
if(schedule[OFF].size() > 1 && schedule[DEACTIVATION].size() > 1 &&
ms >= schedule[OFF].at(1) && schedule[OFF].at(1) >= schedule[DEACTIVATION].at(1)) //first because sometimes off and on times are the same
{
schedule[DEACTIVATION].erase(++schedule[DEACTIVATION].begin());
}
if(schedule[STARTUP].size() > 1 && schedule[OFF].size() > 1 && ms >= schedule[STARTUP].at(1) &&
schedule[STARTUP].at(1) >= schedule[OFF].at(1))
{
schedule[OFF].erase(++schedule[OFF].begin());
sendMidiToProMicro(id, 127);
}
if(schedule[ACTIVATION].size() > 1 && schedule[STARTUP].size() > 1 && schedule[VELOCITY].size() > 1 &&
ms >= schedule[ACTIVATION].at(1) && schedule[ACTIVATION].at(1) >= schedule[STARTUP].at(1))
{
schedule[STARTUP].erase(++schedule[STARTUP].begin());
sendMidiToProMicro(id, schedule[VELOCITY].at(1));
}
if(schedule[ON].size() > 1 && schedule[ACTIVATION].size() > 1 &&
ms >= schedule[ON].at(1) && schedule[ON].at(1) >= schedule[ACTIVATION].at(1))
{
schedule[ACTIVATION].erase(++schedule[ACTIVATION].begin());
schedule[VELOCITY].erase(++schedule[VELOCITY].begin());
sendMidiToProMicro(id, 127);
}
if(schedule[DEACTIVATION].size() > 1 && schedule[ON].size() > 1 &&
ms >= schedule[DEACTIVATION].at(1) && schedule[DEACTIVATION].at(1) >= schedule[ON].at(1))
{
schedule[ON].erase(++schedule[ON].begin());
sendMidiToProMicro(id, 0);
}
}
void Note::checkForErrors()
{
unsigned long ms = millis();
if(ms >= timeSinceActivation + Setting::noteTimeoutMs && timeSinceActivation > 0) resetSchedule();
if(schedule[ON].size() > 1 && ms >= schedule[ON].at(1) + Setting::noteTimeoutMs) resetSchedule();
}
void Note::resetSchedule()
{
if(DEBUG_MODE) Serial.print("Resetting schedule for note: ");
if(DEBUG_MODE) Serial.println(id);
for(int index = 0; index < 6; index++)
{
schedule[index].resize(1);
schedule[index].at(0) = 0;
}
schedule[OFF].push_back(millis());
if(timeSinceActivation > 0)
updateInstance(false);
timeSinceActivation = 0;
instances = 0;
sendMidiToProMicro(id, 0);
}
void Note::resetInstances()
{
//warning: only call this function in conjunction with resetSchedule()!
scheduledLeftNotes = 0;
scheduledRightNotes = 0;
}
void Note::scheduleNote(uint8_t velocity)
{
if(DEBUG_MODE) sendScheduleToSerial();
unsigned long ms = millis();
unsigned long msAndDelay = ms + fullDelay;
using namespace Setting;
if(velocity > 0) //if note on command
{
int velocityMs = noteVelocityMs[velocity - 1];
instances++;
if(instances == 1) //if note is scheduled to deactivate (was 0 before instances++)
{
if(msAndDelay - velocityMs - startupMs >= schedule[OFF].back()) //if new note can be scheduled with current scheduling
{
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION].push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
timeSinceActivation == ms;
updateInstance(true);
} else if(msAndDelay - deactivateMs - velocityMs - startupMs >= schedule[ON].back()) //if current scheduling can be modified to still schedule the new note
{
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - deactivateMs);
schedule[DEACTIVATION].erase(----schedule[DEACTIVATION].end());
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[OFF]. erase(----schedule[OFF].end());
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
timeSinceActivation == ms;
updateInstance(true);
} else if(msAndDelay - fastDeactivateMs - velocityMs - startupMs >= schedule[ACTIVATION].back()) //if current scheduling can be modified with fast deactivation to schedule the new note
{
schedule[ON]. push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[DEACTIVATION].erase(----schedule[DEACTIVATION].end());
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[OFF]. erase(----schedule[OFF].end());
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
timeSinceActivation == ms;
updateInstance(true);
}
} else //note is scheduled to activate and not deactivate
{
if(msAndDelay - deactivateMs - velocityMs - startupMs >= schedule[ON].back()) //if current scheduling can be modified to still schedule the new note
{
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - deactivateMs);
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
} else if(msAndDelay - fastDeactivateMs - velocityMs - startupMs - 35 >= schedule[ACTIVATION].back() && schedule[ACTIVATION].back() > 0) //if current scheduling can be modified with fast deactivation to still schedule the new note
{
schedule[ON]. push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(msAndDelay - velocityMs - startupMs - fastDeactivateMs);
schedule[OFF]. push_back(msAndDelay - velocityMs - startupMs);
schedule[STARTUP]. push_back(msAndDelay - velocityMs - startupMs);
schedule[ACTIVATION]. push_back(msAndDelay - velocityMs);
schedule[ON]. push_back(msAndDelay);
schedule[VELOCITY]. push_back(velocity);
}
}
} else if(instances > 0 /*&& velocity == 0*/) //if note off command and note is not already off
{
if(instances > 1) //if this isn't the last instance
{
//remove instance and exit
instances--;
} else //this is the last instance of the note and it should be scheduled
{
instances = 0;
timeSinceActivation == 0;
updateInstance(false);
if(msAndDelay - fastDeactivateMs - 35 >= schedule[ACTIVATION].back() && msAndDelay - fastDeactivateMs - 35 <= schedule[ON].back() && schedule[ACTIVATION].back() > 0) //if it's efficient to use fast deactivation
{
schedule[ON]. push_back(msAndDelay - fastDeactivateMs);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(msAndDelay - fastDeactivateMs);
schedule[OFF]. push_back(msAndDelay);
} else if(msAndDelay - deactivateMs >= schedule[ON].back()) //if regular deactivation works
{
schedule[DEACTIVATION].push_back(msAndDelay - deactivateMs);
schedule[OFF]. push_back(msAndDelay);
} else //if all else fails the key shouldn't stay stuck on
{
if(schedule[ACTIVATION].back() > 0)
{
//immediately deactivate the key as soon as it makes sound
schedule[ON]. push_back(schedule[ACTIVATION].back() + 35);
schedule[ON]. erase(----schedule[ON].end());
schedule[DEACTIVATION].push_back(schedule[ACTIVATION].back());
schedule[OFF]. push_back(schedule[ACTIVATION].back() + fastDeactivateMs);
} else //this should never happen
{
schedule[DEACTIVATION].push_back(msAndDelay);
schedule[OFF]. push_back(msAndDelay + deactivateMs);
}
}
}
}
if(DEBUG_MODE) sendScheduleToSerial();
}
void Note::calculateVolume(uint8_t& velocity)
{
if(velocity > 0)
{
velocity = round((velocity * Setting::volume) / (double)100);
if(velocity > 127)
velocity = 127;
else if(velocity < 1)
velocity = 0;
}
}
void Note::updateInstance(bool state)
{
if(id < 44)
{
if(state)
scheduledLeftNotes++;
else
scheduledLeftNotes--;
} else
{
if(state)
scheduledRightNotes++;
else
scheduledRightNotes--;
}
}
void Note::sendScheduleToSerial()
{
Serial.println("-----------------------");
Serial.print("Schedule for note: ");
Serial.print(id);
Serial.print(" At the time: ");
Serial.println(millis());
for(int i = 0; i < 6; i++)
{
for(int j = 0; j < schedule[i].size(); j++)
{
Serial.print(schedule[i].at(j), DEC);
Serial.print(" ");
}
Serial.println(" ");
Serial.print(" ");
}
}
bool Note::canBeScheduled()
{
if(id < 44)
{
if(scheduledLeftNotes < Setting::maxLeftNotes)
return true;
} else
if(id >= 44)
{
if(scheduledRightNotes < Setting::maxRightNotes)
return true;
}
return false;
}