Skip to content

Commit c985c00

Browse files
committed
Implement custom auto note-off offset
- Auto note-off offset can be set per-instrument - Adds tabs to the track settings dialog
1 parent 27b3c44 commit c985c00

24 files changed

+430
-198
lines changed

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ New features:
1717

1818
* Add tabs to track settings dialog
1919

20+
* Implement custom auto note-off offset
21+
- Auto note-off offset can be set per-instrument
22+
- Adds tabs to the track settings dialog
23+
2024
Bug fixes:
2125

2226
* Fix auto note-off offset calculation

src/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,11 @@ set(QML_SOURCE_FILES
195195
${QML_BASE_DIR}/Dialogs/SettingsDialog_GeneralSettings.qml
196196
${QML_BASE_DIR}/Dialogs/SettingsDialog_MidiSettings.qml
197197
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog.qml
198+
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_InstrumentSettings.qml
198199
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_MidiCcSettings_Custom.qml
199200
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_MidiCcSettings_Standard.qml
200-
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_MidiInstrumentSettings.qml
201-
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_MidiSettingsMiscellaneous.qml
201+
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_MidiEffects.qml
202+
${QML_BASE_DIR}/Dialogs/TrackSettingsDialog_TimingSettings.qml
202203
${QML_BASE_DIR}/Dialogs/UnsavedChangesDialog.qml
203204
${QML_BASE_DIR}/Editor/Cursor.qml
204205
${QML_BASE_DIR}/Editor/EditorView.qml

src/application/models/event_selection_model.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ EventSelectionModel::InstrumentSettingsU EventSelectionModel::toInstrumentSettin
162162
};
163163
}
164164
if (m_cutoffEnabled) {
165-
instrumentSettings->predefinedMidiCcSettings.cutoff = m_cutoff;
165+
instrumentSettings->standardMidiCcSettings.cutoff = m_cutoff;
166166
}
167167
if (m_panEnabled) {
168-
instrumentSettings->predefinedMidiCcSettings.pan = m_pan;
168+
instrumentSettings->standardMidiCcSettings.pan = m_pan;
169169
}
170170
if (m_volumeEnabled) {
171-
instrumentSettings->predefinedMidiCcSettings.volume = m_volume;
171+
instrumentSettings->standardMidiCcSettings.volume = m_volume;
172172
}
173173

174174
return instrumentSettings;
@@ -188,19 +188,19 @@ void EventSelectionModel::fromInstrumentSettings(const InstrumentSettings & inst
188188
setBankByteOrderSwapped(instrumentSettings.bank->byteOrderSwapped);
189189
}
190190

191-
setCutoffEnabled(instrumentSettings.predefinedMidiCcSettings.cutoff.has_value());
191+
setCutoffEnabled(instrumentSettings.standardMidiCcSettings.cutoff.has_value());
192192
if (cutoffEnabled()) {
193-
setCutoff(*instrumentSettings.predefinedMidiCcSettings.cutoff);
193+
setCutoff(*instrumentSettings.standardMidiCcSettings.cutoff);
194194
}
195195

196-
setPanEnabled(instrumentSettings.predefinedMidiCcSettings.pan.has_value());
196+
setPanEnabled(instrumentSettings.standardMidiCcSettings.pan.has_value());
197197
if (panEnabled()) {
198-
setPan(*instrumentSettings.predefinedMidiCcSettings.pan);
198+
setPan(*instrumentSettings.standardMidiCcSettings.pan);
199199
}
200200

201-
setVolumeEnabled(instrumentSettings.predefinedMidiCcSettings.volume.has_value());
201+
setVolumeEnabled(instrumentSettings.standardMidiCcSettings.volume.has_value());
202202
if (volumeEnabled()) {
203-
setVolume(*instrumentSettings.predefinedMidiCcSettings.volume);
203+
setVolume(*instrumentSettings.standardMidiCcSettings.volume);
204204
}
205205

