Skip to content

Commit

Permalink
Feature / Enable a new menu in Sound menu to make synths and sounddru…
Browse files Browse the repository at this point in the history
…ms also send midi at the same time as they play a sound (#3313)

* Enable a new menu in SoundDrum to make rows also send midi at the same time as they play a sound

* DOcs

* docs

* docs

* format

* docs

* format

* Move midi sending code to SoundDrum

* Send also aftertouch (same as MidiDrum does)

* Revert "Send also aftertouch (same as MidiDrum does)"

This reverts commit f809e91.

* Revert "Move midi sending code to SoundDrum"

This reverts commit 68d86d5.

* format

* Docs

* wip

* wip

* wip

* Cleanup

* finally!

* Remove log

* remove logs
  • Loading branch information
soymonitus authored Feb 1, 2025
1 parent 93333f1 commit 3b718c0
Show file tree
Hide file tree
Showing 20 changed files with 385 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ at velocity 0 it would look the same as its tail (but you can't have 0 velocity)

##### Synth/Kit Clips
- Added Auto-Load feature to sample browser, so you can load the sounds to the instrument as you preview them. Auto-Load can be engaged while in sample browser, if you press the `Load` button.
- Sounds have now the ability to send MIDI notes at the same time as they play a sample. This will allow your synths and drums to trigger external devices. A new menu `MIDI` has been added at the bottom of the `SOUND` menu to set the MIDI channel and the note (in case of drum sounds).

##### CV Clips
- Added the ability to set a CV instrument to use both 1 and 2 channels, which makes the cv2 source selectable between mod wheel, velocity, and aftertouch
Expand Down
9 changes: 9 additions & 0 deletions docs/community_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,13 @@ as an oscillator type within the subtractive engine, so it can be combined with

- ([#3279]) Added two more envelopes (Envelope 3 and Envelope 4), which you can access from the sound editor menu.

#### 4.5.9 - Send Midi

- ([#3313]) There is a new submenu `MIDI` added to the `SOUND` menu for synths and sound drums, where you can select the MIDI channel
(and also base note for drums) that will be sent at the same time as the sound triggers.
In case of drums, it is like having a Sound row + a Midi row together triggering at the same time. And in case of synths, it is like
having a Synth clip + a Midi clip together triggering at the same time. This feature is limited to regular MIDI (that is, not for MPE).

### 4.6 - Instrument Clip View - Kit Clip Features

#### 4.6.1 - Keyboard View
Expand Down Expand Up @@ -1592,6 +1599,8 @@ different firmware

[#3291]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3291

[#3313]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3313

[Automation View Documentation]: features/automation_view.md

[Arpeggiator Documentation]: features/arpeggiator.md
Expand Down
5 changes: 5 additions & 0 deletions docs/menu_hierarchies.md
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,11 @@ The Sound menu contains the following menu hierarchy:
- Reversed
- Ping-Pong
</details>
<details><summary>MIDI</summary>

- Channel
- Note (only available for Kits)
</details>

</details>

Expand Down
2 changes: 2 additions & 0 deletions src/definitions_cxx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ constexpr int32_t MIDI_CHANNEL_MPE_LOWER_ZONE = 16;
constexpr int32_t MIDI_CHANNEL_MPE_UPPER_ZONE = 17;
constexpr int32_t NUM_CHANNELS = 18;
constexpr int32_t MIDI_CHANNEL_NONE = 255;
constexpr int32_t MIDI_NOTE_NONE = 255;
constexpr int32_t MIDI_CC_NONE = 255;

constexpr int32_t NUM_INTERNAL_DESTS = 1;
Expand Down Expand Up @@ -950,6 +951,7 @@ constexpr int32_t kSubmenuIconSpacingX = 7;

// For kits
constexpr int32_t kNoteForDrum = 60;
constexpr int32_t kDefaultNoteOffVelocity = 64;

enum BendRange {
BEND_RANGE_MAIN,
Expand Down
1 change: 1 addition & 0 deletions src/deluge/gui/l10n/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@
"STRING_FOR_MPE_LOWER_ZONE": "MPE lower zone",
"STRING_FOR_MPE_UPPER_ZONE": "MPE upper zone",
"STRING_FOR_CHANNEL": "Channel",
"STRING_FOR_NOTE": "Note",
"STRING_FOR_SET": "SET",
"STRING_FOR_UNLEARNED": "UNLEARNED",
"STRING_FOR_LEARNED": "LEARNED",
Expand Down
1 change: 1 addition & 0 deletions src/deluge/gui/l10n/g_english.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ PLACE_SDRAM_DATA Language english{
{STRING_FOR_MPE_LOWER_ZONE, "MPE lower zone"},
{STRING_FOR_MPE_UPPER_ZONE, "MPE upper zone"},
{STRING_FOR_CHANNEL, "Channel"},
{STRING_FOR_NOTE, "Note"},
{STRING_FOR_SET, "SET"},
{STRING_FOR_UNLEARNED, "UNLEARNED"},
{STRING_FOR_LEARNED, "LEARNED"},
Expand Down
1 change: 1 addition & 0 deletions src/deluge/gui/l10n/strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,7 @@ enum class String : size_t {
STRING_FOR_MPE_LOWER_ZONE,
STRING_FOR_MPE_UPPER_ZONE,
STRING_FOR_CHANNEL,
STRING_FOR_NOTE,
STRING_FOR_SET,
STRING_FOR_UNLEARNED,
STRING_FOR_LEARNED,
Expand Down
78 changes: 78 additions & 0 deletions src/deluge/gui/menu_item/midi/sound/channel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "definitions_cxx.hpp"
#include "gui/menu_item/integer.h"
#include "gui/ui/sound_editor.h"
#include "hid/display/oled.h"
#include "processing/sound/sound.h"

namespace deluge::gui::menu_item::midi::sound {

class OutputMidiChannel final : public Integer {
public:
using Integer::Integer;
[[nodiscard]] int32_t getMinValue() const override { return 0; }
[[nodiscard]] int32_t getMaxValue() const override { return 16; }
void readCurrentValue() override {
int32_t value = soundEditor.currentSound->outputMidiChannel;
if (value == MIDI_CHANNEL_NONE) {
value = 0;
}
else {
value = value + 1;
}
this->setValue(value);
}
void writeCurrentValue() override {
int32_t value = this->getValue();
if (value == 0) {
value = MIDI_CHANNEL_NONE;
}
else {
value = value - 1;
}
soundEditor.currentSound->outputMidiChannel = value;
}

void drawValue() override {
int32_t value = this->getValue();
if (value == 0) {
display->setScrollingText(l10n::get(l10n::String::STRING_FOR_OFF));
}
else {
char name[12];
snprintf(name, sizeof(name), "%d", value);
display->setScrollingText(name);
}
}

void drawInteger(int32_t textWidth, int32_t textHeight, int32_t yPixel) override {
deluge::hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
int32_t value = this->getValue();
if (value == 0) {
canvas.drawStringCentred(l10n::get(l10n::String::STRING_FOR_OFF), yPixel + OLED_MAIN_TOPMOST_PIXEL,
textWidth, textHeight);
}
else {
char name[12];
snprintf(name, sizeof(name), "%d", value);
canvas.drawStringCentred(name, yPixel + OLED_MAIN_TOPMOST_PIXEL, textWidth, textHeight);
}
}
};
} // namespace deluge::gui::menu_item::midi::sound
44 changes: 44 additions & 0 deletions src/deluge/gui/menu_item/midi/sound/note_for_drum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "definitions_cxx.hpp"
#include "gui/menu_item/integer.h"
#include "gui/ui/sound_editor.h"
#include "hid/display/oled.h"
#include "processing/sound/sound.h"
#include <cstdint>

namespace deluge::gui::menu_item::midi::sound {

class OutputMidiNoteForDrum final : public Integer {
public:
using Integer::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();
}
void readCurrentValue() override {
int32_t value = soundEditor.currentSound->outputMidiNoteForDrum;
if (value == MIDI_NOTE_NONE) {
value = kNoteForDrum;
}
this->setValue(value);
}
void writeCurrentValue() override { soundEditor.currentSound->outputMidiNoteForDrum = this->getValue(); }
};
} // namespace deluge::gui::menu_item::midi::sound
8 changes: 8 additions & 0 deletions src/deluge/gui/ui/menus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
#include "gui/menu_item/midi/follow/follow_kit_root_note.h"
#include "gui/menu_item/midi/mpe_to_mono.h"
#include "gui/menu_item/midi/pgm.h"
#include "gui/menu_item/midi/sound/channel.h"
#include "gui/menu_item/midi/sound/note_for_drum.h"
#include "gui/menu_item/midi/sub.h"
#include "gui/menu_item/midi/takeover.h"
#include "gui/menu_item/midi/transpose.h"
Expand Down Expand Up @@ -546,6 +548,11 @@ Submenu soundDistortionMenu{
},
};

// Output MIDI for sound drums --------------------------------------------------------------
midi::sound::OutputMidiChannel outputMidiChannelMenu{STRING_FOR_CHANNEL, STRING_FOR_CHANNEL};
midi::sound::OutputMidiNoteForDrum outputMidiNoteForDrumMenu{STRING_FOR_NOTE, STRING_FOR_NOTE};
Submenu outputMidiSubmenu{STRING_FOR_MIDI, {&outputMidiChannelMenu, &outputMidiNoteForDrumMenu}};

// MIDIInstrument menu ----------------------------------------------------------------------
midi::device_definition::Linked midiDeviceLinkedMenu{STRING_FOR_MIDI_DEVICE_DEFINITION_LINKED,
STRING_FOR_MIDI_DEVICE_DEFINITION_LINKED};
Expand Down Expand Up @@ -1336,6 +1343,7 @@ menu_item::Submenu soundEditorRootMenu{
&drumBendRangeMenu,
&patchCablesMenu,
&sequenceDirectionMenu,
&outputMidiSubmenu,
},
};

Expand Down
2 changes: 2 additions & 0 deletions src/deluge/io/midi/midi_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ struct MIDISource {
MIDISource(PlaybackHandler const* handler) : source_(handler) {};
MIDISource(MidiFollow const* follow) : source_(follow) {};
MIDISource(MIDIDrum const* drum) : source_(drum) {};
MIDISource(Sound const* sound) : source_(sound) {};

MIDISource(MIDICable const& cable) : source_(&cable) {};
MIDISource(MIDIInstrument const& instrument) : source_(&instrument) {};
MIDISource(MIDIDrum const& drum) : source_(&drum) {};
MIDISource(Sound const& sound) : source_(&sound) {};
MIDISource(MidiFollow const& follow) : source_(&follow) {};
MIDISource(PlaybackHandler const& handler) : source_(&handler) {};

Expand Down
7 changes: 5 additions & 2 deletions src/deluge/model/drum/midi_drum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ void MIDIDrum::noteOnPostArp(int32_t noteCodePostArp, ArpNote* arpNote, int32_t
}

void MIDIDrum::noteOffPostArp(int32_t noteCodePostArp) {
midiEngine.sendNote(this, false, noteCodePostArp, 64, channel, kMIDIOutputFilterNoMPE);
midiEngine.sendNote(this, false, noteCodePostArp, kDefaultNoteOffVelocity, channel, kMIDIOutputFilterNoMPE);
state = false;
}

Expand Down Expand Up @@ -156,7 +156,10 @@ void MIDIDrum::expressionEvent(int32_t newValue, int32_t expressionDimension) {
// Aftertouch only
if (expressionDimension == 2) {
int32_t value7 = newValue >> 24;
midiEngine.sendPolyphonicAftertouch(this, channel, value7, note, kMIDIOutputFilterNoMPE);
// Note: use the note code currently on post-arp, because this drum supports "Chord Simulator" and "Octaves" and
// the note code could be different
midiEngine.sendPolyphonicAftertouch(this, channel, value7, arpeggiator.arpNote.noteCodeOnPostArp[0],
kMIDIOutputFilterNoMPE);
}
}

Expand Down
2 changes: 0 additions & 2 deletions src/deluge/model/instrument/non_audio_instrument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ void NonAudioInstrument::sendNote(ModelStackWithThreeMainThings* modelStack, boo
void NonAudioInstrument::polyphonicExpressionEventOnChannelOrNote(int32_t newValue, int32_t expressionDimension,
int32_t channelOrNoteNumber,
MIDICharacteristic whichCharacteristic) {
ArpeggiatorSettings* settings = getArpSettings();

int32_t n;
int32_t nEnd;

Expand Down
16 changes: 16 additions & 0 deletions src/deluge/modulation/arpeggiator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,14 @@ void ArpeggiatorForDrum::noteOff(ArpeggiatorSettings* settings, int32_t noteCode
if ((settings == nullptr) || settings->mode == ArpMode::OFF) {
instruction->noteCodeOffPostArp[0] = noteCodePreArp;
instruction->outputMIDIChannelOff[0] = arpNote.outputMemberChannel[0];
noteCodeCurrentlyOnPostArp[0] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[0] = MIDI_CHANNEL_NONE;
for (int32_t n = 1; n < ARP_MAX_INSTRUCTION_NOTES; n++) {
// If no arp, rest of chord notes are for sure disabled
instruction->noteCodeOffPostArp[n] = ARP_NOTE_NONE;
instruction->outputMIDIChannelOff[n] = MIDI_CHANNEL_NONE;
noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE;
}
}

Expand All @@ -180,6 +184,9 @@ void ArpeggiatorForDrum::noteOff(ArpeggiatorSettings* settings, int32_t noteCode
// Set all chord notes
instruction->noteCodeOffPostArp[n] = noteCodeCurrentlyOnPostArp[n];
instruction->outputMIDIChannelOff[n] = outputMIDIChannelForNoteCurrentlyOnPostArp[n];
// Clean the temp state
noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE;
}
}
}
Expand Down Expand Up @@ -317,10 +324,14 @@ void Arpeggiator::noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp,
if (arpOff) {
instruction->noteCodeOffPostArp[0] = noteCodePreArp;
instruction->outputMIDIChannelOff[0] = arpNote->outputMemberChannel[0];
noteCodeCurrentlyOnPostArp[0] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[0] = MIDI_CHANNEL_NONE;
for (int32_t n = 1; n < ARP_MAX_INSTRUCTION_NOTES; n++) {
// If no arp, rest of chord notes are for sure disabled
instruction->noteCodeOffPostArp[n] = ARP_NOTE_NONE;
instruction->outputMIDIChannelOff[n] = MIDI_CHANNEL_NONE;
noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE;
}
}

Expand All @@ -333,6 +344,8 @@ void Arpeggiator::noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp,
// Set all chord notes
instruction->noteCodeOffPostArp[n] = noteCodeCurrentlyOnPostArp[n];
instruction->outputMIDIChannelOff[n] = outputMIDIChannelForNoteCurrentlyOnPostArp[n];
noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE;
}
}
}
Expand Down Expand Up @@ -397,6 +410,9 @@ void ArpeggiatorBase::switchAnyNoteOff(ArpReturnInstruction* instruction) {
// Set all chord notes
instruction->noteCodeOffPostArp[n] = noteCodeCurrentlyOnPostArp[n];
instruction->outputMIDIChannelOff[n] = outputMIDIChannelForNoteCurrentlyOnPostArp[n];
// Clean the temp state
noteCodeCurrentlyOnPostArp[n] = ARP_NOTE_NONE;
outputMIDIChannelForNoteCurrentlyOnPostArp[n] = MIDI_CHANNEL_NONE;
}
gateCurrentlyActive = false;
}
Expand Down
13 changes: 11 additions & 2 deletions src/deluge/modulation/arpeggiator.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ constexpr uint32_t PATTERN_MAX_BUFFER_SIZE = 16;

constexpr uint32_t ARP_NOTE_NONE = 32767;

enum class ArpType : uint8_t {
SYNTH,
DRUM,
};

class ArpeggiatorSettings {
public:
ArpeggiatorSettings();
Expand Down Expand Up @@ -173,13 +178,15 @@ class ArpeggiatorBase {
}
virtual void noteOn(ArpeggiatorSettings* settings, int32_t noteCode, int32_t velocity,
ArpReturnInstruction* instruction, int32_t fromMIDIChannel, int16_t const* mpeValues) = 0;
virtual void noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp, ArpReturnInstruction* instruction) = 0;
void render(ArpeggiatorSettings* settings, ArpReturnInstruction* instruction, int32_t numSamples,
uint32_t gateThreshold, uint32_t phaseIncrement);
int32_t doTickForward(ArpeggiatorSettings* settings, ArpReturnInstruction* instruction, uint32_t ClipCurrentPos,
bool currentlyPlayingReversed);
void calculateRandomizerAmounts(ArpeggiatorSettings* settings);
virtual bool hasAnyInputNotesActive() = 0;
virtual void reset() = 0;
virtual ArpType getArpType() = 0;

bool gateCurrentlyActive = false;
uint32_t gatePos = 0;
Expand Down Expand Up @@ -266,8 +273,9 @@ class ArpeggiatorForDrum final : public ArpeggiatorBase {
ArpeggiatorForDrum();
void noteOn(ArpeggiatorSettings* settings, int32_t noteCode, int32_t velocity, ArpReturnInstruction* instruction,
int32_t fromMIDIChannel, int16_t const* mpeValues) override;
void noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp, ArpReturnInstruction* instruction);
void noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp, ArpReturnInstruction* instruction) override;
void reset() override;
ArpType getArpType() override { return ArpType::DRUM; }
ArpNote arpNote; // For the one note. noteCode will always be 60. velocity will be 0 if off.
int16_t noteForDrum;

Expand All @@ -281,10 +289,11 @@ class Arpeggiator final : public ArpeggiatorBase {
Arpeggiator();

void reset() override;
ArpType getArpType() override { return ArpType::SYNTH; }

void noteOn(ArpeggiatorSettings* settings, int32_t noteCode, int32_t velocity, ArpReturnInstruction* instruction,
int32_t fromMIDIChannel, int16_t const* mpeValues) override;
void noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp, ArpReturnInstruction* instruction);
void noteOff(ArpeggiatorSettings* settings, int32_t noteCodePreArp, ArpReturnInstruction* instruction) override;
bool hasAnyInputNotesActive() override;

// This array tracks the notes ordered by noteCode
Expand Down
Loading

0 comments on commit 3b718c0

Please sign in to comment.