diff --git a/CHANGELOG.md b/CHANGELOG.md index 821f999ad6..28901e32e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -171,6 +171,7 @@ also affect normal sequenced notes while arpeggiator is Off. - Added `LOCK` parameter to allow you to freeze the current set of randomized values so the sequence has a repeatable pattern. - Added new `WALK1`, `WALK2`, `WALK3` and `PATTERN` note modes. - Exposed several parameters from the `Randomizer` also for non-arpeggiated notes: `Velocity Spread`, `Note probability`, and `Reverse Probability`. +- Added `KIT ARPEGGIATOR` for `KIT` clips. This arpeggiator is a new layer on top of the kit rows, which will control which rows receive note ON's and note OFF's. That means that the kit rows can have their own arpeggiators also enabled. You can access the new menu when `Affect-Entire` is enabled in the `Kit` and you access the menu with `Select` knob. ### MIDI diff --git a/docs/community_features.md b/docs/community_features.md index 62984a18f9..a0206a3544 100644 --- a/docs/community_features.md +++ b/docs/community_features.md @@ -856,6 +856,9 @@ to each individual note onset. ([#1978]) - ([#1198] [#2978] [#2985] [#2990] [#3079] [#3285]) For a detailed description of this feature, please refer to the feature documentation: [Arpeggiator Documentation] +- ([#3388]) Added `KIT ARPEGGIATOR` for `KIT` clips. This allows the user to engage an arpeggiator layer on top of the kit rows, + which will control which rows receive note ON's and note OFF's. That means that the kit rows can have their own arpeggiators also + enabled, which allows for unlimited possible pattern combinations. This feature is also explained in [Arpeggiator Documentation] #### 4.3.9 - Randomizer @@ -1622,6 +1625,8 @@ different firmware [#3352]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3352 +[#3388]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3388 + [Automation View Documentation]: features/automation_view.md [Arpeggiator Documentation]: features/arpeggiator.md diff --git a/docs/features/arpeggiator.md b/docs/features/arpeggiator.md index 99c02e1fd0..ffd5996692 100644 --- a/docs/features/arpeggiator.md +++ b/docs/features/arpeggiator.md @@ -204,3 +204,39 @@ This submenu contains parameters that are useful if you have an MPE controller c - `Off`: This disables control of velocity with MPE. - `Aftertouch (AFTE)`: The pressure applied to the key sets the velocity of the note. - `MPE Y (Y)`: The Y position on the MPE controller sets the velocity of the note. + +## Kit Arpeggiator + +The `Kit Arpeggiator` is a new layer on top of the kit rows, which will control which rows receive note ON's and note OFF's. +That means that the kit rows can have their own arpeggiators also enabled. If you want to use the `Kit Arpeggiator`, you need to +enable `Affect-Entire` in the `Kit`. Then access the menu with `Select` knob and go to the `Kit Arpeggiator` submenu. + +The `Kit Arpeggiator` parameters are also controllable with `MIDI FOLLOW`. + +### Opting in and out of the Kit Arpeggiator + +Kit rows by default opt-in to the `Kit Arpeggiator`. You can make a kit row opt-out of it by going into +the `Arpeggiator` submenu from the kit row menu and disabling the option `Include in Kit Arp`. + +There is a special case where the kit row is forced to opt-out of the `Kit Arpeggiator`, which is when their notes in the sequencer +don't have "tails". That is, if when you paint a note in the sequencer, it doesn't allow you to extend its length, it means this kit row +is going to be excluded from the `Kit Arpeggiator`. This happens when you load a sample in the row and the sample mode is set to `Once`. +Or it could also happen when you load a synth preset and you set the `Env1 Sustain` to zero. + +### Reverse Probability + +The `Reverse Probability` parameter is available for the `Kit Arpeggiator`, but it works a little bit differently than the same +parameter for sounds. In this case it is able to "invert" the `Reverse` setting for each note, which means that: +- If `Reverse Probability` evaluates to `Yes` for a note, and the kit row's own `Reverse` evaluates to "play forward", then + this will be inverted and the sample will play in reverse. +- If `Reverse Probability` evaluates to `Yes` for a note, and the kit row's own `Reverse` evaluates to "play in reverse", then + this will be inverted and the sample will play forward. +- If `Reverse Probability` evaluates to `No` for a note, then, the kit row's `Reverse` settings will be respected. + +### Tips for using the Kit Arpeggiator + +- To be able to use the `Kit Arpeggiator` with a sliced sample, remember to set all kit rows to sample mode `Cut`. You can do this by + entering sample mode menu for any kit row, and while holding the `Affect-Entire` button, you can change the sample mode to `Cut`. + That will apply the change to all kit rows. Then it is up to you how you place the notes, but a simple experiment could be to + place a long note occupying the whole length of the clip for all the kit rows, then enable the `Kit Arpeggiator` and have fun + playing with the settings. diff --git a/docs/menu_hierarchies.md b/docs/menu_hierarchies.md index fa93ffc5bf..1e166bf1aa 100644 --- a/docs/menu_hierarchies.md +++ b/docs/menu_hierarchies.md @@ -748,6 +748,7 @@ The Sound menu contains the following menu hierarchy: - Disabled (OFF) - Aftertouch - MPE Y (Y) + - Include in Kit Arp (KARP) (NOTE: available only for Kit Rows)
Randomizer (RAND) @@ -1146,6 +1147,63 @@ The Kit FX menu contains the following menu hierarchy: - Pitch (PITC) - Pan
+
Kit Arpeggiator (KARP) + + - Enabled (ON) + - Basic + - Gate + - Sync + NOTE: These options can change depending on how your default resolution is set + + - Off + - 2-Bar + - 1-Bar + - 2nd-Notes + - 4th-Notes + - 8th-Notes + - 16th-Notes + - 32nd-Notes + - 64th-Notes + - 128th-Notes + - 2-Bar-TPLTS + - 1-Bar-TPLTS + - 2nd-TPLTS + - 4th-TPLTS + - 8th-TPLTS + - 16th-TPLTS + - 32nd-TPLTS + - 64th-TPLTS + - 128th-TPLTS + - 2-Bar-DTTED + - 1-Bar-DTTED + - 2nd-DTTED + - 4th-DTTED + - 8th-DTTED + - 16th-DTTED + - 32nd-DTTED + - 64th-DTTED + - 128th-DTTED + - Rate + - Pattern + - Note Mode (NMOD) + - Up + - Down + - Up & Down (UPDN) + - Played Order (PLAY) + - Random (RAND) + - Step Repeat (REPE) + - Rhythm (RHYT) + - Sequence Length (LENG) + - Randomizer + - Lock Randomizer (LOCK) + - Gate Spread (GATE) + - Velocity Spread (VELO) + - Ratchet Amount (RATC) + - Ratchet Probability (RPRO) + - Note Probability (NOTE) + - Bass Probability (BASS) + - Reverse Probability (RVRS) +
Compressor (COMP) - Threshold (THRE) @@ -1398,6 +1456,7 @@ The MIDI menu contains the following menu hierarchy: - Disabled (OFF) - Aftertouch - MPE Y (Y) + - Include in Kit Arp (KARP) (NOTE: available only for Kit Rows)
Randomizer (RAND) @@ -1506,6 +1565,7 @@ The CV menu contains the following menu hierarchy: - Disabled (OFF) - Aftertouch - MPE Y (Y) + - Include in Kit Arp (KARP) (NOTE: available only for Kit Rows)
Randomizer (RAND) diff --git a/src/deluge/gui/l10n/english.json b/src/deluge/gui/l10n/english.json index 93b72bb6fe..cc1ec6e5be 100644 --- a/src/deluge/gui/l10n/english.json +++ b/src/deluge/gui/l10n/english.json @@ -209,7 +209,6 @@ "STRING_FOR_UP_DOWN": "Up & Down", "STRING_FOR_AS_PLAYED": "Played Order", "STRING_FOR_ALTERNATE": "Alternate", - "STRING_FOR_ARP": "Arpeggiator", "STRING_FOR_SEQUENCE": "Sequencer", "STRING_FOR_FIFTH": "5TH", @@ -436,6 +435,7 @@ "STRING_FOR_RATCHET_PROBABILITY": "Ratchet Probability", "STRING_FOR_REVERSE_PROBABILITY": "Reverse Probability", "STRING_FOR_SEQUENCE_LENGTH": "Sequence Length", + "STRING_FOR_INCLUDE_IN_KIT_ARP": "Include in Kit Arp", "STRING_FOR_RANDOMIZER_LOCK": "Lock Randomizer", "STRING_FOR_WALK": "Walk", "STRING_FOR_WALK1": "Walk1", @@ -594,6 +594,7 @@ "STRING_FOR_PRESET": "PRESET", "STRING_FOR_MODE": "MODE", "STRING_FOR_ARPEGGIATOR": "ARPEGGIATOR", + "STRING_FOR_KIT_ARPEGGIATOR": "KIT ARPEGGIATOR", "STRING_FOR_RANDOMIZER": "RANDOMIZER", "STRING_FOR_PATTERN": "PATTERN", "STRING_FOR_POLYPHONY": "POLYPHONY", diff --git a/src/deluge/gui/l10n/g_english.cpp b/src/deluge/gui/l10n/g_english.cpp index ba8f82b4ad..0eaf39935f 100644 --- a/src/deluge/gui/l10n/g_english.cpp +++ b/src/deluge/gui/l10n/g_english.cpp @@ -180,7 +180,6 @@ PLACE_SDRAM_DATA Language english{ {STRING_FOR_UP_DOWN, "Up & Down"}, {STRING_FOR_AS_PLAYED, "Played Order"}, {STRING_FOR_ALTERNATE, "Alternate"}, - {STRING_FOR_ARP, "Arpeggiator"}, {STRING_FOR_SEQUENCE, "Sequencer"}, {STRING_FOR_FIFTH, "5TH"}, {STRING_FOR_SUS2, "SUS2"}, @@ -394,6 +393,7 @@ PLACE_SDRAM_DATA Language english{ {STRING_FOR_RATCHET_PROBABILITY, "Ratchet Probability"}, {STRING_FOR_REVERSE_PROBABILITY, "Reverse Probability"}, {STRING_FOR_SEQUENCE_LENGTH, "Sequence Length"}, + {STRING_FOR_INCLUDE_IN_KIT_ARP, "Include in Kit Arp"}, {STRING_FOR_RANDOMIZER_LOCK, "Lock Randomizer"}, {STRING_FOR_WALK, "Walk"}, {STRING_FOR_WALK1, "Walk1"}, @@ -544,6 +544,7 @@ PLACE_SDRAM_DATA Language english{ {STRING_FOR_PRESET, "PRESET"}, {STRING_FOR_MODE, "MODE"}, {STRING_FOR_ARPEGGIATOR, "ARPEGGIATOR"}, + {STRING_FOR_KIT_ARPEGGIATOR, "KIT ARPEGGIATOR"}, {STRING_FOR_RANDOMIZER, "RANDOMIZER"}, {STRING_FOR_PATTERN, "PATTERN"}, {STRING_FOR_POLYPHONY, "POLYPHONY"}, diff --git a/src/deluge/gui/l10n/g_seven_segment.cpp b/src/deluge/gui/l10n/g_seven_segment.cpp index a90ce7134f..92e397c1ad 100644 --- a/src/deluge/gui/l10n/g_seven_segment.cpp +++ b/src/deluge/gui/l10n/g_seven_segment.cpp @@ -91,8 +91,8 @@ PLACE_SDRAM_DATA Language seven_segment{ {STRING_FOR_AS_PLAYED, "PLAY"}, {STRING_FOR_PATTERN, "PATT"}, {STRING_FOR_ALTERNATE, "ALT"}, - {STRING_FOR_ARP, "ARP"}, {STRING_FOR_SEQUENCE, "SEQ"}, + {STRING_FOR_KIT_ARPEGGIATOR, "KARP"}, {STRING_FOR_CV_OUTPUT_N, "OUT*"}, {STRING_FOR_CV_OUTPUT_1, "OUT1"}, {STRING_FOR_CV_OUTPUT_2, "OUT2"}, @@ -282,6 +282,7 @@ PLACE_SDRAM_DATA Language seven_segment{ {STRING_FOR_RATCHET_PROBABILITY, "RPRO"}, {STRING_FOR_REVERSE_PROBABILITY, "RVRS"}, {STRING_FOR_SEQUENCE_LENGTH, "LENG"}, + {STRING_FOR_INCLUDE_IN_KIT_ARP, "KARP"}, {STRING_FOR_RANDOMIZER_LOCK, "LOCK"}, {STRING_FOR_WALK1, "WLK1"}, {STRING_FOR_WALK2, "WLK2"}, diff --git a/src/deluge/gui/l10n/seven_segment.json b/src/deluge/gui/l10n/seven_segment.json index cbcbd65a5b..87ae4c6dfe 100644 --- a/src/deluge/gui/l10n/seven_segment.json +++ b/src/deluge/gui/l10n/seven_segment.json @@ -90,9 +90,10 @@ "STRING_FOR_AS_PLAYED": "PLAY", "STRING_FOR_PATTERN": "PATT", "STRING_FOR_ALTERNATE": "ALT", - "STRING_FOR_ARP": "ARP", "STRING_FOR_SEQUENCE": "SEQ", + "STRING_FOR_KIT_ARPEGGIATOR": "KARP", + "STRING_FOR_CV_OUTPUT_N": "OUT*", "STRING_FOR_CV_OUTPUT_1": "OUT1", "STRING_FOR_CV_OUTPUT_2": "OUT2", @@ -291,6 +292,7 @@ "STRING_FOR_RATCHET_PROBABILITY": "RPRO", "STRING_FOR_REVERSE_PROBABILITY": "RVRS", "STRING_FOR_SEQUENCE_LENGTH": "LENG", + "STRING_FOR_INCLUDE_IN_KIT_ARP": "KARP", "STRING_FOR_RANDOMIZER_LOCK": "LOCK", "STRING_FOR_WALK1": "WLK1", "STRING_FOR_WALK2": "WLK2", diff --git a/src/deluge/gui/l10n/strings.h b/src/deluge/gui/l10n/strings.h index 3b80a71315..8527391be3 100644 --- a/src/deluge/gui/l10n/strings.h +++ b/src/deluge/gui/l10n/strings.h @@ -171,7 +171,6 @@ enum class String : size_t { STRING_FOR_UP_DOWN, STRING_FOR_AS_PLAYED, STRING_FOR_ALTERNATE, - STRING_FOR_ARP, STRING_FOR_SEQUENCE, // gui/menu_item/arpeggiator/chord_type.h @@ -418,6 +417,7 @@ enum class String : size_t { STRING_FOR_RATCHET_PROBABILITY, STRING_FOR_REVERSE_PROBABILITY, STRING_FOR_SEQUENCE_LENGTH, + STRING_FOR_INCLUDE_IN_KIT_ARP, STRING_FOR_RANDOMIZER_LOCK, STRING_FOR_WALK, STRING_FOR_WALK1, @@ -582,6 +582,7 @@ enum class String : size_t { STRING_FOR_MODE, STRING_FOR_PRESET, STRING_FOR_ARPEGGIATOR, + STRING_FOR_KIT_ARPEGGIATOR, STRING_FOR_RANDOMIZER, STRING_FOR_PATTERN, STRING_FOR_POLYPHONY, diff --git a/src/deluge/gui/menu_item/arpeggiator/arp_unpatched_param.h b/src/deluge/gui/menu_item/arpeggiator/arp_unpatched_param.h index 2546f731fc..017f934aec 100644 --- a/src/deluge/gui/menu_item/arpeggiator/arp_unpatched_param.h +++ b/src/deluge/gui/menu_item/arpeggiator/arp_unpatched_param.h @@ -19,7 +19,7 @@ #include "gui/ui/sound_editor.h" namespace deluge::gui::menu_item::arpeggiator { -class ArpSoundUnpatchedParam final : public UnpatchedParam { +class ArpUnpatchedParam final : public UnpatchedParam { public: using UnpatchedParam::UnpatchedParam; bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { @@ -30,6 +30,18 @@ class ArpSoundUnpatchedParam final : public UnpatchedParam { } }; +class ArpSoundOnlyUnpatchedParam final : public UnpatchedParam { +public: + using UnpatchedParam::UnpatchedParam; + bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { + return !soundEditor.editingCVOrMIDIClip() && !soundEditor.editingKitAffectEntire() + && !soundEditor.editingNonAudioDrumRow(); + } + void getColumnLabel(StringBuf& label) override { + label.append(deluge::l10n::getView(deluge::l10n::built_in::seven_segment, this->name).data()); + } +}; + class ArpNonKitSoundUnpatchedParam final : public UnpatchedParam { public: using UnpatchedParam::UnpatchedParam; diff --git a/src/deluge/gui/menu_item/arpeggiator/chord_type.h b/src/deluge/gui/menu_item/arpeggiator/chord_type.h index eacdef1d00..03c946bb94 100644 --- a/src/deluge/gui/menu_item/arpeggiator/chord_type.h +++ b/src/deluge/gui/menu_item/arpeggiator/chord_type.h @@ -36,7 +36,7 @@ class ChordType : public Selection { } } bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return soundEditor.editingKit() && !soundEditor.editingGateDrumRow(); + return soundEditor.editingKitRow() && !soundEditor.editingGateDrumRow(); } void getColumnLabel(StringBuf& label) override { label.append(deluge::l10n::get(deluge::l10n::built_in::seven_segment, this->name)); diff --git a/src/deluge/gui/menu_item/arpeggiator/include_in_kit_arp.h b/src/deluge/gui/menu_item/arpeggiator/include_in_kit_arp.h new file mode 100644 index 0000000000..4c9adeb00d --- /dev/null +++ b/src/deluge/gui/menu_item/arpeggiator/include_in_kit_arp.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014-2023 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ +#pragma once +#include "gui/l10n/strings.h" +#include "gui/menu_item/selection.h" +#include "gui/ui/sound_editor.h" +#include "model/song/song.h" + +namespace deluge::gui::menu_item::arpeggiator { +class IncludeInKitArp final : public Selection { +public: + using Selection::Selection; + void readCurrentValue() override { + if (!soundEditor.allowsNoteTails) { + this->setValue(0); + } + else { + this->setValue(soundEditor.currentArpSettings->includeInKitArp); + } + } + void writeCurrentValue() override { + if (soundEditor.allowsNoteTails) { + auto current_value = this->getValue(); + soundEditor.currentArpSettings->includeInKitArp = this->getValue() != 0; + } + } + + deluge::vector getOptions(OptType optType) override { + (void)optType; + using enum l10n::String; + return { + l10n::getView(STRING_FOR_OFF), //< + l10n::getView(STRING_FOR_ON), //< + }; + } + + bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { + return soundEditor.editingKitRow(); + } + + void getColumnLabel(StringBuf& label) override { + label.append(deluge::l10n::getView(deluge::l10n::built_in::seven_segment, this->name).data()); + } + + // flag this selection menu as a toggle menu so we can use a checkbox to toggle value + bool isToggle() override { return true; } + + // don't enter menu on select button press + bool shouldEnterSubmenu() override { return false; } +}; +} // namespace deluge::gui::menu_item::arpeggiator diff --git a/src/deluge/gui/menu_item/arpeggiator/mode.h b/src/deluge/gui/menu_item/arpeggiator/mode.h index 4bedf60bc2..ad8feedb88 100644 --- a/src/deluge/gui/menu_item/arpeggiator/mode.h +++ b/src/deluge/gui/menu_item/arpeggiator/mode.h @@ -36,7 +36,7 @@ class Mode final : public Selection { // If was off, or is now becoming off... if (soundEditor.currentArpSettings->mode == ArpMode::OFF || current_value == ArpMode::OFF) { - if (getCurrentClip()->isActiveOnOutput()) { + if (getCurrentClip()->isActiveOnOutput() && !soundEditor.editingKitAffectEntire()) { char modelStackMemory[MODEL_STACK_MAX_SIZE]; ModelStackWithThreeMainThings* modelStack = soundEditor.getCurrentModelStack(modelStackMemory); diff --git a/src/deluge/gui/menu_item/arpeggiator/note_mode.h b/src/deluge/gui/menu_item/arpeggiator/note_mode.h index 9a7ea4f36d..c7d6773054 100644 --- a/src/deluge/gui/menu_item/arpeggiator/note_mode.h +++ b/src/deluge/gui/menu_item/arpeggiator/note_mode.h @@ -35,7 +35,7 @@ class NoteMode : public Selection { soundEditor.currentArpSettings->flagForceArpRestart = true; } bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return !soundEditor.editingKit(); + return !soundEditor.editingKitRow(); } void getColumnLabel(StringBuf& label) override { label.append(deluge::l10n::get(deluge::l10n::built_in::seven_segment, this->name)); diff --git a/src/deluge/gui/menu_item/arpeggiator/note_mode_for_drums.h b/src/deluge/gui/menu_item/arpeggiator/note_mode_for_drums.h index 9e889fb61b..886e0bde7d 100644 --- a/src/deluge/gui/menu_item/arpeggiator/note_mode_for_drums.h +++ b/src/deluge/gui/menu_item/arpeggiator/note_mode_for_drums.h @@ -32,7 +32,7 @@ class NoteModeForDrums : public Selection { soundEditor.currentArpSettings->flagForceArpRestart = true; } bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return soundEditor.editingKit() && !soundEditor.editingGateDrumRow(); + return soundEditor.editingKitRow() && !soundEditor.editingGateDrumRow(); } void getColumnLabel(StringBuf& label) override { label.append(deluge::l10n::get(deluge::l10n::built_in::seven_segment, this->name)); diff --git a/src/deluge/gui/menu_item/arpeggiator/octave_mode.h b/src/deluge/gui/menu_item/arpeggiator/octave_mode.h index 902e8f43cc..6046a08b31 100644 --- a/src/deluge/gui/menu_item/arpeggiator/octave_mode.h +++ b/src/deluge/gui/menu_item/arpeggiator/octave_mode.h @@ -33,7 +33,7 @@ class OctaveMode : public Selection { soundEditor.currentArpSettings->flagForceArpRestart = true; } bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return !soundEditor.editingGateDrumRow(); + return !soundEditor.editingGateDrumRow() && !soundEditor.editingKitAffectEntire(); } void getColumnLabel(StringBuf& label) override { label.append(deluge::l10n::get(deluge::l10n::built_in::seven_segment, this->name)); diff --git a/src/deluge/gui/menu_item/arpeggiator/octaves.h b/src/deluge/gui/menu_item/arpeggiator/octaves.h index d9f1df3f12..abf12a5887 100644 --- a/src/deluge/gui/menu_item/arpeggiator/octaves.h +++ b/src/deluge/gui/menu_item/arpeggiator/octaves.h @@ -27,7 +27,7 @@ class Octaves final : public Integer { [[nodiscard]] int32_t getMinValue() const override { return 1; } [[nodiscard]] int32_t getMaxValue() const override { return 8; } bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return !soundEditor.editingGateDrumRow(); + return !soundEditor.editingGateDrumRow() && !soundEditor.editingKitAffectEntire(); } void getColumnLabel(StringBuf& label) override { label.append(deluge::l10n::get(deluge::l10n::built_in::seven_segment, this->name)); diff --git a/src/deluge/gui/menu_item/arpeggiator/preset_mode.h b/src/deluge/gui/menu_item/arpeggiator/preset_mode.h index cdd87dd41f..cb62115b6f 100644 --- a/src/deluge/gui/menu_item/arpeggiator/preset_mode.h +++ b/src/deluge/gui/menu_item/arpeggiator/preset_mode.h @@ -37,7 +37,7 @@ class PresetMode final : public Selection { // If was off, or is now becoming off... if (soundEditor.currentArpSettings->mode == ArpMode::OFF || current_value == ArpPreset::OFF) { - if (getCurrentClip()->isActiveOnOutput()) { + if (getCurrentClip()->isActiveOnOutput() && !soundEditor.editingKitAffectEntire()) { char modelStackMemory[MODEL_STACK_MAX_SIZE]; ModelStackWithThreeMainThings* modelStack = soundEditor.getCurrentModelStack(modelStackMemory); @@ -84,7 +84,7 @@ class PresetMode final : public Selection { MenuItem* selectButtonPress() override { auto current_value = this->getValue(); if (current_value == ArpPreset::CUSTOM) { - if (soundEditor.editingKit()) { + if (soundEditor.editingKitRow()) { return &arpeggiator::arpOctaveModeToNoteModeMenuForDrums; } return &arpeggiator::arpOctaveModeToNoteModeMenu; diff --git a/src/deluge/gui/menu_item/arpeggiator/rate.h b/src/deluge/gui/menu_item/arpeggiator/rate.h index 4c5ba888dc..13bd5bf54d 100644 --- a/src/deluge/gui/menu_item/arpeggiator/rate.h +++ b/src/deluge/gui/menu_item/arpeggiator/rate.h @@ -16,6 +16,7 @@ */ #pragma once #include "gui/menu_item/patched_param/integer.h" +#include "gui/menu_item/unpatched_param.h" #include "gui/ui/sound_editor.h" namespace deluge::gui::menu_item::arpeggiator { @@ -24,7 +25,15 @@ class Rate final : public patched_param::Integer { using patched_param::Integer::Integer; bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { return !soundEditor.editingCVOrMIDIClip() && !soundEditor.editingNonAudioDrumRow() - && soundEditor.currentArpSettings->syncLevel == SYNC_LEVEL_NONE; + && !soundEditor.editingKitAffectEntire() && soundEditor.currentArpSettings->syncLevel == SYNC_LEVEL_NONE; + } +}; + +class KitRate final : public UnpatchedParam { +public: + using UnpatchedParam::UnpatchedParam; + bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { + return soundEditor.editingKitAffectEntire() && soundEditor.currentArpSettings->syncLevel == SYNC_LEVEL_NONE; } }; diff --git a/src/deluge/gui/menu_item/midi/sound/note_for_drum.h b/src/deluge/gui/menu_item/midi/sound/note_for_drum.h index 053f1e790b..267b417f8c 100644 --- a/src/deluge/gui/menu_item/midi/sound/note_for_drum.h +++ b/src/deluge/gui/menu_item/midi/sound/note_for_drum.h @@ -30,7 +30,7 @@ class OutputMidiNoteForDrum final : public Integer { [[nodiscard]] int32_t getMinValue() const override { return 0; } [[nodiscard]] int32_t getMaxValue() const override { return kMaxMIDIValue; } bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return soundEditor.editingKit() && !soundEditor.editingNonAudioDrumRow(); + return soundEditor.editingKitRow() && !soundEditor.editingNonAudioDrumRow(); } void readCurrentValue() override { int32_t value = soundEditor.currentSound->outputMidiNoteForDrum; diff --git a/src/deluge/gui/menu_item/sample/repeat.h b/src/deluge/gui/menu_item/sample/repeat.h index 74dcccb104..b2b333b649 100644 --- a/src/deluge/gui/menu_item/sample/repeat.h +++ b/src/deluge/gui/menu_item/sample/repeat.h @@ -43,11 +43,11 @@ class Repeat final : public Selection, public FormattedTitle { void writeCurrentValue() override { auto current_value = this->getValue(); + Kit* kit = getCurrentKit(); + // If affect-entire button held, do whole kit if (currentUIMode == UI_MODE_HOLDING_AFFECT_ENTIRE_IN_SOUND_EDITOR && soundEditor.editingKit()) { - Kit* kit = getCurrentKit(); - for (Drum* thisDrum = kit->firstDrum; thisDrum != nullptr; thisDrum = thisDrum->next) { if (thisDrum->type == DrumType::SOUND) { auto* soundDrum = static_cast(thisDrum); @@ -63,6 +63,20 @@ class Repeat final : public Selection, public FormattedTitle { soundEditor.currentSource->sampleControls.pitchAndSpeedAreIndependent = false; } + if (current_value == SampleRepeatMode::ONCE) { + // Send note-off for kit arpeggiator to avoid stuck notes + int32_t noteRowIndex; + NoteRow* noteRow = getCurrentInstrumentClip()->getNoteRowForDrum(soundDrum, ¬eRowIndex); + char modelStackMemory[MODEL_STACK_MAX_SIZE]; + ModelStack* modelStack = (ModelStack*)modelStackMemory; + ModelStackWithThreeMainThings* modelStackWithThreeMainThings = + modelStack->addTimelineCounter(getCurrentClip()) + ->addNoteRow(noteRowIndex, noteRow) + ->addOtherTwoThings(soundEditor.currentModControllable, + soundEditor.currentParamManager); + kit->noteOffPreKitArp(modelStackWithThreeMainThings, soundDrum); + } + source->repeatMode = current_value; } } @@ -80,6 +94,19 @@ class Repeat final : public Selection, public FormattedTitle { soundEditor.currentSource->sampleControls.pitchAndSpeedAreIndependent = false; } + if (current_value == SampleRepeatMode::ONCE) { + // Send note-off for kit arpeggiator to avoid stuck notes + int32_t noteRowIndex; + NoteRow* noteRow = getCurrentInstrumentClip()->getNoteRowForDrum(kit->selectedDrum, ¬eRowIndex); + char modelStackMemory[MODEL_STACK_MAX_SIZE]; + ModelStack* modelStack = (ModelStack*)modelStackMemory; + ModelStackWithThreeMainThings* modelStackWithThreeMainThings = + modelStack->addTimelineCounter(getCurrentClip()) + ->addNoteRow(noteRowIndex, noteRow) + ->addOtherTwoThings(soundEditor.currentModControllable, soundEditor.currentParamManager); + kit->noteOffPreKitArp(modelStackWithThreeMainThings, kit->selectedDrum); + } + soundEditor.currentSource->repeatMode = current_value; } diff --git a/src/deluge/gui/menu_item/submenu/arp_mpe_submenu.h b/src/deluge/gui/menu_item/submenu/arp_mpe_submenu.h index a826766a78..5ec80717c0 100644 --- a/src/deluge/gui/menu_item/submenu/arp_mpe_submenu.h +++ b/src/deluge/gui/menu_item/submenu/arp_mpe_submenu.h @@ -22,7 +22,7 @@ class ArpMpeSubmenu final : public Submenu { public: using Submenu::Submenu; bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override { - return !soundEditor.editingGateDrumRow(); + return !soundEditor.editingGateDrumRow() && !soundEditor.editingKitAffectEntire(); } }; diff --git a/src/deluge/gui/ui/menus.cpp b/src/deluge/gui/ui/menus.cpp index 410a6c31ff..cf3cfdb843 100644 --- a/src/deluge/gui/ui/menus.cpp +++ b/src/deluge/gui/ui/menus.cpp @@ -2,6 +2,7 @@ #include "gui/menu_item/active_scales.h" #include "gui/menu_item/arpeggiator/arp_unpatched_param.h" #include "gui/menu_item/arpeggiator/chord_type.h" +#include "gui/menu_item/arpeggiator/include_in_kit_arp.h" #include "gui/menu_item/arpeggiator/midi_cv/bass_probability.h" #include "gui/menu_item/arpeggiator/midi_cv/chord_polyphony.h" #include "gui/menu_item/arpeggiator/midi_cv/chord_probability.h" @@ -227,6 +228,7 @@ arpeggiator::PresetMode arpPresetModeMenu{STRING_FOR_PRESET, STRING_FOR_ARP_PRES arpeggiator::Mode arpModeMenu{STRING_FOR_ENABLED, STRING_FOR_ARP_MODE_MENU_TITLE}; arpeggiator::Sync arpSyncMenu{STRING_FOR_SYNC, STRING_FOR_ARP_SYNC_MENU_TITLE}; arpeggiator::Rate arpRateMenu{STRING_FOR_RATE, STRING_FOR_ARP_RATE_MENU_TITLE, params::GLOBAL_ARP_RATE}; +arpeggiator::KitRate arpKitRateMenu{STRING_FOR_RATE, STRING_FOR_ARP_RATE_MENU_TITLE, params::UNPATCHED_ARP_RATE}; arpeggiator::midi_cv::Rate arpRateMenuMIDIOrCV{STRING_FOR_RATE, STRING_FOR_ARP_RATE_MENU_TITLE}; // Pattern arpeggiator::Octaves arpOctavesMenu{STRING_FOR_NUMBER_OF_OCTAVES, STRING_FOR_ARP_OCTAVES_MENU_TITLE}; @@ -244,8 +246,7 @@ arpeggiator::NoteModeFromOctaveModeForDrums arpeggiator::arpNoteModeFromOctaveMo arpeggiator::ChordType arpChordSimulatorMenuKit{STRING_FOR_CHORD_SIMULATOR, STRING_FOR_ARP_CHORD_SIMULATOR_MENU_TITLE}; arpeggiator::StepRepeat arpStepRepeatMenu{STRING_FOR_STEP_REPEAT, STRING_FOR_ARP_STEP_REPEAT_MENU_TITLE}; // Note and rhythm settings -arpeggiator::ArpSoundUnpatchedParam arpGateMenu{STRING_FOR_GATE, STRING_FOR_ARP_GATE_MENU_TITLE, - params::UNPATCHED_ARP_GATE}; +arpeggiator::ArpUnpatchedParam arpGateMenu{STRING_FOR_GATE, STRING_FOR_ARP_GATE_MENU_TITLE, params::UNPATCHED_ARP_GATE}; arpeggiator::midi_cv::Gate arpGateMenuMIDIOrCV{STRING_FOR_GATE, STRING_FOR_ARP_GATE_MENU_TITLE}; arpeggiator::Rhythm arpRhythmMenu{STRING_FOR_RHYTHM, STRING_FOR_ARP_RHYTHM_MENU_TITLE, params::UNPATCHED_ARP_RHYTHM}; arpeggiator::midi_cv::Rhythm arpRhythmMenuMIDIOrCV{STRING_FOR_RHYTHM, STRING_FOR_ARP_RHYTHM_MENU_TITLE}; @@ -253,21 +254,22 @@ arpeggiator::ArpNonKitSoundUnpatchedParam arpChordPolyphonyMenu{ STRING_FOR_CHORD_POLYPHONY, STRING_FOR_ARP_CHORD_POLYPHONY_MENU_TITLE, params::UNPATCHED_ARP_CHORD_POLYPHONY}; arpeggiator::midi_cv::ChordPolyphony arpChordPolyphonyMenuMIDIOrCV{STRING_FOR_CHORD_POLYPHONY, STRING_FOR_ARP_CHORD_POLYPHONY_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpSequenceLengthMenu{ +arpeggiator::ArpUnpatchedParam arpSequenceLengthMenu{ STRING_FOR_SEQUENCE_LENGTH, STRING_FOR_ARP_SEQUENCE_LENGTH_MENU_TITLE, params::UNPATCHED_ARP_SEQUENCE_LENGTH}; arpeggiator::midi_cv::SequenceLength arpSequenceLengthMenuMIDIOrCV{STRING_FOR_SEQUENCE_LENGTH, STRING_FOR_ARP_SEQUENCE_LENGTH_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpRatchetAmountMenu{ - STRING_FOR_NUMBER_OF_RATCHETS, STRING_FOR_ARP_RATCHETS_MENU_TITLE, params::UNPATCHED_ARP_RATCHET_AMOUNT}; +arpeggiator::ArpUnpatchedParam arpRatchetAmountMenu{STRING_FOR_NUMBER_OF_RATCHETS, STRING_FOR_ARP_RATCHETS_MENU_TITLE, + params::UNPATCHED_ARP_RATCHET_AMOUNT}; arpeggiator::midi_cv::RatchetAmount arpRatchetAmountMenuMIDIOrCV{STRING_FOR_NUMBER_OF_RATCHETS, STRING_FOR_ARP_RATCHETS_MENU_TITLE}; // Randomizer +arpeggiator::IncludeInKitArp arpIncludeInKitArpMenu{STRING_FOR_INCLUDE_IN_KIT_ARP, STRING_FOR_INCLUDE_IN_KIT_ARP}; arpeggiator::RandomizerLock arpRandomizerLockMenu{STRING_FOR_RANDOMIZER_LOCK, STRING_FOR_ARP_RANDOMIZER_LOCK_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpNoteProbabilityMenu{ +arpeggiator::ArpUnpatchedParam arpNoteProbabilityMenu{ STRING_FOR_NOTE_PROBABILITY, STRING_FOR_NOTE_PROBABILITY_MENU_TITLE, params::UNPATCHED_NOTE_PROBABILITY}; arpeggiator::midi_cv::NoteProbability arpNoteProbabilityMenuMIDIOrCV{STRING_FOR_NOTE_PROBABILITY, STRING_FOR_NOTE_PROBABILITY_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpBassProbabilityMenu{ +arpeggiator::ArpUnpatchedParam arpBassProbabilityMenu{ STRING_FOR_BASS_PROBABILITY, STRING_FOR_ARP_BASS_PROBABILITY_MENU_TITLE, params::UNPATCHED_ARP_BASS_PROBABILITY}; arpeggiator::midi_cv::BassProbability arpBassProbabilityMenuMIDIOrCV{STRING_FOR_BASS_PROBABILITY, STRING_FOR_ARP_BASS_PROBABILITY_MENU_TITLE}; @@ -275,22 +277,22 @@ arpeggiator::ArpNonKitSoundUnpatchedParam arpChordProbabilityMenu{ STRING_FOR_CHORD_PROBABILITY, STRING_FOR_ARP_CHORD_PROBABILITY_MENU_TITLE, params::UNPATCHED_ARP_CHORD_PROBABILITY}; arpeggiator::midi_cv::ChordProbability arpChordProbabilityMenuMIDIOrCV{STRING_FOR_CHORD_PROBABILITY, STRING_FOR_ARP_CHORD_PROBABILITY_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpRatchetProbabilityMenu{STRING_FOR_RATCHET_PROBABILITY, - STRING_FOR_ARP_RATCHET_PROBABILITY_MENU_TITLE, - params::UNPATCHED_ARP_RATCHET_PROBABILITY}; +arpeggiator::ArpUnpatchedParam arpRatchetProbabilityMenu{STRING_FOR_RATCHET_PROBABILITY, + STRING_FOR_ARP_RATCHET_PROBABILITY_MENU_TITLE, + params::UNPATCHED_ARP_RATCHET_PROBABILITY}; arpeggiator::midi_cv::RatchetProbability arpRatchetProbabilityMenuMIDIOrCV{ STRING_FOR_RATCHET_PROBABILITY, STRING_FOR_ARP_RATCHET_PROBABILITY_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpReverseProbabilityMenu{ +arpeggiator::ArpUnpatchedParam arpReverseProbabilityMenu{ STRING_FOR_REVERSE_PROBABILITY, STRING_FOR_REVERSE_PROBABILITY_MENU_TITLE, params::UNPATCHED_REVERSE_PROBABILITY}; -arpeggiator::ArpSoundUnpatchedParam arpSpreadVelocityMenu{ - STRING_FOR_SPREAD_VELOCITY, STRING_FOR_SPREAD_VELOCITY_MENU_TITLE, params::UNPATCHED_SPREAD_VELOCITY}; +arpeggiator::ArpUnpatchedParam arpSpreadVelocityMenu{STRING_FOR_SPREAD_VELOCITY, STRING_FOR_SPREAD_VELOCITY_MENU_TITLE, + params::UNPATCHED_SPREAD_VELOCITY}; arpeggiator::midi_cv::SpreadVelocity arpSpreadVelocityMenuMIDIOrCV{STRING_FOR_SPREAD_VELOCITY, STRING_FOR_SPREAD_VELOCITY_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpSpreadGateMenu{STRING_FOR_SPREAD_GATE, STRING_FOR_ARP_SPREAD_GATE_MENU_TITLE, - params::UNPATCHED_ARP_SPREAD_GATE}; +arpeggiator::ArpUnpatchedParam arpSpreadGateMenu{STRING_FOR_SPREAD_GATE, STRING_FOR_ARP_SPREAD_GATE_MENU_TITLE, + params::UNPATCHED_ARP_SPREAD_GATE}; arpeggiator::midi_cv::SpreadGate arpSpreadGateMenuMIDIOrCV{STRING_FOR_SPREAD_GATE, STRING_FOR_ARP_SPREAD_GATE_MENU_TITLE}; -arpeggiator::ArpSoundUnpatchedParam arpSpreadOctaveMenu{ +arpeggiator::ArpSoundOnlyUnpatchedParam arpSpreadOctaveMenu{ STRING_FOR_SPREAD_OCTAVE, STRING_FOR_ARP_SPREAD_OCTAVE_MENU_TITLE, params::UNPATCHED_ARP_SPREAD_OCTAVE}; arpeggiator::midi_cv::SpreadOctave arpSpreadOctaveMenuMIDIOrCV{STRING_FOR_SPREAD_OCTAVE, STRING_FOR_ARP_SPREAD_OCTAVE_MENU_TITLE}; @@ -300,7 +302,7 @@ HorizontalMenu arpBasicMenu{STRING_FOR_BASIC, {// Gate &arpGateMenu, &arpGateMenuMIDIOrCV, // Sync - &arpSyncMenu, &arpRateMenu, &arpRateMenuMIDIOrCV}}; + &arpSyncMenu, &arpRateMenu, &arpKitRateMenu, &arpRateMenuMIDIOrCV}}; // Arp: Pattern HorizontalMenu arpPatternMenu{STRING_FOR_PATTERN, {// Pattern @@ -361,6 +363,22 @@ Submenu arpMenu{ &arpRandomizerMenu, // MPE &arpMpeMenu, + // Include in kit arp + &arpIncludeInKitArpMenu, + }, +}; + +Submenu kitArpMenu{ + STRING_FOR_KIT_ARPEGGIATOR, + { + // Mode + &arpModeMenu, + // Basic + &arpBasicMenu, + // Pattern + &arpPatternMenu, + // Randomizer + &arpRandomizerMenu, }, }; @@ -1587,6 +1605,7 @@ menu_item::Submenu soundEditorRootMenuKitGlobalFX{ STRING_FOR_KIT_GLOBAL_FX, { &kitClipMasterMenu, + &kitArpMenu, &audioCompMenu, &globalFiltersMenu, &globalFXMenu, diff --git a/src/deluge/gui/ui/sound_editor.cpp b/src/deluge/gui/ui/sound_editor.cpp index 5f1fb0e116..43bb22be9b 100644 --- a/src/deluge/gui/ui/sound_editor.cpp +++ b/src/deluge/gui/ui/sound_editor.cpp @@ -141,6 +141,14 @@ bool SoundEditor::editingKit() { return getCurrentOutputType() == OutputType::KIT; } +bool SoundEditor::editingKitAffectEntire() { + return getCurrentOutputType() == OutputType::KIT && setupKitGlobalFXMenu; +} + +bool SoundEditor::editingKitRow() { + return getCurrentOutputType() == OutputType::KIT && !setupKitGlobalFXMenu; +} + bool SoundEditor::editingCVOrMIDIClip() { return (getCurrentOutputType() == OutputType::MIDI_OUT || getCurrentOutputType() == OutputType::CV); } @@ -1383,7 +1391,7 @@ void SoundEditor::modEncoderAction(int32_t whichModEncoder, int32_t offset) { if (currentUIMode == UI_MODE_MIDI_LEARN) { // But, can't do it if it's a Kit and affect-entire is on! - if (editingKit() && getCurrentInstrumentClip()->affectEntire) { + if (editingKitAffectEntire()) { // IndicatorLEDs::indicateErrorOnLed(affectEntireLedX, affectEntireLedY); } @@ -1449,6 +1457,7 @@ bool SoundEditor::setup(Clip* clip, const MenuItem* item, int32_t sourceIndex) { if (setupKitGlobalFXMenu) { newModControllable = (ModControllableAudio*)(Instrument*)output->toModControllable(); newParamManager = &instrumentClip->paramManager; + newArpSettings = &instrumentClip->arpSettings; } // If a SoundDrum is selected... @@ -1595,6 +1604,13 @@ bool SoundEditor::setup(Clip* clip, const MenuItem* item, int32_t sourceIndex) { display->cancelPopup(); } + allowsNoteTails = true; + if (newSound != nullptr) { + char modelStackMemory[MODEL_STACK_MAX_SIZE]; + ModelStackWithSoundFlags* modelStack = getCurrentModelStack(modelStackMemory)->addSoundFlags(); + allowsNoteTails = newSound->allowNoteTails(modelStack, true); + } + currentSound = newSound; currentArpSettings = newArpSettings; currentMultiRange = newRange; diff --git a/src/deluge/gui/ui/sound_editor.h b/src/deluge/gui/ui/sound_editor.h index 7b917fe32e..100595d2d6 100644 --- a/src/deluge/gui/ui/sound_editor.h +++ b/src/deluge/gui/ui/sound_editor.h @@ -57,6 +57,7 @@ class SoundEditor final : public UI { void displayOrLanguageChanged() final; bool getGreyoutColsAndRows(uint32_t* cols, uint32_t* rows) override; Sound* currentSound; + bool allowsNoteTails; ModControllableAudio* currentModControllable; int8_t currentSourceIndex; Source* currentSource; @@ -78,6 +79,8 @@ class SoundEditor final : public UI { ActionResult horizontalEncoderAction(int32_t offset) override; void scrollFinished() override; bool editingKit(); + bool editingKitAffectEntire(); + bool editingKitRow(); ActionResult timerCallback() override; void setupShortcutBlink(int32_t x, int32_t y, int32_t frequency); diff --git a/src/deluge/gui/views/automation_view.cpp b/src/deluge/gui/views/automation_view.cpp index 0cedb52d5e..02ddc23731 100644 --- a/src/deluge/gui/views/automation_view.cpp +++ b/src/deluge/gui/views/automation_view.cpp @@ -122,7 +122,7 @@ const uint32_t mutePadActionUIModes[] = {UI_MODE_NOTES_PRESSED, UI_MODE_AUDITION const uint32_t verticalScrollUIModes[] = {UI_MODE_NOTES_PRESSED, UI_MODE_AUDITIONING, UI_MODE_RECORD_COUNT_IN, 0}; constexpr int32_t kNumNonGlobalParamsForAutomation = 81; -constexpr int32_t kNumGlobalParamsForAutomation = 26; +constexpr int32_t kNumGlobalParamsForAutomation = 37; constexpr int32_t kParamNodeWidth = 3; // synth and kit rows FX - sorted in the order that Parameters are scrolled through on the display @@ -282,6 +282,19 @@ const std::array, kNumGlobalParamsForAutomati {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_STUTTER_RATE}, // Compressor Threshold {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_COMPRESSOR_THRESHOLD}, + // Arp Rate, Gate, Rhythm, Chord Polyphony, Sequence Length, Ratchet Amount, Note Prob, Bass Prob, Chord Prob, + // Ratchet Prob, Spread Gate, Spread Octave, Spread Velocity + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_RATE}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_GATE}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_SPREAD_GATE}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_SPREAD_VELOCITY}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_RATCHET_AMOUNT}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_RATCHET_PROBABILITY}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_NOTE_PROBABILITY}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_BASS_PROBABILITY}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_REVERSE_PROBABILITY}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_RHYTHM}, + {params::Kind::UNPATCHED_GLOBAL, params::UNPATCHED_ARP_SEQUENCE_LENGTH}, }}; // VU meter style colours for the automation editor diff --git a/src/deluge/gui/views/instrument_clip_view.cpp b/src/deluge/gui/views/instrument_clip_view.cpp index 3e8f8a5b74..2c4e119751 100644 --- a/src/deluge/gui/views/instrument_clip_view.cpp +++ b/src/deluge/gui/views/instrument_clip_view.cpp @@ -4256,6 +4256,14 @@ ActionResult InstrumentClipView::scrollVertical(int32_t scrollAmount, bool inCar clip->noteRows.getElement(noteRowToShiftI)->y = -32768; // Need to remember not to try and use the yNote value // of this NoteRow if we switch back out of Kit mode clip->noteRows.swapElements(noteRowToShiftI, noteRowToSwapWithI); + + // If the Kit arpeggiator is active and playback is stopped, we must reset it so notes are reassigned + // If playback is active, the rendering will take care of it + if (clip->output->type == OutputType::KIT && !playbackHandler.isEitherClockActive()) { + clip->noteRows.getElement(noteRowToShiftI)->drum->arpeggiator.reset(); + clip->noteRows.getElement(noteRowToSwapWithI)->drum->arpeggiator.reset(); + ((Kit*)clip->output)->arpeggiator.reset(); + } } // Do actual scroll diff --git a/src/deluge/io/midi/midi_follow.cpp b/src/deluge/io/midi/midi_follow.cpp index 1d0f1045c9..be4c587f22 100644 --- a/src/deluge/io/midi/midi_follow.cpp +++ b/src/deluge/io/midi/midi_follow.cpp @@ -270,6 +270,8 @@ void MidiFollow::initDefaultMappings() { globalParamToCC[params::UNPATCHED_MOD_FX_OFFSET] = 18; ccToGlobalParam[20] = params::UNPATCHED_STUTTER_RATE; globalParamToCC[params::UNPATCHED_STUTTER_RATE] = 20; + ccToGlobalParam[51] = params::UNPATCHED_ARP_RATE; + globalParamToCC[params::UNPATCHED_ARP_RATE] = 51; ccToGlobalParam[52] = params::UNPATCHED_DELAY_AMOUNT; globalParamToCC[params::UNPATCHED_DELAY_AMOUNT] = 52; ccToGlobalParam[53] = params::UNPATCHED_DELAY_RATE; diff --git a/src/deluge/model/clip/instrument_clip.cpp b/src/deluge/model/clip/instrument_clip.cpp index 6fec3d9739..5346d77f37 100644 --- a/src/deluge/model/clip/instrument_clip.cpp +++ b/src/deluge/model/clip/instrument_clip.cpp @@ -37,6 +37,7 @@ #include "model/instrument/midi_instrument.h" #include "model/iterance/iterance.h" #include "model/note/note.h" +#include "model/output.h" #include "model/scale/note_set.h" #include "model/scale/preset_scales.h" #include "model/scale/scale_change.h" @@ -918,9 +919,9 @@ void InstrumentClip::sendPendingNoteOn(ModelStackWithTimelineCounter* modelStack ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStackWithNoteRow->addOtherTwoThings( pendingNoteOn->noteRow->drum->toModControllable(), &pendingNoteOn->noteRow->paramManager); - pendingNoteOn->noteRow->drum->noteOn(modelStackWithThreeMainThings, pendingNoteOn->velocity, (Kit*)output, - mpeValues, MIDI_CHANNEL_NONE, pendingNoteOn->sampleSyncLength, - pendingNoteOn->ticksLate); + pendingNoteOn->noteRow->drum->kit->noteOnPreKitArp(modelStackWithThreeMainThings, pendingNoteOn->noteRow->drum, + pendingNoteOn->velocity, mpeValues, MIDI_CHANNEL_NONE, + pendingNoteOn->sampleSyncLength, pendingNoteOn->ticksLate); } else { ModelStackWithThreeMainThings* modelStackWithThreeMainThings = @@ -2280,6 +2281,9 @@ Error InstrumentClip::setAudioInstrument(Instrument* newInstrument, Song* song, if (newInstrument->type == OutputType::SYNTH) { arpSettings.cloneFrom(&((SoundInstrument*)newInstrument)->defaultArpSettings); } + else if (newInstrument->type == OutputType::KIT) { + arpSettings.cloneFrom(&((Kit*)newInstrument)->defaultArpSettings); + } if (shouldSetupPatching) { char modelStackMemory[MODEL_STACK_MAX_SIZE]; @@ -2367,18 +2371,16 @@ void InstrumentClip::writeDataToFile(Serializer& writer, Song* song) { paramManager.getMIDIParamCollection()->writeToFile(writer); } - if (output->type != OutputType::KIT) { - writer.writeOpeningTagBeginning("arpeggiator"); + writer.writeOpeningTagBeginning("arpeggiator"); - arpSettings.writeCommonParamsToFile(writer, nullptr); + arpSettings.writeCommonParamsToFile(writer, nullptr); - if (output->type == OutputType::MIDI_OUT || output->type == OutputType::CV) { - arpSettings.writeNonAudioParamsToFile(writer); - } - - writer.closeTag(); + if (output->type == OutputType::MIDI_OUT || output->type == OutputType::CV) { + arpSettings.writeNonAudioParamsToFile(writer); } + writer.closeTag(); + if (output->type == OutputType::KIT) { writer.writeOpeningTagBeginning("kitParams"); GlobalEffectableForClip::writeParamAttributesToFile(writer, ¶mManager, true); @@ -2681,6 +2683,9 @@ Error InstrumentClip::readFromFile(Deserializer& reader, Song* song) { if (outputTypeWhileLoading == OutputType::SYNTH) { arpSettings.cloneFrom(&((SoundInstrument*)output)->defaultArpSettings); } + else if (outputTypeWhileLoading == OutputType::KIT) { + arpSettings.cloneFrom(&((Kit*)output)->defaultArpSettings); + } } reader.exitTag("referToTrackId"); } @@ -2715,6 +2720,9 @@ Error InstrumentClip::readFromFile(Deserializer& reader, Song* song) { if (outputTypeWhileLoading == OutputType::SYNTH) { arpSettings.cloneFrom(&((SoundInstrument*)output)->defaultArpSettings); } + else if (outputTypeWhileLoading == OutputType::KIT) { + arpSettings.cloneFrom(&((Kit*)output)->defaultArpSettings); + } // Add the Instrument to the Song song->addOutput(output); @@ -3250,6 +3258,9 @@ void InstrumentClip::deleteNoteRow(ModelStackWithTimelineCounter* modelStack, in noteRow->stopCurrentlyPlayingNote(modelStackWithNoteRow); + Kit* kit = (Kit*)output; + kit->removeDrumFromKitArpeggiator(noteRowIndex); + noteRow->setDrum(nullptr, (Kit*)output, modelStackWithNoteRow); noteRows.deleteNoteRowAtIndex(noteRowIndex); } diff --git a/src/deluge/model/clip/instrument_clip.h b/src/deluge/model/clip/instrument_clip.h index b970c16c97..38204adf0b 100644 --- a/src/deluge/model/clip/instrument_clip.h +++ b/src/deluge/model/clip/instrument_clip.h @@ -99,7 +99,7 @@ class InstrumentClip final : public Clip { void copyBasicsFrom(Clip const* otherClip) override; - ArpeggiatorSettings arpSettings; // Not valid for Kits + ArpeggiatorSettings arpSettings; ParamManagerForTimeline backedUpParamManagerMIDI; diff --git a/src/deluge/model/drum/drum.h b/src/deluge/model/drum/drum.h index fd415930b0..b151a5ac54 100644 --- a/src/deluge/model/drum/drum.h +++ b/src/deluge/model/drum/drum.h @@ -65,7 +65,7 @@ class Drum { ArpeggiatorForDrum arpeggiator; ArpeggiatorSettings arpSettings; - virtual void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, + virtual void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel = MIDI_CHANNEL_NONE, uint32_t sampleSyncLength = 0, int32_t ticksLate = 0, uint32_t samplesLate = 0) = 0; virtual void noteOff(ModelStackWithThreeMainThings* modelStack, int32_t velocity = kDefaultLiftValue) = 0; diff --git a/src/deluge/model/drum/gate_drum.cpp b/src/deluge/model/drum/gate_drum.cpp index 410fd9cd78..69c72fab7c 100644 --- a/src/deluge/model/drum/gate_drum.cpp +++ b/src/deluge/model/drum/gate_drum.cpp @@ -28,7 +28,7 @@ GateDrum::GateDrum() : NonAudioDrum(DrumType::GATE) { arpSettings.numOctaves = 1; } -void GateDrum::noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, +void GateDrum::noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel, uint32_t sampleSyncLength, int32_t ticksLate, uint32_t samplesLate) { ArpeggiatorSettings* arpSettings = getArpSettings(); ArpReturnInstruction instruction; @@ -57,6 +57,13 @@ void GateDrum::noteOff(ModelStackWithThreeMainThings* modelStack, int32_t veloci } } +void GateDrum::unassignAllVoices() { + if (hasAnyVoices()) { + noteOff(nullptr); + } + arpeggiator.reset(); +} + void GateDrum::writeToFile(Serializer& writer, bool savingSong, ParamManager* paramManager) { writer.writeOpeningTagBeginning("gateOutput", true); diff --git a/src/deluge/model/drum/gate_drum.h b/src/deluge/model/drum/gate_drum.h index 6ba4f35126..b5a7052747 100644 --- a/src/deluge/model/drum/gate_drum.h +++ b/src/deluge/model/drum/gate_drum.h @@ -28,14 +28,15 @@ class ParamManager; class GateDrum final : public NonAudioDrum { public: GateDrum(); - void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, + void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel = MIDI_CHANNEL_NONE, uint32_t sampleSyncLength = 0, int32_t ticksLate = 0, uint32_t samplesLate = 0) override; void noteOnPostArp(int32_t noteCodePostArp, ArpNote* arpNote, int32_t noteIndex) override; void noteOffPostArp(int32_t noteCodePostArp) override; - void noteOff(ModelStackWithThreeMainThings* modelStack, int32_t velocity) override; + void noteOff(ModelStackWithThreeMainThings* modelStack, int32_t velocity = kDefaultLiftValue) override; void writeToFile(Serializer& writer, bool savingSong, ParamManager* paramManager) override; Error readFromFile(Deserializer& reader, Song* song, Clip* clip, int32_t readAutomationUpToPos) override; void getName(char* buffer) override; int32_t getNumChannels() override; + void unassignAllVoices() override; }; diff --git a/src/deluge/model/drum/midi_drum.cpp b/src/deluge/model/drum/midi_drum.cpp index d20e6196e9..ebcc33d7e8 100644 --- a/src/deluge/model/drum/midi_drum.cpp +++ b/src/deluge/model/drum/midi_drum.cpp @@ -28,7 +28,7 @@ MIDIDrum::MIDIDrum() : NonAudioDrum(DrumType::MIDI) { note = 0; } -void MIDIDrum::noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, +void MIDIDrum::noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel, uint32_t sampleSyncLength, int32_t ticksLate, uint32_t samplesLate) { ArpeggiatorSettings* arpSettings = getArpSettings(); ArpReturnInstruction instruction; @@ -72,6 +72,7 @@ void MIDIDrum::unassignAllVoices() { if (hasAnyVoices()) { noteOff(nullptr); } + arpeggiator.reset(); } void MIDIDrum::writeToFile(Serializer& writer, bool savingSong, ParamManager* paramManager) { diff --git a/src/deluge/model/drum/midi_drum.h b/src/deluge/model/drum/midi_drum.h index 7e87c91247..445782bfa2 100644 --- a/src/deluge/model/drum/midi_drum.h +++ b/src/deluge/model/drum/midi_drum.h @@ -23,7 +23,7 @@ class MIDIDrum final : public NonAudioDrum { public: MIDIDrum(); - void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, + void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel = MIDI_CHANNEL_NONE, uint32_t sampleSyncLength = 0, int32_t ticksLate = 0, uint32_t samplesLate = 0) override; void noteOff(ModelStackWithThreeMainThings* modelStack, int32_t velocity = kDefaultLiftValue) override; diff --git a/src/deluge/model/drum/non_audio_drum.cpp b/src/deluge/model/drum/non_audio_drum.cpp index d6117e6bc9..091641c0da 100644 --- a/src/deluge/model/drum/non_audio_drum.cpp +++ b/src/deluge/model/drum/non_audio_drum.cpp @@ -36,6 +36,7 @@ void NonAudioDrum::unassignAllVoices() { if (hasAnyVoices()) { noteOff(nullptr); } + arpeggiator.reset(); } bool NonAudioDrum::anyNoteIsOn() { @@ -107,7 +108,7 @@ void NonAudioDrum::modChange(ModelStackWithThreeMainThings* modelStack, int32_t instrumentClipView.drawDrumName(this, true); if (wasOn) { - noteOn(modelStack, lastVelocity, nullptr, zeroMPEValues); + noteOn(modelStack, lastVelocity, zeroMPEValues); } } diff --git a/src/deluge/model/global_effectable/global_effectable.cpp b/src/deluge/model/global_effectable/global_effectable.cpp index 4fd8695391..f6c30d2251 100644 --- a/src/deluge/model/global_effectable/global_effectable.cpp +++ b/src/deluge/model/global_effectable/global_effectable.cpp @@ -28,6 +28,7 @@ #include "model/mod_controllable/ModFXProcessor.h" #include "model/settings/runtime_feature_settings.h" #include "model/song/song.h" +#include "modulation/params/param.h" #include "modulation/params/param_collection.h" #include "modulation/params/param_set.h" #include "playback/playback_handler.h" @@ -64,6 +65,10 @@ void GlobalEffectable::initParams(ParamManager* paramManager) { UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); unpatchedParams->kind = deluge::modulation::params::Kind::UNPATCHED_GLOBAL; + // Overwrite default arp Gate to 50 for Kit affect-entire arp + unpatchedParams->params[params::UNPATCHED_ARP_GATE].setCurrentValueBasicForSetup(2147483647); + unpatchedParams->params[params::UNPATCHED_ARP_RATE].setCurrentValueBasicForSetup(0); + unpatchedParams->params[params::UNPATCHED_MOD_FX_RATE].setCurrentValueBasicForSetup(-536870912); unpatchedParams->params[params::UNPATCHED_MOD_FX_FEEDBACK].setCurrentValueBasicForSetup(NEGATIVE_ONE_Q31); unpatchedParams->params[params::UNPATCHED_MOD_FX_DEPTH].setCurrentValueBasicForSetup(0); @@ -842,6 +847,9 @@ void GlobalEffectable::writeParamAttributesToFile(Serializer& writer, ParamManag valuesForOverride); unpatchedParams->writeParamAsAttribute(writer, "tempo", params::UNPATCHED_TEMPO, writeAutomation, false, valuesForOverride); + + unpatchedParams->writeParamAsAttribute(writer, "arpeggiatorRate", params::UNPATCHED_ARP_RATE, writeAutomation, + false, valuesForOverride); } void GlobalEffectable::writeParamTagsToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, @@ -1014,6 +1022,11 @@ bool GlobalEffectable::readParamTagFromFile(Deserializer& reader, char const* ta reader.exitTag("modFXRate"); } + else if (!strcmp(tagName, "arpeggiatorRate")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATE, readAutomationUpToPos); + reader.exitTag("arpeggiatorRate"); + } + else if (ModControllableAudio::readParamTagFromFile(reader, tagName, paramManager, readAutomationUpToPos)) {} else { @@ -1026,7 +1039,7 @@ bool GlobalEffectable::readParamTagFromFile(Deserializer& reader, char const* ta // paramManager is optional Error GlobalEffectable::readTagFromFile(Deserializer& reader, char const* tagName, ParamManagerForTimeline* paramManager, int32_t readAutomationUpToPos, - Song* song) { + ArpeggiatorSettings* arpSettings, Song* song) { // This is here for compatibility only for people (Lou and Ian) who saved songs with firmware in September 2016 // if (paramManager && strcmp(tagName, "delay") && GlobalEffectable::readParamTagFromFile(tagName, paramManager, @@ -1063,7 +1076,7 @@ Error GlobalEffectable::readTagFromFile(Deserializer& reader, char const* tagNam } else { - return ModControllableAudio::readTagFromFile(reader, tagName, NULL, readAutomationUpToPos, song); + return ModControllableAudio::readTagFromFile(reader, tagName, NULL, readAutomationUpToPos, arpSettings, song); } return Error::NONE; diff --git a/src/deluge/model/global_effectable/global_effectable.h b/src/deluge/model/global_effectable/global_effectable.h index 695691417c..8a8ca206d7 100644 --- a/src/deluge/model/global_effectable/global_effectable.h +++ b/src/deluge/model/global_effectable/global_effectable.h @@ -21,6 +21,7 @@ #include "dsp/filter/filter_set.h" #include "gui/l10n/l10n.h" #include "model/mod_controllable/mod_controllable_audio.h" +#include "modulation/arpeggiator.h" #include "util/containers.h" using namespace deluge; class Serializer; @@ -45,7 +46,7 @@ class GlobalEffectable : public ModControllableAudio { void writeAttributesToFile(Serializer& writer, bool writeToFile); void writeTagsToFile(Serializer& writer, ParamManager* paramManager, bool writeToFile); Error readTagFromFile(Deserializer& reader, char const* tagName, ParamManagerForTimeline* paramManager, - int32_t readAutomationUpToPos, Song* song) override; + int32_t readAutomationUpToPos, ArpeggiatorSettings* arpSettings, Song* song) override; static void writeParamAttributesToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, int32_t* valuesForOverride = nullptr); static void writeParamTagsToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, diff --git a/src/deluge/model/instrument/kit.cpp b/src/deluge/model/instrument/kit.cpp index 8826564647..56cac32521 100644 --- a/src/deluge/model/instrument/kit.cpp +++ b/src/deluge/model/instrument/kit.cpp @@ -35,6 +35,7 @@ #include "model/drum/non_audio_drum.h" #include "model/note/note_row.h" #include "model/song/song.h" +#include "modulation/arpeggiator.h" #include "modulation/params/param_set.h" #include "modulation/patch/patch_cable_set.h" #include "playback/mode/playback_mode.h" @@ -48,13 +49,17 @@ namespace params = deluge::modulation::params; -Kit::Kit() : Instrument(OutputType::KIT), drumsWithRenderingActive(sizeof(Drum*)) { +Kit::Kit() : Instrument(OutputType::KIT), drumsWithRenderingActive(sizeof(Drum*)), arpeggiator(), defaultArpSettings() { + defaultArpSettings.numOctaves = 1; firstDrum = nullptr; selectedDrum = nullptr; drumsWithRenderingActive.emptyingShouldFreeMemory = false; } Kit::~Kit() { + // Reset arpeggiator + arpeggiator.reset(); + // Delete all Drums while (firstDrum) { AudioEngine::logAction("~Kit"); @@ -303,8 +308,8 @@ Error Kit::readFromFile(Deserializer& reader, Song* song, Clip* clip, int32_t re reader.exitTag(); } else { - Error result = - GlobalEffectableForClip::readTagFromFile(reader, tagName, ¶mManager, readAutomationUpToPos, song); + Error result = GlobalEffectableForClip::readTagFromFile(reader, tagName, ¶mManager, + readAutomationUpToPos, &defaultArpSettings, song); if (result == Error::NONE) {} else if (result != Error::RESULT_TAG_UNUSED) { return result; @@ -424,8 +429,11 @@ void Kit::addDrum(Drum* newDrum) { newDrum->kit = this; } -void Kit::removeDrum(Drum* drum) { +void Kit::removeDrumFromKitArpeggiator(int32_t drumIndex) { + arpeggiator.removeDrumIndex(getArpSettings(), drumIndex); +} +void Kit::removeDrum(Drum* drum) { removeDrumFromLinkedList(drum); drumRemoved(drum); } @@ -662,6 +670,60 @@ void Kit::renderOutput(ModelStack* modelStack, std::span output, i ModelStackWithTimelineCounter* modelStackWithTimelineCounter = modelStack->addTimelineCounter(activeClip); // Beware - modelStackWithThreeMainThings might have a NULL timelineCounter + ArpeggiatorSettings* arpSettings = getArpSettings(); + + UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); + arpSettings->updateParamsFromUnpatchedParamSet(unpatchedParams); + // Nullify parameters not supported by Kit Arpeggiator (to avoid Midi Follow to modify them) + arpSettings->chordPolyphony = 0; + arpSettings->chordProbability = 0; + arpSettings->spreadOctave = 0; + + if (arpSettings->mode != ArpMode::OFF) { + uint32_t gateThreshold = (uint32_t)unpatchedParams->getValue(params::UNPATCHED_ARP_GATE) + 2147483648; + uint32_t phaseIncrement = arpSettings->getPhaseIncrement( + getFinalParameterValueExp(paramNeutralValues[deluge::modulation::params::GLOBAL_ARP_RATE], + cableToExpParamShortcut(unpatchedParams->getValue(params::UNPATCHED_ARP_RATE)))); + + ArpReturnInstruction kitInstruction; + arpeggiator.render(arpSettings, &kitInstruction, output.size(), gateThreshold, phaseIncrement); + + if (kitInstruction.noteCodeOffPostArp[0] != ARP_NOTE_NONE) { + if (kitInstruction.noteCodeOffPostArp[0] < ((InstrumentClip*)activeClip)->noteRows.getNumElements()) { + NoteRow* thisNoteRow = + ((InstrumentClip*)activeClip)->noteRows.getElement(kitInstruction.noteCodeOffPostArp[0]); + if (thisNoteRow->drum != nullptr) { + // Reset invertReverse for drum arpeggiator (done for every noteOff) + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = false; + // Do row note off + ModelStackWithThreeMainThings* modelStackWithThreeMainThings = + modelStackWithTimelineCounter->addNoteRow(kitInstruction.noteCodeOffPostArp[0], thisNoteRow) + ->addOtherTwoThings(thisNoteRow->drum->toModControllable(), &thisNoteRow->paramManager); + thisNoteRow->drum->noteOff(modelStackWithThreeMainThings); + } + } + } + if (kitInstruction.arpNoteOn != nullptr && kitInstruction.arpNoteOn->noteCodeOnPostArp[0] != ARP_NOTE_NONE) { + if (kitInstruction.arpNoteOn->noteCodeOnPostArp[0] + < ((InstrumentClip*)activeClip)->noteRows.getNumElements()) { + NoteRow* thisNoteRow = + ((InstrumentClip*)activeClip)->noteRows.getElement(kitInstruction.arpNoteOn->noteCodeOnPostArp[0]); + if (thisNoteRow->drum != nullptr) { + // Set the invertReverse flag for the drum arpeggiator + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = kitInstruction.invertReversed; + // Do row note on + ModelStackWithThreeMainThings* modelStackWithThreeMainThings = + modelStackWithTimelineCounter + ->addNoteRow(kitInstruction.arpNoteOn->noteCodeOnPostArp[0], thisNoteRow) + ->addOtherTwoThings(thisNoteRow->drum->toModControllable(), &thisNoteRow->paramManager); + thisNoteRow->drum->noteOn(modelStackWithThreeMainThings, kitInstruction.arpNoteOn->velocity, + kitInstruction.arpNoteOn->mpeValues, 0, kitInstruction.sampleSyncLengthOn, + 0, 0); + } + } + } + } + GlobalEffectableForClip::renderOutput(modelStackWithTimelineCounter, paramManager, output, reverbBuffer, reverbAmountAdjust, sideChainHitPending, shouldLimitDelayFeedback, isClipActive, OutputType::KIT, recorder); @@ -705,6 +767,25 @@ void Kit::renderOutput(ModelStack* modelStack, std::span output, i } } +ArpeggiatorSettings* Kit::getArpSettings(InstrumentClip* clip) { + if (clip) { + return &clip->arpSettings; + } + else if (activeClip) { + return &((InstrumentClip*)activeClip)->arpSettings; + } + else { + return nullptr; + } +} + +void Kit::beenEdited(bool shouldMoveToEmptySlot) { + if (activeClip) { + defaultArpSettings.cloneFrom(&((InstrumentClip*)activeClip)->arpSettings); + } + Instrument::beenEdited(shouldMoveToEmptySlot); +} + // offer the CC to kit gold knobs without also offering to all drums void Kit::offerReceivedCCToModControllable(MIDICable& cable, uint8_t channel, uint8_t ccNumber, uint8_t value, ModelStackWithTimelineCounter* modelStack) { @@ -996,6 +1077,60 @@ int32_t Kit::doTickForwardForArp(ModelStack* modelStack, int32_t currentPos) { ModelStackWithTimelineCounter* modelStackWithTimelineCounter = modelStack->addTimelineCounter(activeClip); int32_t ticksTilNextArpEvent = 2147483647; + + // kit arp + + ParamManager* paramManager = getParamManager(modelStack->song); + + ArpeggiatorSettings* arpSettings = getArpSettings(); + + UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); + arpSettings->updateParamsFromUnpatchedParamSet(unpatchedParams); + // Nullify parameters not supported by Kit Arpeggiator (to avoid Midi Follow to modify them) + arpSettings->chordPolyphony = 0; + arpSettings->chordProbability = 0; + arpSettings->spreadOctave = 0; + + ArpReturnInstruction kitInstruction; + int32_t ticksTilNextKitArpEvent = + arpeggiator.doTickForward(arpSettings, &kitInstruction, currentPos, activeClip->currentlyPlayingReversed); + + if (kitInstruction.noteCodeOffPostArp[0] != ARP_NOTE_NONE) { + if (kitInstruction.noteCodeOffPostArp[0] < ((InstrumentClip*)activeClip)->noteRows.getNumElements()) { + NoteRow* thisNoteRow = + ((InstrumentClip*)activeClip)->noteRows.getElement(kitInstruction.noteCodeOffPostArp[0]); + if (thisNoteRow->drum != nullptr) { + // reset invertReverse for drum arpeggiator (done for every noteOff) + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = false; + // Do row note off + ModelStackWithThreeMainThings* modelStackWithThreeMainThings = + modelStackWithTimelineCounter->addNoteRow(kitInstruction.noteCodeOffPostArp[0], thisNoteRow) + ->addOtherTwoThings(thisNoteRow->drum->toModControllable(), &thisNoteRow->paramManager); + thisNoteRow->drum->noteOff(modelStackWithThreeMainThings); + } + } + } + if (kitInstruction.arpNoteOn != nullptr && kitInstruction.arpNoteOn->noteCodeOnPostArp[0] != ARP_NOTE_NONE) { + if (kitInstruction.arpNoteOn->noteCodeOnPostArp[0] < ((InstrumentClip*)activeClip)->noteRows.getNumElements()) { + NoteRow* thisNoteRow = + ((InstrumentClip*)activeClip)->noteRows.getElement(kitInstruction.arpNoteOn->noteCodeOnPostArp[0]); + if (thisNoteRow->drum != nullptr) { + // Set the invertReverse flag for the drum arpeggiator + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = kitInstruction.invertReversed; + // Do row note on + ModelStackWithThreeMainThings* modelStackWithThreeMainThings = + modelStackWithTimelineCounter + ->addNoteRow(kitInstruction.arpNoteOn->noteCodeOnPostArp[0], thisNoteRow) + ->addOtherTwoThings(thisNoteRow->drum->toModControllable(), &thisNoteRow->paramManager); + thisNoteRow->drum->noteOn(modelStackWithThreeMainThings, kitInstruction.arpNoteOn->velocity, + kitInstruction.arpNoteOn->mpeValues, 0, kitInstruction.sampleSyncLengthOn, 0, + 0); + } + } + } + + ticksTilNextArpEvent = std::min(ticksTilNextArpEvent, ticksTilNextKitArpEvent); + for (int32_t i = 0; i < ((InstrumentClip*)activeClip)->noteRows.getNumElements(); i++) { NoteRow* thisNoteRow = ((InstrumentClip*)activeClip)->noteRows.getElement(i); if (thisNoteRow->drum) { @@ -1027,18 +1162,16 @@ int32_t Kit::doTickForwardForArp(ModelStack* modelStack, int32_t currentPos) { soundDrum->noteOffPostArpeggiator(modelStackWithSoundFlags, instruction.noteCodeOffPostArp[n]); } for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { - if (instruction.arpNoteOn != nullptr - && instruction.arpNoteOn->noteCodeOnPostArp[n] != ARP_NOTE_NONE) { - soundDrum->invertReversed = instruction.invertReversed; - soundDrum->noteOnPostArpeggiator( - modelStackWithSoundFlags, - instruction.arpNoteOn->inputCharacteristics[util::to_underlying(MIDICharacteristic::NOTE)], - instruction.arpNoteOn->noteCodeOnPostArp[n], instruction.arpNoteOn->velocity, - instruction.arpNoteOn->mpeValues, instruction.sampleSyncLengthOn, 0, 0); - } - else { + if (instruction.arpNoteOn == nullptr + || instruction.arpNoteOn->noteCodeOnPostArp[n] == ARP_NOTE_NONE) { break; } + soundDrum->invertReversed = instruction.invertReversed; + soundDrum->noteOnPostArpeggiator( + modelStackWithSoundFlags, + instruction.arpNoteOn->inputCharacteristics[util::to_underlying(MIDICharacteristic::NOTE)], + instruction.arpNoteOn->noteCodeOnPostArp[n], instruction.arpNoteOn->velocity, + instruction.arpNoteOn->mpeValues, instruction.sampleSyncLengthOn, 0, 0); } } else if (thisNoteRow->drum->type == DrumType::MIDI || thisNoteRow->drum->type == DrumType::GATE) { @@ -1051,14 +1184,11 @@ int32_t Kit::doTickForwardForArp(ModelStack* modelStack, int32_t currentPos) { nonAudioDrum->noteOffPostArp(instruction.noteCodeOffPostArp[n]); } for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { - if (instruction.arpNoteOn != nullptr - && instruction.arpNoteOn->noteCodeOnPostArp[n] != ARP_NOTE_NONE) { - nonAudioDrum->noteOnPostArp(instruction.arpNoteOn->noteCodeOnPostArp[n], instruction.arpNoteOn, - n); - } - else { + if (instruction.arpNoteOn == nullptr + || instruction.arpNoteOn->noteCodeOnPostArp[n] == ARP_NOTE_NONE) { break; } + nonAudioDrum->noteOnPostArp(instruction.arpNoteOn->noteCodeOnPostArp[n], instruction.arpNoteOn, n); } } @@ -1069,6 +1199,85 @@ int32_t Kit::doTickForwardForArp(ModelStack* modelStack, int32_t currentPos) { return ticksTilNextArpEvent; } +void Kit::noteOnPreKitArp(ModelStackWithThreeMainThings* modelStack, Drum* drum, uint8_t velocity, + int16_t const* mpeValues, int32_t fromMIDIChannel, uint32_t sampleSyncLength, + int32_t ticksLate, uint32_t samplesLate) { + ArpeggiatorSettings* arpSettings = getArpSettings(); + ArpReturnInstruction kitInstruction; + // Run everything by the Kit Arp... + int32_t drumIndex = -1; + NoteRow* thisNoteRow = ((InstrumentClip*)activeClip)->getNoteRowForDrum(drum, &drumIndex); + if (drumIndex != -1 && thisNoteRow->drum != nullptr) { + // Check if kit arp is bypassed + if (!thisNoteRow->drum->arpSettings.includeInKitArp) { + thisNoteRow->drum->noteOn(modelStack, velocity, mpeValues, fromMIDIChannel, sampleSyncLength, ticksLate, + samplesLate); + return; + } + else if (thisNoteRow->drum->type == DrumType::SOUND) { + ModelStackWithSoundFlags* modelStackWithSoundFlags = modelStack->addSoundFlags(); + if (!((SoundDrum*)thisNoteRow->drum)->allowNoteTails(modelStackWithSoundFlags, true)) { + // If sound doesn't allow note tails, it cannot be included in the kit arp, as it doesn't produce note + // offs and will get us stuck notes + thisNoteRow->drum->noteOn(modelStack, velocity, mpeValues, fromMIDIChannel, sampleSyncLength, ticksLate, + samplesLate); + return; + } + } + + // If kit arp not bypassed, execute instruction + arpeggiator.noteOn(arpSettings, drumIndex, velocity, &kitInstruction, fromMIDIChannel, mpeValues); + if (kitInstruction.arpNoteOn != nullptr && kitInstruction.arpNoteOn->noteCodeOnPostArp[0] != ARP_NOTE_NONE) { + // Set the invertReverse flag for the drum arpeggiator + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = kitInstruction.invertReversed; + // Do row note on + thisNoteRow->drum->noteOn(modelStack, kitInstruction.arpNoteOn->velocity, + kitInstruction.arpNoteOn->mpeValues, 0, sampleSyncLength, ticksLate, samplesLate); + } + } +} + +void Kit::noteOffPreKitArp(ModelStackWithThreeMainThings* modelStack, Drum* drum, int32_t velocity) { + ArpeggiatorSettings* arpSettings = getArpSettings(); + ArpReturnInstruction kitInstruction; + // Run everything by the Kit Arp... + int32_t drumIndex = -1; + NoteRow* thisNoteRow = ((InstrumentClip*)activeClip)->getNoteRowForDrum(drum, &drumIndex); + if (drumIndex != -1 && thisNoteRow->drum != nullptr) { + // Check if kit arp is bypassed + if (!thisNoteRow->drum->arpSettings.includeInKitArp) { + // Forced to be excluded from kit arp + // reset invertReverse for drum arpeggiator (done for every noteOff) + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = false; + // Do row note off + thisNoteRow->drum->noteOff(modelStack, velocity); + return; + } + else if (thisNoteRow->drum->type == DrumType::SOUND) { + ModelStackWithSoundFlags* modelStackWithSoundFlags = modelStack->addSoundFlags(); + if (!((SoundDrum*)thisNoteRow->drum)->allowNoteTails(modelStackWithSoundFlags, true)) { + // If sound doesn't allow note tails, it cannot be included in the kit arp, as it doesn't produce note + // offs and will get us stuck notes + // Just send the note directly to the drum + // reset invertReverse for drum arpeggiator (done for every noteOff) + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = false; + // Do row note off + thisNoteRow->drum->noteOff(modelStack, velocity); + return; + } + } + + // If kit arp not bypassed, execute instruction + arpeggiator.noteOff(arpSettings, drumIndex, &kitInstruction); + if (kitInstruction.noteCodeOffPostArp[0] != ARP_NOTE_NONE) { + // reset invertReverse for drum arpeggiator (done for every noteOff) + thisNoteRow->drum->arpeggiator.invertReversedFromKitArp = false; + // Do row note off + thisNoteRow->drum->noteOff(modelStack, velocity); + } + } +} + GateDrum* Kit::getGateDrumForChannel(int32_t gateChannel) { for (Drum* thisDrum = firstDrum; thisDrum; thisDrum = thisDrum->next) { if (thisDrum->type == DrumType::GATE) { @@ -1647,7 +1856,7 @@ void Kit::beginAuditioningforDrum(ModelStackWithNoteRow* modelStack, Drum* drum, ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStack->addOtherTwoThings(drum->toModControllable(), paramManagerForDrum); - drum->noteOn(modelStackWithThreeMainThings, velocity, this, mpeValues, fromMIDIChannel); + noteOnPreKitArp(modelStackWithThreeMainThings, drum, velocity, mpeValues, fromMIDIChannel); if (!activeClip || ((InstrumentClip*)activeClip)->allowNoteTails(modelStack)) { drum->auditioned = true; @@ -1692,7 +1901,7 @@ void Kit::endAuditioningForDrum(ModelStackWithNoteRow* modelStack, Drum* drum, i ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStack->addOtherTwoThings(drum->toModControllable(), paramManagerForDrum); - drum->noteOff(modelStackWithThreeMainThings); + noteOffPreKitArp(modelStackWithThreeMainThings, drum); if (activeClip) { activeClip->expectEvent(); // Because the absence of auditioning here means sequenced notes may play diff --git a/src/deluge/model/instrument/kit.h b/src/deluge/model/instrument/kit.h index fc23bfff59..6e3b032f41 100644 --- a/src/deluge/model/instrument/kit.h +++ b/src/deluge/model/instrument/kit.h @@ -21,6 +21,7 @@ #include "dsp/stereo_sample.h" #include "model/global_effectable/global_effectable_for_clip.h" #include "model/instrument/instrument.h" +#include "modulation/arpeggiator.h" class InstrumentClip; class Drum; class Sound; @@ -33,6 +34,10 @@ enum class MIDIMatchType; class Kit final : public Instrument, public GlobalEffectableForClip { public: Kit(); + + ArpeggiatorForKit arpeggiator; + ArpeggiatorSettings defaultArpSettings; + Drum* getNextDrum(Drum* fromSoundSource); Drum* getPrevDrum(Drum* fromSoundSource); bool writeDataToFile(Serializer& writer, Clip* clipForSavingOutputOnly, Song* song) override; @@ -98,8 +103,11 @@ class Kit final : public Instrument, public GlobalEffectableForClip { void processParamFromInputMIDIChannel(int32_t cc, int32_t newValue, ModelStackWithTimelineCounter* modelStack) override {} + ArpeggiatorSettings* getArpSettings(InstrumentClip* clip = nullptr); + void beenEdited(bool shouldMoveToEmptySlot = true) override; void choke(); void resyncLFOs() override; + void removeDrumFromKitArpeggiator(int32_t drumIndex); void removeDrum(Drum* drum); ModControllable* toModControllable() override; SoundDrum* getDrumFromName(char const* name, bool onlyIfNoNoteRow = false); @@ -156,6 +164,11 @@ class Kit final : public Instrument, public GlobalEffectableForClip { Drum* getDrumFromNoteCode(InstrumentClip* clip, int32_t noteCode); + void noteOnPreKitArp(ModelStackWithThreeMainThings* modelStack, Drum* drum, uint8_t velocity, + int16_t const* mpeValues, int32_t fromMIDIChannel = MIDI_CHANNEL_NONE, + uint32_t sampleSyncLength = 0, int32_t ticksLate = 0, uint32_t samplesLate = 0); + void noteOffPreKitArp(ModelStackWithThreeMainThings* modelStack, Drum* drum, int32_t velocity = kDefaultLiftValue); + protected: bool isKit() override { return true; } diff --git a/src/deluge/model/mod_controllable/mod_controllable_audio.cpp b/src/deluge/model/mod_controllable/mod_controllable_audio.cpp index 249151f2a8..f368392b8c 100644 --- a/src/deluge/model/mod_controllable/mod_controllable_audio.cpp +++ b/src/deluge/model/mod_controllable/mod_controllable_audio.cpp @@ -108,9 +108,24 @@ void ModControllableAudio::initParams(ParamManager* paramManager) { unpatchedParams->params[params::UNPATCHED_BASS_FREQ].setCurrentValueBasicForSetup(0); unpatchedParams->params[params::UNPATCHED_TREBLE_FREQ].setCurrentValueBasicForSetup(0); + unpatchedParams->params[params::UNPATCHED_ARP_GATE].setCurrentValueBasicForSetup(0); + unpatchedParams->params[params::UNPATCHED_NOTE_PROBABILITY].setCurrentValueBasicForSetup(2147483647); + unpatchedParams->params[params::UNPATCHED_ARP_BASS_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_REVERSE_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_CHORD_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_RATCHET_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_RATCHET_AMOUNT].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_SEQUENCE_LENGTH].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_CHORD_POLYPHONY].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_RHYTHM].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_SPREAD_VELOCITY].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_SPREAD_GATE].setCurrentValueBasicForSetup(-2147483648); + unpatchedParams->params[params::UNPATCHED_ARP_SPREAD_OCTAVE].setCurrentValueBasicForSetup(-2147483648); + Stutterer::initParams(paramManager); unpatchedParams->params[params::UNPATCHED_MOD_FX_OFFSET].setCurrentValueBasicForSetup(0); + unpatchedParams->params[params::UNPATCHED_MOD_FX_FEEDBACK].setCurrentValueBasicForSetup(0); unpatchedParams->params[params::UNPATCHED_SAMPLE_RATE_REDUCTION].setCurrentValueBasicForSetup(-2147483648); @@ -485,6 +500,30 @@ void ModControllableAudio::writeParamAttributesToFile(Serializer& writer, ParamM // Community Firmware parameters (always write them after the official ones, just before closing the parent tag) unpatchedParams->writeParamAsAttribute(writer, "compressorThreshold", params::UNPATCHED_COMPRESSOR_THRESHOLD, writeAutomation, false, valuesForOverride); + + unpatchedParams->writeParamAsAttribute(writer, "arpeggiatorGate", params::UNPATCHED_ARP_GATE, writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "noteProbability", params::UNPATCHED_NOTE_PROBABILITY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "bassProbability", params::UNPATCHED_ARP_BASS_PROBABILITY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "reverseProbability", params::UNPATCHED_REVERSE_PROBABILITY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "chordProbability", params::UNPATCHED_ARP_CHORD_PROBABILITY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "ratchetProbability", params::UNPATCHED_ARP_RATCHET_PROBABILITY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "ratchetAmount", params::UNPATCHED_ARP_RATCHET_AMOUNT, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "sequenceLength", params::UNPATCHED_ARP_SEQUENCE_LENGTH, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "chordPolyphony", params::UNPATCHED_ARP_CHORD_POLYPHONY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "rhythm", params::UNPATCHED_ARP_RHYTHM, writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "spreadVelocity", params::UNPATCHED_SPREAD_VELOCITY, + writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "spreadGate", params::UNPATCHED_ARP_SPREAD_GATE, writeAutomation); + unpatchedParams->writeParamAsAttribute(writer, "spreadOctave", params::UNPATCHED_ARP_SPREAD_OCTAVE, + writeAutomation); } void ModControllableAudio::writeParamTagsToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, @@ -570,6 +609,84 @@ bool ModControllableAudio::readParamTagFromFile(Deserializer& reader, char const reader.exitTag("compressorThreshold"); } + // Arpeggiator stuff + + else if (!strcmp(tagName, "arpeggiatorGate")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_GATE, readAutomationUpToPos); + reader.exitTag("arpeggiatorGate"); + } + + else if (!strcmp(tagName, "ratchetAmount")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATCHET_AMOUNT, + readAutomationUpToPos); + reader.exitTag("ratchetAmount"); + } + + else if (!strcmp(tagName, "ratchetProbability")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATCHET_PROBABILITY, + readAutomationUpToPos); + reader.exitTag("ratchetProbability"); + } + + else if (!strcmp(tagName, "chordPolyphony")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_CHORD_POLYPHONY, + readAutomationUpToPos); + reader.exitTag("chordPolyphony"); + } + + else if (!strcmp(tagName, "chordProbability")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_CHORD_PROBABILITY, + readAutomationUpToPos); + reader.exitTag("chordProbability"); + } + + else if (!strcmp(tagName, "reverseProbability")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_REVERSE_PROBABILITY, + readAutomationUpToPos); + reader.exitTag("reverseProbability"); + } + + else if (!strcmp(tagName, "bassProbability")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_BASS_PROBABILITY, + readAutomationUpToPos); + reader.exitTag("bassProbability"); + } + + else if (!strcmp(tagName, "noteProbability")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_NOTE_PROBABILITY, + readAutomationUpToPos); + reader.exitTag("noteProbability"); + } + + else if (!strcmp(tagName, "sequenceLength")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SEQUENCE_LENGTH, + readAutomationUpToPos); + reader.exitTag("sequenceLength"); + } + + else if (!strcmp(tagName, "rhythm")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RHYTHM, readAutomationUpToPos); + reader.exitTag("rhythm"); + } + + else if (!strcmp(tagName, "spreadVelocity")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_SPREAD_VELOCITY, + readAutomationUpToPos); + reader.exitTag("spreadVelocity"); + } + + else if (!strcmp(tagName, "spreadGate")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SPREAD_GATE, + readAutomationUpToPos); + reader.exitTag("spreadGate"); + } + + else if (!strcmp(tagName, "spreadOctave")) { + unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SPREAD_OCTAVE, + readAutomationUpToPos); + reader.exitTag("spreadOctave"); + } + else { return false; } @@ -580,7 +697,7 @@ bool ModControllableAudio::readParamTagFromFile(Deserializer& reader, char const // paramManager is optional Error ModControllableAudio::readTagFromFile(Deserializer& reader, char const* tagName, ParamManagerForTimeline* paramManager, int32_t readAutomationUpToPos, - Song* song) { + ArpeggiatorSettings* arpSettings, Song* song) { int32_t p; @@ -602,6 +719,25 @@ Error ModControllableAudio::readTagFromFile(Deserializer& reader, char const* ta reader.exitTag("clippingAmount"); } + // Arpeggiator + + else if (!strcmp(tagName, "arpeggiator") && arpSettings != nullptr) { + // Set default values in case they are not configured + arpSettings->syncType = SYNC_TYPE_EVEN; + arpSettings->syncLevel = SYNC_LEVEL_NONE; + reader.match('{'); + while (*(tagName = reader.readNextTagOrAttributeName())) { + bool readAndExited = arpSettings->readCommonTagsFromFile(reader, tagName, song); + if (!readAndExited) { + reader.exitTag(tagName); + } + } + + reader.exitTag("arpeggiator", true); + } + + // Stutter + else if (!strcmp(tagName, "stutter")) { // Set default values in case they are not configured stutterConfig.useSongStutter = true; diff --git a/src/deluge/model/mod_controllable/mod_controllable_audio.h b/src/deluge/model/mod_controllable/mod_controllable_audio.h index 89311f2602..d02e2d03fb 100644 --- a/src/deluge/model/mod_controllable/mod_controllable_audio.h +++ b/src/deluge/model/mod_controllable/mod_controllable_audio.h @@ -56,7 +56,7 @@ class ModControllableAudio : public ModControllable { void writeAttributesToFile(Serializer& writer); void writeTagsToFile(Serializer& writer); virtual Error readTagFromFile(Deserializer& reader, char const* tagName, ParamManagerForTimeline* paramManager, - int32_t readAutomationUpToPos, Song* song); + int32_t readAutomationUpToPos, ArpeggiatorSettings* arpSettings, Song* song); void processSRRAndBitcrushing(std::span buffer, int32_t* postFXVolume, ParamManager* paramManager); static void writeParamAttributesToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, int32_t* valuesForOverride = nullptr); diff --git a/src/deluge/model/note/note_row.cpp b/src/deluge/model/note/note_row.cpp index 9af97632fc..d0d5983537 100644 --- a/src/deluge/model/note/note_row.cpp +++ b/src/deluge/model/note/note_row.cpp @@ -2513,7 +2513,6 @@ void NoteRow::attemptLateStartOfNextNoteToPlay(ModelStackWithNoteRow* modelStack // Note may be NULL if it's a note-off, in which case you don't get lift-velocity void NoteRow::playNote(bool on, ModelStackWithNoteRow* modelStack, Note* thisNote, int32_t ticksLate, uint32_t samplesLate, bool noteMightBeConstant, PendingNoteOnList* pendingNoteOnList) { - InstrumentClip* clip = (InstrumentClip*)modelStack->getTimelineCounter(); Output* output = clip->output; @@ -2618,8 +2617,8 @@ void NoteRow::playNote(bool on, ModelStackWithNoteRow* modelStack, Note* thisNot ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStack->addOtherTwoThings(drum->toModControllable(), ¶mManager); - drum->noteOn(modelStackWithThreeMainThings, thisNote->velocity, (Kit*)output, mpeValues, - MIDI_CHANNEL_NONE, thisNote->length, ticksLate, samplesLate); + drum->kit->noteOnPreKitArp(modelStackWithThreeMainThings, drum, thisNote->velocity, mpeValues, + MIDI_CHANNEL_NONE, thisNote->length, ticksLate, samplesLate); } } } @@ -2629,7 +2628,7 @@ void NoteRow::playNote(bool on, ModelStackWithNoteRow* modelStack, Note* thisNot lift = thisNote->getLift(); } - drum->noteOff(modelStackWithThreeMainThings, lift); + drum->kit->noteOffPreKitArp(modelStackWithThreeMainThings, drum, lift); } } diff --git a/src/deluge/model/song/song.cpp b/src/deluge/model/song/song.cpp index aa40bd456b..bb02994839 100644 --- a/src/deluge/model/song/song.cpp +++ b/src/deluge/model/song/song.cpp @@ -2007,7 +2007,8 @@ Error Song::readFromFile(Deserializer& reader) { } else { - Error result = globalEffectable.readTagFromFile(reader, tagName, ¶mManager, 2147483647, this); + Error result = + globalEffectable.readTagFromFile(reader, tagName, ¶mManager, 2147483647, nullptr, this); if (result == Error::NONE) {} else if (result != Error::RESULT_TAG_UNUSED) { return result; @@ -4206,6 +4207,9 @@ void Song::sortOutWhichClipsAreActiveWithoutSendingPGMs(ModelStack* modelStack, ((SoundInstrument*)output) ->defaultArpSettings.cloneFrom(&((InstrumentClip*)output->getActiveClip())->arpSettings); } + else if (output->type == OutputType::KIT) { + ((Kit*)output)->defaultArpSettings.cloneFrom(&((InstrumentClip*)output->getActiveClip())->arpSettings); + } } // Ok, back to the main task - if there's no activeClip... diff --git a/src/deluge/modulation/arpeggiator.cpp b/src/deluge/modulation/arpeggiator.cpp index 99238049df..23eac13922 100644 --- a/src/deluge/modulation/arpeggiator.cpp +++ b/src/deluge/modulation/arpeggiator.cpp @@ -61,7 +61,7 @@ ArpeggiatorSettings::ArpeggiatorSettings() { generateNewNotePattern(); } -ArpeggiatorForDrum::ArpeggiatorForDrum() { +ArpeggiatorForDrum::ArpeggiatorForDrum() : invertReversedFromKitArp(false) { arpNote.velocity = 0; } @@ -73,6 +73,44 @@ Arpeggiator::Arpeggiator() notesByPattern.emptyingShouldFreeMemory = false; } +ArpeggiatorForKit::ArpeggiatorForKit() : Arpeggiator::Arpeggiator() { +} + +void ArpeggiatorForKit::removeDrumIndex(ArpeggiatorSettings* arpSettings, int32_t drumIndex) { + int32_t n = notes.search(drumIndex, GREATER_OR_EQUAL); + int32_t numNotes = notes.getNumElements(); + if (n < numNotes) { + // Delete drumIndex from notes array + notes.deleteAtIndex(n); + numNotes = notesAsPlayed.getNumElements(); + int32_t nAsPlayed = 0; + for (int32_t i = 0; i < numNotes; i++) { + ArpJustNoteCode* arpAsPlayedNote = (ArpJustNoteCode*)notesAsPlayed.getElementAddress(i); + if (arpAsPlayedNote->noteCode == drumIndex) { + nAsPlayed = i; + notesAsPlayed.deleteAtIndex(i); + break; + } + } + // Now shift all the arpeggiator drumIndexes by 1 + numNotes = notes.getNumElements(); + for (int32_t i = n; i < numNotes; i++) { + ArpNote* arpNote = (ArpNote*)notes.getElementAddress(i); + arpNote->inputCharacteristics[util::to_underlying(MIDICharacteristic::NOTE)] = + arpNote->inputCharacteristics[util::to_underlying(MIDICharacteristic::NOTE)] - 1; + } + numNotes = notesAsPlayed.getNumElements(); + for (int32_t i = 0; i < numNotes; i++) { + ArpJustNoteCode* arpAsPlayedNote = (ArpJustNoteCode*)notesAsPlayed.getElementAddress(i); + if (arpAsPlayedNote->noteCode > drumIndex) { + arpAsPlayedNote->noteCode = arpAsPlayedNote->noteCode - 1; + } + } + // Rearrange pattern + rearrangePatterntArpNotes(arpSettings); + } +} + void Arpeggiator::reset() { notes.empty(); notesAsPlayed.empty(); @@ -161,7 +199,8 @@ void ArpeggiatorForDrum::noteOn(ArpeggiatorSettings* settings, int32_t noteCode, noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE; arpNote.noteCodeOnPostArp[n] = ARP_NOTE_NONE; } - instruction->invertReversed = isPlayReverseForCurrentStep; + instruction->invertReversed = + invertReversedFromKitArp ? !isPlayReverseForCurrentStep : isPlayReverseForCurrentStep; instruction->arpNoteOn = &arpNote; } } @@ -349,18 +388,15 @@ void Arpeggiator::noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp, } } - // Or if yes arpeggiation, we'll only stop right now if that was the last note to switch off. Otherwise, - // it'll turn off soon with the arpeggiation. + // Or if yes arpeggiation else { - if (notes.getNumElements() == 1) { - if (whichNoteCurrentlyOnPostArp == notesKey && gateCurrentlyActive) { - for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { - // Set all chord notes - instruction->noteCodeOffPostArp[n] = noteCodeCurrentlyOnPostArp[n]; - instruction->outputMIDIChannelOff[n] = outputMIDIChannelForNoteCurrentlyOnPostArp[n]; - noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE; - outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE; - } + if (whichNoteCurrentlyOnPostArp == notesKey && gateCurrentlyActive) { + for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { + // Set all chord notes + instruction->noteCodeOffPostArp[n] = noteCodeCurrentlyOnPostArp[n]; + instruction->outputMIDIChannelOff[n] = outputMIDIChannelForNoteCurrentlyOnPostArp[n]; + noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE; + outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE; } } } @@ -713,7 +749,7 @@ void ArpeggiatorForDrum::switchNoteOn(ArpeggiatorSettings* settings, ArpReturnIn noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE; arpNote.noteCodeOnPostArp[n] = ARP_NOTE_NONE; } - instruction->invertReversed = shouldPlayReverseNote; + instruction->invertReversed = invertReversedFromKitArp ? !shouldPlayReverseNote : shouldPlayReverseNote; instruction->arpNoteOn = &arpNote; } } @@ -1395,6 +1431,7 @@ void ArpeggiatorSettings::cloneFrom(ArpeggiatorSettings const* other) { chordTypeIndex = other->chordTypeIndex; numOctaves = other->numOctaves; numStepRepeats = other->numStepRepeats; + includeInKitArp = other->includeInKitArp; randomizerLock = other->randomizerLock; syncType = other->syncType; syncLevel = other->syncLevel; @@ -1424,6 +1461,9 @@ bool ArpeggiatorSettings::readCommonTagsFromFile(Deserializer& reader, char cons else if (!strcmp(tagName, "stepRepeat")) { numStepRepeats = reader.readTagOrAttributeValueInt(); } + else if (!strcmp(tagName, "kitArp")) { + includeInKitArp = reader.readTagOrAttributeValueInt(); + } else if (!strcmp(tagName, "randomizerLock")) { randomizerLock = reader.readTagOrAttributeValueInt(); } @@ -1615,6 +1655,7 @@ void ArpeggiatorSettings::writeCommonParamsToFile(Serializer& writer, Song* song writer.writeAttribute("mpeVelocity", (char*)arpMpeModSourceToString(mpeVelocity)); writer.writeAttribute("stepRepeat", numStepRepeats); writer.writeAttribute("randomizerLock", randomizerLock); + writer.writeAttribute("kitArp", includeInKitArp); // Note probability writer.writeAttribute("lastLockedNoteProb", lastLockedNoteProbabilityParameterValue); diff --git a/src/deluge/modulation/arpeggiator.h b/src/deluge/modulation/arpeggiator.h index b05d5a646f..abaf481725 100644 --- a/src/deluge/modulation/arpeggiator.h +++ b/src/deluge/modulation/arpeggiator.h @@ -40,6 +40,7 @@ constexpr uint32_t ARP_NOTE_NONE = 32767; enum class ArpType : uint8_t { SYNTH, DRUM, + KIT, }; class ArpeggiatorSettings { @@ -70,6 +71,8 @@ class ArpeggiatorSettings { ArpPreset preset{ArpPreset::OFF}; ArpMode mode{ArpMode::OFF}; + bool includeInKitArp{true}; + // Sequence settings ArpOctaveMode octaveMode{ArpOctaveMode::UP}; ArpNoteMode noteMode{ArpNoteMode::UP}; @@ -286,7 +289,7 @@ class ArpeggiatorBase { int8_t getRandomWeighted2BitsAmount(uint32_t value); }; -class ArpeggiatorForDrum final : public ArpeggiatorBase { +class ArpeggiatorForDrum : public ArpeggiatorBase { public: ArpeggiatorForDrum(); void noteOn(ArpeggiatorSettings* settings, int32_t noteCode, int32_t velocity, ArpReturnInstruction* instruction, @@ -297,12 +300,14 @@ class ArpeggiatorForDrum final : public ArpeggiatorBase { ArpNote arpNote; // For the one note. noteCode will always be 60. velocity will be 0 if off. int16_t noteForDrum; + bool invertReversedFromKitArp; + protected: void switchNoteOn(ArpeggiatorSettings* settings, ArpReturnInstruction* instruction, bool isRatchet) override; bool hasAnyInputNotesActive() override; }; -class Arpeggiator final : public ArpeggiatorBase { +class Arpeggiator : public ArpeggiatorBase { public: Arpeggiator(); @@ -325,3 +330,10 @@ class Arpeggiator final : public ArpeggiatorBase { void rearrangePatterntArpNotes(ArpeggiatorSettings* settings); void switchNoteOn(ArpeggiatorSettings* settings, ArpReturnInstruction* instruction, bool isRatchet) override; }; + +class ArpeggiatorForKit : public Arpeggiator { +public: + ArpeggiatorForKit(); + ArpType getArpType() override { return ArpType::KIT; } + void removeDrumIndex(ArpeggiatorSettings* arpSettings, int32_t drumIndex); +}; diff --git a/src/deluge/modulation/params/param.cpp b/src/deluge/modulation/params/param.cpp index 88bbf43178..eec342280e 100644 --- a/src/deluge/modulation/params/param.cpp +++ b/src/deluge/modulation/params/param.cpp @@ -268,6 +268,19 @@ char const* getParamDisplayName(Kind kind, int32_t p) { [UNPATCHED_MOD_FX_FEEDBACK] = STRING_FOR_MODFX_FEEDBACK, [UNPATCHED_SIDECHAIN_SHAPE] = STRING_FOR_SIDECHAIN_SHAPE, [UNPATCHED_COMPRESSOR_THRESHOLD] = STRING_FOR_THRESHOLD, + [UNPATCHED_ARP_GATE] = STRING_FOR_ARP_GATE_MENU_TITLE, + [UNPATCHED_ARP_RHYTHM] = STRING_FOR_ARP_RHYTHM_MENU_TITLE, + [UNPATCHED_ARP_SEQUENCE_LENGTH] = STRING_FOR_ARP_SEQUENCE_LENGTH_MENU_TITLE, + [UNPATCHED_ARP_CHORD_POLYPHONY] = STRING_FOR_ARP_CHORD_POLYPHONY_MENU_TITLE, + [UNPATCHED_ARP_RATCHET_AMOUNT] = STRING_FOR_ARP_RATCHETS_MENU_TITLE, + [UNPATCHED_NOTE_PROBABILITY] = STRING_FOR_NOTE_PROBABILITY_MENU_TITLE, + [UNPATCHED_REVERSE_PROBABILITY] = STRING_FOR_REVERSE_PROBABILITY_MENU_TITLE, + [UNPATCHED_ARP_BASS_PROBABILITY] = STRING_FOR_ARP_BASS_PROBABILITY_MENU_TITLE, + [UNPATCHED_ARP_CHORD_PROBABILITY] = STRING_FOR_ARP_CHORD_PROBABILITY_MENU_TITLE, + [UNPATCHED_ARP_RATCHET_PROBABILITY] = STRING_FOR_ARP_RATCHET_PROBABILITY_MENU_TITLE, + [UNPATCHED_ARP_SPREAD_GATE] = STRING_FOR_ARP_SPREAD_GATE_MENU_TITLE, + [UNPATCHED_ARP_SPREAD_OCTAVE] = STRING_FOR_ARP_SPREAD_OCTAVE_MENU_TITLE, + [UNPATCHED_SPREAD_VELOCITY] = STRING_FOR_SPREAD_VELOCITY_MENU_TITLE, }; return l10n::get(NAMES[p]); } @@ -286,19 +299,6 @@ char const* getParamDisplayName(Kind kind, int32_t p) { if (kind == Kind::UNPATCHED_SOUND && p < util::to_underlying(UNPATCHED_SOUND_MAX_NUM)) { using enum UnpatchedSound; static l10n::String const NAMES[UNPATCHED_SOUND_MAX_NUM - unc] = { - [UNPATCHED_ARP_GATE - unc] = STRING_FOR_ARP_GATE_MENU_TITLE, - [UNPATCHED_ARP_RHYTHM - unc] = STRING_FOR_ARP_RHYTHM_MENU_TITLE, - [UNPATCHED_ARP_SEQUENCE_LENGTH - unc] = STRING_FOR_ARP_SEQUENCE_LENGTH_MENU_TITLE, - [UNPATCHED_ARP_CHORD_POLYPHONY - unc] = STRING_FOR_ARP_CHORD_POLYPHONY_MENU_TITLE, - [UNPATCHED_ARP_RATCHET_AMOUNT - unc] = STRING_FOR_ARP_RATCHETS_MENU_TITLE, - [UNPATCHED_NOTE_PROBABILITY - unc] = STRING_FOR_NOTE_PROBABILITY_MENU_TITLE, - [UNPATCHED_REVERSE_PROBABILITY - unc] = STRING_FOR_REVERSE_PROBABILITY_MENU_TITLE, - [UNPATCHED_ARP_BASS_PROBABILITY - unc] = STRING_FOR_ARP_BASS_PROBABILITY_MENU_TITLE, - [UNPATCHED_ARP_CHORD_PROBABILITY - unc] = STRING_FOR_ARP_CHORD_PROBABILITY_MENU_TITLE, - [UNPATCHED_ARP_RATCHET_PROBABILITY - unc] = STRING_FOR_ARP_RATCHET_PROBABILITY_MENU_TITLE, - [UNPATCHED_ARP_SPREAD_GATE - unc] = STRING_FOR_ARP_SPREAD_GATE_MENU_TITLE, - [UNPATCHED_ARP_SPREAD_OCTAVE - unc] = STRING_FOR_ARP_SPREAD_OCTAVE_MENU_TITLE, - [UNPATCHED_SPREAD_VELOCITY - unc] = STRING_FOR_SPREAD_VELOCITY_MENU_TITLE, [UNPATCHED_PORTAMENTO - unc] = STRING_FOR_PORTAMENTO, }; return l10n::get(NAMES[p - unc]); @@ -312,6 +312,7 @@ char const* getParamDisplayName(Kind kind, int32_t p) { [UNPATCHED_MOD_FX_DEPTH - unc] = STRING_FOR_MOD_FX_DEPTH, [UNPATCHED_DELAY_RATE - unc] = STRING_FOR_DELAY_RATE, [UNPATCHED_DELAY_AMOUNT - unc] = STRING_FOR_DELAY_AMOUNT, + [UNPATCHED_ARP_RATE - unc] = STRING_FOR_ARP_RATE_MENU_TITLE, [UNPATCHED_PAN - unc] = STRING_FOR_PAN, [UNPATCHED_LPF_FREQ - unc] = STRING_FOR_LPF_FREQUENCY, [UNPATCHED_LPF_RES - unc] = STRING_FOR_LPF_RESONANCE, @@ -370,45 +371,6 @@ constexpr char const* paramNameForFileConst(Kind const kind, ParamType const par if (kind == UNPATCHED_SOUND && param >= UNPATCHED_START + UNPATCHED_NUM_SHARED) { // Unpatched params just for Sounds switch (static_cast(param - UNPATCHED_START)) { - case UNPATCHED_ARP_GATE: - return "arpGate"; - - case UNPATCHED_NOTE_PROBABILITY: - return "noteProbability"; - - case UNPATCHED_ARP_BASS_PROBABILITY: - return "bassProbability"; - - case UNPATCHED_REVERSE_PROBABILITY: - return "reverseProbability"; - - case UNPATCHED_ARP_CHORD_POLYPHONY: - return "chordPolyphony"; - - case UNPATCHED_ARP_CHORD_PROBABILITY: - return "chordProbability"; - - case UNPATCHED_ARP_RATCHET_PROBABILITY: - return "ratchetProbability"; - - case UNPATCHED_ARP_RATCHET_AMOUNT: - return "ratchetAmount"; - - case UNPATCHED_ARP_SEQUENCE_LENGTH: - return "sequenceLength"; - - case UNPATCHED_ARP_RHYTHM: - return "rhythm"; - - case UNPATCHED_ARP_SPREAD_GATE: - return "spreadGate"; - - case UNPATCHED_ARP_SPREAD_OCTAVE: - return "spreadOctave"; - - case UNPATCHED_SPREAD_VELOCITY: - return "spreadVelocity"; - case UNPATCHED_PORTAMENTO: return "portamento"; @@ -432,6 +394,9 @@ constexpr char const* paramNameForFileConst(Kind const kind, ParamType const par case UNPATCHED_DELAY_AMOUNT: return "delayFeedback"; + case UNPATCHED_ARP_RATE: + return "arpRate"; + case UNPATCHED_PAN: return "pan"; @@ -502,6 +467,45 @@ constexpr char const* paramNameForFileConst(Kind const kind, ParamType const par case UNPATCHED_COMPRESSOR_THRESHOLD: return "compressorThreshold"; + case UNPATCHED_ARP_GATE: + return "arpGate"; + + case UNPATCHED_NOTE_PROBABILITY: + return "noteProbability"; + + case UNPATCHED_ARP_BASS_PROBABILITY: + return "bassProbability"; + + case UNPATCHED_REVERSE_PROBABILITY: + return "reverseProbability"; + + case UNPATCHED_ARP_CHORD_POLYPHONY: + return "chordPolyphony"; + + case UNPATCHED_ARP_CHORD_PROBABILITY: + return "chordProbability"; + + case UNPATCHED_ARP_RATCHET_PROBABILITY: + return "ratchetProbability"; + + case UNPATCHED_ARP_RATCHET_AMOUNT: + return "ratchetAmount"; + + case UNPATCHED_ARP_SEQUENCE_LENGTH: + return "sequenceLength"; + + case UNPATCHED_ARP_RHYTHM: + return "rhythm"; + + case UNPATCHED_ARP_SPREAD_GATE: + return "spreadGate"; + + case UNPATCHED_ARP_SPREAD_OCTAVE: + return "spreadOctave"; + + case UNPATCHED_SPREAD_VELOCITY: + return "spreadVelocity"; + case UNPATCHED_NUM_SHARED: // Intentionally not handled ; diff --git a/src/deluge/modulation/params/param.h b/src/deluge/modulation/params/param.h index acf916caa0..3cd534bd66 100644 --- a/src/deluge/modulation/params/param.h +++ b/src/deluge/modulation/params/param.h @@ -186,13 +186,8 @@ enum UnpatchedShared : ParamType { UNPATCHED_MOD_FX_FEEDBACK, UNPATCHED_SIDECHAIN_SHAPE, UNPATCHED_COMPRESSOR_THRESHOLD, - /// Special value for chaining the UNPATCHED_* params - UNPATCHED_NUM_SHARED, -}; - -/// Unpatched params which are only used for Sounds -enum UnpatchedSound : ParamType { - UNPATCHED_ARP_GATE = UNPATCHED_NUM_SHARED, + // Arp + UNPATCHED_ARP_GATE, UNPATCHED_ARP_RHYTHM, UNPATCHED_ARP_SEQUENCE_LENGTH, UNPATCHED_ARP_CHORD_POLYPHONY, @@ -205,7 +200,13 @@ enum UnpatchedSound : ParamType { UNPATCHED_ARP_SPREAD_GATE, UNPATCHED_ARP_SPREAD_OCTAVE, UNPATCHED_SPREAD_VELOCITY, - UNPATCHED_PORTAMENTO, + /// Special value for chaining the UNPATCHED_* params + UNPATCHED_NUM_SHARED, +}; + +/// Unpatched params which are only used for Sounds +enum UnpatchedSound : ParamType { + UNPATCHED_PORTAMENTO = UNPATCHED_NUM_SHARED, UNPATCHED_SOUND_MAX_NUM, }; @@ -215,6 +216,7 @@ enum UnpatchedGlobal : ParamType { UNPATCHED_MOD_FX_DEPTH, UNPATCHED_DELAY_RATE, UNPATCHED_DELAY_AMOUNT, + UNPATCHED_ARP_RATE, UNPATCHED_PAN, UNPATCHED_LPF_FREQ, UNPATCHED_LPF_RES, @@ -352,7 +354,7 @@ const uint32_t unpatchedGlobalParamShortcuts[kDisplayWidth][kDisplayHeight] = { {kNoParamID , kNoParamID , kNoParamID , kNoParamID , UNPATCHED_LPF_MORPH , kNoParamID , UNPATCHED_LPF_RES , UNPATCHED_LPF_FREQ}, {kNoParamID , kNoParamID , kNoParamID , kNoParamID , UNPATCHED_HPF_MORPH , kNoParamID , UNPATCHED_HPF_RES , UNPATCHED_HPF_FREQ}, {kNoParamID , kNoParamID , UNPATCHED_SIDECHAIN_VOLUME, kNoParamID , UNPATCHED_SIDECHAIN_SHAPE , kNoParamID , UNPATCHED_BASS , UNPATCHED_BASS_FREQ}, - {kNoParamID , kNoParamID , kNoParamID , kNoParamID , kNoParamID , kNoParamID , UNPATCHED_TREBLE , UNPATCHED_TREBLE_FREQ}, + {UNPATCHED_ARP_RATE , kNoParamID , UNPATCHED_ARP_GATE , kNoParamID , kNoParamID , kNoParamID , UNPATCHED_TREBLE , UNPATCHED_TREBLE_FREQ}, {kNoParamID , kNoParamID , kNoParamID , kNoParamID , UNPATCHED_MOD_FX_OFFSET , UNPATCHED_MOD_FX_FEEDBACK , UNPATCHED_MOD_FX_DEPTH, UNPATCHED_MOD_FX_RATE}, {kNoParamID , kNoParamID , kNoParamID , UNPATCHED_REVERB_SEND_AMOUNT, kNoParamID , kNoParamID , kNoParamID , kNoParamID}, {UNPATCHED_DELAY_RATE, kNoParamID , kNoParamID , UNPATCHED_DELAY_AMOUNT , kNoParamID , kNoParamID , kNoParamID , kNoParamID}, diff --git a/src/deluge/modulation/params/param_set.cpp b/src/deluge/modulation/params/param_set.cpp index 9e5ad1b958..9b83731fd1 100644 --- a/src/deluge/modulation/params/param_set.cpp +++ b/src/deluge/modulation/params/param_set.cpp @@ -532,7 +532,6 @@ bool PatchedParamSet::shouldParamIndicateMiddleValue(ModelStackWithParamId const case params::LOCAL_MODULATOR_1_PITCH_ADJUST: case params::GLOBAL_DELAY_FEEDBACK: case params::GLOBAL_DELAY_RATE: - case params::GLOBAL_ARP_RATE: return true; default: return false; diff --git a/src/deluge/processing/audio_output.cpp b/src/deluge/processing/audio_output.cpp index 567e31c329..b04b6ae34e 100644 --- a/src/deluge/processing/audio_output.cpp +++ b/src/deluge/processing/audio_output.cpp @@ -337,7 +337,7 @@ Error AudioOutput::readFromFile(Deserializer& reader, Song* song, Clip* clip, in else { - Error result = GlobalEffectableForClip::readTagFromFile(reader, tagName, ¶mManager, 0, song); + Error result = GlobalEffectableForClip::readTagFromFile(reader, tagName, ¶mManager, 0, nullptr, song); if (result == Error::NONE) {} else if (result == Error::RESULT_TAG_UNUSED) { reader.exitTag(); diff --git a/src/deluge/processing/sound/sound.cpp b/src/deluge/processing/sound/sound.cpp index b652eec516..2ab31685c1 100644 --- a/src/deluge/processing/sound/sound.cpp +++ b/src/deluge/processing/sound/sound.cpp @@ -162,20 +162,6 @@ void Sound::initParams(ParamManager* paramManager) { UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); unpatchedParams->kind = params::Kind::UNPATCHED_SOUND; - unpatchedParams->params[params::UNPATCHED_ARP_GATE].setCurrentValueBasicForSetup(0); - unpatchedParams->params[params::UNPATCHED_NOTE_PROBABILITY].setCurrentValueBasicForSetup(2147483647); - unpatchedParams->params[params::UNPATCHED_ARP_BASS_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_REVERSE_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_CHORD_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_RATCHET_PROBABILITY].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_RATCHET_AMOUNT].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_SEQUENCE_LENGTH].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_CHORD_POLYPHONY].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_RHYTHM].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_SPREAD_VELOCITY].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_SPREAD_GATE].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_ARP_SPREAD_OCTAVE].setCurrentValueBasicForSetup(-2147483648); - unpatchedParams->params[params::UNPATCHED_MOD_FX_FEEDBACK].setCurrentValueBasicForSetup(0); unpatchedParams->params[params::UNPATCHED_PORTAMENTO].setCurrentValueBasicForSetup(-2147483648); PatchedParamSet* patchedParams = paramManager->getPatchedParamSet(); @@ -622,31 +608,6 @@ Error Sound::readTagFromFileOrError(Deserializer& reader, char const* tagName, P reader.exitTag("modulator2", true); } - else if (!strcmp(tagName, "arpeggiator")) { - // Set default values in case they are not configured - arpSettings->syncType = SYNC_TYPE_EVEN; - arpSettings->syncLevel = SYNC_LEVEL_NONE; - reader.match('{'); - while (*(tagName = reader.readNextTagOrAttributeName())) { - if (!strcmp(tagName, - "gate")) { // This is here for compatibility only for people (Lou and Ian) who saved songs - // with firmware in September 2016 - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_GATE, - readAutomationUpToPos); - reader.exitTag("gate"); - } - else if (arpSettings != nullptr) { - bool readAndExited = arpSettings->readCommonTagsFromFile(reader, tagName, song); - if (!readAndExited) { - reader.exitTag(tagName); - } - } - } - - reader.exitTag("arpeggiator", true); - } - else if (!strcmp(tagName, "transpose")) { transpose = reader.readTagOrAttributeValueInt(); reader.exitTag("transpose"); @@ -658,89 +619,6 @@ Error Sound::readTagFromFileOrError(Deserializer& reader, char const* tagName, P reader.exitTag("noiseVolume"); } - else if (!strcmp(tagName, "ratchetAmount")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATCHET_AMOUNT, - readAutomationUpToPos); - reader.exitTag("ratchetAmount"); - } - - else if (!strcmp(tagName, "ratchetProbability")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATCHET_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("ratchetProbability"); - } - - else if (!strcmp(tagName, "chordPolyphony")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_CHORD_POLYPHONY, - readAutomationUpToPos); - reader.exitTag("chordPolyphony"); - } - - else if (!strcmp(tagName, "chordProbability")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_CHORD_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("chordProbability"); - } - - else if (!strcmp(tagName, "reverseProbability")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_REVERSE_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("reverseProbability"); - } - - else if (!strcmp(tagName, "bassProbability")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_BASS_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("bassProbability"); - } - - else if (!strcmp(tagName, "noteProbability")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_NOTE_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("noteProbability"); - } - - else if (!strcmp(tagName, "sequenceLength")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SEQUENCE_LENGTH, - readAutomationUpToPos); - reader.exitTag("sequenceLength"); - } - - else if (!strcmp(tagName, "rhythm")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RHYTHM, readAutomationUpToPos); - reader.exitTag("rhythm"); - } - - else if (!strcmp(tagName, "spreadVelocity")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_SPREAD_VELOCITY, - readAutomationUpToPos); - reader.exitTag("spreadVelocity"); - } - - else if (!strcmp(tagName, "spreadGate")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SPREAD_GATE, - readAutomationUpToPos); - reader.exitTag("spreadGate"); - } - - else if (!strcmp(tagName, "spreadOctave")) { - ENSURE_PARAM_MANAGER_EXISTS - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SPREAD_OCTAVE, - readAutomationUpToPos); - reader.exitTag("spreadOctave"); - } - else if (!strcmp(tagName, "portamento")) { // This is here for compatibility only for people (Lou and Ian) who saved songs // with firmware in September 2016 @@ -1449,8 +1327,8 @@ Error Sound::readTagFromFileOrError(Deserializer& reader, char const* tagName, P } else { - Error result = - ModControllableAudio::readTagFromFile(reader, tagName, paramManager, readAutomationUpToPos, song); + Error result = ModControllableAudio::readTagFromFile(reader, tagName, paramManager, readAutomationUpToPos, + arpSettings, song); if (result == Error::NONE) {} else if (result != Error::RESULT_TAG_UNUSED) { return result; @@ -1940,8 +1818,6 @@ void Sound::allNotesOff(ModelStackWithThreeMainThings* modelStack, ArpeggiatorBa // noteCode = ALL_NOTES_OFF (default) means stop *any* voice, regardless of noteCode void Sound::noteOffPostArpeggiator(ModelStackWithSoundFlags* modelStack, int32_t noteCode) { - ArpeggiatorSettings* arpSettings = getArpSettings(); - // Send midi note offs out for specific notes, // but only if the type of sound allows note tails (if not, note off was already sent right after its note on) if (outputMidiChannel != MIDI_CHANNEL_NONE && allowNoteTails(modelStack, true)) { @@ -2004,6 +1880,8 @@ void Sound::noteOffPostArpeggiator(ModelStackWithSoundFlags* modelStack, int32_t return; } + ArpeggiatorSettings* arpSettings = getArpSettings(); + int32_t ends[2]; AudioEngine::activeVoices.getRangeForSound(this, ends); for (int32_t v = ends[0]; v < ends[1]; v++) { @@ -3995,70 +3873,7 @@ bool Sound::readParamTagFromFile(Deserializer& reader, char const* tagName, Para ParamCollectionSummary* patchedParamsSummary = paramManager->getPatchedParamSetSummary(); PatchedParamSet* patchedParams = (PatchedParamSet*)patchedParamsSummary->paramCollection; - if (!strcmp(tagName, "arpeggiatorGate")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_GATE, readAutomationUpToPos); - reader.exitTag("arpeggiatorGate"); - } - else if (!strcmp(tagName, "noteProbability")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_NOTE_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("noteProbability"); - } - else if (!strcmp(tagName, "bassProbability")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_BASS_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("bassProbability"); - } - else if (!strcmp(tagName, "reverseProbability")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_REVERSE_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("reverseProbability"); - } - else if (!strcmp(tagName, "chordProbability")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_CHORD_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("chordProbability"); - } - else if (!strcmp(tagName, "chordPolyphony")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_CHORD_POLYPHONY, - readAutomationUpToPos); - reader.exitTag("chordPolyphony"); - } - else if (!strcmp(tagName, "ratchetProbability")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATCHET_PROBABILITY, - readAutomationUpToPos); - reader.exitTag("ratchetProbability"); - } - else if (!strcmp(tagName, "ratchetAmount")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RATCHET_AMOUNT, - readAutomationUpToPos); - reader.exitTag("ratchetAmount"); - } - else if (!strcmp(tagName, "sequenceLength")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SEQUENCE_LENGTH, - readAutomationUpToPos); - reader.exitTag("sequenceLength"); - } - else if (!strcmp(tagName, "rhythm")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_RHYTHM, readAutomationUpToPos); - reader.exitTag("rhythm"); - } - else if (!strcmp(tagName, "spreadVelocity")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_SPREAD_VELOCITY, - readAutomationUpToPos); - reader.exitTag("spreadVelocity"); - } - else if (!strcmp(tagName, "spreadGate")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SPREAD_GATE, - readAutomationUpToPos); - reader.exitTag("spreadGate"); - } - else if (!strcmp(tagName, "spreadOctave")) { - unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_ARP_SPREAD_OCTAVE, - readAutomationUpToPos); - reader.exitTag("spreadOctave"); - } - else if (!strcmp(tagName, "portamento")) { + if (!strcmp(tagName, "portamento")) { unpatchedParams->readParam(reader, unpatchedParamsSummary, params::UNPATCHED_PORTAMENTO, readAutomationUpToPos); reader.exitTag("portamento"); } @@ -4343,7 +4158,6 @@ void Sound::writeParamsToFile(Serializer& writer, ParamManager* paramManager, bo PatchedParamSet* patchedParams = paramManager->getPatchedParamSet(); UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); - unpatchedParams->writeParamAsAttribute(writer, "arpeggiatorGate", params::UNPATCHED_ARP_GATE, writeAutomation); unpatchedParams->writeParamAsAttribute(writer, "portamento", params::UNPATCHED_PORTAMENTO, writeAutomation); unpatchedParams->writeParamAsAttribute(writer, "compressorShape", params::UNPATCHED_SIDECHAIN_SHAPE, writeAutomation); @@ -4411,29 +4225,6 @@ void Sound::writeParamsToFile(Serializer& writer, ParamManager* paramManager, bo patchedParams->writeParamAsAttribute(writer, "waveFold", params::LOCAL_FOLD, writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "noteProbability", params::UNPATCHED_NOTE_PROBABILITY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "bassProbability", params::UNPATCHED_ARP_BASS_PROBABILITY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "reverseProbability", params::UNPATCHED_REVERSE_PROBABILITY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "chordProbability", params::UNPATCHED_ARP_CHORD_PROBABILITY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "ratchetProbability", params::UNPATCHED_ARP_RATCHET_PROBABILITY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "ratchetAmount", params::UNPATCHED_ARP_RATCHET_AMOUNT, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "sequenceLength", params::UNPATCHED_ARP_SEQUENCE_LENGTH, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "chordPolyphony", params::UNPATCHED_ARP_CHORD_POLYPHONY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "rhythm", params::UNPATCHED_ARP_RHYTHM, writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "spreadVelocity", params::UNPATCHED_SPREAD_VELOCITY, - writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "spreadGate", params::UNPATCHED_ARP_SPREAD_GATE, writeAutomation); - unpatchedParams->writeParamAsAttribute(writer, "spreadOctave", params::UNPATCHED_ARP_SPREAD_OCTAVE, - writeAutomation); - writer.writeOpeningTagEnd(); // Envelopes diff --git a/src/deluge/processing/sound/sound_drum.cpp b/src/deluge/processing/sound/sound_drum.cpp index 3ef4a0b654..144fdcef6c 100644 --- a/src/deluge/processing/sound/sound_drum.cpp +++ b/src/deluge/processing/sound/sound_drum.cpp @@ -90,11 +90,11 @@ void SoundDrum::resetTimeEnteredState() { } } -void SoundDrum::noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, +void SoundDrum::noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel, uint32_t sampleSyncLength, int32_t ticksLate, uint32_t samplesLate) { // If part of a Kit, and in choke mode, choke other drums - if (polyphonic == PolyphonyMode::CHOKE) { + if (polyphonic == PolyphonyMode::CHOKE && (kit != nullptr)) { kit->choke(); } @@ -146,6 +146,7 @@ void SoundDrum::polyphonicExpressionEventOnChannelOrNote(int32_t newValue, int32 void SoundDrum::unassignAllVoices() { Sound::unassignAllVoices(); + arpeggiator.reset(); } void SoundDrum::setupPatchingForAllParamManagers(Song* song) { diff --git a/src/deluge/processing/sound/sound_drum.h b/src/deluge/processing/sound/sound_drum.h index 053f432c2a..21f2f286b3 100644 --- a/src/deluge/processing/sound/sound_drum.h +++ b/src/deluge/processing/sound/sound_drum.h @@ -36,10 +36,10 @@ class SoundDrum final : public Sound, public Drum { bool allowNoteTails(ModelStackWithSoundFlags* modelStack, bool disregardSampleLoop = false) override; bool anyNoteIsOn() override; bool hasAnyVoices() override; - void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, Kit* kit, int16_t const* mpeValues, + void noteOn(ModelStackWithThreeMainThings* modelStack, uint8_t velocity, int16_t const* mpeValues, int32_t fromMIDIChannel = MIDI_CHANNEL_NONE, uint32_t sampleSyncLength = 0, int32_t ticksLate = 0, uint32_t samplesLate = 0) override; - void noteOff(ModelStackWithThreeMainThings* modelStack, int32_t velocity) override; + void noteOff(ModelStackWithThreeMainThings* modelStack, int32_t velocity = kDefaultLiftValue) override; void unassignAllVoices() override; void setupPatchingForAllParamManagers(Song* song) override; bool readTagFromFile(Deserializer& reader, char const* tagName) override;