206206
emit dataReceived();

src/application/models/track_settings_model.cpp

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -231,24 +231,30 @@ void TrackSettingsModel::setInstrumentData(const Instrument & instrument)
231231
setBankMsb(instrument.settings().bank->msb);
232232
setBankByteOrderSwapped(instrument.settings().bank->byteOrderSwapped);
233233
}
234-
setCutoffEnabled(instrument.settings().predefinedMidiCcSettings.cutoff.has_value());
234+
setTranspose(instrument.settings().transpose);
235+
236+
setCutoffEnabled(instrument.settings().standardMidiCcSettings.cutoff.has_value());
235237
if (cutoffEnabled()) {
236-
setCutoff(*instrument.settings().predefinedMidiCcSettings.cutoff);
238+
setCutoff(*instrument.settings().standardMidiCcSettings.cutoff);
237239
}
238-
setPanEnabled(instrument.settings().predefinedMidiCcSettings.pan.has_value());
240+
setPanEnabled(instrument.settings().standardMidiCcSettings.pan.has_value());
239241
if (panEnabled()) {
240-
setPan(*instrument.settings().predefinedMidiCcSettings.pan);
242+
setPan(*instrument.settings().standardMidiCcSettings.pan);
241243
}
242-
setVolumeEnabled(instrument.settings().predefinedMidiCcSettings.volume.has_value());
244+
setVolumeEnabled(instrument.settings().standardMidiCcSettings.volume.has_value());
243245
if (volumeEnabled()) {
244-
setVolume(*instrument.settings().predefinedMidiCcSettings.volume);
246+
setVolume(*instrument.settings().standardMidiCcSettings.volume);
245247
}
246-
setSendMidiClock(instrument.settings().sendMidiClock.has_value() && *instrument.settings().sendMidiClock);
247-
setSendTransport(instrument.settings().sendTransport.has_value() && *instrument.settings().sendTransport);
248248

249-
setDelay(static_cast<int>(instrument.settings().delay.count()));
250-
setTranspose(instrument.settings().transpose);
251-
setVelocityJitter(instrument.settings().velocityJitter);
249+
setSendMidiClock(instrument.settings().timing.sendMidiClock.has_value() && *instrument.settings().timing.sendMidiClock);
250+
setSendTransport(instrument.settings().timing.sendTransport.has_value() && *instrument.settings().timing.sendTransport);
251+
setAutoNoteOffOffsetEnabled(instrument.settings().timing.autoNoteOffOffset.has_value());
252+
if (autoNoteOffOffsetEnabled()) {
253+
setAutoNoteOffOffset(static_cast<int>(instrument.settings().timing.autoNoteOffOffset.value().count()));
254+
}
255+
setDelay(static_cast<int>(instrument.settings().timing.delay.count()));
256+
257+
setVelocityJitter(instrument.settings().midiEffects.velocityJitter);
252258

253259
setMidiCcSettings(instrument.settings().midiCcSettings);
254260

@@ -266,25 +272,27 @@ void TrackSettingsModel::reset()
266272
m_instrumentPortName = {};
267273
setAvailableMidiPorts(m_availableMidiPorts); // Update the list with instrument port name
268274

269-
m_portName = {};
270-
m_channel = 0;
271-
m_patchEnabled = false;
272-
m_patch = 0;
275+
m_autoNoteOffOffset = {};
276+
m_autoNoteOffOffsetEnabled = false;
277+
m_bankByteOrderSwapped = false;
273278
m_bankEnabled = false;
274279
m_bankLsb = 0;
275280
m_bankMsb = 0;
276-
m_bankByteOrderSwapped = false;
277-
m_cutoffEnabled = false;
281+
m_channel = 0;
278282
m_cutoff = m_defaultCutoff;
279-
m_panEnabled = false;
283+
m_cutoffEnabled = false;
284+
m_delay = 0;
280285
m_pan = m_defaultPan;
281-
m_volumeEnabled = false;
282-
m_volume = m_defaultVolume;
286+
m_panEnabled = false;
287+
m_patch = 0;
288+
m_patchEnabled = false;
289+
m_portName = {};
283290
m_sendMidiClock = false;
284291
m_sendTransport = false;
285-
m_delay = 0;
286292
m_transpose = 0;
287293
m_velocityJitter = 0;
294+
m_volume = m_defaultVolume;
295+
m_volumeEnabled = false;
288296

