From 27a14aea15b43627625b6acb7e2f271dbf67fb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yves=20Par=C3=A8s?= Date: Wed, 1 Mar 2023 18:43:56 +0100 Subject: [PATCH] Added Hold current chord param --- README.md | 1 + Source/Arp.cpp | 61 +++++++++++++++++++------------------- Source/Arp.h | 3 ++ Source/PluginProcessor.cpp | 2 ++ Source/PluginProcessor.h | 3 +- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 623736f..2db558b 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,7 @@ different live players have different preferences. | | |`Use as is`|Output the pattern note as it is| | | |`Transpose from 1st degree`|Use Arpligner as a "dynamic" transposer: ignore all chord degrees besides the first (lowest) one. Pattern notes are just transposed accordingly. This allows you to play notes that are outside the current chord, but keeping your patterns centered around the reference note| | | |`Play all degrees up to note`|Play the full chord, using the played note as a filter (all chord degrees above will be silenced)| +|**Hold current chord**|`Off`|`On` or `Off`|Latch onto the current chord/scale until deactivated. This parameter is never saved, and is meant to be mapped to some MIDI control, like a sustain pedal or a switch.| ## Current limitations diff --git a/Source/Arp.cpp b/Source/Arp.cpp index 31fbb08..66c988e 100644 --- a/Source/Arp.cpp +++ b/Source/Arp.cpp @@ -186,39 +186,40 @@ void Arp::processPatternNotes(ChordStore* chd, Array& noteOns, Arra auto wrapMode = (PatternNotesWraparound::Enum)patternNotesWraparound->getIndex(); auto unmappedBeh = (UnmappedNotesBehaviour::Enum)unmappedNotesBehaviour->getIndex(); auto referenceNote = firstDegreeCode->getIndex(); - - Chord curChord; - bool shouldProcess, shouldSilence; - chd->getCurrentChord(curChord, shouldProcess, shouldSilence); + auto updateState = !holdCurState->get(); - if (shouldSilence) - noteOns.clear(); - - // If wanted, pre-process the current chord to turn it into a scale: + if (updateState) { + chd->getCurrentChord(mChordToUse, mShouldProcess, mShouldSilence); - if (chordToScaleMode != ChordToScale::NONE) { - Chord scale; - // We first "regroup" all the chord notes so they fit into one octave, - // starting from lowest chord note: - NoteNumber root = curChord[0]; - for (auto note : curChord) - scale.add(root + (note - root)%12); - // If we don't have a 7th, we add it: - if (scale.getLast() < root + 10) { - if (chordToScaleMode == ChordToScale::ADD_WHOLE_STEPS_DEF_NAT7) - scale.add(root + 11); - else - scale.add(root + 10); + // If wanted, pre-process the current chord to turn it into a scale: + + if (chordToScaleMode != ChordToScale::NONE) { + Chord scale; + // We first "regroup" all the chord notes so they fit into one octave, + // starting from lowest chord note: + NoteNumber root = mChordToUse[0]; + for (auto note : mChordToUse) + scale.add(root + (note - root)%12); + // If we don't have a 7th, we add it: + if (scale.getLast() < root + 10) { + if (chordToScaleMode == ChordToScale::ADD_WHOLE_STEPS_DEF_NAT7) + scale.add(root + 11); + else + scale.add(root + 10); + } + // Then we fill in the gaps by adding 1 whole step to each degree below a + // gap (a gap being any interval strictly larger than a whole step): + for (int i=0; i 2) + scale.add(scale[i]+2); + // Then, we override curChord: + mChordToUse.swapWith(scale); } - // Then we fill in the gaps by adding 1 whole step to each degree below a - // gap (a gap being any interval strictly larger than a whole step): - for (int i=0; i 2) - scale.add(scale[i]+2); - // Then, we override curChord: - curChord.swapWith(scale); } + if (mShouldSilence) + noteOns.clear(); + // Process and add processable messages: for (auto& msg : noteOffs) { // Note OFFs first @@ -244,12 +245,12 @@ void Arp::processPatternNotes(ChordStore* chd, Array& noteOns, Arra midibuf.addEvent(MidiMessage::noteOff(msg.getChannel(), nn), 0); thisNoteMappings.clear(); - if (shouldProcess) // The ChordStore tells us to process + if (mShouldProcess) // The ChordStore tells us to process Mapping::mapPatternNote(referenceNote, mappingMode, wrapMode, unmappedBeh, - curChord, + mChordToUse, noteCodeIn, thisNoteMappings); else // We map the note to itself diff --git a/Source/Arp.h b/Source/Arp.h index ffecb87..6cbe6ba 100644 --- a/Source/Arp.h +++ b/Source/Arp.h @@ -23,6 +23,9 @@ using Mappings = HashMap< NoteOnChan, Array >; class Arp : public ArplignerAudioProcessor { private: ChordStore mLocalChordStore; + Chord mChordToUse; + bool mShouldProcess; + bool mShouldSilence; // On each pattern chan, to which note has been mapped each incoming // NoteNumber, so we can send the correct NOTE OFFs afterwards diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 660df28..fe26271 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -86,6 +86,8 @@ ArplignerAudioProcessor::ArplignerAudioProcessor() (unmappedNotesBehaviour = new AudioParameterChoice ("unmappedNotesBehaviour", "Unmapped notes behaviour", unmappedBehs, UnmappedNotesBehaviour::SILENCE)); + + addParameter(holdCurState = new AudioParameterBool("holdCurState", "Hold current chord", false)); } ArplignerAudioProcessor::~ArplignerAudioProcessor() diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 65be3cf..04367f6 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -66,10 +66,11 @@ class ArplignerAudioProcessor : public AudioProcessor AudioParameterChoice* whenSingleChordNote; AudioParameterChoice* firstDegreeCode; AudioParameterChoice* patternNotesMapping; - AudioParameterInt* numMillisecsOfLatency; + AudioParameterInt* numMillisecsOfLatency; AudioParameterChoice* patternNotesWraparound; AudioParameterChoice* unmappedNotesBehaviour; AudioParameterChoice* chordToScale; + AudioParameterBool* holdCurState; private: //==============================================================================