Skip to content

Commit

Permalink
Preliminary work for aftertouch handling
Browse files Browse the repository at this point in the history
- Add the aftertouch modulation source
- Dispatch aftertouch events and track the aftertouch status in the midi state
- Parse cutoff_chanaft
- Convert aftertouch messages to an extended CC message
  • Loading branch information
paulfd committed Feb 5, 2021
1 parent 70d26b5 commit 738a18d
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 27 deletions.
1 change: 1 addition & 0 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ SFIZZ_SOURCES = \
src/sfizz/modulations/ModKeyHash.cpp \
src/sfizz/modulations/ModMatrix.cpp \
src/sfizz/modulations/sources/ADSREnvelope.cpp \
src/sfizz/modulations/sources/ChannelAftertouch.cpp \
src/sfizz/modulations/sources/Controller.cpp \
src/sfizz/modulations/sources/FlexEnvelope.cpp \
src/sfizz/modulations/sources/LFO.cpp \
Expand Down
4 changes: 2 additions & 2 deletions external/atomic_queue/include/atomic_queue/atomic_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class AtomicQueueCommon {

// The special member functions are not thread-safe.

AtomicQueueCommon() noexcept = default;
AtomicQueueCommon() = default;

AtomicQueueCommon(AtomicQueueCommon const& b) noexcept
: head_(b.head_.load(X))
Expand Down Expand Up @@ -403,7 +403,7 @@ class AtomicQueue2 : public AtomicQueueCommon<AtomicQueue2<T, SIZE, MINIMIZE_CON
public:
using value_type = T;

AtomicQueue2() noexcept = default;
AtomicQueue2() = default;
AtomicQueue2(AtomicQueue2 const&) = delete;
AtomicQueue2& operator=(AtomicQueue2 const&) = delete;
};
Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ set(SFIZZ_HEADERS
sfizz/modulations/ModMatrix.h
sfizz/modulations/ModGenerator.h
sfizz/modulations/sources/ADSREnvelope.h
sfizz/modulations/sources/ChannelAftertouch.h
sfizz/modulations/sources/Controller.h
sfizz/modulations/sources/FlexEnvelope.h
sfizz/modulations/sources/LFO.h
Expand Down Expand Up @@ -157,9 +158,10 @@ set(SFIZZ_SOURCES
sfizz/modulations/ModKey.cpp
sfizz/modulations/ModKeyHash.cpp
sfizz/modulations/ModMatrix.cpp
sfizz/modulations/sources/ADSREnvelope.cpp
sfizz/modulations/sources/ChannelAftertouch.cpp
sfizz/modulations/sources/Controller.cpp
sfizz/modulations/sources/FlexEnvelope.cpp
sfizz/modulations/sources/ADSREnvelope.cpp
sfizz/modulations/sources/LFO.cpp
sfizz/effects/Nothing.cpp
sfizz/effects/Filter.cpp
Expand Down
13 changes: 13 additions & 0 deletions src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ enum class Oversampling: int {
x8 = 8
};

enum ExtendedCCs {
pitchBend = 128,
channelAftertouch,
polyphonicAftertouch,
noteOnVelocity,
noteOffVelocity,
keyboardNoteNumber,
keyboardNoteGate,
unipolarRandom,
bipolarRandom,
alternate
};

namespace config {
constexpr float defaultSampleRate { 48000 };
constexpr float maxSampleRate { 192000 };
Expand Down
74 changes: 52 additions & 22 deletions src/sfizz/MidiState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,33 @@ void sfz::MidiState::setSampleRate(float sampleRate) noexcept

void sfz::MidiState::advanceTime(int numSamples) noexcept
{
auto clearEvents = [] (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);
};

internalClock += numSamples;
for (auto& ccEvents : cc) {
ASSERT(!ccEvents.empty()); // CC event vectors should never be empty
ccEvents.front().value = ccEvents.back().value;
ccEvents.front().delay = 0;
ccEvents.resize(1);
}
ASSERT(!pitchEvents.empty());
pitchEvents.front().value = pitchEvents.back().value;
pitchEvents.front().delay = 0;
pitchEvents.resize(1);
for (auto& ccEvents : cc)
clearEvents(ccEvents);

clearEvents(pitchEvents);
clearEvents(channelAftertouchEvents);
}

void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
{
auto updateEventBufferSize = [=] (EventVector& events) {
events.shrink_to_fit();
events.reserve(samplesPerBlock);
};
this->samplesPerBlock = samplesPerBlock;
for (auto& ccEvents : cc) {
ccEvents.shrink_to_fit();
ccEvents.reserve(samplesPerBlock);
}
pitchEvents.shrink_to_fit();
pitchEvents.reserve(samplesPerBlock);
for (auto& ccEvents : cc)
updateEventBufferSize(ccEvents);

updateEventBufferSize(pitchEvents);
updateEventBufferSize(channelAftertouchEvents);
}

float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const
Expand Down Expand Up @@ -117,6 +122,23 @@ float sfz::MidiState::getPitchBend() const noexcept
return pitchEvents.back().value;
}

void sfz::MidiState::channelAftertouchEvent(int delay, float aftertouch) noexcept
{
ASSERT(aftertouch >= -1.0f && aftertouch <= 1.0f);

const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator {});
if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay)
channelAftertouchEvents.insert(insertionPoint, { delay, aftertouch });
else
insertionPoint->value = aftertouch;
}