289297
setMidiCcSettings({});
290298

@@ -312,20 +320,29 @@ TrackSettingsModel::InstrumentU TrackSettingsModel::toInstrument() const
312320
m_bankByteOrderSwapped
313321
};
314322
}
323+
324+
settings.transpose = m_transpose;
325+
315326
if (m_cutoffEnabled) {
316-
settings.predefinedMidiCcSettings.cutoff = m_cutoff;
327+
settings.standardMidiCcSettings.cutoff = m_cutoff;
317328
}
318329
if (m_panEnabled) {
319-
settings.predefinedMidiCcSettings.pan = m_pan;
330+
settings.standardMidiCcSettings.pan = m_pan;
320331
}
321332
if (m_volumeEnabled) {
322-
settings.predefinedMidiCcSettings.volume = m_volume;
333+
settings.standardMidiCcSettings.volume = m_volume;
323334
}
324-
settings.sendMidiClock = m_sendMidiClock;
325-
settings.sendTransport = m_sendTransport;
326-
settings.delay = std::chrono::milliseconds { m_delay };
327-
settings.transpose = m_transpose;
328-
settings.velocityJitter = m_velocityJitter;
335+
336+
settings.timing.sendMidiClock = m_sendMidiClock;
337+
settings.timing.sendTransport = m_sendTransport;
338+
settings.timing.delay = std::chrono::milliseconds { m_delay };
339+
340+
if (m_autoNoteOffOffsetEnabled) {
341+
settings.timing.autoNoteOffOffset = std::chrono::milliseconds { m_autoNoteOffOffset };
342+
}
343+
344+
settings.midiEffects.velocityJitter = m_velocityJitter;
345+
329346
settings.midiCcSettings = midiCcSettings();
330347
instrument->setSettings(settings);
331348

@@ -521,6 +538,37 @@ void TrackSettingsModel::setVelocityJitter(int velocityJitter)
521538
}
522539
}
523540

541+
int TrackSettingsModel::autoNoteOffOffset() const
542+
{
543+
return m_autoNoteOffOffset;
544+
}
545+
546+
void TrackSettingsModel::setAutoNoteOffOffset(int autoNoteOffOffset)
547+
{
548+
juzzlin::L(TAG).debug() << "Setting auto note-off offset to " << autoNoteOffOffset;
549+
550+
if (m_autoNoteOffOffset != autoNoteOffOffset) {
551+
m_autoNoteOffOffset = autoNoteOffOffset;
552+
emit autoNoteOffOffsetChanged();
553+
}
554+
}
555+
556+
bool TrackSettingsModel::autoNoteOffOffsetEnabled() const
557+
{
558+
return m_autoNoteOffOffsetEnabled;
559+
}
560+
561+
void TrackSettingsModel::setAutoNoteOffOffsetEnabled(bool enabled)
562+
{
563+
juzzlin::L(TAG).debug() << "Enabling auto note-off offset: " << static_cast<int>(enabled);
564+
565+
if (m_autoNoteOffOffsetEnabled != enabled) {
566+
m_autoNoteOffOffsetEnabled = enabled;
567+
emit autoNoteOffOffsetEnabledChanged();
568+
applyAll();
569+
}
570+
}
571+
524572
void TrackSettingsModel::pushApplyDisabled()
525573
{
526574
m_applyDisabledStack.push_back(m_applyDisabled);

src/application/models/track_settings_model.hpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ class TrackSettingsModel : public MidiCcSelectionModel
5858

5959
Q_PROPERTY(bool sendMidiClock READ sendMidiClock WRITE setSendMidiClock NOTIFY sendMidiClockChanged)
6060
Q_PROPERTY(bool sendTransport READ sendTransport WRITE setSendTransport NOTIFY sendTransportChanged)
61+
6162
Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged)
6263
Q_PROPERTY(int transpose READ transpose WRITE setTranspose NOTIFY transposeChanged)
63-
6464
Q_PROPERTY(int velocityJitter READ velocityJitter WRITE setVelocityJitter NOTIFY velocityJitterChanged)
65+
Q_PROPERTY(int autoNoteOffOffset READ autoNoteOffOffset WRITE setAutoNoteOffOffset NOTIFY autoNoteOffOffsetChanged)
66+
Q_PROPERTY(bool autoNoteOffOffsetEnabled READ autoNoteOffOffsetEnabled WRITE setAutoNoteOffOffsetEnabled NOTIFY autoNoteOffOffsetEnabledChanged)
6567

6668
public:
6769
explicit TrackSettingsModel(QObject * parent = nullptr);
@@ -138,9 +140,12 @@ class TrackSettingsModel : public MidiCcSelectionModel
138140
void setDelay(int delay);
139141
int transpose() const;
140142
void setTranspose(int transpose);
141-
142143
int velocityJitter() const;
143144
void setVelocityJitter(int velocityJitter);
145+
int autoNoteOffOffset() const;
146+
void setAutoNoteOffOffset(int autoNoteOffOffset);
147+
bool autoNoteOffOffsetEnabled() const;
148+
void setAutoNoteOffOffsetEnabled(bool enabled);
144149

145150
signals:
146151
void applyAllRequested();
@@ -184,8 +189,9 @@ class TrackSettingsModel : public MidiCcSelectionModel
184189

185190
void delayChanged();
186191
void transposeChanged();
187-
188192
void velocityJitterChanged();
193+
void autoNoteOffOffsetChanged();
194+
void autoNoteOffOffsetEnabledChanged();
189195

190196
private:
191197
void pushApplyDisabled();
@@ -196,7 +202,7 @@ class TrackSettingsModel : public MidiCcSelectionModel
196202
QString m_portName;
197203
QStringList m_availableMidiPorts;
198204

199-
bool m_applyDisabled = false;
205+
bool m_applyDisabled { false };
200206
bool m_bankByteOrderSwapped { false };
201207
bool m_bankEnabled { false };
202208
bool m_cutoffEnabled { false };
@@ -205,6 +211,7 @@ class TrackSettingsModel : public MidiCcSelectionModel
205211
bool m_volumeEnabled { false };
206212
bool m_sendMidiClock { false };
207213
bool m_sendTransport { false };
214+
bool m_autoNoteOffOffsetEnabled { false };
208215

209216
quint64 m_trackIndex { 0 };
210217

@@ -227,6 +234,7 @@ class TrackSettingsModel : public MidiCcSelectionModel
227234
int m_transpose { 0 };
228235

229236
int m_velocityJitter { 0 };
237+
int m_autoNoteOffOffset { 0 };
230238
};
231239

232240
} // namespace noteahead

