diff --git a/data/locale/ar.ts b/data/locale/ar.ts index 1f159c42a..0d44c22bf 100644 --- a/data/locale/ar.ts +++ b/data/locale/ar.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/bs.ts b/data/locale/bs.ts index 506b401bd..7abf0baf1 100644 --- a/data/locale/bs.ts +++ b/data/locale/bs.ts @@ -3677,7 +3677,7 @@ You can remove and move mixer channels in the context menu, which is accessed by - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ca.ts b/data/locale/ca.ts index 765cf3b60..0e27c39db 100644 --- a/data/locale/ca.ts +++ b/data/locale/ca.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/cs.ts b/data/locale/cs.ts index 0ed175022..022f55459 100644 --- a/data/locale/cs.ts +++ b/data/locale/cs.ts @@ -6361,7 +6361,7 @@ Ověřte si prosím, zda máte povolen zápis do souboru a do složky, ve které - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/de.ts b/data/locale/de.ts index 51ca7d562..7817857fd 100644 --- a/data/locale/de.ts +++ b/data/locale/de.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/el.ts b/data/locale/el.ts index 320a6657f..07e61778f 100644 --- a/data/locale/el.ts +++ b/data/locale/el.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/en.ts b/data/locale/en.ts index e52ae39ab..15c3ab1f0 100644 --- a/data/locale/en.ts +++ b/data/locale/en.ts @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/eo.ts b/data/locale/eo.ts index 005ee8100..0dd9c405f 100644 --- a/data/locale/eo.ts +++ b/data/locale/eo.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/es.ts b/data/locale/es.ts index 4fc4951ef..3953ddc11 100644 --- a/data/locale/es.ts +++ b/data/locale/es.ts @@ -6361,7 +6361,7 @@ Asegúrate de tener permisos de escritura tanto del archivo como del directorio - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/eu.ts b/data/locale/eu.ts index 25c165f81..fe6495c0a 100644 --- a/data/locale/eu.ts +++ b/data/locale/eu.ts @@ -6641,7 +6641,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/fa.ts b/data/locale/fa.ts index 181ca0ca1..b376a8424 100644 --- a/data/locale/fa.ts +++ b/data/locale/fa.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/fr.ts b/data/locale/fr.ts index 2c65444a8..4862f4263 100644 --- a/data/locale/fr.ts +++ b/data/locale/fr.ts @@ -6645,7 +6645,7 @@ Veuillez vous assurez que vous avez les droits d'écriture sur le fichier e - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/gl.ts b/data/locale/gl.ts index cf04fd5d4..a1a9e6bf1 100644 --- a/data/locale/gl.ts +++ b/data/locale/gl.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/he.ts b/data/locale/he.ts index ee5a23613..fef0caa91 100644 --- a/data/locale/he.ts +++ b/data/locale/he.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/hi_IN.ts b/data/locale/hi_IN.ts index 15550231f..82cf364e3 100644 --- a/data/locale/hi_IN.ts +++ b/data/locale/hi_IN.ts @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/hu_HU.ts b/data/locale/hu_HU.ts index 836059946..a0f1e4d45 100644 --- a/data/locale/hu_HU.ts +++ b/data/locale/hu_HU.ts @@ -6366,7 +6366,7 @@ Ellenőrizd, hogy rendelkezel-e a szükséges engedélyekkel és próbáld újra - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/id.ts b/data/locale/id.ts index e381ea726..c504740e9 100644 --- a/data/locale/id.ts +++ b/data/locale/id.ts @@ -6362,7 +6362,7 @@ Pastikan Anda memiliki izin menulis ke file dan direktori yang berisi berkas ter - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/it.ts b/data/locale/it.ts index ff146d471..d5a68e6e7 100644 --- a/data/locale/it.ts +++ b/data/locale/it.ts @@ -6366,7 +6366,7 @@ Si prega di controllare i permessi di scrittura sul file e la cartella che lo co - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ja.ts b/data/locale/ja.ts index e10ca5118..14b38c698 100644 --- a/data/locale/ja.ts +++ b/data/locale/ja.ts @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ka.ts b/data/locale/ka.ts index 1956d8d04..51eededf2 100644 --- a/data/locale/ka.ts +++ b/data/locale/ka.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ko.ts b/data/locale/ko.ts index 7373b5ca9..43b99e7f4 100644 --- a/data/locale/ko.ts +++ b/data/locale/ko.ts @@ -6364,7 +6364,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ms_MY.ts b/data/locale/ms_MY.ts index 209d51d10..ff3478421 100644 --- a/data/locale/ms_MY.ts +++ b/data/locale/ms_MY.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/nb.ts b/data/locale/nb.ts index 3675b7f58..659344d64 100644 --- a/data/locale/nb.ts +++ b/data/locale/nb.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/nl.ts b/data/locale/nl.ts index ad630a249..7ff3e8735 100644 --- a/data/locale/nl.ts +++ b/data/locale/nl.ts @@ -6362,7 +6362,7 @@ Zorg ervoor dat u schrijfbevoegdheid heeft voor het bestand en voor de map die h - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/oc.ts b/data/locale/oc.ts index 58c81c964..045eaf3ad 100644 --- a/data/locale/oc.ts +++ b/data/locale/oc.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/pl.ts b/data/locale/pl.ts index bb0c64ede..ff36a8dac 100644 --- a/data/locale/pl.ts +++ b/data/locale/pl.ts @@ -6646,7 +6646,7 @@ Upewnij się, że masz uprawnienia do zapisu do pliku i katalogu zawierającego - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/pt.ts b/data/locale/pt.ts index b375e289f..f8cfe7618 100644 --- a/data/locale/pt.ts +++ b/data/locale/pt.ts @@ -6363,7 +6363,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ro.ts b/data/locale/ro.ts index eceb45a64..58abbba99 100644 --- a/data/locale/ro.ts +++ b/data/locale/ro.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ru.ts b/data/locale/ru.ts index 8235f291f..73b7e06ad 100644 --- a/data/locale/ru.ts +++ b/data/locale/ru.ts @@ -6375,7 +6375,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sl.ts b/data/locale/sl.ts index 3ad55a4c0..e7bfbc308 100644 --- a/data/locale/sl.ts +++ b/data/locale/sl.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sr.ts b/data/locale/sr.ts index 9b90164ab..183936bc7 100644 --- a/data/locale/sr.ts +++ b/data/locale/sr.ts @@ -2956,7 +2956,7 @@ You can remove and move mixer channels in the context menu, which is accessed by - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sv.ts b/data/locale/sv.ts index 4963b07a9..f5d4e0fb4 100644 --- a/data/locale/sv.ts +++ b/data/locale/sv.ts @@ -6644,7 +6644,7 @@ Se till att du har skrivbehörighet till filen och mappen som innehåller filen - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/tr.ts b/data/locale/tr.ts index 387be6d8b..b899337a5 100644 --- a/data/locale/tr.ts +++ b/data/locale/tr.ts @@ -6646,7 +6646,7 @@ Lütfen dosyaya ve dosyayı içeren dizine yazma izniniz olduğundan emin olun v - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/uk.ts b/data/locale/uk.ts index 50df10e4b..9fb6389c9 100644 --- a/data/locale/uk.ts +++ b/data/locale/uk.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/zh_CN.ts b/data/locale/zh_CN.ts index 63b22df99..9b783b963 100644 --- a/data/locale/zh_CN.ts +++ b/data/locale/zh_CN.ts @@ -6370,7 +6370,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/zh_TW.ts b/data/locale/zh_TW.ts index 791a45599..a3a727edb 100644 --- a/data/locale/zh_TW.ts +++ b/data/locale/zh_TW.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/samples/drums/kick04.ogg b/data/samples/drums/kick04.ogg index 567480abd..8f7dce527 100644 Binary files a/data/samples/drums/kick04.ogg and b/data/samples/drums/kick04.ogg differ diff --git a/data/samples/effects/scratch01.ogg b/data/samples/effects/scratch01.ogg index 9f216038d..0b05505cd 100644 Binary files a/data/samples/effects/scratch01.ogg and b/data/samples/effects/scratch01.ogg differ diff --git a/data/samples/effects/wind_chimes01.ogg b/data/samples/effects/wind_chimes01.ogg index 35d3374a2..7fb3c441a 100644 Binary files a/data/samples/effects/wind_chimes01.ogg and b/data/samples/effects/wind_chimes01.ogg differ diff --git a/data/samples/instruments/harpsichord01.ogg b/data/samples/instruments/harpsichord01.ogg index 028bbd912..c84ffd7df 100644 Binary files a/data/samples/instruments/harpsichord01.ogg and b/data/samples/instruments/harpsichord01.ogg differ diff --git a/data/samples/misc/hit01.ogg b/data/samples/misc/hit01.ogg index d5e93633e..30dd86135 100644 Binary files a/data/samples/misc/hit01.ogg and b/data/samples/misc/hit01.ogg differ diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index d1f4d0588..ac22a14ec 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -209,6 +209,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; background: url(resources:cpuload_bg.png); + qproperty-stepSize: 4; } /* scrollbar: trough */ diff --git a/data/themes/default/edit_draw_small.png b/data/themes/default/edit_draw_small.png new file mode 100644 index 000000000..9979c8223 Binary files /dev/null and b/data/themes/default/edit_draw_small.png differ diff --git a/data/themes/default/style.css b/data/themes/default/style.css index e88e51e5b..092332eee 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -241,6 +241,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; background: url(resources:cpuload_bg.png); + qproperty-stepSize: 1; } /* scrollbar: trough */ diff --git a/data/themes/default/tuning_tab.png b/data/themes/default/tuning_tab.png new file mode 100644 index 000000000..41c4f2d9f Binary files /dev/null and b/data/themes/default/tuning_tab.png differ diff --git a/include/AudioEngine.h b/include/AudioEngine.h index 030c5bce3..f056c22e1 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -275,6 +275,11 @@ public: return m_profiler.cpuLoad(); } + int detailLoad(const AudioEngineProfiler::DetailType type) const + { + return m_profiler.detailLoad(type); + } + const qualitySettings & currentQualitySettings() const { return m_qualitySettings; @@ -401,6 +406,10 @@ private: AudioDevice * tryAudioDevices(); MidiClient * tryMidiClients(); + void renderStageNoteSetup(); + void renderStageInstruments(); + void renderStageEffects(); + void renderStageMix(); const surroundSampleFrame * renderNextBuffer(); diff --git a/include/AudioEngineProfiler.h b/include/AudioEngineProfiler.h index 7b5191e76..b0d62a1dc 100644 --- a/include/AudioEngineProfiler.h +++ b/include/AudioEngineProfiler.h @@ -25,6 +25,8 @@ #ifndef LMMS_AUDIO_ENGINE_PROFILER_H #define LMMS_AUDIO_ENGINE_PROFILER_H +#include +#include #include #include "lmms_basics.h" @@ -53,11 +55,55 @@ public: void setOutputFile( const QString& outputFile ); + enum class DetailType { + NoteSetup, + Instruments, + Effects, + Mixing, + Count + }; + + constexpr static auto DetailCount = static_cast(DetailType::Count); + + int detailLoad(const DetailType type) const + { + return m_detailLoad[static_cast(type)].load(std::memory_order_relaxed); + } + + class Probe + { + public: + Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type) + : m_profiler(profiler) + , m_type(type) + { + profiler.startDetail(type); + } + ~Probe() { m_profiler.finishDetail(m_type); } + Probe& operator=(const Probe&) = delete; + Probe(const Probe&) = delete; + Probe(Probe&&) = delete; + + private: + AudioEngineProfiler &m_profiler; + const AudioEngineProfiler::DetailType m_type; + }; private: + void startDetail(const DetailType type) { m_detailTimer[static_cast(type)].reset(); } + void finishDetail(const DetailType type) + { + m_detailTime[static_cast(type)] = m_detailTimer[static_cast(type)].elapsed(); + } + MicroTimer m_periodTimer; - int m_cpuLoad; + std::atomic m_cpuLoad; QFile m_outputFile; + + // Use arrays to avoid dynamic allocations in realtime code + std::array m_detailTimer; + std::array m_detailTime{0}; + std::array, DetailCount> m_detailLoad{0}; }; } // namespace lmms diff --git a/include/CPULoadWidget.h b/include/CPULoadWidget.h index 904445c67..dfa5bac73 100644 --- a/include/CPULoadWidget.h +++ b/include/CPULoadWidget.h @@ -26,6 +26,7 @@ #ifndef LMMS_GUI_CPU_LOAD_WIDGET_H #define LMMS_GUI_CPU_LOAD_WIDGET_H +#include #include #include #include @@ -40,6 +41,7 @@ namespace lmms::gui class CPULoadWidget : public QWidget { Q_OBJECT + Q_PROPERTY(int stepSize MEMBER m_stepSize) public: CPULoadWidget( QWidget * _parent ); ~CPULoadWidget() override = default; @@ -54,6 +56,8 @@ protected slots: private: + int stepSize() const { return std::max(1, m_stepSize); } + int m_currentLoad; QPixmap m_temp; @@ -64,6 +68,8 @@ private: QTimer m_updateTimer; + int m_stepSize; + } ; diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index f21723363..5efafe0c7 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -51,7 +51,7 @@ namespace gui class InstrumentTrackView; class InstrumentTrackWindow; -class InstrumentMiscView; +class InstrumentTuningView; class MidiCCRackView; } // namespace gui @@ -315,7 +315,7 @@ private: friend class gui::InstrumentTrackView; friend class gui::InstrumentTrackWindow; friend class NotePlayHandle; - friend class gui::InstrumentMiscView; + friend class gui::InstrumentTuningView; friend class gui::MidiCCRackView; } ; diff --git a/include/InstrumentTrackWindow.h b/include/InstrumentTrackWindow.h index d41bbdac8..971c63899 100644 --- a/include/InstrumentTrackWindow.h +++ b/include/InstrumentTrackWindow.h @@ -47,7 +47,7 @@ class MixerLineLcdSpinBox; class InstrumentFunctionArpeggioView; class InstrumentFunctionNoteStackingView; class InstrumentMidiIOView; -class InstrumentMiscView; +class InstrumentTuningView; class InstrumentSoundShapingView; class InstrumentTrackShapingView; class InstrumentTrackView; @@ -154,7 +154,7 @@ private: InstrumentFunctionArpeggioView* m_arpeggioView; InstrumentMidiIOView * m_midiView; EffectRackView * m_effectView; - InstrumentMiscView *m_miscView; + InstrumentTuningView *m_tuningView; // test-piano at the bottom of every instrument-settings-window diff --git a/include/InstrumentMiscView.h b/include/InstrumentTuningView.h similarity index 72% rename from include/InstrumentMiscView.h rename to include/InstrumentTuningView.h index 28f3c6a8e..4ee18dc84 100644 --- a/include/InstrumentMiscView.h +++ b/include/InstrumentTuningView.h @@ -1,9 +1,9 @@ /* - * InstrumentMiscView.h - widget in instrument-track-window for setting up - * miscellaneous options not covered by other tabs + * InstrumentTuningView.h - widget in instrument-track-window for setting up + * tuning and transposition options * * Copyright (c) 2005-2014 Tobias Doerffel - * Copyright (c) 2020 Martin Pavelek + * Copyright (c) 2020-2022 Martin Pavelek * * This file is part of LMMS - https://lmms.io * @@ -24,11 +24,13 @@ * */ -#ifndef LMMS_GUI_INSTRUMENT_MISC_VIEW_H -#define LMMS_GUI_INSTRUMENT_MISC_VIEW_H +#ifndef LMMS_GUI_INSTRUMENT_TUNING_VIEW_H +#define LMMS_GUI_INSTRUMENT_TUNING_VIEW_H #include +class QLabel; + namespace lmms { @@ -42,15 +44,17 @@ class GroupBox; class LedCheckBox; -class InstrumentMiscView : public QWidget +class InstrumentTuningView : public QWidget { Q_OBJECT public: - InstrumentMiscView(InstrumentTrack *it, QWidget *parent); + InstrumentTuningView(InstrumentTrack *it, QWidget *parent); GroupBox *pitchGroupBox() {return m_pitchGroupBox;} GroupBox *microtunerGroupBox() {return m_microtunerGroupBox;} + QLabel *microtunerNotSupportedLabel() {return m_microtunerNotSupportedLabel;} + ComboBox *scaleCombo() {return m_scaleCombo;} ComboBox *keymapCombo() {return m_keymapCombo;} @@ -60,6 +64,8 @@ private: GroupBox *m_pitchGroupBox; GroupBox *m_microtunerGroupBox; + QLabel *m_microtunerNotSupportedLabel; + ComboBox *m_scaleCombo; ComboBox *m_keymapCombo; @@ -71,4 +77,4 @@ private: } // namespace lmms -#endif // LMMS_GUI_INSTRUMENT_MISC_VIEW_H +#endif // LMMS_GUI_INSTRUMENT_TUNING_VIEW_H diff --git a/include/MidiClip.h b/include/MidiClip.h index 43b322f80..c2287bd00 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -79,7 +79,7 @@ public: void setStep( int step, bool enabled ); // Split the list of notes on the given position - void splitNotes(NoteVector notes, TimePos pos); + void splitNotes(const NoteVector& notes, TimePos pos); // clip-type stuff inline Type type() const diff --git a/include/Note.h b/include/Note.h index 5e3a1b8a2..2df196af2 100644 --- a/include/Note.h +++ b/include/Note.h @@ -91,7 +91,7 @@ const int DefaultMiddleKey = Octave::Octave_4 + Key::C; const int DefaultBaseKey = Octave::Octave_4 + Key::A; const float DefaultBaseFreq = 440.f; -const float MaxDetuning = 4 * 12.0f; +const float MaxDetuning = 5 * 12.0f; diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 61df5a77a..7105d6672 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -248,7 +248,7 @@ public: } /*! Process note detuning automation */ - void processTimePos( const TimePos& time ); + void processTimePos(const TimePos& time, float pitchValue, bool isRecording); /*! Updates total length (m_frames) depending on a new tempo */ void resize( const bpm_t newTempo ); diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 9f3bbcd7d..38788180f 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -308,9 +308,9 @@ private: TimePos newNoteLen() const; void shiftPos(int amount); - void shiftPos(NoteVector notes, int amount); + void shiftPos(const NoteVector& notes, int amount); void shiftSemiTone(int amount); - void shiftSemiTone(NoteVector notes, int amount); + void shiftSemiTone(const NoteVector& notes, int amount); bool isSelection() const; int selectionCount() const; void testPlayNote( Note * n ); diff --git a/plugins/LadspaEffect/caps/Descriptor.h b/plugins/LadspaEffect/caps/Descriptor.h index 12c5d1c88..c3e1c325e 100644 --- a/plugins/LadspaEffect/caps/Descriptor.h +++ b/plugins/LadspaEffect/caps/Descriptor.h @@ -53,7 +53,7 @@ class DescriptorStub PortCount = 0; } - ~DescriptorStub() + virtual ~DescriptorStub() { if (PortCount) { @@ -87,6 +87,7 @@ class Descriptor public: Descriptor() { setup(); } + ~Descriptor() override = default; void setup(); void autogen() diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 21a9a3598..2d077babc 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -333,12 +333,9 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) - -const surroundSampleFrame * AudioEngine::renderNextBuffer() +void AudioEngine::renderStageNoteSetup() { - m_profiler.startPeriod(); - - s_renderingThread = true; + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::NoteSetup); if( m_clearSignal ) { @@ -387,9 +384,15 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() m_newPlayHandles.free( e ); e = next; } +} - // STAGE 1: run and render all play handles - AudioEngineWorkerThread::fillJobQueue( m_playHandles ); + + +void AudioEngine::renderStageInstruments() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments); + + AudioEngineWorkerThread::fillJobQueue(m_playHandles); AudioEngineWorkerThread::startAndWaitForJobs(); // removed all play handles which are done @@ -417,16 +420,28 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() ++it; } } +} + + + +void AudioEngine::renderStageEffects() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects); // STAGE 2: process effects of all instrument- and sampletracks AudioEngineWorkerThread::fillJobQueue(m_audioPorts); AudioEngineWorkerThread::startAndWaitForJobs(); +} - // STAGE 3: do master mix in mixer + +void AudioEngine::renderStageMix() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing); + + Mixer *mixer = Engine::mixer(); mixer->masterMix(m_outputBufferWrite); - emit nextAudioBuffer(m_outputBufferRead); runChangesInModel(); @@ -435,10 +450,22 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() EnvelopeAndLfoParameters::instances()->trigger(); Controller::triggerFrameCounter(); AutomatableModel::incrementPeriodCounter(); +} + + + +const surroundSampleFrame *AudioEngine::renderNextBuffer() +{ + m_profiler.startPeriod(); + s_renderingThread = true; + + renderStageNoteSetup(); // STAGE 0: clear old play handles and buffers, setup new play handles + renderStageInstruments(); // STAGE 1: run and render all play handles + renderStageEffects(); // STAGE 2: process effects of all instrument- and sampletracks + renderStageMix(); // STAGE 3: do master mix in mixer s_renderingThread = false; - - m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod ); + m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod); return m_outputBufferRead; } diff --git a/src/core/AudioEngineProfiler.cpp b/src/core/AudioEngineProfiler.cpp index 9e05ff80a..82a412cbb 100644 --- a/src/core/AudioEngineProfiler.cpp +++ b/src/core/AudioEngineProfiler.cpp @@ -24,6 +24,8 @@ #include "AudioEngineProfiler.h" +#include + namespace lmms { @@ -38,10 +40,24 @@ AudioEngineProfiler::AudioEngineProfiler() : void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod ) { - int periodElapsed = m_periodTimer.elapsed(); + // Time taken to process all data and fill the audio buffer. + const unsigned int periodElapsed = m_periodTimer.elapsed(); + // Maximum time the processing can take before causing buffer underflow. Convert to us. + const uint64_t timeLimit = static_cast(1000000) * framesPerPeriod / sampleRate; - const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod; - m_cpuLoad = std::clamp((newCpuLoad * 0.1f + m_cpuLoad * 0.9f), 0, 100); + // Compute new overall CPU load and apply exponential averaging. + // The result is used for overload detection in AudioEngine::criticalXRuns() + // → the weight of a new sample must be high enough to allow relatively fast changes! + const auto newCpuLoad = 100.f * periodElapsed / timeLimit; + m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f; + + // Compute detailed load analysis. Can use stronger averaging to get more stable readout. + for (std::size_t i = 0; i < DetailCount; i++) + { + const auto newLoad = 100.f * m_detailTime[i] / timeLimit; + const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed); + m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed); + } if( m_outputFile.isOpen() ) { diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index 906cb148c..3b36f6b49 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -1106,16 +1106,16 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) { QMutexLocker m(&m_clipMutex); - if( m_timeMap.size() < 2 && numToGenerate > 0 ) + for (int i = 0; i < numToGenerate && it != m_timeMap.end(); ++i, ++it) { - it.value().setInTangent(0); - it.value().setOutTangent(0); - return; - } - - for( int i = 0; i < numToGenerate; i++ ) - { - if( it == m_timeMap.begin() ) + if (it + 1 == m_timeMap.end()) + { + // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents + // of the last node + it.value().setInTangent(0); + it.value().setOutTangent(0); + } + else if (it == m_timeMap.begin()) { // On the first node there's no curve behind it, so we will only calculate the outTangent // and inTangent will be set to 0. @@ -1123,14 +1123,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) it.value().setInTangent(0); it.value().setOutTangent(tangent); } - else if( it+1 == m_timeMap.end() ) - { - // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents - // of the last node - it.value().setInTangent(0); - it.value().setOutTangent(0); - return; - } else { // When we are in a node that is in the middle of two other nodes, we need to check if we @@ -1159,7 +1151,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) it.value().setOutTangent(outTangent); } } - it++; } } diff --git a/src/core/EffectChain.cpp b/src/core/EffectChain.cpp index b07a7227b..4da5c5197 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -25,6 +25,7 @@ #include +#include #include "EffectChain.h" #include "Effect.h" @@ -162,6 +163,7 @@ void EffectChain::moveDown( Effect * _effect ) if (_effect != m_effects.back()) { auto it = std::find(m_effects.begin(), m_effects.end(), _effect); + assert(it != m_effects.end()); std::swap(*std::next(it), *it); } } @@ -174,6 +176,7 @@ void EffectChain::moveUp( Effect * _effect ) if (_effect != m_effects.front()) { auto it = std::find(m_effects.begin(), m_effects.end(), _effect); + assert(it != m_effects.end()); std::swap(*std::prev(it), *it); } } diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index e14660e1f..59c2dd72e 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -394,8 +394,8 @@ void Mixer::moveChannelLeft( int index ) else if (m_lastSoloed == b) { m_lastSoloed = a; } // go through every instrument and adjust for the channel index change - TrackContainer::TrackList songTrackList = Engine::getSong()->tracks(); - TrackContainer::TrackList patternTrackList = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& songTrackList = Engine::getSong()->tracks(); + const TrackContainer::TrackList& patternTrackList = Engine::patternStore()->tracks(); for (const auto& trackList : {songTrackList, patternTrackList}) { diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 70007ebf1..eb9c7ddbf 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -557,14 +557,20 @@ void NotePlayHandle::updateFrequency() -void NotePlayHandle::processTimePos( const TimePos& time ) +void NotePlayHandle::processTimePos(const TimePos& time, float pitchValue, bool isRecording) { - if( detuning() && time >= songGlobalParentOffset()+pos() ) + if (!detuning() || time < songGlobalParentOffset() + pos()) { return; } + + if (isRecording && m_origin == Origin::MidiInput) { - const float v = detuning()->automationClip()->valueAt( time - songGlobalParentOffset() - pos() ); - if( !typeInfo::isEqual( v, m_baseDetuning->value() ) ) + detuning()->automationClip()->recordValue(time - songGlobalParentOffset() - pos(), pitchValue / 100); + } + else + { + const float v = detuning()->automationClip()->valueAt(time - songGlobalParentOffset() - pos()); + if (!typeInfo::isEqual(v, m_baseDetuning->value())) { - m_baseDetuning->setValue( v ); + m_baseDetuning->setValue(v); updateFrequency(); } } diff --git a/src/core/PatternStore.cpp b/src/core/PatternStore.cpp index c5a352139..6af434f65 100644 --- a/src/core/PatternStore.cpp +++ b/src/core/PatternStore.cpp @@ -61,7 +61,7 @@ bool PatternStore::play(TimePos start, fpp_t frames, f_cnt_t offset, int clipNum start = start % (lengthOfPattern(clipNum) * TimePos::ticksPerBar()); - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { if (t->play(start, frames, offset, clipNum)) @@ -117,7 +117,7 @@ int PatternStore::numOfPatterns() const void PatternStore::removePattern(int pattern) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { delete t->getClip(pattern); @@ -134,7 +134,7 @@ void PatternStore::removePattern(int pattern) void PatternStore::swapPattern(int pattern1, int pattern2) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { t->swapPositionOfClips(pattern1, pattern2); @@ -159,7 +159,7 @@ void PatternStore::updatePatternTrack(Clip* clip) void PatternStore::fixIncorrectPositions() { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { for (int i = 0; i < numOfPatterns(); ++i) @@ -215,7 +215,7 @@ void PatternStore::updateComboBox() void PatternStore::currentPatternChanged() { // now update all track-labels (the current one has to become white, the others gray) - TrackList tl = Engine::getSong()->tracks(); + const TrackList& tl = Engine::getSong()->tracks(); for (Track * t : tl) { if (t->type() == Track::Type::Pattern) @@ -230,7 +230,7 @@ void PatternStore::currentPatternChanged() void PatternStore::createClipsForPattern(int pattern) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { t->createClipsForPattern(pattern); diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index 969cad15b..9f6192039 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -97,7 +97,7 @@ void RenderManager::renderNextTrack() // Render the song into individual tracks void RenderManager::renderTracks() { - const TrackContainer::TrackList & tl = Engine::getSong()->tracks(); + const TrackContainer::TrackList& tl = Engine::getSong()->tracks(); // find all currently unnmuted tracks -- we want to render these. for (const auto& tk : tl) @@ -112,7 +112,7 @@ void RenderManager::renderTracks() } } - const TrackContainer::TrackList t2 = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& t2 = Engine::patternStore()->tracks(); for (const auto& tk : t2) { Track::Type type = tk->type(); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index e8073f225..3a735331c 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -383,7 +383,7 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp } values = container->automatedValuesAt(timeStart, clipNum); - TrackList tracks = container->tracks(); + const TrackList& tracks = container->tracks(); Track::clipVector clips; for (Track* track : tracks) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 24d97aa97..146da9ab9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -62,7 +62,7 @@ SET(LMMS_SRCS gui/instrument/EnvelopeAndLfoView.cpp gui/instrument/InstrumentFunctionViews.cpp gui/instrument/InstrumentMidiIOView.cpp - gui/instrument/InstrumentMiscView.cpp + gui/instrument/InstrumentTuningView.cpp gui/instrument/InstrumentSoundShapingView.cpp gui/instrument/InstrumentTrackWindow.cpp gui/instrument/InstrumentView.cpp diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 559756169..10805fe01 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -363,10 +363,12 @@ void MainWindow::finalize() } edit_menu->addSeparator(); - edit_menu->addAction( embed::getIconPixmap( "setup_general" ), - tr( "Settings" ), - this, SLOT(showSettingsDialog())); - connect( edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); + edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"), + this, SLOT(toggleMicrotunerWin())); + edit_menu->addAction(embed::getIconPixmap("setup_general"), tr("Settings"), + this, SLOT(showSettingsDialog())); + + connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); m_viewMenu = new QMenu( this ); menuBar()->addMenu( m_viewMenu )->setText( tr( "&View" ) ); @@ -485,10 +487,6 @@ void MainWindow::finalize() tr("Show/hide project notes") + " (Ctrl+7)", this, SLOT(toggleProjectNotesWin()), m_toolBar); project_notes_window->setShortcut( Qt::CTRL + Qt::Key_7 ); - auto microtuner_window = new ToolButton(embed::getIconPixmap("microtuner"), - tr("Microtuner configuration") + " (Ctrl+8)", this, SLOT(toggleMicrotunerWin()), m_toolBar); - microtuner_window->setShortcut( Qt::CTRL + Qt::Key_8 ); - m_toolBarLayout->addWidget( song_editor_window, 1, 1 ); m_toolBarLayout->addWidget( pattern_editor_window, 1, 2 ); m_toolBarLayout->addWidget( piano_roll_window, 1, 3 ); @@ -496,7 +494,6 @@ void MainWindow::finalize() m_toolBarLayout->addWidget( mixer_window, 1, 5 ); m_toolBarLayout->addWidget( controllers_window, 1, 6 ); m_toolBarLayout->addWidget( project_notes_window, 1, 7 ); - m_toolBarLayout->addWidget( microtuner_window, 1, 8 ); m_toolBarLayout->setColumnStretch( 100, 1 ); // setup-dialog opened before? @@ -1100,10 +1097,6 @@ void MainWindow::updateViewMenu() tr( "Project Notes" ) + "\tCtrl+7", this, SLOT(toggleProjectNotesWin()) ); - m_viewMenu->addAction(embed::getIconPixmap( "microtuner" ), - tr( "Microtuner" ) + "\tCtrl+8", - this, SLOT(toggleMicrotunerWin()) - ); m_viewMenu->addSeparator(); diff --git a/src/gui/MicrotunerConfig.cpp b/src/gui/MicrotunerConfig.cpp index 7ab4cc0b1..4156b9e79 100644 --- a/src/gui/MicrotunerConfig.cpp +++ b/src/gui/MicrotunerConfig.cpp @@ -56,8 +56,8 @@ namespace lmms::gui MicrotunerConfig::MicrotunerConfig() : QWidget(), - m_scaleComboModel(nullptr, tr("Selected scale")), - m_keymapComboModel(nullptr, tr("Selected keymap")), + m_scaleComboModel(nullptr, tr("Selected scale slot")), + m_keymapComboModel(nullptr, tr("Selected keymap slot")), m_firstKeyModel(0, 0, NumKeys - 1, nullptr, tr("First key")), m_lastKeyModel(NumKeys - 1, 0, NumKeys - 1, nullptr, tr("Last key")), m_middleKeyModel(DefaultMiddleKey, 0, NumKeys - 1, nullptr, tr("Middle key")), @@ -75,7 +75,7 @@ MicrotunerConfig::MicrotunerConfig() : #endif setWindowIcon(embed::getIconPixmap("microtuner")); - setWindowTitle(tr("Microtuner")); + setWindowTitle(tr("Microtuner Configuration")); // Organize into 2 main columns: scales and keymaps auto microtunerLayout = new QGridLayout(); @@ -84,7 +84,7 @@ MicrotunerConfig::MicrotunerConfig() : // ---------------------------------- // Scale sub-column // - auto scaleLabel = new QLabel(tr("Scale:")); + auto scaleLabel = new QLabel(tr("Scale slot to edit:")); microtunerLayout->addWidget(scaleLabel, 0, 0, 1, 2, Qt::AlignBottom); for (unsigned int i = 0; i < MaxScaleCount; i++) @@ -102,6 +102,8 @@ MicrotunerConfig::MicrotunerConfig() : auto loadScaleButton = new QPushButton(tr("Load")); auto saveScaleButton = new QPushButton(tr("Save")); + loadScaleButton->setToolTip(tr("Load scale definition from a file.")); + saveScaleButton->setToolTip(tr("Save scale definition to a file.")); microtunerLayout->addWidget(loadScaleButton, 3, 0, 1, 1); microtunerLayout->addWidget(saveScaleButton, 3, 1, 1, 1); connect(loadScaleButton, &QPushButton::clicked, [=] {loadScaleFromFile();}); @@ -112,14 +114,15 @@ MicrotunerConfig::MicrotunerConfig() : m_scaleTextEdit->setToolTip(tr("Enter intervals on separate lines. Numbers containing a decimal point are treated as cents.\nOther inputs are treated as integer ratios and must be in the form of \'a/b\' or \'a\'.\nUnity (0.0 cents or ratio 1/1) is always present as a hidden first value; do not enter it manually.")); microtunerLayout->addWidget(m_scaleTextEdit, 4, 0, 2, 2); - auto applyScaleButton = new QPushButton(tr("Apply scale")); + auto applyScaleButton = new QPushButton(tr("Apply scale changes")); + applyScaleButton->setToolTip(tr("Verify and apply changes made to the selected scale. To use the scale, select it in the settings of a supported instrument.")); microtunerLayout->addWidget(applyScaleButton, 6, 0, 1, 2); connect(applyScaleButton, &QPushButton::clicked, [=] {applyScale();}); // ---------------------------------- // Mapping sub-column // - auto keymapLabel = new QLabel(tr("Keymap:")); + auto keymapLabel = new QLabel(tr("Keymap slot to edit:")); microtunerLayout->addWidget(keymapLabel, 0, 2, 1, 2, Qt::AlignBottom); for (unsigned int i = 0; i < MaxKeymapCount; i++) @@ -137,6 +140,8 @@ MicrotunerConfig::MicrotunerConfig() : auto loadKeymapButton = new QPushButton(tr("Load")); auto saveKeymapButton = new QPushButton(tr("Save")); + loadKeymapButton->setToolTip(tr("Load key mapping definition from a file.")); + saveKeymapButton->setToolTip(tr("Save key mapping definition to a file.")); microtunerLayout->addWidget(loadKeymapButton, 3, 2, 1, 1); microtunerLayout->addWidget(saveKeymapButton, 3, 3, 1, 1); connect(loadKeymapButton, &QPushButton::clicked, [=] {loadKeymapFromFile();}); @@ -181,7 +186,8 @@ MicrotunerConfig::MicrotunerConfig() : baseFreqSpin->setToolTip(tr("Base note frequency")); keymapRangeLayout->addWidget(baseFreqSpin, 1, 1, 1, 2); - auto applyKeymapButton = new QPushButton(tr("Apply keymap")); + auto applyKeymapButton = new QPushButton(tr("Apply keymap changes")); + applyKeymapButton->setToolTip(tr("Verify and apply changes made to the selected key mapping. To use the mapping, select it in the settings of a supported instrument.")); microtunerLayout->addWidget(applyKeymapButton, 6, 2, 1, 2); connect(applyKeymapButton, &QPushButton::clicked, [=] {applyKeymap();}); diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 0edebcb8a..dff19ca3e 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -248,8 +248,8 @@ void MixerView::refreshDisplay() // update the and max. channel number for every instrument void MixerView::updateMaxChannelSelector() { - TrackContainer::TrackList songTracks = Engine::getSong()->tracks(); - TrackContainer::TrackList patternStoreTracks = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& songTracks = Engine::getSong()->tracks(); + const TrackContainer::TrackList& patternStoreTracks = Engine::patternStore()->tracks(); for (const auto& trackList : {songTracks, patternStoreTracks}) { diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index ff25a367b..034ad135c 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -25,6 +25,7 @@ #include "ClipView.h" #include +#include #include #include @@ -545,6 +546,7 @@ DataFile ClipView::createClipDataFiles( // Insert into the dom under the "clips" element Track* clipTrack = clipView->m_trackView->getTrack(); int trackIndex = std::distance(tc->tracks().begin(), std::find(tc->tracks().begin(), tc->tracks().end(), clipTrack)); + assert(trackIndex != tc->tracks().size()); QDomElement clipElement = dataFile.createElement("clip"); clipElement.setAttribute( "trackIndex", trackIndex ); clipElement.setAttribute( "trackType", static_cast(clipTrack->type()) ); @@ -1308,7 +1310,7 @@ void ClipView::mergeClips(QVector clipvs) continue; } - NoteVector currentClipNotes = mcView->getMidiClip()->notes(); + const NoteVector& currentClipNotes = mcView->getMidiClip()->notes(); TimePos mcViewPos = mcView->getMidiClip()->startPosition(); for (Note* note: currentClipNotes) diff --git a/src/gui/editors/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index 229c90bc2..5237690a7 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -69,7 +69,7 @@ void PatternEditor::cloneSteps() void PatternEditor::removeSteps() { - TrackContainer::TrackList tl = model()->tracks(); + const TrackContainer::TrackList& tl = model()->tracks(); for (const auto& track : tl) { @@ -176,7 +176,7 @@ void PatternEditor::updatePosition() void PatternEditor::makeSteps( bool clone ) { - TrackContainer::TrackList tl = model()->tracks(); + const TrackContainer::TrackList& tl = model()->tracks(); for (const auto& track : tl) { diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 0cc825725..b38335995 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -741,10 +741,10 @@ void PianoRoll::fitNoteLengths(bool fill) { if (!hasValidMidiClip()) { return; } m_midiClip->addJournalCheckPoint(); + m_midiClip->rearrangeAllNotes(); // Reference notes - NoteVector refNotes = m_midiClip->notes(); - std::sort(refNotes.begin(), refNotes.end(), Note::lessThan); + const NoteVector& refNotes = m_midiClip->notes(); // Notes to edit NoteVector notes = getSelectedNotes(); @@ -762,7 +762,7 @@ void PianoRoll::fitNoteLengths(bool fill) } int length; - NoteVector::iterator ref = refNotes.begin(); + auto ref = refNotes.begin(); for (Note* note : notes) { // Fast forward to next reference note @@ -797,14 +797,11 @@ void PianoRoll::constrainNoteLengths(bool constrainMax) if (!hasValidMidiClip()) { return; } m_midiClip->addJournalCheckPoint(); - NoteVector notes = getSelectedNotes(); - if (notes.empty()) - { - notes = m_midiClip->notes(); - } + const NoteVector selectedNotes = getSelectedNotes(); + const auto& notes = selectedNotes.empty() ? m_midiClip->notes() : selectedNotes; - TimePos bound = m_lenOfNewNotes; // will be length of last note - for (Note* note : notes) + TimePos bound = m_lenOfNewNotes; // will be length of last note + for (auto note : notes) { if (constrainMax ? note->length() > bound : note->length() < bound) { @@ -1207,11 +1204,11 @@ void PianoRoll::shiftSemiTone(int amount) //Shift notes by amount semitones auto selectedNotes = getSelectedNotes(); //If no notes are selected, shift all of them, otherwise shift selection - if (selectedNotes.empty()) { return shiftSemiTone(m_midiClip->notes(), amount); } - else { return shiftSemiTone(selectedNotes, amount); } + if (selectedNotes.empty()) { shiftSemiTone(m_midiClip->notes(), amount); } + else { shiftSemiTone(selectedNotes, amount); } } -void PianoRoll::shiftSemiTone(NoteVector notes, int amount) +void PianoRoll::shiftSemiTone(const NoteVector& notes, int amount) { m_midiClip->addJournalCheckPoint(); for (Note *note : notes) { note->setKey( note->key() + amount ); } @@ -1232,11 +1229,11 @@ void PianoRoll::shiftPos(int amount) //Shift notes pos by amount auto selectedNotes = getSelectedNotes(); //If no notes are selected, shift all of them, otherwise shift selection - if (selectedNotes.empty()) { return shiftPos(m_midiClip->notes(), amount); } - else { return shiftPos(selectedNotes, amount); } + if (selectedNotes.empty()) { shiftPos(m_midiClip->notes(), amount); } + else { shiftPos(selectedNotes, amount); } } -void PianoRoll::shiftPos(NoteVector notes, int amount) +void PianoRoll::shiftPos(const NoteVector& notes, int amount) { m_midiClip->addJournalCheckPoint(); @@ -4134,9 +4131,9 @@ void PianoRoll::finishRecordNote(const Note & n ) { if( it->key() == n.key() ) { - Note n1( n.length(), it->pos(), + Note n1(n.length(), it->pos(), it->key(), it->getVolume(), - it->getPanning() ); + it->getPanning(), n.detuning()); n1.quantizeLength( quantization() ); m_midiClip->addNote( n1 ); update(); diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 43cca0dac..28cd8c6c8 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -49,7 +49,7 @@ #include "InstrumentFunctions.h" #include "InstrumentFunctionViews.h" #include "InstrumentMidiIOView.h" -#include "InstrumentMiscView.h" +#include "InstrumentTuningView.h" #include "InstrumentSoundShapingView.h" #include "InstrumentTrack.h" #include "InstrumentTrackView.h" @@ -255,25 +255,25 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : instrumentFunctionsLayout->addStretch(); // MIDI tab - m_midiView = new InstrumentMidiIOView( m_tabWidget ); + m_midiView = new InstrumentMidiIOView(m_tabWidget); // FX tab - m_effectView = new EffectRackView( m_track->m_audioPort.effects(), m_tabWidget ); + m_effectView = new EffectRackView(m_track->m_audioPort.effects(), m_tabWidget); - // MISC tab - m_miscView = new InstrumentMiscView( m_track, m_tabWidget ); + // Tuning tab + m_tuningView = new InstrumentTuningView(m_track, m_tabWidget); - m_tabWidget->addTab( m_ssView, tr( "Envelope, filter & LFO" ), "env_lfo_tab", 1 ); - m_tabWidget->addTab( instrumentFunctions, tr( "Chord stacking & arpeggio" ), "func_tab", 2 ); - m_tabWidget->addTab( m_effectView, tr( "Effects" ), "fx_tab", 3 ); - m_tabWidget->addTab( m_midiView, tr( "MIDI" ), "midi_tab", 4 ); - m_tabWidget->addTab( m_miscView, tr( "Miscellaneous" ), "misc_tab", 5 ); + m_tabWidget->addTab(m_ssView, tr("Envelope, filter & LFO"), "env_lfo_tab", 1); + m_tabWidget->addTab(instrumentFunctions, tr("Chord stacking & arpeggio"), "func_tab", 2); + m_tabWidget->addTab(m_effectView, tr("Effects"), "fx_tab", 3); + m_tabWidget->addTab(m_midiView, tr("MIDI"), "midi_tab", 4); + m_tabWidget->addTab(m_tuningView, tr("Tuning and transposition"), "tuning_tab", 5); adjustTabSize(m_ssView); adjustTabSize(instrumentFunctions); m_effectView->resize(EffectRackView::DEFAULT_WIDTH, INSTRUMENT_HEIGHT - 4 - 1); adjustTabSize(m_midiView); - adjustTabSize(m_miscView); + adjustTabSize(m_tuningView); // setup piano-widget m_pianoView = new PianoView( this ); @@ -376,12 +376,14 @@ void InstrumentTrackWindow::modelChanged() if (m_track->instrument() && m_track->instrument()->flags().testFlag(Instrument::Flag::IsMidiBased)) { - m_miscView->microtunerGroupBox()->hide(); + m_tuningView->microtunerNotSupportedLabel()->show(); + m_tuningView->microtunerGroupBox()->hide(); m_track->m_microtuner.enabledModel()->setValue(false); } else { - m_miscView->microtunerGroupBox()->show(); + m_tuningView->microtunerNotSupportedLabel()->hide(); + m_tuningView->microtunerGroupBox()->show(); } m_ssView->setModel( &m_track->m_soundShaping ); @@ -389,11 +391,11 @@ void InstrumentTrackWindow::modelChanged() m_arpeggioView->setModel( &m_track->m_arpeggio ); m_midiView->setModel( &m_track->m_midiPort ); m_effectView->setModel( m_track->m_audioPort.effects() ); - m_miscView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel); - m_miscView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel()); - m_miscView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel()); - m_miscView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel()); - m_miscView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel()); + m_tuningView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel); + m_tuningView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel()); + m_tuningView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel()); + m_tuningView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel()); + m_tuningView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel()); updateName(); } diff --git a/src/gui/instrument/InstrumentMiscView.cpp b/src/gui/instrument/InstrumentTuningView.cpp similarity index 69% rename from src/gui/instrument/InstrumentMiscView.cpp rename to src/gui/instrument/InstrumentTuningView.cpp index 514db579c..355d7d18c 100644 --- a/src/gui/instrument/InstrumentMiscView.cpp +++ b/src/gui/instrument/InstrumentTuningView.cpp @@ -1,8 +1,8 @@ /* - * InstrumentMiscView.cpp - Miscellaneous instrument settings + * InstrumentTuningView.cpp - Instrument settings for tuning and transpositions * * Copyright (c) 2005-2014 Tobias Doerffel - * Copyright (c) 2020 Martin Pavelek + * Copyright (c) 2020-2022 Martin Pavelek * * This file is part of LMMS - https://lmms.io * @@ -23,24 +23,28 @@ * */ -#include "InstrumentMiscView.h" +#include "InstrumentTuningView.h" #include #include +#include #include #include "ComboBox.h" #include "GroupBox.h" +#include "GuiApplication.h" #include "gui_templates.h" #include "InstrumentTrack.h" #include "LedCheckBox.h" +#include "MainWindow.h" +#include "PixmapButton.h" namespace lmms::gui { -InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) : +InstrumentTuningView::InstrumentTuningView(InstrumentTrack *it, QWidget *parent) : QWidget(parent) { auto layout = new QVBoxLayout(this); @@ -60,6 +64,11 @@ InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) : masterPitchLayout->addWidget(tlabel); // Microtuner settings + m_microtunerNotSupportedLabel = new QLabel(tr("Microtuner is not available for MIDI-based instruments.")); + m_microtunerNotSupportedLabel->setWordWrap(true); + m_microtunerNotSupportedLabel->hide(); + layout->addWidget(m_microtunerNotSupportedLabel); + m_microtunerGroupBox = new GroupBox(tr("MICROTUNER")); m_microtunerGroupBox->setModel(it->m_microtuner.enabledModel()); layout->addWidget(m_microtunerGroupBox); @@ -67,8 +76,22 @@ InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) : auto microtunerLayout = new QVBoxLayout(m_microtunerGroupBox); microtunerLayout->setContentsMargins(8, 18, 8, 8); + auto scaleEditLayout = new QHBoxLayout(); + scaleEditLayout->setContentsMargins(0, 0, 4, 0); + microtunerLayout->addLayout(scaleEditLayout); + auto scaleLabel = new QLabel(tr("Active scale:")); - microtunerLayout->addWidget(scaleLabel); + scaleEditLayout->addWidget(scaleLabel); + + QPixmap editPixmap(embed::getIconPixmap("edit_draw_small")); + auto editPixButton = new PixmapButton(this, tr("Edit scales and keymaps")); + editPixButton->setToolTip(tr("Edit scales and keymaps")); + editPixButton->setInactiveGraphic(editPixmap); + editPixButton->setActiveGraphic(editPixmap); + editPixButton->setFixedSize(16, 16); + connect(editPixButton, SIGNAL(clicked()), getGUI()->mainWindow(), SLOT(toggleMicrotunerWin())); + + scaleEditLayout->addWidget(editPixButton); m_scaleCombo = new ComboBox(); m_scaleCombo->setModel(it->m_microtuner.scaleModel()); diff --git a/src/gui/instrument/PianoView.cpp b/src/gui/instrument/PianoView.cpp index 20db2e8e8..d20cbcac5 100644 --- a/src/gui/instrument/PianoView.cpp +++ b/src/gui/instrument/PianoView.cpp @@ -322,70 +322,65 @@ void PianoView::modelChanged() -// gets the key from the given mouse-position +// Gets the key from the given mouse position /*! \brief Get the key from the mouse position in the piano display * - * First we determine it roughly by the position of the point given in - * white key widths from our start. We then add in any black keys that - * might have been skipped over (they take a key number, but no 'white - * key' space). We then add in our starting key number. - * - * We then determine whether it was a black key that was pressed by - * checking whether it was within the vertical range of black keys. - * Black keys sit exactly between white keys on this keyboard, so - * we then shift the note down or up if we were in the left or right - * half of the white note. We only do this, of course, if the white - * note has a black key on that side, so to speak. - * - * This function returns const because there is a linear mapping from - * the point given to the key returned that never changes. - * - * \param _p The point that the mouse was pressed. + * \param p The point that the mouse was pressed. */ -int PianoView::getKeyFromMouse( const QPoint & _p ) const +int PianoView::getKeyFromMouse(const QPoint& p) const { - int offset = _p.x() % PW_WHITE_KEY_WIDTH; - if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH; - int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH; + // The left-most key visible in the piano display is always white + const int startingWhiteKey = m_pianoScroll->value(); - for( int i = 0; i <= key_num; ++i ) + // Adjust the mouse x position as if x == 0 was the left side of the lowest key + const int adjX = p.x() + (startingWhiteKey * PW_WHITE_KEY_WIDTH); + + // Can early return for notes too low + if (adjX <= 0) { return 0; } + + // Now we can calculate the key number (in only white keys) and the octave + const int whiteKey = adjX / PW_WHITE_KEY_WIDTH; + const int octave = whiteKey / Piano::WhiteKeysPerOctave; + + // Calculate for full octaves + int key = octave * KeysPerOctave; + + // Adjust for white notes in the current octave + // (WhiteKeys maps each white key to the number of notes to their left in the octave) + key += static_cast(WhiteKeys[whiteKey % Piano::WhiteKeysPerOctave]); + + // Might be a black key, which would require further adjustment + if (p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT) { - if ( Piano::isBlackKey( m_startKey+i ) ) + // Maps white keys to neighboring black keys + static constexpr std::array neighboringKeyMap { + std::pair{ 0, 1 }, // C --> no B#; C# + std::pair{ 1, 1 }, // D --> C#; D# + std::pair{ 1, 0 }, // E --> D#; no E# + std::pair{ 0, 1 }, // F --> no E#; F# + std::pair{ 1, 1 }, // G --> F#; G# + std::pair{ 1, 1 }, // A --> G#; A# + std::pair{ 1, 0 }, // B --> A#; no B# + }; + + const auto neighboringBlackKeys = neighboringKeyMap[whiteKey % Piano::WhiteKeysPerOctave]; + const int offset = adjX - (whiteKey * PW_WHITE_KEY_WIDTH); // mouse X offset from white key + + if (offset < PW_BLACK_KEY_WIDTH / 2) { - ++key_num; + // At the location of a (possibly non-existent) black key on the left side + key -= neighboringBlackKeys.first; } - } - for( int i = 0; i >= key_num; --i ) - { - if ( Piano::isBlackKey( m_startKey+i ) ) + else if (offset > PW_WHITE_KEY_WIDTH - (PW_BLACK_KEY_WIDTH / 2)) { - --key_num; + // At the location of a (possibly non-existent) black key on the right side + key += neighboringBlackKeys.second; } + + // For white keys in between black keys, no further adjustment is needed } - key_num += m_startKey; - - // is it a black key? - if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT ) - { - // then do extra checking whether the mouse-cursor is over - // a black key - if( key_num > 0 && Piano::isBlackKey( key_num-1 ) && - offset <= ( PW_WHITE_KEY_WIDTH / 2 ) - - ( PW_BLACK_KEY_WIDTH / 2 ) ) - { - --key_num; - } - if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) && - offset >= ( PW_WHITE_KEY_WIDTH - - PW_BLACK_KEY_WIDTH / 2 ) ) - { - ++key_num; - } - } - - // some range-checking-stuff - return qBound( 0, key_num, NumKeys - 1 ); + return std::clamp(key, 0, NumKeys - 1); } @@ -396,12 +391,12 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const * * We need to update our start key position based on the new position. * - * \param _new_pos the new key position. + * \param newPos the new key position, counting only white keys. */ -void PianoView::pianoScrolled(int new_pos) +void PianoView::pianoScrolled(int newPos) { - m_startKey = static_cast(new_pos / Piano::WhiteKeysPerOctave) - + WhiteKeys[new_pos % Piano::WhiteKeysPerOctave]; + m_startKey = static_cast(newPos / Piano::WhiteKeysPerOctave) + + WhiteKeys[newPos % Piano::WhiteKeysPerOctave]; update(); } diff --git a/src/gui/tracks/TrackContentWidget.cpp b/src/gui/tracks/TrackContentWidget.cpp index 442d717bf..619eff831 100644 --- a/src/gui/tracks/TrackContentWidget.cpp +++ b/src/gui/tracks/TrackContentWidget.cpp @@ -344,7 +344,7 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md const int initialTrackIndex = tiAttr.value().toInt(); // Get the current track's index - const TrackContainer::TrackList tracks = t->trackContainer()->tracks(); + const TrackContainer::TrackList& tracks = t->trackContainer()->tracks(); const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), t); const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1; @@ -443,7 +443,7 @@ bool TrackContentWidget::pasteSelection( TimePos clipPos, const QMimeData * md, TimePos grabbedClipPos = clipPosAttr.value().toInt(); // Snap the mouse position to the beginning of the dropped bar, in ticks - const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks(); + const TrackContainer::TrackList& tracks = getTrack()->trackContainer()->tracks(); const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), getTrack()); const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1; diff --git a/src/gui/widgets/CPULoadWidget.cpp b/src/gui/widgets/CPULoadWidget.cpp index 799e037ef..db1f5cacc 100644 --- a/src/gui/widgets/CPULoadWidget.cpp +++ b/src/gui/widgets/CPULoadWidget.cpp @@ -24,6 +24,7 @@ */ +#include #include #include "AudioEngine.h" @@ -72,10 +73,9 @@ void CPULoadWidget::paintEvent( QPaintEvent * ) QPainter p( &m_temp ); p.drawPixmap( 0, 0, m_background ); - // as load-indicator consists of small 2-pixel wide leds with - // 1 pixel spacing, we have to make sure, only whole leds are - // shown which we achieve by the following formula - int w = ( m_leds.width() * m_currentLoad / 300 ) * 3; + // Normally the CPU load indicator moves smoothly, with 1 pixel resolution. However, some themes may want to + // draw discrete elements (like LEDs), so the stepSize property can be used to specify a larger step size. + int w = (m_leds.width() * std::min(m_currentLoad, 100) / (stepSize() * 100)) * stepSize(); if( w > 0 ) { p.drawPixmap( 23, 3, m_leds, 0, 0, w, @@ -91,10 +91,21 @@ void CPULoadWidget::paintEvent( QPaintEvent * ) void CPULoadWidget::updateCpuLoad() { - // smooth load-values a bit - int new_load = ( m_currentLoad + Engine::audioEngine()->cpuLoad() ) / 2; - if( new_load != m_currentLoad ) + // Additional display smoothing for the main load-value. Stronger averaging + // cannot be used directly in the profiler: cpuLoad() must react fast enough + // to be useful as overload indicator in AudioEngine::criticalXRuns(). + const int new_load = (m_currentLoad + Engine::audioEngine()->cpuLoad()) / 2; + + if (new_load != m_currentLoad) { + auto engine = Engine::audioEngine(); + setToolTip( + tr("DSP total: %1%").arg(new_load) + "\n" + + tr(" - Notes and setup: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::NoteSetup)) + "\n" + + tr(" - Instruments: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Instruments)) + "\n" + + tr(" - Effects: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Effects)) + "\n" + + tr(" - Mixing: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Mixing)) + ); m_currentLoad = new_load; m_changed = true; update(); @@ -102,4 +113,4 @@ void CPULoadWidget::updateCpuLoad() } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/widgets/SimpleTextFloat.cpp b/src/gui/widgets/SimpleTextFloat.cpp index df438423e..e37753229 100644 --- a/src/gui/widgets/SimpleTextFloat.cpp +++ b/src/gui/widgets/SimpleTextFloat.cpp @@ -46,11 +46,11 @@ SimpleTextFloat::SimpleTextFloat() : m_textLabel = new QLabel(this); layout->addWidget(m_textLabel); - m_showTimer = new QTimer(); + m_showTimer = new QTimer(this); m_showTimer->setSingleShot(true); QObject::connect(m_showTimer, &QTimer::timeout, this, &SimpleTextFloat::show); - m_hideTimer = new QTimer(); + m_hideTimer = new QTimer(this); m_hideTimer->setSingleShot(true); QObject::connect(m_hideTimer, &QTimer::timeout, this, &SimpleTextFloat::hide); } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 84b73614b..a4de188a5 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -28,6 +28,7 @@ #include "ConfigManager.h" #include "ControllerConnection.h" #include "DataFile.h" +#include "GuiApplication.h" #include "Mixer.h" #include "InstrumentTrackView.h" #include "Instrument.h" @@ -37,6 +38,7 @@ #include "MixHelpers.h" #include "PatternStore.h" #include "PatternTrack.h" +#include "PianoRoll.h" #include "Pitch.h" #include "Song.h" @@ -72,6 +74,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_microtuner() { m_pitchModel.setCenterValue( 0 ); + m_pitchModel.setStrictStepSize(true); m_panningModel.setCenterValue( DefaultPanning ); m_baseNoteModel.setInitValue( DefaultKey ); m_firstKeyModel.setInitValue(0); @@ -341,9 +344,10 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim NotePlayHandleManager::acquire( this, offset, typeInfo::max() / 2, - Note( TimePos(), TimePos(), event.key(), event.volume( midiPort()->baseVelocity() ) ), + Note(TimePos(), Engine::getSong()->getPlayPos(Engine::getSong()->playMode()), + event.key(), event.volume(midiPort()->baseVelocity())), nullptr, event.channel(), - NotePlayHandle::Origin::MidiInput ); + NotePlayHandle::Origin::MidiInput); m_notes[event.key()] = nph; if( ! Engine::audioEngine()->addPlayHandle( nph ) ) { @@ -710,7 +714,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, // Handle automation: detuning for (const auto& processHandle : m_processHandles) { - processHandle->processTimePos(_start); + processHandle->processTimePos(_start, m_pitchModel.value(), gui::GuiApplication::instance()->pianoRoll()->isRecording()); } if ( clips.size() == 0 ) diff --git a/src/tracks/MidiClip.cpp b/src/tracks/MidiClip.cpp index b35979f61..490f6e6d0 100644 --- a/src/tracks/MidiClip.cpp +++ b/src/tracks/MidiClip.cpp @@ -305,7 +305,7 @@ void MidiClip::setStep( int step, bool enabled ) -void MidiClip::splitNotes(NoteVector notes, TimePos pos) +void MidiClip::splitNotes(const NoteVector& notes, TimePos pos) { if (notes.empty()) { return; } @@ -472,7 +472,7 @@ MidiClip * MidiClip::nextMidiClip() const MidiClip * MidiClip::adjacentMidiClipByOffset(int offset) const { - std::vector clips = m_instrumentTrack->getClips(); + auto& clips = m_instrumentTrack->getClips(); int clipNum = m_instrumentTrack->getClipNum(this); if (clipNum < 0 || clipNum > clips.size() - 1) { return nullptr; } return dynamic_cast(clips[clipNum + offset]);