MIDI range MKII (extracted from microtonal PR) (#5868)

* Update MIDI range to match MIDI specification

Co-authored-by: IanCaio <iancaio_dev@hotmail.com>
Co-authored-by: Kevin Zander <veratil@gmail.com>
Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
Co-authored-by: Spekular <Spekular@users.noreply.github.com>
This commit is contained in:
Martin Pavelek
2021-04-21 07:29:47 +02:00
committed by GitHub
parent fbea78945b
commit f288137c22
30 changed files with 480 additions and 240 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -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 );

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -120,6 +120,7 @@ private:
void upgrade_1_3_0();
void upgrade_noHiddenClipNames();
void upgrade_automationNodes();
void upgrade_extendedNoteRange();
// List of all upgrade methods
static const std::vector<UpgradeMethod> UPGRADE_METHODS;

View File

@@ -174,7 +174,19 @@ public:
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 @@ private:
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 @@ private:
IntModel m_effectChannelModel;
BoolModel m_useMasterPitchModel;
Instrument * m_instrument;
InstrumentSoundShaping m_soundShaping;
InstrumentFunctionArpeggio m_arpeggio;

View File

@@ -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;

View File

@@ -125,7 +125,7 @@ public:
/*! 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 */

View File

@@ -65,6 +65,10 @@ public:
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 )

View File

@@ -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 @@ private:
int m_pianoKeysVisible;
int m_keyLineHeight;
int m_octaveHeight;
int m_whiteKeySmallHeight;
int m_whiteKeyBigHeight;
int m_blackKeyHeight;
@@ -475,10 +476,12 @@ private:
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 & );

View File

@@ -28,6 +28,7 @@
#include <QPixmap>
#include <QScrollBar>
#include "AutomatableModel.h"
#include "ModelView.h"
class Piano;
@@ -63,17 +64,24 @@ protected:
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 @@ signals:
#endif

View File

@@ -122,7 +122,9 @@ public:
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);
}
};

View File

@@ -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;

View File

@@ -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 );

View File

@@ -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; voice<OPL2_VOICES; ++voice) {
if( voiceNote[voice] == key ) {
theEmulator->write(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;

View File

@@ -824,8 +824,8 @@ QString XpressiveHelpView::s_helpText=
"<h4>Available variables:</h4><br>"
"<b>t</b> - Time in seconds.<br>"
"<b>f</b> - Note's pitched frequency. Available only in the output expressions.<br>"
"<b>key</b> - Note's keyboard key. 0 denotes C0, 48 denotes C4, 96 denotes C8. Available only in the output expressions.<br>"
"<b>bnote</b> - Base note. By default it is 57 which means A5, unless you change it.<br>"
"<b>key</b> - Note's keyboard key. 0 denotes C-1, 60 denotes C4, 127 denotes G9. Available only in the output expressions.<br>"
"<b>bnote</b> - Base note. By default it is 69 which means A4, unless you change it.<br>"
"<b>srate</b> - Sample rate. In wave expression it returns the wave's number of samples.<br>"
"<b>tempo</b> - Song's Tempo. Available only in the output expressions.<br>"
"<b>v</b> - Note's volume. Note that the output is already multiplied by the volume. Available only in the output expressions.<br>"

View File

@@ -33,7 +33,6 @@
#include "Instrument.h"
#include "InstrumentView.h"
#include "Note.h"
#include "CustomTextKnob.h"
#include "SubWindow.h"
#include "AutomatableModel.h"

View File

@@ -34,7 +34,6 @@
#define BUILD_REMOTE_PLUGIN_CLIENT
#include "Note.h"
#include "RemotePlugin.h"
#include "RemoteZynAddSubFx.h"
#include "LocalZynAddSubFx.h"

View File

@@ -31,7 +31,6 @@
#include "AutomationTrack.h"
#include "BBTrackContainer.h"
#include "LocaleHelper.h"
#include "Note.h"
#include "ProjectJournal.h"
#include "Song.h"

View File

@@ -69,7 +69,7 @@ const std::vector<DataFile::UpgradeMethod> 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

View File

@@ -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

View File

@@ -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() );

View File

@@ -318,28 +318,28 @@ void MidiApple::HandleReadCallback( const MIDIPacketList *pktlist, void *srcConn
}
unsigned char messageChannel = status & 0xF;
const MidiEventTypes cmdtype = static_cast<MidiEventTypes>( status & 0xF0 );
const MidiEventTypes cmdtype = static_cast<MidiEventTypes>(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:

View File

@@ -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<MidiEventTypes>( m_midiParseData.m_status ) );
m_midiParseData.m_midiEvent.setChannel( m_midiParseData.m_channel );
m_midiParseData.m_midiEvent.setType(static_cast<MidiEventTypes>(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;
}

View File

@@ -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

View File

@@ -41,15 +41,18 @@
#include <QCursor>
#include <QKeyEvent>
#include <QPainter>
#include <QPainterPath>
#include <QVBoxLayout>
#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<Piano>();
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<int>(
static_cast<float>(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<float>(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<float>(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<int>(floor(static_cast<float>(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 ) )
// 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, PW_WHITE_KEY_WIDTH-1,
PIANO_BASE-2 ), baseKeyColor );
}
else
{
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<Keys>(--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;}
}
}

View File

@@ -31,8 +31,9 @@
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QMessageBox>
#include <QMargins>
#include <QMdiArea>
#include <QMessageBox>
#include <QPainter>
#include <QPointer>
#include <QScrollBar>
@@ -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<int>( key / KeysPerOctave ) );
return s_noteStrings[key % 12] + QString::number(static_cast<int>(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);

View File

@@ -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: