Real-time (manual) note entry mode
This commit is contained in:
parent
201194209a
commit
8371a2520a
8 changed files with 161 additions and 28 deletions
|
@ -1779,12 +1779,21 @@ bool Score::processMidiInput()
|
|||
if (MScore::debugMode)
|
||||
qDebug("processMidiInput");
|
||||
|
||||
NoteEntryMethod entryMethod = _is.noteEntryMethod();
|
||||
bool cmdActive = false;
|
||||
while (!midiInputQueue()->empty()) {
|
||||
MidiInputEvent ev = midiInputQueue()->dequeue();
|
||||
for (auto itr = activeMidiPitches()->begin(); itr != activeMidiPitches()->end();) {
|
||||
if ((*itr).pitch == ev.pitch)
|
||||
itr = activeMidiPitches()->erase(itr);
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
if (MScore::debugMode)
|
||||
qDebug("<-- !noteentry dequeue %i", ev.pitch);
|
||||
if (!noteEntryMode()) {
|
||||
if (!noteEntryMode()
|
||||
|| entryMethod == NoteEntryMethod::REALTIME_AUTO
|
||||
|| entryMethod == NoteEntryMethod::REALTIME_MANUAL) {
|
||||
int staffIdx = selection().staffStart();
|
||||
Part* p;
|
||||
if (staffIdx < 0 || staffIdx >= nstaves())
|
||||
|
@ -1802,25 +1811,39 @@ bool Score::processMidiInput()
|
|||
0.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ev.velocity == 0)
|
||||
if (noteEntryMode()) {
|
||||
if (ev.velocity == 0) {
|
||||
// delete note in realtime mode
|
||||
//Chord* chord = static_cast<Chord*>(_is.cr());
|
||||
//std::vector<Note*> notes = chord->notes();
|
||||
if (entryMethod == NoteEntryMethod::REALTIME_AUTO || entryMethod == NoteEntryMethod::REALTIME_MANUAL) {
|
||||
if (_is.cr()->isChord()) {
|
||||
Note* n = static_cast<Chord*>(_is.cr())->findNote(ev.pitch);
|
||||
if (n) {
|
||||
qDebug("Pitches match! Note %i, Pitch %i", n->pitch(), ev.pitch);
|
||||
if (!cmdActive) {
|
||||
startCmd();
|
||||
cmdActive = true;
|
||||
}
|
||||
deleteItem(n->tieBack());
|
||||
deleteItem(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!cmdActive) {
|
||||
startCmd();
|
||||
cmdActive = true;
|
||||
}
|
||||
NoteVal nval(ev.pitch);
|
||||
Staff* st = staff(inputState().track() / VOICES);
|
||||
|
||||
// if transposing, interpret MIDI pitch as representing desired written pitch
|
||||
// set pitch based on corresponding sounding pitch
|
||||
if (!styleB(StyleIdx::concertPitch))
|
||||
nval.pitch += st->part()->instrument(inputState().tick())->transpose().chromatic;
|
||||
// let addPitch calculate tpc values from pitch
|
||||
//Key key = st->key(inputState().tick());
|
||||
//nval.tpc1 = pitch2tpc(nval.pitch, key, Prefer::NEAREST);
|
||||
|
||||
addPitch(nval, ev.chord);
|
||||
if (activeMidiPitches()->empty())
|
||||
ev.chord = false;
|
||||
else
|
||||
ev.chord = true;
|
||||
// TODO: add shadow note instead of real note in realtime modes
|
||||
// (note becomes real when realtime-advance triggered).
|
||||
addMidiPitch(ev.pitch, ev.chord);
|
||||
activeMidiPitches()->push_back(ev);
|
||||
}
|
||||
}
|
||||
if (cmdActive) {
|
||||
|
|
|
@ -360,8 +360,11 @@ Note* Score::addNote(Chord* chord, NoteVal& noteVal)
|
|||
setPlayNote(true);
|
||||
setPlayChord(true);
|
||||
select(note, SelectType::SINGLE, 0);
|
||||
if (!chord->staff()->isTabStaff())
|
||||
_is.moveToNextInputPos();
|
||||
if (!chord->staff()->isTabStaff()) {
|
||||
NoteEntryMethod entryMethod = _is.noteEntryMethod();
|
||||
if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL)
|
||||
_is.moveToNextInputPos();
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
||||
|
@ -1030,6 +1033,47 @@ NoteVal Score::noteValForPosition(Position pos, bool &error)
|
|||
return nval;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// addTiedMidiPitch
|
||||
//---------------------------------------------------------
|
||||
|
||||
Note* Score::addTiedMidiPitch(int pitch, bool addFlag, Chord* prevChord)
|
||||
{
|
||||
if (prevChord->isChord()) {
|
||||
Note* n = addMidiPitch(pitch, addFlag);
|
||||
Note* nn = prevChord->findNote(n->pitch());
|
||||
if (nn) {
|
||||
Tie* tie = new Tie(this);
|
||||
tie->setStartNote(nn);
|
||||
tie->setEndNote(n);
|
||||
tie->setTrack(n->track());
|
||||
undoAddElement(tie);
|
||||
return n;
|
||||
}
|
||||
undoRemoveElement(n);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// addMidiPitch
|
||||
//---------------------------------------------------------
|
||||
|
||||
Note* Score::addMidiPitch(int pitch, bool addFlag)
|
||||
{
|
||||
NoteVal nval(pitch);
|
||||
Staff* st = staff(inputState().track() / VOICES);
|
||||
|
||||
// if transposing, interpret MIDI pitch as representing desired written pitch
|
||||
// set pitch based on corresponding sounding pitch
|
||||
if (!styleB(StyleIdx::concertPitch))
|
||||
nval.pitch += st->part()->instrument(inputState().tick())->transpose().chromatic;
|
||||
// let addPitch calculate tpc values from pitch
|
||||
//Key key = st->key(inputState().tick());
|
||||
//nval.tpc1 = pitch2tpc(nval.pitch, key, Prefer::NEAREST);
|
||||
return addPitch(nval, addFlag);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// addPitch
|
||||
//---------------------------------------------------------
|
||||
|
@ -1044,8 +1088,11 @@ Note* Score::addPitch(NoteVal& nval, bool addFlag)
|
|||
return 0;
|
||||
}
|
||||
Note* note = addNote(c, nval);
|
||||
if (_is.lastSegment() == _is.segment())
|
||||
_is.moveToNextInputPos();
|
||||
if (_is.lastSegment() == _is.segment()) {
|
||||
NoteEntryMethod entryMethod = _is.noteEntryMethod();
|
||||
if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL)
|
||||
_is.moveToNextInputPos();
|
||||
}
|
||||
return note;
|
||||
}
|
||||
expandVoice();
|
||||
|
@ -1172,8 +1219,11 @@ Note* Score::addPitch(NoteVal& nval, bool addFlag)
|
|||
if (next)
|
||||
_is.moveInputPos(next->segment());
|
||||
}
|
||||
else
|
||||
_is.moveToNextInputPos();
|
||||
else {
|
||||
NoteEntryMethod entryMethod = _is.noteEntryMethod();
|
||||
if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL)
|
||||
_is.moveToNextInputPos();
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
||||
|
@ -2725,7 +2775,8 @@ void Score::cmdEnterRest(const TDuration& d)
|
|||
NoteVal nval;
|
||||
setNoteRest(_is.segment(), track, nval, d.fraction(), Direction::AUTO);
|
||||
_is.moveToNextInputPos();
|
||||
_is.setRest(false); // continue with normal note entry
|
||||
if (!noteEntryMode() || usingNoteEntryMethod(NoteEntryMethod::STEPTIME))
|
||||
_is.setRest(false); // continue with normal note entry
|
||||
endCmd();
|
||||
}
|
||||
|
||||
|
|
|
@ -644,6 +644,8 @@ class Score : public QObject, public ScoreElement {
|
|||
|
||||
Note* addPitch(NoteVal&, bool addFlag);
|
||||
void addPitch(int pitch, bool addFlag, bool insert);
|
||||
Note* addTiedMidiPitch(int pitch, bool addFlag, Chord* prevChord);
|
||||
Note* addMidiPitch(int pitch, bool addFlag);
|
||||
Note* addNote(Chord*, NoteVal& noteVal);
|
||||
|
||||
NoteVal noteValForPosition(Position pos, bool &error);
|
||||
|
@ -1099,6 +1101,7 @@ class Score : public QObject, public ScoreElement {
|
|||
virtual QVariant propertyDefault(P_ID) const override;
|
||||
|
||||
virtual inline QQueue<MidiInputEvent>* midiInputQueue();
|
||||
virtual inline std::list<MidiInputEvent>* activeMidiPitches();
|
||||
|
||||
friend class ChangeSynthesizerState;
|
||||
friend class Chord;
|
||||
|
@ -1124,7 +1127,8 @@ class MasterScore : public Score {
|
|||
|
||||
// bool _undoRedo; ///< true if in processing a undo/redo
|
||||
int _midiPortCount { 0 }; // A count of JACK/ALSA midi out ports
|
||||
QQueue<MidiInputEvent> _midiInputQueue;
|
||||
QQueue<MidiInputEvent> _midiInputQueue; // MIDI events that have yet to be processed
|
||||
std::list<MidiInputEvent> _activeMidiPitches; // MIDI keys currently being held down
|
||||
QList<MidiMapping> _midiMapping;
|
||||
bool isSimpleMidiMaping; // midi mapping is simple if all ports and channels
|
||||
// don't decrease and don't have gaps
|
||||
|
@ -1152,7 +1156,8 @@ class MasterScore : public Score {
|
|||
virtual RepeatList* repeatList() const override { return _repeatList; }
|
||||
virtual QList<Excerpt*>& excerpts() override { return _excerpts; }
|
||||
virtual const QList<Excerpt*>& excerpts() const override { return _excerpts; }
|
||||
virtual QQueue<MidiInputEvent>* midiInputQueue() override { return &_midiInputQueue; }
|
||||
virtual QQueue<MidiInputEvent>* midiInputQueue() override { return &_midiInputQueue; }
|
||||
virtual std::list<MidiInputEvent>* activeMidiPitches() override { return &_activeMidiPitches; }
|
||||
|
||||
virtual void setUpdateAll() override { _cmdState.setUpdateMode(UpdateMode::UpdateAll); }
|
||||
virtual void setLayoutAll() override { _cmdState.setUpdateMode(UpdateMode::LayoutAll); }
|
||||
|
@ -1210,7 +1215,8 @@ inline TempoMap* Score::tempomap() const { return _masterScore->te
|
|||
inline TimeSigMap* Score::sigmap() const { return _masterScore->sigmap(); }
|
||||
inline QList<Excerpt*>& Score::excerpts() { return _masterScore->excerpts(); }
|
||||
inline const QList<Excerpt*>& Score::excerpts() const { return _masterScore->excerpts(); }
|
||||
inline QQueue<MidiInputEvent>* Score::midiInputQueue() { return _masterScore->midiInputQueue(); }
|
||||
inline QQueue<MidiInputEvent>* Score::midiInputQueue() { return _masterScore->midiInputQueue(); }
|
||||
inline std::list<MidiInputEvent>* Score::activeMidiPitches() { return _masterScore->activeMidiPitches(); }
|
||||
inline void Score::setUpdateAll() { _masterScore->setUpdateAll(); }
|
||||
inline void Score::setLayoutAll() { _masterScore->setLayoutAll(); }
|
||||
inline void Score::setLayout(int tick) { _masterScore->setLayout(tick); }
|
||||
|
|
|
@ -192,6 +192,10 @@
|
|||
<key>rest</key>
|
||||
<seq>0</seq>
|
||||
</SC>
|
||||
<SC>
|
||||
<key>realtime-advance</key>
|
||||
<seq>Enter</seq>
|
||||
</SC>
|
||||
<SC>
|
||||
<key>add-staccato</key>
|
||||
<seq>Shift+S</seq>
|
||||
|
|
|
@ -4516,7 +4516,10 @@ void MuseScore::endCmd()
|
|||
if (samePitch && !cs->selection().elements().empty())
|
||||
e = cs->selection().elements()[0];
|
||||
|
||||
if (e && (cs->playNote() || cs->playChord())) {
|
||||
NoteEntryMethod entryMethod = cs->noteEntryMethod();
|
||||
if (e && (cs->playNote() || cs->playChord())
|
||||
&& entryMethod != NoteEntryMethod::REALTIME_AUTO
|
||||
&& entryMethod != NoteEntryMethod::REALTIME_MANUAL) {
|
||||
if (cs->playChord() && preferences.playChordOnAddNote && e->type() == Element::Type::NOTE)
|
||||
play(static_cast<Note*>(e)->chord());
|
||||
else
|
||||
|
|
|
@ -3259,6 +3259,14 @@ void ScoreView::cmd(const QAction* a)
|
|||
cmdCopyLyricsToClipboard();
|
||||
}
|
||||
|
||||
// STATE_NOTE_ENTRY_REALTIME actions (auto or manual)
|
||||
|
||||
else if (cmd == "realtime-advance") {
|
||||
// The user will want to press notes "on the beat" and not before the beat, so wait a
|
||||
// little in case midi input event is received just after realtime-advance was called.
|
||||
QTimer::singleShot(100, this, SLOT(cmdRealtimeAdvance()));
|
||||
}
|
||||
|
||||
// STATE_HARMONY_FIGBASS_EDIT actions
|
||||
|
||||
else if (cmd == "advance-longa") {
|
||||
|
@ -4322,7 +4330,10 @@ void ScoreView::cmdEnterRest(const TDuration& d)
|
|||
qDebug("cmdEnterRest %s", qPrintable(d.name()));
|
||||
if (!noteEntryMode())
|
||||
sm->postEvent(new CommandEvent("note-input"));
|
||||
_score->cmdEnterRest(d);
|
||||
if (_score->usingNoteEntryMethod(NoteEntryMethod::RHYTHM))
|
||||
_score->cmd(getAction("pad-rest"));
|
||||
else
|
||||
_score->cmdEnterRest(d);
|
||||
#if 0
|
||||
expandVoice();
|
||||
if (_is.cr() == 0) {
|
||||
|
@ -5254,6 +5265,33 @@ qDebug("midiNoteReceived %d chord %d", pitch, chord);
|
|||
cmd(0);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// cmdRealtimeAdvance
|
||||
// move input forwards and extend current chord/rest.
|
||||
//---------------------------------------------------------
|
||||
|
||||
void ScoreView::cmdRealtimeAdvance()
|
||||
{
|
||||
InputState& is = _score->inputState();
|
||||
if (!is.noteEntryMode())
|
||||
return;
|
||||
_score->startCmd();
|
||||
if (is.cr()->duration() != is.duration().fraction())
|
||||
_score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
|
||||
Chord* prevChord = static_cast<Chord*>(is.cr());
|
||||
is.moveToNextInputPos();
|
||||
if (_score->activeMidiPitches()->empty())
|
||||
_score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), Direction::AUTO);
|
||||
else {
|
||||
bool partOfChord = false;
|
||||
for (const MidiInputEvent &ev : *_score->activeMidiPitches()) {
|
||||
_score->addTiedMidiPitch(ev.pitch, partOfChord, prevChord);
|
||||
partOfChord = true;
|
||||
}
|
||||
}
|
||||
_score->endCmd();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// cmdAddPitch
|
||||
/// insert note or add note to chord
|
||||
|
@ -6233,4 +6271,3 @@ void ScoreView::updateShadowNotes()
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -279,6 +279,7 @@ class ScoreView : public QWidget, public MuseScoreView {
|
|||
|
||||
void posChanged(POS pos, unsigned tick);
|
||||
void loopToggled(bool);
|
||||
void cmdRealtimeAdvance();
|
||||
|
||||
public slots:
|
||||
void setViewRect(const QRectF&);
|
||||
|
|
|
@ -584,6 +584,14 @@ Shortcut Shortcut::_sc[] = {
|
|||
0,
|
||||
Icons::quartrest_ICON
|
||||
},
|
||||
{
|
||||
MsWidget::SCORE_TAB,
|
||||
STATE_NOTE_ENTRY_METHOD_REALTIME_AUTO | STATE_NOTE_ENTRY_METHOD_REALTIME_MANUAL,
|
||||
"realtime-advance",
|
||||
QT_TRANSLATE_NOOP("action","Real-time advance"),
|
||||
QT_TRANSLATE_NOOP("action","Move the cursor forward in real-time input mode"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
MsWidget::SCORE_TAB,
|
||||
STATE_NORMAL | STATE_NOTE_ENTRY,
|
||||
|
|
Loading…
Reference in a new issue