diff --git a/data/themes/classic/black_key_disabled.png b/data/themes/classic/black_key_disabled.png new file mode 100644 index 00000000000..7b73b5557a5 Binary files /dev/null and b/data/themes/classic/black_key_disabled.png differ diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 1afd767002d..29508c0db44 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -165,9 +165,11 @@ PianoRoll { qproperty-whiteKeyInactiveTextColor: rgb( 128, 128, 128); qproperty-whiteKeyInactiveTextShadow: rgb( 240, 240, 240 ); qproperty-whiteKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #eeeeee, stop:1 #ffffff); + qproperty-whiteKeyDisabledBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #a0a0a0, stop:1 #b0b0b0); qproperty-blackKeyWidth: 48; qproperty-blackKeyActiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #43e97b, stop:1 #3bcd6c); qproperty-blackKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #333, stop:1 #000); + qproperty-blackKeyDisabledBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #606060, stop:1 #505050); /* Grid colors */ qproperty-lineColor: rgba( 128, 128, 128, 80 ); qproperty-beatLineColor: rgba( 128, 128, 128, 160 ); diff --git a/data/themes/classic/white_key_disabled.png b/data/themes/classic/white_key_disabled.png new file mode 100644 index 00000000000..00e3924ae6b Binary files /dev/null and b/data/themes/classic/white_key_disabled.png differ diff --git a/data/themes/default/black_key_disabled.png b/data/themes/default/black_key_disabled.png new file mode 100644 index 00000000000..1d001f9d760 Binary files /dev/null and b/data/themes/default/black_key_disabled.png differ diff --git a/data/themes/default/style.css b/data/themes/default/style.css index f03d00abd58..9246edcf881 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -197,9 +197,11 @@ PianoRoll { qproperty-whiteKeyInactiveTextColor: #000; qproperty-whiteKeyInactiveTextShadow: #fff; qproperty-whiteKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #eeeeee, stop:1 #ffffff); + qproperty-whiteKeyDisabledBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #a0a0a0, stop:1 #b0b0b0); qproperty-blackKeyWidth: 48; qproperty-blackKeyActiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #43e97b, stop:1 #3bcd6c); qproperty-blackKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #333, stop:1 #000); + qproperty-blackKeyDisabledBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #606060, stop:1 #505050); /* Grid colors */ qproperty-lineColor: #292929; qproperty-beatLineColor: #2d6b45; diff --git a/data/themes/default/white_key_disabled.png b/data/themes/default/white_key_disabled.png new file mode 100644 index 00000000000..498bccbe7eb Binary files /dev/null and b/data/themes/default/white_key_disabled.png differ diff --git a/include/DataFile.h b/include/DataFile.h index 1398c64fa70..ff3b934f08b 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -120,6 +120,7 @@ class LMMS_EXPORT DataFile : public QDomDocument void upgrade_1_3_0(); void upgrade_noHiddenClipNames(); void upgrade_automationNodes(); + void upgrade_extendedNoteRange(); // List of all upgrade methods static const std::vector UPGRADE_METHODS; diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index ec278996fec..d3c84a4413b 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -174,7 +174,19 @@ class LMMS_EXPORT InstrumentTrack : public Track, public MidiEventProcessor return &m_baseNoteModel; } + IntModel *firstKeyModel() + { + return &m_firstKeyModel; + } + + IntModel *lastKeyModel() + { + return &m_lastKeyModel; + } + int baseNote() const; + int firstKey() const; + int lastKey() const; Piano *pianoModel() { @@ -265,11 +277,13 @@ protected slots: bool m_previewMode; + IntModel m_baseNoteModel; //!< The "A4" or "440 Hz" key (default 69) + IntModel m_firstKeyModel; //!< First key the instrument reacts to + IntModel m_lastKeyModel; //!< Last key the instrument reacts to + bool m_hasAutoMidiDev; static InstrumentTrack *s_autoAssignedTrack; - IntModel m_baseNoteModel; - NotePlayHandleList m_processHandles; FloatModel m_volumeModel; @@ -282,7 +296,6 @@ protected slots: IntModel m_effectChannelModel; BoolModel m_useMasterPitchModel; - Instrument * m_instrument; InstrumentSoundShaping m_soundShaping; InstrumentFunctionArpeggio m_arpeggio; diff --git a/include/Note.h b/include/Note.h index c08a3e24e55..82c47ab7115 100644 --- a/include/Note.h +++ b/include/Note.h @@ -55,6 +55,7 @@ enum Keys enum Octaves { + Octave_m1, // MIDI standard starts at C-1 Octave_0, Octave_1, Octave_2, @@ -64,15 +65,19 @@ enum Octaves Octave_6, Octave_7, Octave_8, + Octave_9, // incomplete octave, MIDI only goes up to G9 NumOctaves -} ; +}; +const int FirstOctave = -1; +const int KeysPerOctave = 12; +const int DefaultKey = DefaultOctave * KeysPerOctave + Key_A; +//! Number of physical keys, limited to MIDI range (valid for both MIDI 1.0 and 2.0) +const int NumKeys = 128; -const int WhiteKeysPerOctave = 7; -const int BlackKeysPerOctave = 5; -const int KeysPerOctave = WhiteKeysPerOctave + BlackKeysPerOctave; -const int NumKeys = NumOctaves * KeysPerOctave; -const int DefaultKey = DefaultOctave*KeysPerOctave + Key_A; +const int DefaultMiddleKey = Octave_4 * KeysPerOctave + Key_C; +const int DefaultBaseKey = Octave_4 * KeysPerOctave + Key_A; +const float DefaultBaseFreq = 440.f; const float MaxDetuning = 4 * 12.0f; diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index de324729afa..e97b4607fe8 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -125,7 +125,7 @@ class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note /*! Returns whether the play handle plays on a certain track */ bool isFromTrack( const Track* _track ) const override; - /*! Releases the note (and plays release frames */ + /*! Releases the note (and plays release frames) */ void noteOff( const f_cnt_t offset = 0 ); /*! Returns number of frames to be played until the note is going to be released */ diff --git a/include/Piano.h b/include/Piano.h index 633236f4ac7..ece3abb0631 100644 --- a/include/Piano.h +++ b/include/Piano.h @@ -65,6 +65,10 @@ class Piano final : public Model static bool isWhiteKey(int key); static bool isBlackKey(int key); + static const unsigned int WhiteKeysPerOctave = 7; + static const unsigned int BlackKeysPerOctave = 5; + static const unsigned int NumWhiteKeys = 75; + static const unsigned int NumBlackKeys = 53; private: static bool isValidKey( int key ) diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 12bb16abfab..cda19cca061 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -86,10 +86,12 @@ class PianoRoll : public QWidget Q_PROPERTY(QColor whiteKeyActiveTextColor MEMBER m_whiteKeyActiveTextColor) Q_PROPERTY(QColor whiteKeyActiveTextShadow MEMBER m_whiteKeyActiveTextShadow) Q_PROPERTY(QBrush whiteKeyActiveBackground MEMBER m_whiteKeyActiveBackground) + Q_PROPERTY(QBrush whiteKeyDisabledBackground MEMBER m_whiteKeyDisabledBackground) /* black key properties */ Q_PROPERTY(int blackKeyWidth MEMBER m_blackKeyWidth) Q_PROPERTY(QBrush blackKeyInactiveBackground MEMBER m_blackKeyInactiveBackground) Q_PROPERTY(QBrush blackKeyActiveBackground MEMBER m_blackKeyActiveBackground) + Q_PROPERTY(QBrush blackKeyDisabledBackground MEMBER m_blackKeyDisabledBackground) public: enum EditModes { @@ -398,7 +400,6 @@ protected slots: int m_pianoKeysVisible; int m_keyLineHeight; - int m_octaveHeight; int m_whiteKeySmallHeight; int m_whiteKeyBigHeight; int m_blackKeyHeight; @@ -475,10 +476,12 @@ protected slots: QColor m_whiteKeyInactiveTextColor; QColor m_whiteKeyInactiveTextShadow; QBrush m_whiteKeyInactiveBackground; + QBrush m_whiteKeyDisabledBackground; /* black key properties */ int m_blackKeyWidth; QBrush m_blackKeyActiveBackground; QBrush m_blackKeyInactiveBackground; + QBrush m_blackKeyDisabledBackground; signals: void positionChanged( const TimePos & ); diff --git a/include/PianoView.h b/include/PianoView.h index 2f2fea2c935..88854943176 100644 --- a/include/PianoView.h +++ b/include/PianoView.h @@ -28,6 +28,7 @@ #include #include +#include "AutomatableModel.h" #include "ModelView.h" class Piano; @@ -63,17 +64,24 @@ class PianoView : public QWidget, public ModelView private: int getKeyFromMouse( const QPoint & _p ) const; int getKeyX( int _key_num ) const; + int getKeyWidth(int key_num) const; + int getKeyHeight(int key_num) const; + IntModel *getNearestMarker(int key, QString* title = nullptr); static QPixmap * s_whiteKeyPm; static QPixmap * s_blackKeyPm; static QPixmap * s_whiteKeyPressedPm; static QPixmap * s_blackKeyPressedPm; + static QPixmap * s_whiteKeyDisabledPm; + static QPixmap * s_blackKeyDisabledPm; Piano * m_piano; QScrollBar * m_pianoScroll; - int m_startKey; // first key when drawing - int m_lastKey; + int m_startKey; //!< first key when drawing + int m_lastKey; //!< previously pressed key + IntModel *m_movedNoteModel; //!< note marker which is being moved + private slots: @@ -87,4 +95,3 @@ private slots: #endif - diff --git a/plugins/HydrogenImport/HydrogenImport.cpp b/plugins/HydrogenImport/HydrogenImport.cpp index c39c52416fc..72b5dd7aa94 100644 --- a/plugins/HydrogenImport/HydrogenImport.cpp +++ b/plugins/HydrogenImport/HydrogenImport.cpp @@ -122,7 +122,9 @@ class NoteKey else if ( sKey == "B" ) { m_key = NoteKey::B; } - return m_key + (nOctave*12)+57; + + // Hydrogen records MIDI notes from C-1 to B5, and exports them as a number ranging from -3 to 3 + return m_key + ((nOctave + 3) * 12); } }; diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 929f7d38cf5..b29755a1463 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -130,8 +130,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (n.nodeName() == "instrumenttrack") { QDomElement it = n.toElement(); - // transpose +12 semitones, workaround for #1857 - base_pitch = (69 - it.attribute("basenote", "57").toInt()); + base_pitch = (69 - it.attribute("basenote", "69").toInt()); if (it.attribute("usemasterpitch", "1").toInt()) { base_pitch += masterPitch; @@ -201,8 +200,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (n.nodeName() == "instrumenttrack") { QDomElement it = n.toElement(); - // transpose +12 semitones, workaround for #1857 - base_pitch = (69 - it.attribute("basenote", "57").toInt()); + base_pitch = (69 - it.attribute("basenote", "69").toInt()); if (it.attribute("usemasterpitch", "1").toInt()) { base_pitch += masterPitch; diff --git a/plugins/MidiImport/MidiImport.cpp b/plugins/MidiImport/MidiImport.cpp index 97c81460424..a2b877394a2 100644 --- a/plugins/MidiImport/MidiImport.cpp +++ b/plugins/MidiImport/MidiImport.cpp @@ -454,7 +454,7 @@ bool MidiImport::readSMF( TrackContainer* tc ) int ticks = noteEvt->get_duration() * ticksPerBeat; Note n( (ticks < 1 ? 1 : ticks ), noteEvt->get_start_time() * ticksPerBeat, - noteEvt->get_identifier() - 12, + noteEvt->get_identifier(), noteEvt->get_loud() * (200.f / 127.f)); // Map from MIDI velocity to LMMS volume ch->addNote( n ); diff --git a/plugins/OpulenZ/OpulenZ.cpp b/plugins/OpulenZ/OpulenZ.cpp index de3cce9ce56..422f635f85c 100644 --- a/plugins/OpulenZ/OpulenZ.cpp +++ b/plugins/OpulenZ/OpulenZ.cpp @@ -300,8 +300,7 @@ bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& switch(event.type()) { case MidiNoteOn: - // to get us in line with MIDI(?) - key = event.key() +12; + key = event.key(); vel = event.velocity(); voice = popVoice(); @@ -316,7 +315,7 @@ bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& } break; case MidiNoteOff: - key = event.key() +12; + key = event.key(); for(voice=0; voicewrite(0xA0+voice, fnums[key] & 0xff); @@ -328,7 +327,7 @@ bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& velocities[key] = 0; break; case MidiKeyPressure: - key = event.key() +12; + key = event.key(); vel = event.velocity(); if( velocities[key] != 0) { velocities[key] = vel; diff --git a/plugins/Xpressive/Xpressive.cpp b/plugins/Xpressive/Xpressive.cpp index e4eff06bdc8..b83c2686e1a 100644 --- a/plugins/Xpressive/Xpressive.cpp +++ b/plugins/Xpressive/Xpressive.cpp @@ -824,8 +824,8 @@ QString XpressiveHelpView::s_helpText= "

Available variables:


" "t - Time in seconds.
" "f - Note's pitched frequency. Available only in the output expressions.
" -"key - Note's keyboard key. 0 denotes C0, 48 denotes C4, 96 denotes C8. Available only in the output expressions.
" -"bnote - Base note. By default it is 57 which means A5, unless you change it.
" +"key - Note's keyboard key. 0 denotes C-1, 60 denotes C4, 127 denotes G9. Available only in the output expressions.
" +"bnote - Base note. By default it is 69 which means A4, unless you change it.
" "srate - Sample rate. In wave expression it returns the wave's number of samples.
" "tempo - Song's Tempo. Available only in the output expressions.
" "v - Note's volume. Note that the output is already multiplied by the volume. Available only in the output expressions.
" diff --git a/plugins/vestige/vestige.h b/plugins/vestige/vestige.h index 48c89f14d4c..60c75cd9d23 100644 --- a/plugins/vestige/vestige.h +++ b/plugins/vestige/vestige.h @@ -33,7 +33,6 @@ #include "Instrument.h" #include "InstrumentView.h" -#include "Note.h" #include "CustomTextKnob.h" #include "SubWindow.h" #include "AutomatableModel.h" diff --git a/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp b/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp index 88c7296079c..5b491d8c96e 100644 --- a/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp @@ -34,7 +34,6 @@ #define BUILD_REMOTE_PLUGIN_CLIENT -#include "Note.h" #include "RemotePlugin.h" #include "RemoteZynAddSubFx.h" #include "LocalZynAddSubFx.h" diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index a56e09af532..a2b831d0793 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -31,7 +31,6 @@ #include "AutomationTrack.h" #include "BBTrackContainer.h" #include "LocaleHelper.h" -#include "Note.h" #include "ProjectJournal.h" #include "Song.h" diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 118be7c9a09..1ca63eb92a5 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -69,7 +69,7 @@ const std::vector DataFile::UPGRADE_METHODS = { &DataFile::upgrade_1_0_99 , &DataFile::upgrade_1_1_0, &DataFile::upgrade_1_1_91 , &DataFile::upgrade_1_2_0_rc3, &DataFile::upgrade_1_3_0 , &DataFile::upgrade_noHiddenClipNames, - &DataFile::upgrade_automationNodes + &DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange }; // Vector of all versions that have upgrade routines. @@ -1656,6 +1656,84 @@ void DataFile::upgrade_automationNodes() } +/** \brief Note range has been extended to match MIDI specification + * + * The non-standard note range previously affected all MIDI-based instruments + * except OpulenZ, and made them sound an octave lower than they should (#1857). + */ +void DataFile::upgrade_extendedNoteRange() +{ + auto affected = [](const QDomElement& instrument) + { + return instrument.attribute("name") == "zynaddsubfx" || + instrument.attribute("name") == "vestige" || + instrument.attribute("name") == "lv2instrument" || + instrument.attribute("name") == "carlapatchbay" || + instrument.attribute("name") == "carlarack"; + }; + + if (!elementsByTagName("song").item(0).isNull()) + { + // Dealing with a project file, go through all the tracks + QDomNodeList tracks = elementsByTagName("track"); + for (int i = 0; !tracks.item(i).isNull(); i++) + { + // Ignore BB container tracks + if (tracks.item(i).toElement().attribute("type").toInt() == 1) { continue; } + + QDomNodeList instruments = tracks.item(i).toElement().elementsByTagName("instrument"); + if (instruments.isEmpty()) { continue; } + QDomElement instrument = instruments.item(0).toElement(); + // Raise the base note of every instrument by 12 to compensate for the change + // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. + instrument.parentNode().toElement().setAttribute( + "basenote", + instrument.parentNode().toElement().attribute("basenote").toInt() + 12); + // Raise the pitch of all notes in patterns assigned to instruments not affected + // by #1857 by an octave. This negates the base note change for normal instruments, + // but leaves the MIDI-based instruments sounding an octave lower, preserving their + // pitch in existing projects. + if (!affected(instrument)) + { + QDomNodeList patterns = tracks.item(i).toElement().elementsByTagName("pattern"); + for (int i = 0; !patterns.item(i).isNull(); i++) + { + QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); + for (int i = 0; !notes.item(i).isNull(); i++) + { + notes.item(i).toElement().setAttribute( + "key", + notes.item(i).toElement().attribute("key").toInt() + 12 + ); + } + } + } + } + } + else + { + // Dealing with a preset, not a song + QDomNodeList presets = elementsByTagName("instrumenttrack"); + if (presets.item(0).isNull()) { return; } + QDomElement preset = presets.item(0).toElement(); + // Common correction for all instrument presets (make base notes match the new MIDI range). + // NOTE: Many older presets do not have any basenote defined, assume they were "made for 57". + // (Specifying a default like this also happens to fix a FileBrowser bug where previews of presets + // with undefined basenote always play with the basenote inherited from previously played preview.) + int oldBase = preset.attribute("basenote", "57").toInt(); + preset.setAttribute("basenote", oldBase + 12); + // Extra correction for Zyn, VeSTige, LV2 and Carla (to preserve the original buggy behavior). + QDomNodeList instruments = presets.item(0).toElement().elementsByTagName("instrument"); + if (instruments.isEmpty()) { return; } + QDomElement instrument = instruments.item(0).toElement(); + if (affected(instrument)) + { + preset.setAttribute("basenote", preset.attribute("basenote").toInt() + 12); + } + } +} + + void DataFile::upgrade() { // Runs all necessary upgrade methods diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 22ac6d2af16..0caf39b6a77 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -182,7 +182,7 @@ int NotePlayHandle::midiKey() const void NotePlayHandle::play( sampleFrame * _working_buffer ) { - if( m_muted ) + if (m_muted) { return; } @@ -196,6 +196,22 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) lock(); + // Don't play the note if it falls outside of the user defined key range + // TODO: handle the range check by Microtuner, and if the key becomes "not mapped", save the current frequency + // so that the note release can finish playing using a valid frequency instead of a 1 Hz placeholder + if (key() < m_instrumentTrack->m_firstKeyModel.value() || + key() > m_instrumentTrack->m_lastKeyModel.value()) + { + // Release the note in case it started playing before going out of range + noteOff(0); + // Exit if the note did not start playing before going out of range (i.e. there is no need to play release) + if (m_totalFramesPlayed == 0) + { + unlock(); + return; + } + } + /* It is possible for NotePlayHandle::noteOff to be called before NotePlayHandle::play, * which results in a note-on message being sent without a subsequent note-off message. * Therefore, we check here whether the note has already been released before sending diff --git a/src/core/midi/MidiAlsaSeq.cpp b/src/core/midi/MidiAlsaSeq.cpp index 70c5a40c80a..5dfe346fc66 100644 --- a/src/core/midi/MidiAlsaSeq.cpp +++ b/src/core/midi/MidiAlsaSeq.cpp @@ -176,21 +176,21 @@ void MidiAlsaSeq::processOutEvent( const MidiEvent& event, const TimePos& time, case MidiNoteOn: snd_seq_ev_set_noteon( &ev, event.channel(), - event.key() + KeysPerOctave, + event.key(), event.velocity() ); break; case MidiNoteOff: snd_seq_ev_set_noteoff( &ev, event.channel(), - event.key() + KeysPerOctave, + event.key(), event.velocity() ); break; case MidiKeyPressure: snd_seq_ev_set_keypress( &ev, event.channel(), - event.key() + KeysPerOctave, + event.key(), event.velocity() ); break; @@ -531,8 +531,7 @@ void MidiAlsaSeq::run() case SND_SEQ_EVENT_NOTEON: dest->processInEvent( MidiEvent( MidiNoteOn, ev->data.note.channel, - ev->data.note.note - - KeysPerOctave, + ev->data.note.note, ev->data.note.velocity, source ), @@ -542,8 +541,7 @@ void MidiAlsaSeq::run() case SND_SEQ_EVENT_NOTEOFF: dest->processInEvent( MidiEvent( MidiNoteOff, ev->data.note.channel, - ev->data.note.note - - KeysPerOctave, + ev->data.note.note, ev->data.note.velocity, source ), @@ -554,8 +552,7 @@ void MidiAlsaSeq::run() dest->processInEvent( MidiEvent( MidiKeyPressure, ev->data.note.channel, - ev->data.note.note - - KeysPerOctave, + ev->data.note.note, ev->data.note.velocity, source ), TimePos() ); diff --git a/src/core/midi/MidiApple.cpp b/src/core/midi/MidiApple.cpp index 67e2084cfde..66a996d0fec 100644 --- a/src/core/midi/MidiApple.cpp +++ b/src/core/midi/MidiApple.cpp @@ -318,28 +318,28 @@ void MidiApple::HandleReadCallback( const MIDIPacketList *pktlist, void *srcConn } unsigned char messageChannel = status & 0xF; - const MidiEventTypes cmdtype = static_cast( status & 0xF0 ); + const MidiEventTypes cmdtype = static_cast(status & 0xF0); const int par1 = packet->data[iByte + 1]; const int par2 = packet->data[iByte + 2]; switch (cmdtype) { - case MidiNoteOff: //0x80: - case MidiNoteOn: //0x90: - case MidiKeyPressure: //0xA0: - notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1 - KeysPerOctave, par2 & 0xff, &endPointRef )); + case MidiNoteOff: //0x80: + case MidiNoteOn: //0x90: + case MidiKeyPressure: //0xA0: + case MidiControlChange: //0xB0: + case MidiProgramChange: //0xC0: + case MidiChannelPressure: //0xD0: + notifyMidiPortList( + m_inputSubs[refName], + MidiEvent(cmdtype, messageChannel, par1, par2 & 0xff, &endPointRef)); break; - - case MidiControlChange: //0xB0: - case MidiProgramChange: //0xC0: - case MidiChannelPressure: //0xD0: - notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1, par2 & 0xff, &endPointRef )); + case MidiPitchBend: //0xE0: + notifyMidiPortList( + m_inputSubs[refName], + MidiEvent(cmdtype, messageChannel, par1 + par2 * 128, 0, &endPointRef)); break; - - case MidiPitchBend: //0xE0: - notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1 + par2 * 128, 0, &endPointRef )); - break; - case MidiActiveSensing: //0xF0 + case MidiActiveSensing: //0xF0 case 0xF0: break; default: diff --git a/src/core/midi/MidiClient.cpp b/src/core/midi/MidiClient.cpp index b3d7e63635d..06de8c4b090 100644 --- a/src/core/midi/MidiClient.cpp +++ b/src/core/midi/MidiClient.cpp @@ -214,34 +214,30 @@ void MidiClientRaw::parseData( const unsigned char c ) * We simply keep the status as it is, just reset the parameter counter. * If another status byte comes in, it will overwrite the status. */ - m_midiParseData.m_midiEvent.setType( static_cast( m_midiParseData.m_status ) ); - m_midiParseData.m_midiEvent.setChannel( m_midiParseData.m_channel ); + m_midiParseData.m_midiEvent.setType(static_cast(m_midiParseData.m_status)); + m_midiParseData.m_midiEvent.setChannel(m_midiParseData.m_channel); m_midiParseData.m_bytes = 0; /* Related to running status! */ - switch( m_midiParseData.m_midiEvent.type() ) + switch (m_midiParseData.m_midiEvent.type()) { case MidiNoteOff: case MidiNoteOn: case MidiKeyPressure: case MidiChannelPressure: - m_midiParseData.m_midiEvent.setKey( m_midiParseData.m_buffer[0] - KeysPerOctave ); - m_midiParseData.m_midiEvent.setVelocity( m_midiParseData.m_buffer[1] ); - break; - case MidiProgramChange: - m_midiParseData.m_midiEvent.setKey( m_midiParseData.m_buffer[0] ); - m_midiParseData.m_midiEvent.setVelocity( m_midiParseData.m_buffer[1] ); + m_midiParseData.m_midiEvent.setKey(m_midiParseData.m_buffer[0]); + m_midiParseData.m_midiEvent.setVelocity(m_midiParseData.m_buffer[1]); break; case MidiControlChange: - m_midiParseData.m_midiEvent.setControllerNumber( m_midiParseData.m_buffer[0] ); - m_midiParseData.m_midiEvent.setControllerValue( m_midiParseData.m_buffer[1] ); + m_midiParseData.m_midiEvent.setControllerNumber(m_midiParseData.m_buffer[0]); + m_midiParseData.m_midiEvent.setControllerValue( m_midiParseData.m_buffer[1]); break; case MidiPitchBend: // Pitch-bend is transmitted with 14-bit precision. // Note: '|' does here the same as '+' (no common bits), // but might be faster - m_midiParseData.m_midiEvent.setPitchBend( ( m_midiParseData.m_buffer[1] * 128 ) | m_midiParseData.m_buffer[0] ); + m_midiParseData.m_midiEvent.setPitchBend((m_midiParseData.m_buffer[1] * 128) | m_midiParseData.m_buffer[0]); break; default: @@ -266,22 +262,21 @@ void MidiClientRaw::processParsedEvent() -void MidiClientRaw::processOutEvent( const MidiEvent& event, const TimePos & , const MidiPort* port ) +void MidiClientRaw::processOutEvent(const MidiEvent& event, const TimePos&, const MidiPort* port) { // TODO: also evaluate _time and queue event if necessary - switch( event.type() ) + switch (event.type()) { case MidiNoteOn: case MidiNoteOff: case MidiKeyPressure: - sendByte( event.type() | event.channel() ); - sendByte( event.key() + KeysPerOctave ); - sendByte( event.velocity() ); + sendByte(event.type() | event.channel()); + sendByte(event.key()); + sendByte(event.velocity()); break; default: - qWarning( "MidiClientRaw: unhandled MIDI-event %d\n", - (int) event.type() ); + qWarning("MidiClientRaw: unhandled MIDI-event %d\n", (int)event.type()); break; } } @@ -330,5 +325,3 @@ int MidiClientRaw::eventLength( const unsigned char event ) } return 1; } - - diff --git a/src/core/midi/MidiWinMM.cpp b/src/core/midi/MidiWinMM.cpp index dbbc06ae1c8..eb1a00c7fb1 100644 --- a/src/core/midi/MidiWinMM.cpp +++ b/src/core/midi/MidiWinMM.cpp @@ -201,28 +201,25 @@ void MidiWinMM::handleInputEvent( HMIDIIN hm, DWORD ev ) } const MidiPortList & l = m_inputSubs[d]; - for( MidiPortList::ConstIterator it = l.begin(); it != l.end(); ++it ) + for (MidiPortList::ConstIterator it = l.begin(); it != l.end(); ++it) { - switch( cmdtype ) + switch (cmdtype) { case MidiNoteOn: case MidiNoteOff: case MidiKeyPressure: - ( *it )->processInEvent( MidiEvent( cmdtype, chan, par1 - KeysPerOctave, par2 & 0xff, &hm ) ); - break; - case MidiControlChange: case MidiProgramChange: case MidiChannelPressure: - ( *it )->processInEvent( MidiEvent( cmdtype, chan, par1, par2 & 0xff, &hm ) ); + (*it)->processInEvent(MidiEvent(cmdtype, chan, par1, par2 & 0xff, &hm)); break; case MidiPitchBend: - ( *it )->processInEvent( MidiEvent( cmdtype, chan, par1 + par2*128, 0, &hm ) ); + (*it)->processInEvent(MidiEvent(cmdtype, chan, par1 + par2 * 128, 0, &hm)); break; default: - qWarning( "MidiWinMM: unhandled input event %d\n", cmdtype ); + qWarning("MidiWinMM: unhandled input event %d\n", cmdtype); break; } } @@ -304,4 +301,3 @@ void MidiWinMM::openDevices() #endif - diff --git a/src/gui/PianoView.cpp b/src/gui/PianoView.cpp index 58eb59dafbc..64da4895cf7 100644 --- a/src/gui/PianoView.cpp +++ b/src/gui/PianoView.cpp @@ -41,15 +41,18 @@ #include #include #include +#include #include #include "PianoView.h" #include "Piano.h" #include "CaptionMenu.h" #include "embed.h" +#include "Engine.h" #include "gui_templates.h" #include "InstrumentTrack.h" #include "Knob.h" +#include "Song.h" #include "StringPairDrag.h" #include "MainWindow.h" @@ -62,10 +65,12 @@ Keys WhiteKeys[] = } ; -QPixmap * PianoView::s_whiteKeyPm = NULL; /*!< A white key released */ -QPixmap * PianoView::s_blackKeyPm = NULL; /*!< A black key released */ -QPixmap * PianoView::s_whiteKeyPressedPm = NULL; /*!< A white key pressed */ -QPixmap * PianoView::s_blackKeyPressedPm = NULL; /*!< A black key pressed */ +QPixmap * PianoView::s_whiteKeyPm = nullptr; /*!< A white key released */ +QPixmap * PianoView::s_blackKeyPm = nullptr; /*!< A black key released */ +QPixmap * PianoView::s_whiteKeyPressedPm = nullptr; /*!< A white key pressed */ +QPixmap * PianoView::s_blackKeyPressedPm = nullptr; /*!< A black key pressed */ +QPixmap * PianoView::s_whiteKeyDisabledPm = nullptr; /*!< A white key disabled */ +QPixmap * PianoView::s_blackKeyDisabledPm = nullptr; /*!< A black key disabled */ const int PIANO_BASE = 11; /*!< The height of the root note display */ @@ -83,39 +88,54 @@ const int LABEL_TEXT_SIZE = 7; /*!< The height of the key label text */ * \param _parent the parent instrument plugin window * \todo are the descriptions of the m_startkey and m_lastkey properties correct? */ -PianoView::PianoView( QWidget * _parent ) : - QWidget( _parent ), /*!< Our parent */ - ModelView( NULL, this ), /*!< Our view Model */ - m_piano( NULL ), /*!< Our piano Model */ - m_startKey( Key_C + Octave_3*KeysPerOctave ), /*!< The first key displayed? */ - m_lastKey( -1 ) /*!< The last key displayed? */ +PianoView::PianoView(QWidget *parent) : + QWidget(parent), /*!< Our parent */ + ModelView(nullptr, this), /*!< Our view Model */ + m_piano(nullptr), /*!< Our piano Model */ + m_startKey(Key_C + Octave_3*KeysPerOctave), /*!< The first key displayed? */ + m_lastKey(-1), /*!< The last key displayed? */ + m_movedNoteModel(nullptr) /*!< Key marker which is being moved */ { - if( s_whiteKeyPm == NULL ) + if (s_whiteKeyPm == nullptr) { - s_whiteKeyPm = new QPixmap( embed::getIconPixmap( "white_key" ) ); + s_whiteKeyPm = new QPixmap(embed::getIconPixmap("white_key")); } - if( s_blackKeyPm == NULL ) + if (s_blackKeyPm == nullptr) { - s_blackKeyPm = new QPixmap( embed::getIconPixmap( "black_key" ) ); + s_blackKeyPm = new QPixmap(embed::getIconPixmap("black_key")); } - if( s_whiteKeyPressedPm == NULL ) + if (s_whiteKeyPressedPm == nullptr) { - s_whiteKeyPressedPm = new QPixmap( embed::getIconPixmap( "white_key_pressed" ) ); + s_whiteKeyPressedPm = new QPixmap(embed::getIconPixmap("white_key_pressed")); } - if ( s_blackKeyPressedPm == NULL ) + if (s_blackKeyPressedPm == nullptr) { - s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap( "black_key_pressed" ) ); + s_blackKeyPressedPm = new QPixmap(embed::getIconPixmap("black_key_pressed")); + } + if (s_whiteKeyDisabledPm == nullptr) + { + s_whiteKeyDisabledPm = new QPixmap(embed::getIconPixmap("white_key_disabled")); + } + if (s_blackKeyDisabledPm == nullptr) + { + s_blackKeyDisabledPm = new QPixmap(embed::getIconPixmap("black_key_disabled")); } - setAttribute( Qt::WA_OpaquePaintEvent, true ); - setFocusPolicy( Qt::StrongFocus ); - setMaximumWidth( WhiteKeysPerOctave * NumOctaves * PW_WHITE_KEY_WIDTH ); + setAttribute(Qt::WA_OpaquePaintEvent, true); + setFocusPolicy(Qt::StrongFocus); + + // Black keys are drawn halfway between successive white keys, so they do not + // contribute to the total width. Half of a black key is added in case the last + // octave is incomplete and ends with a black key. Drawing always starts at + // a white key, so no similar modification is needed at the beginning. + setMaximumWidth(Piano::NumWhiteKeys * PW_WHITE_KEY_WIDTH + + (Piano::isBlackKey(NumKeys-1) ? PW_BLACK_KEY_WIDTH / 2 : 0)); // create scrollbar at the bottom m_pianoScroll = new QScrollBar( Qt::Horizontal, this ); m_pianoScroll->setSingleStep( 1 ); m_pianoScroll->setPageStep( 20 ); - m_pianoScroll->setValue( Octave_3 * WhiteKeysPerOctave ); + m_pianoScroll->setValue(Octave_3 * Piano::WhiteKeysPerOctave); // and connect it to this widget connect( m_pianoScroll, SIGNAL( valueChanged( int ) ), @@ -127,7 +147,6 @@ PianoView::PianoView( QWidget * _parent ) : layout->setMargin( 0 ); layout->addSpacing( PIANO_BASE+PW_WHITE_KEY_HEIGHT ); layout->addWidget( m_pianoScroll ); - } /*! \brief Map a keyboard key being pressed to a note in our keyboard view @@ -281,17 +300,16 @@ int PianoView::getKeyFromKeyEvent( QKeyEvent * _ke ) void PianoView::modelChanged() { m_piano = castModel(); - if( m_piano != NULL ) + if (m_piano != nullptr) { - connect( m_piano->instrumentTrack()->baseNoteModel(), SIGNAL( dataChanged() ), - this, SLOT( update() ) ); + connect(m_piano->instrumentTrack()->baseNoteModel(), SIGNAL(dataChanged()), this, SLOT(update())); + connect(m_piano->instrumentTrack()->firstKeyModel(), SIGNAL(dataChanged()), this, SLOT(update())); + connect(m_piano->instrumentTrack()->lastKeyModel(), SIGNAL(dataChanged()), this, SLOT(update())); } - } - // gets the key from the given mouse-position /*! \brief Get the key from the mouse position in the piano display * @@ -368,10 +386,10 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const * * \param _new_pos the new key position. */ -void PianoView::pianoScrolled( int _new_pos ) +void PianoView::pianoScrolled(int new_pos) { - m_startKey = WhiteKeys[_new_pos % WhiteKeysPerOctave]+ - ( _new_pos / WhiteKeysPerOctave ) * KeysPerOctave; + m_startKey = WhiteKeys[new_pos % Piano::WhiteKeysPerOctave] + + (new_pos / Piano::WhiteKeysPerOctave) * KeysPerOctave; update(); } @@ -381,21 +399,27 @@ void PianoView::pianoScrolled( int _new_pos ) /*! \brief Handle a context menu selection on the piano display view * - * \param _me the ContextMenuEvent to handle. + * \param me the ContextMenuEvent to handle. * \todo Is this right, or does this create the context menu? */ -void PianoView::contextMenuEvent( QContextMenuEvent * _me ) +void PianoView::contextMenuEvent(QContextMenuEvent *me) { - if( _me->pos().y() > PIANO_BASE || m_piano == NULL ) + if (me->pos().y() > PIANO_BASE || m_piano == nullptr || +// m_piano->instrumentTrack()->microtuner()->keyRangeImport()) + false) { - QWidget::contextMenuEvent( _me ); + QWidget::contextMenuEvent(me); return; } - CaptionMenu contextMenu( tr( "Base note" ) ); - AutomatableModelView amv( m_piano->instrumentTrack()->baseNoteModel(), &contextMenu ); - amv.addDefaultActions( &contextMenu ); - contextMenu.exec( QCursor::pos() ); + // check which control element is closest to the mouse and open the appropriate menu + QString title; + IntModel *noteModel = getNearestMarker(getKeyFromMouse(me->pos()), &title); + + CaptionMenu contextMenu(title); + AutomatableModelView amv(noteModel, &contextMenu); + amv.addDefaultActions(&contextMenu); + contextMenu.exec(QCursor::pos()); } @@ -417,54 +441,56 @@ void PianoView::contextMenuEvent( QContextMenuEvent * _me ) * * We finally update ourselves to show the key press * - * \param _me the mouse click to handle. + * \param me the mouse click to handle. */ -void PianoView::mousePressEvent( QMouseEvent * _me ) +void PianoView::mousePressEvent(QMouseEvent *me) { - if( _me->button() == Qt::LeftButton && m_piano != NULL ) + if (me->button() == Qt::LeftButton && m_piano != nullptr) { // get pressed key - int key_num = getKeyFromMouse( _me->pos() ); - if( _me->pos().y() > PIANO_BASE ) + int key_num = getKeyFromMouse(me->pos()); + if (me->pos().y() > PIANO_BASE) { - int y_diff = _me->pos().y() - PIANO_BASE; - int velocity = (int)( ( float ) y_diff / - ( Piano::isWhiteKey( key_num ) ? - PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) * - (float) m_piano->instrumentTrack()->midiPort()->baseVelocity() ); - if( y_diff < 0 ) + int y_diff = me->pos().y() - PIANO_BASE; + int velocity = static_cast( + static_cast(y_diff) / getKeyHeight(key_num) * + m_piano->instrumentTrack()->midiPort()->baseVelocity()); + if (y_diff < 0) { velocity = 0; } - else if( y_diff > - ( Piano::isWhiteKey( key_num ) ? - PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) ) + else if (y_diff > getKeyHeight(key_num)) { velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity(); } // set note on - m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, -1, key_num, velocity ) ); - m_piano->setKeyState( key_num, true ); + m_piano->midiEventProcessor()->processInEvent(MidiEvent(MidiNoteOn, -1, key_num, velocity)); + m_piano->setKeyState(key_num, true); m_lastKey = key_num; - emit keyPressed( key_num ); + emit keyPressed(key_num); } - else +// else if (!m_piano->instrumentTrack()->microtuner()->keyRangeImport()) + else if (true) { - if( _me->modifiers() & Qt::ControlModifier ) + // upper section, select which marker (base / first / last note) will be moved + m_movedNoteModel = getNearestMarker(key_num); + + if (me->modifiers() & Qt::ControlModifier) { - new StringPairDrag( "automatable_model", - QString::number( m_piano->instrumentTrack()->baseNoteModel()->id() ), - QPixmap(), this ); - _me->accept(); + new StringPairDrag("automatable_model", QString::number(m_movedNoteModel->id()), QPixmap(), this); + me->accept(); } else { - m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num ); - - emit baseNoteChanged(); + m_movedNoteModel->setInitValue(static_cast(key_num)); + if (m_movedNoteModel == m_piano->instrumentTrack()->baseNoteModel()) { emit baseNoteChanged(); } // TODO: not actually used by anything? } } + else + { + m_movedNoteModel = nullptr; + } // and let the user see that he pressed a key... :) update(); @@ -486,7 +512,7 @@ void PianoView::mouseReleaseEvent( QMouseEvent * ) { if( m_lastKey != -1 ) { - if( m_piano != NULL ) + if( m_piano != nullptr ) { m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, m_lastKey, 0 ) ); m_piano->setKeyState( m_lastKey, false ); @@ -496,6 +522,7 @@ void PianoView::mouseReleaseEvent( QMouseEvent * ) update(); m_lastKey = -1; + m_movedNoteModel = nullptr; } } @@ -518,7 +545,7 @@ void PianoView::mouseReleaseEvent( QMouseEvent * ) */ void PianoView::mouseMoveEvent( QMouseEvent * _me ) { - if( m_piano == NULL ) + if( m_piano == nullptr ) { return; } @@ -561,9 +588,10 @@ void PianoView::mouseMoveEvent( QMouseEvent * _me ) m_piano->setKeyState( key_num, true ); m_lastKey = key_num; } - else + else if (m_movedNoteModel != nullptr) { - m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num ); + // upper section, move the base / first / last note marker + m_movedNoteModel->setInitValue(static_cast(key_num)); } } // and let the user see that he pressed a key... :) @@ -594,7 +622,7 @@ void PianoView::keyPressEvent( QKeyEvent * _ke ) if( _ke->isAutoRepeat() == false && key_num > -1 ) { - if( m_piano != NULL ) + if( m_piano != nullptr ) { m_piano->handleKeyPress( key_num ); _ke->accept(); @@ -622,7 +650,7 @@ void PianoView::keyReleaseEvent( QKeyEvent * _ke ) ( DefaultOctave - 1 ) * KeysPerOctave; if( _ke->isAutoRepeat() == false && key_num > -1 ) { - if( m_piano != NULL ) + if( m_piano != nullptr ) { m_piano->handleKeyRelease( key_num ); _ke->accept(); @@ -646,7 +674,7 @@ void PianoView::keyReleaseEvent( QKeyEvent * _ke ) */ void PianoView::focusOutEvent( QFocusEvent * ) { - if( m_piano == NULL ) + if( m_piano == nullptr ) { return; } @@ -654,7 +682,7 @@ void PianoView::focusOutEvent( QFocusEvent * ) // focus just switched to another control inside the instrument track // window we live in? if( parentWidget()->parentWidget()->focusWidget() != this && - parentWidget()->parentWidget()->focusWidget() != NULL && + parentWidget()->parentWidget()->focusWidget() != nullptr && !(parentWidget()->parentWidget()-> focusWidget()->inherits( "QLineEdit" ) || parentWidget()->parentWidget()-> @@ -691,14 +719,13 @@ void PianoView::focusInEvent( QFocusEvent * ) * After resizing we need to adjust range of scrollbar for not allowing * to scroll too far to the right. * - * \param _event resize-event object (unused) + * \param event resize-event object (unused) */ -void PianoView::resizeEvent( QResizeEvent * _event ) +void PianoView::resizeEvent(QResizeEvent* event) { - QWidget::resizeEvent( _event ); - m_pianoScroll->setRange( 0, WhiteKeysPerOctave * NumOctaves - - (int) ceil( (float) width() / - PW_WHITE_KEY_WIDTH ) ); + QWidget::resizeEvent(event); + m_pianoScroll->setRange(0, Piano::NumWhiteKeys - + static_cast(floor(static_cast(width()) / PW_WHITE_KEY_WIDTH))); } @@ -758,6 +785,45 @@ int PianoView::getKeyX( int _key_num ) const } +/*! \brief Return the width of a given key + */ +int PianoView::getKeyWidth(int key_num) const +{ + return Piano::isWhiteKey(key_num) ? PW_WHITE_KEY_WIDTH : PW_BLACK_KEY_WIDTH; +} + +/*! \brief Return the height of a given key + */ +int PianoView::getKeyHeight(int key_num) const +{ + return Piano::isWhiteKey(key_num) ? PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT; +} + + +/*! \brief Return model and title of the marker closest to the given key + */ +IntModel* PianoView::getNearestMarker(int key, QString* title) +{ + const int base = m_piano->instrumentTrack()->baseNote(); + const int first = m_piano->instrumentTrack()->firstKey(); + const int last = m_piano->instrumentTrack()->lastKey(); + + if (abs(key - base) < abs(key - first) && abs(key - base) < abs(key - last)) + { + if (title) {*title = tr("Base note");} + return m_piano->instrumentTrack()->baseNoteModel(); + } + else if (abs(key - first) < abs(key - last)) + { + if (title) {*title = tr("First note");} + return m_piano->instrumentTrack()->firstKeyModel(); + } + else + { + if (title) {*title = tr("Last note");} + return m_piano->instrumentTrack()->lastKeyModel(); + } +} /*! \brief Paint the piano display view in response to an event @@ -786,89 +852,119 @@ void PianoView::paintEvent( QPaintEvent * ) p.setPen( Qt::white ); - const int base_key = ( m_piano != NULL ) ? - m_piano->instrumentTrack()->baseNoteModel()->value() : 0; - - QColor baseKeyColor = QApplication::palette().color( QPalette::Active, - QPalette::BrightText ); - if( Piano::isWhiteKey( base_key ) ) - { - p.fillRect( QRect( getKeyX( base_key ), 1, PW_WHITE_KEY_WIDTH-1, - PIANO_BASE-2 ), baseKeyColor ); - } - else + // Controls for first / last / base key models are shown only if microtuner or its key range import are disabled +// if (m_piano != nullptr && !m_piano->instrumentTrack()->microtuner()->keyRangeImport()) + if (m_piano != nullptr && true) { - p.fillRect( QRect( getKeyX( base_key ) + 1, 1, - PW_BLACK_KEY_WIDTH - 1, PIANO_BASE - 2 ), baseKeyColor); - } + // Draw the base note marker and first / last note boundary markers + const int base_key = m_piano->instrumentTrack()->baseNoteModel()->value(); + const int first_key = m_piano->instrumentTrack()->firstKeyModel()->value(); + const int last_key = m_piano->instrumentTrack()->lastKeyModel()->value(); + QColor marker_color = QApplication::palette().color(QPalette::Active, QPalette::BrightText); + + // - prepare triangle shapes for start / end markers + QPainterPath first_marker(QPoint(getKeyX(first_key) + 1, 1)); + first_marker.lineTo(getKeyX(first_key) + 1, PIANO_BASE); + first_marker.lineTo(getKeyX(first_key) + PIANO_BASE / 2 + 1, PIANO_BASE / 2); + QPainterPath last_marker(QPoint(getKeyX(last_key) + getKeyWidth(last_key), 1)); + last_marker.lineTo(getKeyX(last_key) + getKeyWidth(last_key), PIANO_BASE); + last_marker.lineTo(getKeyX(last_key) + getKeyWidth(last_key) - PIANO_BASE / 2, PIANO_BASE / 2); + + // - fill all markers + p.fillRect(QRect(getKeyX(base_key), 1, getKeyWidth(base_key) - 1, PIANO_BASE - 2), marker_color); + p.fillPath(first_marker, marker_color); + p.fillPath(last_marker, marker_color); + } int cur_key = m_startKey; // draw all white keys... - for( int x = 0; x < width(); ) + for (int x = 0; x < width();) { - while( Piano::isBlackKey( cur_key ) ) + while (Piano::isBlackKey(cur_key)) { ++cur_key; } - // draw pressed or not pressed key, depending on state of - // current key - if( m_piano && m_piano->isKeyPressed( cur_key ) ) + // draw normal, pressed or disabled key, depending on state and position of current key + if (m_piano && + cur_key >= m_piano->instrumentTrack()->firstKeyModel()->value() && + cur_key <= m_piano->instrumentTrack()->lastKeyModel()->value()) { - p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPressedPm ); + if (m_piano && m_piano->isKeyPressed(cur_key)) + { + p.drawPixmap(x, PIANO_BASE, *s_whiteKeyPressedPm); + } + else + { + p.drawPixmap(x, PIANO_BASE, *s_whiteKeyPm); + } } else { - p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPm ); + p.drawPixmap(x, PIANO_BASE, *s_whiteKeyDisabledPm); } x += PW_WHITE_KEY_WIDTH; - if( (Keys) (cur_key%KeysPerOctave) == Key_C ) + if ((Keys)(cur_key % KeysPerOctave) == Key_C) { - // label key of note C with "C" and number of current - // octave - p.drawText( x - PW_WHITE_KEY_WIDTH, LABEL_TEXT_SIZE + 2, - QString( "C" ) + QString::number( - cur_key / KeysPerOctave, 10 ) ); + // label key of note C with "C" and number of current octave + p.drawText(x - PW_WHITE_KEY_WIDTH, LABEL_TEXT_SIZE + 2, + QString("C") + QString::number(FirstOctave + cur_key / KeysPerOctave)); } ++cur_key; } - // reset all values, because now we're going to draw all black keys cur_key = m_startKey; int white_cnt = 0; int startKey = m_startKey; - if( startKey > 0 && Piano::isBlackKey( (Keys)(--startKey) ) ) + if (startKey > 0 && Piano::isBlackKey(static_cast(--startKey))) { - if( m_piano && m_piano->isKeyPressed( startKey ) ) + if (m_piano && + startKey >= m_piano->instrumentTrack()->firstKeyModel()->value() && + startKey <= m_piano->instrumentTrack()->lastKeyModel()->value()) { - p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm ); + if (m_piano && m_piano->isKeyPressed(startKey)) + { + p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm); + } + else + { + p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm); + } } else { - p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm ); + p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyDisabledPm); } } // now draw all black keys... - for( int x = 0; x < width(); ) + for (int x = 0; x < width();) { - if( Piano::isBlackKey( cur_key ) ) + if (Piano::isBlackKey(cur_key)) { - // draw pressed or not pressed key, depending on - // state of current key - if( m_piano && m_piano->isKeyPressed( cur_key ) ) + // draw normal, pressed or disabled key, depending on state and position of current key + if (m_piano && + cur_key >= m_piano->instrumentTrack()->firstKeyModel()->value() && + cur_key <= m_piano->instrumentTrack()->lastKeyModel()->value()) { - p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm ); + if (m_piano && m_piano->isKeyPressed(cur_key)) + { + p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm); + } + else + { + p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm); + } } else { - p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm ); + p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyDisabledPm); } x += PW_WHITE_KEY_WIDTH; white_cnt = 0; @@ -878,17 +974,16 @@ void PianoView::paintEvent( QPaintEvent * ) // simple workaround for increasing x if there were two // white keys (e.g. between E and F) ++white_cnt; - if( white_cnt > 1 ) + if (white_cnt > 1) { x += PW_WHITE_KEY_WIDTH; } } - ++cur_key; + // stop drawing when all keys are drawn, even if an extra black key could fit + if (++cur_key == NumKeys) {break;} } } - - diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index fd71dac766a..a8b0b373e30 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -31,8 +31,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -121,11 +122,11 @@ QPixmap* PianoRoll::s_toolKnife = nullptr; TextFloat * PianoRoll::s_textFloat = NULL; -static QString s_noteStrings[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; +static QString s_noteStrings[12] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; -static QString getNoteString( int key ) +static QString getNoteString(int key) { - return s_noteStrings[key % 12] + QString::number( static_cast( key / KeysPerOctave ) ); + return s_noteStrings[key % 12] + QString::number(static_cast(FirstOctave + key / KeysPerOctave)); } // used for drawing of piano @@ -174,7 +175,6 @@ PianoRoll::PianoRoll() : m_userSetNotesEditHeight(100), m_ppb( DEFAULT_PR_PPB ), m_keyLineHeight(DEFAULT_KEY_LINE_HEIGHT), - m_octaveHeight(m_keyLineHeight * KeysPerOctave), m_whiteKeySmallHeight(qFloor(m_keyLineHeight * 1.5)), m_whiteKeyBigHeight(m_keyLineHeight * 2), m_blackKeyHeight(m_keyLineHeight), @@ -886,11 +886,10 @@ void PianoRoll::setCurrentPattern( Pattern* newPattern ) } } - if( total_notes > 0 ) + if (total_notes > 0) { - central_key = central_key / total_notes - - ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; - m_startKey = qBound( 0, central_key, NumOctaves * KeysPerOctave ); + central_key = central_key / total_notes - (NumKeys - m_totalKeysToScroll) / 2; + m_startKey = qBound(0, central_key, NumKeys); } // resizeEvent() does the rest for us (scrolling, range-checking @@ -904,6 +903,9 @@ void PianoRoll::setCurrentPattern( Pattern* newPattern ) connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) ); connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); + connect(m_pattern->instrumentTrack()->firstKeyModel(), SIGNAL(dataChanged()), this, SLOT(update())); + connect(m_pattern->instrumentTrack()->lastKeyModel(), SIGNAL(dataChanged()), this, SLOT(update())); + update(); emit currentPatternChanged(); } @@ -3007,8 +3009,8 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) f.setPixelSize(m_keyLineHeight * 0.8); p.setFont(f); // font size doesn't change without this for some reason QFontMetrics fontMetrics(p.font()); - // G4 is one of the widest - QRect const boundingRect = fontMetrics.boundingRect(QString("G4")); + // G-1 is one of the widest; plus one pixel margin for the shadow + QRect const boundingRect = fontMetrics.boundingRect(QString("G-1")) + QMargins(0, 0, 1, 0); // Order of drawing // - vertical quantization lines @@ -3160,6 +3162,8 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) const int key, const int yb) { + const bool mapped = m_pattern->instrumentTrack()->firstKeyModel()->value() <= key && + m_pattern->instrumentTrack()->lastKeyModel()->value() >= key; const bool pressed = m_pattern->instrumentTrack()->pianoModel()->isKeyPressed(key); const int keyCode = key % KeysPerOctave; const int yt = yb - gridCorrection(key); @@ -3171,10 +3175,26 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) { case PR_WHITE_KEY_SMALL: case PR_WHITE_KEY_BIG: - p.setBrush(pressed ? m_whiteKeyActiveBackground : m_whiteKeyInactiveBackground); + if (mapped) + { + if (pressed) { p.setBrush(m_whiteKeyActiveBackground); } + else { p.setBrush(m_whiteKeyInactiveBackground); } + } + else + { + p.setBrush(m_whiteKeyDisabledBackground); + } break; case PR_BLACK_KEY: - p.setBrush(pressed ? m_blackKeyActiveBackground : m_blackKeyInactiveBackground); + if (mapped) + { + if (pressed) { p.setBrush(m_blackKeyActiveBackground); } + else { p.setBrush(m_blackKeyInactiveBackground); } + } + else + { + p.setBrush(m_blackKeyDisabledBackground); + } } // draw key p.drawRect(PIANO_X, yt, kw, kh); @@ -3693,7 +3713,7 @@ void PianoRoll::updateScrollbars() } // responsible for moving/resizing scrollbars after window-resizing -void PianoRoll::resizeEvent(QResizeEvent * re) +void PianoRoll::resizeEvent(QResizeEvent* re) { updatePositionLineHeight(); updateScrollbars(); @@ -4261,16 +4281,14 @@ void PianoRoll::updateYScroll() height() - PR_TOP_MARGIN - SCROLLBAR_SIZE); - int total_pixels = m_octaveHeight * NumOctaves - (height() - - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - - m_notesEditHeight); - m_totalKeysToScroll = qMax(0, total_pixels * KeysPerOctave / m_octaveHeight); + const int visible_space = keyAreaBottom() - keyAreaTop(); + m_totalKeysToScroll = qMax(0, NumKeys - 1 - visible_space / m_keyLineHeight); m_topBottomScroll->setRange(0, m_totalKeysToScroll); if(m_startKey > m_totalKeysToScroll) { - m_startKey = qMax(0, m_totalKeysToScroll); + m_startKey = m_totalKeysToScroll; } m_topBottomScroll->setValue(m_totalKeysToScroll - m_startKey); } @@ -4517,7 +4535,6 @@ void PianoRoll::zoomingChanged() void PianoRoll::zoomingYChanged() { m_keyLineHeight = m_zoomYLevels[m_zoomingYModel.value()] * DEFAULT_KEY_LINE_HEIGHT; - m_octaveHeight = m_keyLineHeight * KeysPerOctave; m_whiteKeySmallHeight = qFloor(m_keyLineHeight * 1.5); m_whiteKeyBigHeight = m_keyLineHeight * 2; m_blackKeyHeight = m_keyLineHeight; //round(m_keyLineHeight * 1.3333); diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 4c5304fe9d2..0ca8d657187 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -40,6 +40,7 @@ #include "AutomationPattern.h" #include "BBTrack.h" #include "CaptionMenu.h" +#include "ComboBox.h" #include "ConfigManager.h" #include "ControllerConnection.h" #include "DataFile.h" @@ -61,6 +62,7 @@ #include "LcdSpinBox.h" #include "LedCheckbox.h" #include "LeftRightNav.h" +#include "lmms_constants.h" #include "MainWindow.h" #include "MidiClient.h" #include "MidiPortMenu.h" @@ -92,9 +94,10 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_sustainPedalPressed( false ), m_silentBuffersProcessed( false ), m_previewMode( false ), + m_baseNoteModel(0, 0, NumKeys - 1, this, tr("Base note")), + m_firstKeyModel(0, 0, NumKeys - 1, this, tr("First note")), + m_lastKeyModel(0, 0, NumKeys - 1, this, tr("Last note")), m_hasAutoMidiDev( false ), - m_baseNoteModel( 0, 0, KeysPerOctave * NumOctaves - 1, this, - tr( "Base note" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_audioPort( tr( "unnamed_track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ), @@ -106,11 +109,14 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_soundShaping( this ), m_arpeggio( this ), m_noteStacking( this ), - m_piano( this ) + m_piano(this) +// m_microtuner(this) { m_pitchModel.setCenterValue( 0 ); m_panningModel.setCenterValue( DefaultPanning ); m_baseNoteModel.setInitValue( DefaultKey ); + m_firstKeyModel.setInitValue(0); + m_lastKeyModel.setInitValue(NumKeys - 1); m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels()-1, 1); @@ -138,14 +144,10 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : setName( tr( "Default preset" ) ); - connect( &m_baseNoteModel, SIGNAL( dataChanged() ), - this, SLOT( updateBaseNote() ), Qt::DirectConnection ); - connect( &m_pitchModel, SIGNAL( dataChanged() ), - this, SLOT( updatePitch() ), Qt::DirectConnection ); - connect( &m_pitchRangeModel, SIGNAL( dataChanged() ), - this, SLOT( updatePitchRange() ), Qt::DirectConnection ); - connect( &m_effectChannelModel, SIGNAL( dataChanged() ), - this, SLOT( updateEffectChannel() ), Qt::DirectConnection ); + connect(&m_baseNoteModel, SIGNAL(dataChanged()), this, SLOT(updateBaseNote()), Qt::DirectConnection); + connect(&m_pitchModel, SIGNAL(dataChanged()), this, SLOT(updatePitch()), Qt::DirectConnection); + connect(&m_pitchRangeModel, SIGNAL(dataChanged()), this, SLOT(updatePitchRange()), Qt::DirectConnection); + connect(&m_effectChannelModel, SIGNAL(dataChanged()), this, SLOT(updateEffectChannel()), Qt::DirectConnection); } @@ -156,6 +158,15 @@ int InstrumentTrack::baseNote() const return m_baseNoteModel.value() - mp; } +int InstrumentTrack::firstKey() const +{ + return m_firstKeyModel.value(); +} + +int InstrumentTrack::lastKey() const +{ + return m_lastKeyModel.value(); +} InstrumentTrack::~InstrumentTrack() @@ -294,7 +305,8 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim case MidiNoteOn: if( event.velocity() > 0 ) { - if( m_notes[event.key()] == NULL ) + // play a note only if it is not already playing and if it is within configured bounds + if (m_notes[event.key()] == nullptr && event.key() >= firstKey() && event.key() <= lastKey()) { NotePlayHandle* nph = NotePlayHandleManager::acquire( @@ -775,6 +787,8 @@ void InstrumentTrack::saveTrackSpecificSettings( QDomDocument& doc, QDomElement m_effectChannelModel.saveSettings( doc, thisElement, "fxch" ); m_baseNoteModel.saveSettings( doc, thisElement, "basenote" ); + m_firstKeyModel.saveSettings(doc, thisElement, "firstkey"); + m_lastKeyModel.saveSettings(doc, thisElement, "lastkey"); m_useMasterPitchModel.saveSettings( doc, thisElement, "usemasterpitch"); // Save MIDI CC stuff @@ -839,6 +853,8 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement m_effectChannelModel.loadSettings( thisElement, "fxch" ); } m_baseNoteModel.loadSettings( thisElement, "basenote" ); + m_firstKeyModel.loadSettings(thisElement, "firstkey"); + m_lastKeyModel.loadSettings(thisElement, "lastkey"); m_useMasterPitchModel.loadSettings( thisElement, "usemasterpitch"); // clear effect-chain just in case we load an old preset without FX-data @@ -1006,7 +1022,6 @@ void InstrumentTrack::autoAssignMidiDevice(bool assign) } - // #### ITV: