Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature / Enable a new menu in Sound menu to make synths and sounddrums also send midi at the same time as they play a sound #3313

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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 @@ -1576,6 +1583,8 @@ different firmware

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

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

[Automation View Documentation]: features/automation_view.md

[Arpeggiator Documentation]: features/arpeggiator.md
Expand Down
15 changes: 10 additions & 5 deletions docs/menu_hierarchies.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ The Song menu contains the following menu hierarchy:
- Pan
- Reverb Sidechain (SIDE)
- Volume Ducking (VOLU)

- Stutter (STUT)
- Quantize (QTZ)
- Reverse (REVE)
Expand Down Expand Up @@ -843,7 +843,7 @@ The Sound menu contains the following menu hierarchy:
- Pan
- Reverb Sidechain (SIDE)
- Volume Ducking (VOLU)

- Stutter (STUT)
- Use Song Settings (SONG)
- Quantize (QTZ)
Expand Down Expand Up @@ -1118,6 +1118,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 Expand Up @@ -1228,7 +1233,7 @@ The Kit FX menu contains the following menu hierarchy:
- Pan
- Reverb Sidechain (SIDE)
- Volume Ducking (VOLU)

- Stutter (STUT)
- Use Song Settings (SONG)
- Quantize (QTZ)
Expand Down Expand Up @@ -1430,7 +1435,7 @@ The CV menu contains the following menu hierarchy:
- Gate
- Sync
NOTE: These options can change depending on how your default resolution is set

- Off
- 2-Bar
- 1-Bar
Expand Down Expand Up @@ -1804,7 +1809,7 @@ The Audio Clip menu contains the following menu hierarchy:
- Pan
- Reverb Sidechain (SIDE)
- Volume Ducking (VOLU)

- Stutter (STUT)
- Use Song Settings (SONG)
- Quantize (QTZ)
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 @@ -836,6 +836,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 @@ -782,6 +782,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 @@ -844,6 +844,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
Loading
Loading