src/application/service/midi_out_worker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ void MidiOutWorker::initializeStopTimer()
6767
void MidiOutWorker::sendMidiCcSettings(const MidiDevice & midiDevice, const Instrument & instrument)
6868
{
6969
const auto channel = instrument.midiAddress().channel();
70-
const auto predefinedMidiCcSettings = instrument.settings().predefinedMidiCcSettings;
70+
const auto predefinedMidiCcSettings = instrument.settings().standardMidiCcSettings;
7171
m_midiOut->sendCcData(midiDevice, channel, static_cast<quint8>(MidiCcMapping::Controller::ResetAllControllers), 127);
7272
if (predefinedMidiCcSettings.pan.has_value()) {
7373
m_midiOut->sendCcData(midiDevice, channel, static_cast<quint8>(MidiCcMapping::Controller::PanMSB), *predefinedMidiCcSettings.pan);

src/application/service/player_worker.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,16 @@ void PlayerWorker::handleEvent(const Event & event) const
103103
}
104104
} else if (event.type() == Event::Type::MidiClockOut) {
105105
// There should not be any clock events generated if disabled, but double-checking won't make any harm
106-
if (auto && instrument = event.instrument(); instrument && instrument->settings().sendMidiClock.has_value() && *instrument->settings().sendMidiClock) {
106+
if (auto && instrument = event.instrument(); instrument && instrument->settings().timing.sendMidiClock.has_value() && *instrument->settings().timing.sendMidiClock) {
107107
m_midiService->sendClock(instrument);
108108
}
109109
} else if (event.type() == Event::Type::StartOfSong) {
110-
if (auto && instrument = event.instrument(); instrument && instrument->settings().sendTransport.has_value() && *instrument->settings().sendTransport) {
110+
if (auto && instrument = event.instrument(); instrument && instrument->settings().timing.sendTransport.has_value() && *instrument->settings().timing.sendTransport) {
111111
juzzlin::L(TAG).info() << "Sending start to " << instrument->midiAddress().portName().toStdString();
112112
m_midiService->sendStart(instrument);
113113
}
114114
} else if (event.type() == Event::Type::EndOfSong) {
115-
if (auto && instrument = event.instrument(); instrument && instrument->settings().sendTransport.has_value() && *instrument->settings().sendTransport) {
115+
if (auto && instrument = event.instrument(); instrument && instrument->settings().timing.sendTransport.has_value() && *instrument->settings().timing.sendTransport) {
116116
juzzlin::L(TAG).info() << "Sending stop to " << instrument->midiAddress().portName().toStdString();
117117
m_midiService->sendStop(instrument);
118118
}
@@ -207,7 +207,7 @@ void PlayerWorker::stopTransport()
207207
{
208208
juzzlin::L(TAG).info() << "Stopping transport";
209209
for (auto && instrument : m_allInstruments) {
210-
if (instrument->settings().sendTransport.has_value() && *instrument->settings().sendTransport) {
210+
if (instrument->settings().timing.sendTransport.has_value() && *instrument->settings().timing.sendTransport) {
211211
juzzlin::L(TAG).info() << "Sending stop to " << instrument->midiAddress().portName().toStdString();
212212
m_midiService->sendStop(instrument);
213213
}

src/common/constants.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ QString xmlKeyVelocityJitter()
164164
return "velocityJitter";
165165
}
166166

167+
QString xmlKeyAutoNoteOffOffset()
168+
{
169+
return "autoNoteOffOffset";
170+
}
171+
167172
QString xmlKeyIndex()
168173
{
169174
return "index";

src/common/constants.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ QString xmlKeyDelay();
6666
QString xmlKeyTranspose();
6767

6868
QString xmlKeyVelocityJitter();
69+
QString xmlKeyAutoNoteOffOffset();
6970

7071
QString xmlKeyIndex();
7172

src/common/utils.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,18 @@ std::optional<QString> readStringAttribute(QXmlStreamReader & reader, QString na
113113
return reader.attributes().value(name).toString();
114114
}
115115
}
116+
117+
std::optional<std::chrono::milliseconds> readMSecAttribute(QXmlStreamReader & reader, QString name, bool required)
118+
{
119+
if (!reader.attributes().hasAttribute(name)) {
120+
if (required) {
121+
throw std::runtime_error { "Attribute '" + name.toStdString() + "' not found!" };
122+
}
123+
return {};
124+
} else {
125+
return static_cast<std::chrono::milliseconds>(reader.attributes().value(name).toInt());
126+
}
127+
}
128+
116129
} // namespace Xml
117130
} // namespace noteahead::Utils

0 commit comments

Comments
 (0)