Skip to content

Commit

Permalink
Malekko integration (westlicht#17)
Browse files Browse the repository at this point in the history
* Malekko integration

* Add todos

* Use channel 0

* Program saves

* Midi integration setting

* Refactor

* Midi program offset setting

* Increment project version

* Revert "Increment project version"

This reverts commit dd1fe66.

* Update project version comment

* Dont send pattern changes when snapshot mode is activated

* Send program change message when midi program offset setting is updated
  • Loading branch information
jackpf authored Aug 18, 2022
1 parent 015c13b commit d72af37
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 52 deletions.
66 changes: 48 additions & 18 deletions src/apps/sequencer/engine/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,21 @@ bool Engine::trackEnginesConsistent() const {
return true;
}

bool Engine::trackPatternsConsistent() const {
auto playState = _project.playState();
auto firstTrackState = playState.trackState(0);

for (int trackIndex = 0; trackIndex < CONFIG_TRACK_COUNT; ++trackIndex) {
auto trackState = playState.trackState(trackIndex);

if (trackState.pattern() != firstTrackState.pattern()
|| trackState.requestedPattern() != firstTrackState.requestedPattern()) {
return false;
}
}
return true;
}

bool Engine::sendMidi(MidiPort port, uint8_t cable, const MidiMessage &message) {
switch (port) {
case MidiPort::Midi:
Expand All @@ -327,6 +342,33 @@ bool Engine::sendMidi(MidiPort port, uint8_t cable, const MidiMessage &message)
return false;
}

bool Engine::midiProgramChangesEnabled() {
return _project.midiIntegrationProgramChangesEnabled()
&& trackPatternsConsistent()
&& !_project.playState().snapshotActive();
}

void Engine::sendMidiProgramChange(int programNumber) {
if (_project.midiIntegrationMalekkoEnabled()) {
_midiOutputEngine.sendMalekkoSelectHandshake(0);
}
if (_project.midiIntegrationProgramChangesEnabled()) {
_midiOutputEngine.sendProgramChange(0, _project.midiProgramOffset() + programNumber);
}
if (_project.midiIntegrationMalekkoEnabled()) {
_midiOutputEngine.sendMalekkoSelectReleaseHandshake(0);
}
}

void Engine::sendMidiProgramSave(int programNumber) {
if (_project.midiIntegrationMalekkoEnabled()) {
_midiOutputEngine.sendMalekkoSaveHandshake(0);
}
if (_project.midiIntegrationProgramChangesEnabled()) {
_midiOutputEngine.sendProgramChange(0, _project.midiProgramOffset() + programNumber);
}
}

void Engine::showMessage(const char *text, uint32_t duration) {
if (_messageHandler) {
_messageHandler(text, duration);
Expand Down Expand Up @@ -433,20 +475,6 @@ void Engine::reset() {
_midiOutputEngine.reset();
}

bool allPatternsEqual(PlayState playState) {
auto firstTrackState = playState.trackState(0);

for (int trackIndex = 0; trackIndex < CONFIG_TRACK_COUNT; ++trackIndex) {
auto trackState = playState.trackState(trackIndex);

if (trackState.pattern() != firstTrackState.pattern()
|| trackState.requestedPattern() != firstTrackState.requestedPattern()) {
return false;
}
}
return true;
}

void Engine::updatePlayState(bool ticked) {
auto &playState = _project.playState();
auto &songState = playState.songState();
Expand All @@ -464,9 +492,11 @@ void Engine::updatePlayState(bool ticked) {
// send initial program change if we haven't sent it already
// means that when the sequencer initially starts, it will sync connected devices to the same pattern
// only works when all patterns are equal
if (_project.midiPgmChangeEnabled() && !_midiHasSentInitialPgmChange && allPatternsEqual(playState)) {
_midiOutputEngine.sendProgramChange(0, playState.trackState(0).pattern());
// we also send a program change if the midi program offset setting is updated
if (midiProgramChangesEnabled() && (!_midiHasSentInitialPgmChange || _project.midiProgramOffset() != _midiLastInitialProgramOffset)) {
sendMidiProgramChange(playState.trackState(0).pattern());
_midiHasSentInitialPgmChange = true;
_midiLastInitialProgramOffset = _project.midiProgramOffset();
}

// handle mute & pattern requests
Expand Down Expand Up @@ -504,8 +534,8 @@ void Engine::updatePlayState(bool ticked) {
bool shouldPreSendPgmChange = _preSendMidiPgmChange && ((changedPatterns && !playState.hasSyncedRequests())
|| (preHandleSyncedRequests && playState.hasSyncedRequests()));

if (_project.midiPgmChangeEnabled() && (shouldSendPgmChange || shouldPreSendPgmChange) && allPatternsEqual(playState)) {
_midiOutputEngine.sendProgramChange(0, playState.trackState(0).requestedPattern());
if (midiProgramChangesEnabled() && (shouldSendPgmChange || shouldPreSendPgmChange)) {
sendMidiProgramChange(playState.trackState(0).requestedPattern());
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/apps/sequencer/engine/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,15 @@ class Engine : private Clock::Listener {
MidiLearn &midiLearn() { return _midiLearn; }

bool trackEnginesConsistent() const;
bool trackPatternsConsistent() const;

bool sendMidi(MidiPort port, uint8_t cable, const MidiMessage &message);
void setMidiReceiveHandler(MidiReceiveHandler handler) { _midiReceiveHandler = handler; }
void setUsbMidiConnectHandler(UsbMidiConnectHandler handler) { _usbMidiConnectHandler = handler; }
void setUsbMidiDisconnectHandler(UsbMidiDisconnectHandler handler) { _usbMidiDisconnectHandler = handler; }
bool midiProgramChangesEnabled();
void sendMidiProgramChange(int programNumber);
void sendMidiProgramSave(int programNumber);

// message handling
void showMessage(const char *text, uint32_t duration = 1000);
Expand Down Expand Up @@ -245,6 +249,7 @@ class Engine : private Clock::Listener {
// TODO Could be a setting if needed
static const bool _preSendMidiPgmChange = true;
bool _midiHasSentInitialPgmChange = false;
int _midiLastInitialProgramOffset = -1;

// gate output overrides
bool _gateOutputOverride = false;
Expand Down
13 changes: 13 additions & 0 deletions src/apps/sequencer/engine/MidiOutputEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ void MidiOutputEngine::sendProgramChange(int channel, int programNumber) {
sendMidi(MidiPort::UsbMidi, pgmChangeMessage);
}

void MidiOutputEngine::sendMalekkoSelectHandshake(int channel) {
sendMidi(MidiPort::Midi, MidiMessage::makeControlChange(channel, 20, 127));
sendMidi(MidiPort::Midi, MidiMessage::makeControlChange(channel, 16, 64));
}

void MidiOutputEngine::sendMalekkoSelectReleaseHandshake(int channel) {
sendMidi(MidiPort::Midi, MidiMessage::makeControlChange(channel, 20, 0));
}

void MidiOutputEngine::sendMalekkoSaveHandshake(int channel) {
sendMidi(MidiPort::Midi, MidiMessage::makeControlChange(channel, 16, 127));
}

void MidiOutputEngine::resetOutput(int outputIndex) {
auto &outputState = _outputStates[outputIndex];

Expand Down
5 changes: 5 additions & 0 deletions src/apps/sequencer/engine/MidiOutputEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class MidiOutputEngine {
void sendCv(int trackIndex, float cv);
void sendProgramChange(int channel, int programNumber);

// Malekko integration
void sendMalekkoSelectHandshake(int channel);
void sendMalekkoSelectReleaseHandshake(int channel);
void sendMalekkoSaveHandshake(int channel);

private:
struct OutputState {
enum Requests {
Expand Down
9 changes: 6 additions & 3 deletions src/apps/sequencer/model/Project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ void Project::clear() {
setMonitorMode(Types::MonitorMode::Always);
setRecordMode(Types::RecordMode::Overdub);
setMidiInputMode(Types::MidiInputMode::All);
setMidiPgmChangeEnabled(false);
setMidiIntegrationMode(Types::MidiIntegrationMode::None);
setMidiProgramOffset(0);
setCvGateInput(Types::CvGateInput::Off);
setCurveCvInput(Types::CurveCvInput::Off);

Expand Down Expand Up @@ -113,7 +114,8 @@ void Project::write(VersionedSerializedWriter &writer) const {
writer.write(_monitorMode);
writer.write(_recordMode);
writer.write(_midiInputMode);
writer.write(_midiPgmChange);
writer.write(_midiIntegrationMode);
writer.write(_midiProgramOffset);
_midiInputSource.write(writer);
writer.write(_cvGateInput);
writer.write(_curveCvInput);
Expand Down Expand Up @@ -161,7 +163,8 @@ bool Project::read(VersionedSerializedReader &reader) {
_midiInputSource.read(reader);
}
if (reader.dataVersion() >= ProjectVersion::Version32) {
reader.read(_midiPgmChange);
reader.read(_midiIntegrationMode);
reader.read(_midiProgramOffset);
}
reader.read(_cvGateInput, ProjectVersion::Version6);
reader.read(_curveCvInput, ProjectVersion::Version11);
Expand Down
45 changes: 35 additions & 10 deletions src/apps/sequencer/model/Project.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,22 +235,46 @@ class Project {
const MidiSourceConfig &midiInputSource() const { return _midiInputSource; }
MidiSourceConfig &midiInputSource() { return _midiInputSource; }

// midiPgmChange
// midiIntegrationMode

void editMidiPgmChange(int value, bool shift) {
_midiPgmChange = value == 1;
void editMidiIntegrationMode(int value, bool shift) {
_midiIntegrationMode = ModelUtils::adjustedEnum(_midiIntegrationMode, value);
}

void printMidiPgmChange(StringBuilder &str) const {
if (_midiPgmChange) str("On");
else str("Off");
void printMidiIntegrationMode(StringBuilder &str) const {
str(Types::midiIntegrationModeName(_midiIntegrationMode));
}

void setMidiPgmChangeEnabled(bool enabled) {
_midiPgmChange = enabled;
void setMidiIntegrationMode(Types::MidiIntegrationMode midiIntegrationMode) {
_midiIntegrationMode = midiIntegrationMode;
}

bool midiPgmChangeEnabled() const { return _midiPgmChange; }
bool midiIntegrationProgramChangesEnabled() const {
return _midiIntegrationMode == Types::MidiIntegrationMode::ProgramChanges
|| _midiIntegrationMode == Types::MidiIntegrationMode::Malekko;
}

bool midiIntegrationMalekkoEnabled() const {
return _midiIntegrationMode == Types::MidiIntegrationMode::Malekko;
}

// midiProgramOffset

void editMidiProgramOffset(int value, bool shift) {
_midiProgramOffset += value;
}

void printMidiProgramOffset(StringBuilder &str) const {
str("%d", _midiProgramOffset);
}

void setMidiProgramOffset(int midiProgramOffset) {
_midiProgramOffset = midiProgramOffset;
}

int midiProgramOffset() {
return _midiProgramOffset;
}

// cvGateInput

Expand Down Expand Up @@ -468,7 +492,8 @@ class Project {
Types::MonitorMode _monitorMode;
Types::MidiInputMode _midiInputMode;
MidiSourceConfig _midiInputSource;
bool _midiPgmChange;
Types::MidiIntegrationMode _midiIntegrationMode;
uint8_t _midiProgramOffset;
Types::CvGateInput _cvGateInput;
Types::CurveCvInput _curveCvInput;

Expand Down
2 changes: 1 addition & 1 deletion src/apps/sequencer/model/ProjectVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ enum ProjectVersion {
// changed MidiCvTrack::VoiceConfig to 8-bit value
Version31 = 31,

// added Project::midiPgmChange and Project::alwaysSync
// added Project::midiIntegrationMode, Project::midiProgramOffset, Project::alwaysSync
Version32 = 32,

// automatically derive latest version
Expand Down
18 changes: 18 additions & 0 deletions src/apps/sequencer/model/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ class Types {
Last
};

// MidiIntegrationMode
enum class MidiIntegrationMode : uint8_t {
None,
ProgramChanges,
Malekko,
Last
};

static const char *midiIntegrationModeName(MidiIntegrationMode midiIntegrationMode) {
switch (midiIntegrationMode) {
case MidiIntegrationMode::None: return "None";
case MidiIntegrationMode::ProgramChanges: return "Program Changes";
case MidiIntegrationMode::Malekko: return "Malekko";
case MidiIntegrationMode::Last: break;
}
return nullptr;
}

// CvGateInput

enum class CvGateInput : uint8_t {
Expand Down
48 changes: 28 additions & 20 deletions src/apps/sequencer/ui/model/ProjectListModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,31 @@ class ProjectListModel : public RoutableListModel {
MonitorMode,
RecordMode,
MidiInput,
MidiPgmChange,
MidiIntegrationMode,
MidiProgramOffset,
CvGateInput,
CurveCvInput,
Last
};

static const char *itemName(Item item) {
switch (item) {
case Name: return "Name";
case Tempo: return "Tempo";
case Swing: return "Swing";
case TimeSignature: return "Time Signature";
case SyncMeasure: return "Sync Measure";
case AlwaysSync: return "Sync Patterns";
case Scale: return "Scale";
case RootNote: return "Root Note";
case MonitorMode: return "Monitor Mode";
case RecordMode: return "Record Mode";
case MidiInput: return "MIDI Input";
case MidiPgmChange: return "MIDI Pgm Chng";
case CvGateInput: return "CV/Gate Input";
case CurveCvInput: return "Curve CV Input";
case Last: break;
case Name: return "Name";
case Tempo: return "Tempo";
case Swing: return "Swing";
case TimeSignature: return "Time Signature";
case SyncMeasure: return "Sync Measure";
case AlwaysSync: return "Sync Patterns";
case Scale: return "Scale";
case RootNote: return "Root Note";
case MonitorMode: return "Monitor Mode";
case RecordMode: return "Record Mode";
case MidiInput: return "MIDI Input";
case MidiIntegrationMode: return "MIDI Integr.";
case MidiProgramOffset: return "MIDI Pgm Off.";
case CvGateInput: return "CV/Gate Input";
case CurveCvInput: return "Curve CV Input";
case Last: break;
}
return nullptr;
}
Expand Down Expand Up @@ -124,8 +126,11 @@ class ProjectListModel : public RoutableListModel {
case MidiInput:
_project.printMidiInput(str);
break;
case MidiPgmChange:
_project.printMidiPgmChange(str);
case MidiIntegrationMode:
_project.printMidiIntegrationMode(str);
break;
case MidiProgramOffset:
_project.printMidiProgramOffset(str);
break;
case CvGateInput:
_project.printCvGateInput(str);
Expand Down Expand Up @@ -172,8 +177,11 @@ class ProjectListModel : public RoutableListModel {
case MidiInput:
_project.editMidiInput(value, shift);
break;
case MidiPgmChange:
_project.editMidiPgmChange(value, shift);
case MidiIntegrationMode:
_project.editMidiIntegrationMode(value, shift);
break;
case MidiProgramOffset:
_project.editMidiProgramOffset(value, shift);
break;
case CvGateInput:
_project.editCvGateInput(value, shift);
Expand Down
Loading

0 comments on commit d72af37

Please sign in to comment.