float sfz::MidiState::getChannelAftertouch() const noexcept
{
ASSERT(pitchEvents.size() > 0);
return channelAftertouchEvents.back().value;
}

void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
{
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
Expand All @@ -139,13 +161,16 @@ void sfz::MidiState::reset() noexcept
for (auto& velocity: lastNoteVelocities)
velocity = 0;

for (auto& ccEvents : cc) {
ccEvents.clear();
ccEvents.push_back({ 0, 0.0f });
}
auto clearEvents = [] (EventVector& events) {
events.clear();
events.push_back({ 0, 0.0f });
};

for (auto& ccEvents : cc)
clearEvents(ccEvents);

pitchEvents.clear();
pitchEvents.push_back({ 0, 0.0f });
clearEvents(pitchEvents);
clearEvents(channelAftertouchEvents);

activeNotes = 0;
internalClock = 0;
Expand Down Expand Up @@ -173,3 +198,8 @@ const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
{
return pitchEvents;
}

const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexcept
{
return channelAftertouchEvents;
}
21 changes: 21 additions & 0 deletions src/sfizz/MidiState.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ class MidiState
*/
float getPitchBend() const noexcept;

/**
* @brief Register a channel aftertouch event
*
* @param aftertouch
*/
void channelAftertouchEvent(int delay, float aftertouch) noexcept;

/**
* @brief Get the channel aftertouch status
* @return int
*/
float getChannelAftertouch() const noexcept;

/**
* @brief Register a CC event
*
Expand Down Expand Up @@ -135,6 +149,7 @@ class MidiState

const EventVector& getCCEvents(int ccIdx) const noexcept;
const EventVector& getPitchEvents() const noexcept;
const EventVector& getChannelAftertouchEvents() const noexcept;

private:
int activeNotes { 0 };
Expand Down Expand Up @@ -175,6 +190,12 @@ class MidiState
* @brief Pitch bend status
*/
EventVector pitchEvents;

/**
* @brief Aftertouch status
*/
EventVector channelAftertouchEvents;

float sampleRate { config::defaultSampleRate };
int samplesPerBlock { config::defaultSamplesPerBlock };
unsigned internalClock { 0 };
Expand Down
13 changes: 13 additions & 0 deletions src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,19 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex));
}
break;
case hash("cutoff&_chanaft"):
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;

if (auto value = readOpcode(opcode.value, Default::filterCutoffModRange)) {
const ModKey source = ModKey::createNXYZ(ModId::ChannelAftertouch);
const ModKey target = ModKey::createNXYZ(ModId::FilCutoff, id, filterIndex);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("fil&_keytrack"): // also fil_keytrack
{
const auto filterIndex = opcode.parameters.front() - 1;
Expand Down
27 changes: 26 additions & 1 deletion src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Synth::Impl::Impl()
genLFO_.reset(new LFOSource(voiceManager_));
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState));
}

Synth::Impl::~Impl()
Expand Down Expand Up @@ -1214,32 +1215,50 @@ void Synth::pitchWheel(int delay, int pitch) noexcept
voice.registerPitchWheel(delay, normalizedPitch);
}
}
void Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept

void Synth::aftertouch(int delay, uint8_t aftertouch) noexcept
{
Impl& impl = *impl_;
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };

const auto normalizedAftertouch = normalize7Bits(aftertouch);
impl.resources_.midiState.channelAftertouchEvent(delay, normalizedAftertouch);
impl.resources_.midiState.ccEvent(delay, ExtendedCCs::channelAftertouch, normalizedAftertouch);


for (auto& region : impl.regions_) {
region->registerAftertouch(aftertouch);
}

for (auto& voice : impl.voiceManager_) {
voice.registerAftertouch(delay, aftertouch);
}
}

void Synth::tempo(int delay, float secondsPerBeat) noexcept
{
Impl& impl = *impl_;
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };

impl.resources_.beatClock.setTempo(delay, secondsPerBeat);
}

void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
{
Impl& impl = *impl_;
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };

impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit));
}

void Synth::timePosition(int delay, int bar, double barBeat)
{
Impl& impl = *impl_;
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };

impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat));
}

void Synth::playbackState(int delay, int playbackState)
{
Impl& impl = *impl_;
Expand All @@ -1253,16 +1272,19 @@ int Synth::getNumRegions() const noexcept
Impl& impl = *impl_;
return static_cast<int>(impl.regions_.size());
}

int Synth::getNumGroups() const noexcept
{
Impl& impl = *impl_;
return impl.numGroups_;
}

int Synth::getNumMasters() const noexcept
{
Impl& impl = *impl_;
return impl.numMasters_;
}

int Synth::getNumCurves() const noexcept
{
Impl& impl = *impl_;
Expand Down Expand Up @@ -1563,6 +1585,9 @@ void Synth::Impl::setupModMatrix()
case ModId::FilEG:
gen = genADSREnvelope_.get();
break;
case ModId::ChannelAftertouch:
gen = genChannelAftertouch_.get();
break;
default:
DBG("[sfizz] Have unknown type of source generator");
break;
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/SynthPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "modulations/sources/ADSREnvelope.h"
#include "modulations/sources/Controller.h"
#include "modulations/sources/FlexEnvelope.h"
#include "modulations/sources/ChannelAftertouch.h"
#include "modulations/sources/LFO.h"
#include "utility/BitArray.h"

Expand Down Expand Up @@ -265,6 +266,7 @@ struct Synth::Impl final: public Parser::Listener {
std::unique_ptr<LFOSource> genLFO_;
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_;
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_;
std::unique_ptr<ChannelAftertouchSource> genChannelAftertouch_;

// Settings per voice
struct {
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/modulations/ModId.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ int ModIds::flags(ModId id) noexcept
return kModIsPerVoice;
case ModId::FilEG:
return kModIsPerVoice;
case ModId::ChannelAftertouch:
return kModIsPerCycle;

// targets
case ModId::MasterAmplitude:
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/modulations/ModId.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum class ModId : int {
AmpEG,
PitchEG,
FilEG,
ChannelAftertouch,

_SourcesEnd,

Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/modulations/ModKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ std::string ModKey::toString() const
return absl::StrCat("PitchEG {", region_.number(), "}");
case ModId::FilEG:
return absl::StrCat("FilterEG {", region_.number(), "}");
case ModId::ChannelAftertouch:
return absl::StrCat("ChannelAftertouch");

case ModId::MasterAmplitude:
return absl::StrCat("MasterAmplitude {", region_.number(), "}");
Expand Down
2 changes: 1 addition & 1 deletion src/sfizz/modulations/ModKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ModKey {
: id_(id), region_(region), params_(params), flags_(ModIds::flags(id_)) {}

static ModKey createCC(uint16_t cc, uint8_t curve, uint8_t smooth, float step);
static ModKey createNXYZ(ModId id, NumericId<Region> region, uint8_t N = 0, uint8_t X = 0, uint8_t Y = 0, uint8_t Z = 0);
static ModKey createNXYZ(ModId id, NumericId<Region> region = {}, uint8_t N = 0, uint8_t X = 0, uint8_t Y = 0, uint8_t Z = 0);

explicit operator bool() const noexcept { return id_ != ModId(); }

Expand Down
Loading

0 comments on commit 738a18d

Please sign in to comment.