257 lines
7.5 KiB
C++
257 lines
7.5 KiB
C++
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
|
// license. You should have receive a LICENSE.md file along with the code.
|
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
|
|
|
#include "MidiState.h"
|
|
#include "utility/Macros.h"
|
|
#include "utility/Debug.h"
|
|
|
|
sfz::MidiState::MidiState()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
|
{
|
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
|
ASSERT(velocity >= 0 && velocity <= 1.0);
|
|
|
|
if (noteNumber >= 0 && noteNumber < 128) {
|
|
velocityOverride = lastNoteVelocities[lastNotePlayed];
|
|
lastNoteVelocities[noteNumber] = velocity;
|
|
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
|
lastNotePlayed = noteNumber;
|
|
noteStates[noteNumber] = true;
|
|
ccEvent(delay, ExtendedCCs::noteOnVelocity, velocity);
|
|
ccEvent(delay, ExtendedCCs::keyboardNoteNumber, normalize7Bits(noteNumber));
|
|
ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator));
|
|
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
|
ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f);
|
|
activeNotes++;
|
|
|
|
ccEvent(delay, ExtendedCCs::alternate, alternate);
|
|
alternate = alternate == 0.0f ? 1.0f : 0.0f;
|
|
}
|
|
|
|
}
|
|
|
|
void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noexcept
|
|
{
|
|
ASSERT(delay >= 0);
|
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
|
ASSERT(velocity >= 0.0 && velocity <= 1.0);
|
|
UNUSED(velocity);
|
|
if (noteNumber >= 0 && noteNumber < 128) {
|
|
noteOffTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
|
ccEvent(delay, ExtendedCCs::noteOffVelocity, velocity);
|
|
ccEvent(delay, ExtendedCCs::keyboardNoteNumber, normalize7Bits(noteNumber));
|
|
ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator));
|
|
ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator));
|
|
if (activeNotes > 0)
|
|
activeNotes--;
|
|
noteStates[noteNumber] = false;
|
|
}
|
|
|
|
}
|
|
|
|
void sfz::MidiState::allNotesOff(int delay) noexcept
|
|
{
|
|
for (int note = 0; note < 128; note++)
|
|
noteOffEvent(delay, note, 0.0f);
|
|
}
|
|
|
|
void sfz::MidiState::setSampleRate(float sampleRate) noexcept
|
|
{
|
|
this->sampleRate = sampleRate;
|
|
internalClock = 0;
|
|
absl::c_fill(noteOnTimes, 0);
|
|
absl::c_fill(noteOffTimes, 0);
|
|
}
|
|
|
|
void sfz::MidiState::advanceTime(int numSamples) noexcept
|
|
{
|
|
internalClock += numSamples;
|
|
flushEvents();
|
|
}
|
|
|
|
void sfz::MidiState::flushEvents() noexcept
|
|
{
|
|
auto flushEventVector = [] (EventVector& events) {
|
|
ASSERT(!events.empty()); // CC event vectors should never be empty
|
|
events.front().value = events.back().value;
|
|
events.front().delay = 0;
|
|
events.resize(1);
|
|
};
|
|
|
|
for (auto& events : ccEvents)
|
|
flushEventVector(events);
|
|
|
|
for (auto& events: polyAftertouchEvents)
|
|
flushEventVector(events);
|
|
|
|
flushEventVector(pitchEvents);
|
|
flushEventVector(channelAftertouchEvents);
|
|
}
|
|
|
|
|
|
void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
|
|
{
|
|
auto updateEventBufferSize = [=] (EventVector& events) {
|
|
events.shrink_to_fit();
|
|
events.reserve(samplesPerBlock);
|
|
};
|
|
this->samplesPerBlock = samplesPerBlock;
|
|
for (auto& events: ccEvents)
|
|
updateEventBufferSize(events);
|
|
|
|
for (auto& events: polyAftertouchEvents)
|
|
updateEventBufferSize(events);
|
|
|
|
updateEventBufferSize(pitchEvents);
|
|
updateEventBufferSize(channelAftertouchEvents);
|
|
}
|
|
|
|
float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const
|
|
{
|
|
ASSERT(noteNumber >= 0 && noteNumber < 128);
|
|
if (noteNumber < 0 || noteNumber >= 128)
|
|
return 0.0f;
|
|
|
|
#if 0
|
|
if (!noteStates[noteNumber])
|
|
return 0.0f;
|
|
#endif
|
|
|
|
const unsigned timeInSamples = internalClock + static_cast<unsigned>(delay) - noteOnTimes[noteNumber];
|
|
return static_cast<float>(timeInSamples) / sampleRate;
|
|
}
|
|
|
|
float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
|
|
{
|
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
|
|
|
return lastNoteVelocities[noteNumber];
|
|
}
|
|
|
|
float sfz::MidiState::getVelocityOverride() const noexcept
|
|
{
|
|
return velocityOverride;
|
|
}
|
|
|
|
void sfz::MidiState::insertEventInVector(EventVector& events, int delay, float value)
|
|
{
|
|
const auto insertionPoint = absl::c_lower_bound(events, delay, MidiEventDelayComparator {});
|
|
if (insertionPoint == events.end() || insertionPoint->delay != delay)
|
|
events.insert(insertionPoint, { delay, value });
|
|
else
|
|
insertionPoint->value = value;
|
|
}
|
|
|
|
void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept
|
|
{
|
|
ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f);
|
|
insertEventInVector(pitchEvents, delay, pitchBendValue);
|
|
}
|
|
|
|
float sfz::MidiState::getPitchBend() const noexcept
|
|
{
|
|
ASSERT(pitchEvents.size() > 0);
|
|
return pitchEvents.back().value;
|
|
}
|
|
|
|
void sfz::MidiState::channelAftertouchEvent(int delay, float aftertouch) noexcept
|
|
{
|
|
ASSERT(aftertouch >= -1.0f && aftertouch <= 1.0f);
|
|
insertEventInVector(channelAftertouchEvents, delay, aftertouch);
|
|
}
|
|
|
|
void sfz::MidiState::polyAftertouchEvent(int delay, int noteNumber, float aftertouch) noexcept
|
|
{
|
|
ASSERT(aftertouch >= 0.0f && aftertouch <= 1.0f);
|
|
if (noteNumber < 0 || noteNumber >= static_cast<int>(polyAftertouchEvents.size()))
|
|
return;
|
|
|
|
insertEventInVector(polyAftertouchEvents[noteNumber], delay, aftertouch);
|
|
}
|
|
|
|
float sfz::MidiState::getChannelAftertouch() const noexcept
|
|
{
|
|
ASSERT(channelAftertouchEvents.size() > 0);
|
|
return channelAftertouchEvents.back().value;
|
|
}
|
|
|
|
float sfz::MidiState::getPolyAftertouch(int noteNumber) const noexcept
|
|
{
|
|
if (noteNumber < 0 || noteNumber > 127)
|
|
return 0.0f;
|
|
|
|
ASSERT(polyAftertouchEvents[noteNumber].size() > 0);
|
|
return polyAftertouchEvents[noteNumber].back().value;
|
|
}
|
|
|
|
void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
|
|
{
|
|
insertEventInVector(ccEvents[ccNumber], delay, ccValue);
|
|
}
|
|
|
|
float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
|
{
|
|
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
|
return ccEvents[ccNumber].back().value;
|
|
}
|
|
|
|
void sfz::MidiState::reset() noexcept
|
|
{
|
|
for (auto& velocity: lastNoteVelocities)
|
|
velocity = 0.0f;
|
|
|
|
auto clearEvents = [] (EventVector& events) {
|
|
events.clear();
|
|
events.push_back({ 0, 0.0f });
|
|
};
|
|
|
|
for (auto& events : ccEvents)
|
|
clearEvents(events);
|
|
|
|
for (auto& events : polyAftertouchEvents)
|
|
clearEvents(events);
|
|
|
|
clearEvents(pitchEvents);
|
|
clearEvents(channelAftertouchEvents);
|
|
|
|
velocityOverride = 0.0f;
|
|
activeNotes = 0;
|
|
internalClock = 0;
|
|
lastNotePlayed = 0;
|
|
noteStates.reset();
|
|
absl::c_fill(noteOnTimes, 0);
|
|
absl::c_fill(noteOffTimes, 0);
|
|
}
|
|
|
|
const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
|
{
|
|
if (ccIdx < 0 || ccIdx >= config::numCCs)
|
|
return nullEvent;
|
|
|
|
return ccEvents[ccIdx];
|
|
}
|
|
|
|
const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
|
|
{
|
|
return pitchEvents;
|
|
}
|
|
|
|
const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexcept
|
|
{
|
|
return channelAftertouchEvents;
|
|
}
|
|
|
|
const sfz::EventVector& sfz::MidiState::getPolyAftertouchEvents(int noteNumber) const noexcept
|
|
{
|
|
if (noteNumber < 0 || noteNumber > 127)
|
|
return nullEvent;
|
|
|
|
return polyAftertouchEvents[noteNumber];
|
|
}
|