Skip to content

Commit

Permalink
Added Hold current chord param
Browse files Browse the repository at this point in the history
  • Loading branch information
Yves Parès committed Mar 2, 2023
1 parent 34a41b1 commit 27a14ae
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 31 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
61 changes: 31 additions & 30 deletions Source/Arp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,39 +186,40 @@ void Arp::processPatternNotes(ChordStore* chd, Array<MidiMessage>& 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<scale.size()-1; i++)
if (scale[i+1] - scale[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<scale.size()-1; i++)
if (scale[i+1] - scale[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
Expand All @@ -244,12 +245,12 @@ void Arp::processPatternNotes(ChordStore* chd, Array<MidiMessage>& 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
Expand Down
3 changes: 3 additions & 0 deletions Source/Arp.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ using Mappings = HashMap< NoteOnChan, Array<NoteNumber> >;
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
Expand Down
2 changes: 2 additions & 0 deletions Source/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion Source/PluginProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//==============================================================================
Expand Down

0 comments on commit 27a14ae

Please sign in to comment.