MuseScore/libmscore/noteentry.cpp

648 lines
25 KiB
C++

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2002-2016 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "utils.h"
#include "score.h"
#include "chord.h"
#include "measure.h"
#include "tie.h"
#include "tuplet.h"
#include "staff.h"
#include "part.h"
#include "drumset.h"
#include "slur.h"
#include "navigate.h"
#include "stringdata.h"
#include "undo.h"
#include "range.h"
#include "excerpt.h"
namespace Ms {
//---------------------------------------------------------
// noteValForPosition
//---------------------------------------------------------
NoteVal Score::noteValForPosition(Position pos, bool &error)
{
error = false;
Segment* s = pos.segment;
int line = pos.line;
int tick = s->tick();
int staffIdx = pos.staffIdx;
Staff* st = staff(staffIdx);
ClefType clef = st->clef(tick);
const Instrument* instr = st->part()->instrument(s->tick());
NoteVal nval;
const StringData* stringData = 0;
switch (st->staffType(tick)->group()) {
case StaffGroup::PERCUSSION: {
if (_is.rest())
break;
const Drumset* ds = instr->drumset();
nval.pitch = _is.drumNote();
if (nval.pitch < 0) {
error = true;
return nval;
}
nval.headGroup = ds->noteHead(nval.pitch);
if (nval.headGroup == NoteHead::Group::HEAD_INVALID) {
error = true;
return nval;
}
break;
}
case StaffGroup::TAB: {
if (_is.rest()) {
error = true;
return nval;
}
stringData = instr->stringData();
if (line < 0 || line >= stringData->strings()) {
error = true;
return nval;
}
// build a default NoteVal for that string
nval.string = line;
if (pos.fret != FRET_NONE) // if a fret is given, use it
nval.fret = pos.fret;
else { // if no fret, use 0 as default
_is.setString(line);
nval.fret = 0;
}
// reduce within fret limit
if (nval.fret > stringData->frets())
nval.fret = stringData->frets();
// for open strings, only accepts fret 0 (strings in StringData are from bottom to top)
int strgDataIdx = stringData->strings() - line - 1;
if (nval.fret > 0 && stringData->stringList().at(strgDataIdx).open == true)
nval.fret = 0;
nval.pitch = stringData->getPitch(line, nval.fret, st, tick);
break;
}
case StaffGroup::STANDARD: {
AccidentalVal acci = s->measure()->findAccidental(s, staffIdx, line, error);
if (error)
return nval;
int step = absStep(line, clef);
int octave = step/7;
nval.pitch = step2pitch(step) + octave * 12 + int(acci);
if (styleB(StyleIdx::concertPitch))
nval.tpc1 = step2tpc(step % 7, acci);
else {
nval.pitch += instr->transpose().chromatic;
nval.tpc2 = step2tpc(step % 7, acci);
Interval v = st->part()->instrument(tick)->transpose();
if (v.isZero())
nval.tpc1 = nval.tpc2;
else
nval.tpc1 = Ms::transposeTpc(nval.tpc2, v, true);
}
}
break;
}
return nval;
}
//---------------------------------------------------------
// addPitch
//---------------------------------------------------------
Note* Score::addPitch(NoteVal& nval, bool addFlag)
{
if (addFlag) {
Chord* c = toChord(_is.lastSegment()->element(_is.track()));
if (c == 0 || !c->isChord()) {
qDebug("Score::addPitch: cr %s", c ? c->name() : "zero");
return 0;
}
Note* note = addNote(c, nval);
if (_is.lastSegment() == _is.segment()) {
NoteEntryMethod entryMethod = _is.noteEntryMethod();
if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL)
_is.moveToNextInputPos();
}
return note;
}
expandVoice();
// insert note
Direction stemDirection = Direction::AUTO;
int track = _is.track();
if (_is.drumNote() != -1) {
nval.pitch = _is.drumNote();
const Drumset* ds = _is.drumset();
nval.headGroup = ds->noteHead(nval.pitch);
stemDirection = ds->stemDirection(nval.pitch);
track = ds->voice(nval.pitch) + (_is.track() / VOICES) * VOICES;
_is.setTrack(track);
expandVoice();
}
if (!_is.cr())
return 0;
Fraction duration;
if (_is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
duration = _is.cr()->duration();
}
else {
duration = _is.duration().fraction();
}
Note* note = 0;
Note* firstTiedNote = 0;
Note* lastTiedNote = 0;
if (_is.usingNoteEntryMethod(NoteEntryMethod::REPITCH) && _is.cr()->isChord()) {
// repitch mode for MIDI input (where we are given a pitch) is handled here
// for keyboard input (where we are given a staff position), there is a separate function Score::repitchNote()
// the code is similar enough that it could possibly be refactored
Chord* chord = toChord(_is.cr());
note = new Note(this);
note->setParent(chord);
note->setTrack(chord->track());
note->setNval(nval);
lastTiedNote = note;
if (!addFlag) {
std::vector<Note*> notes = chord->notes();
// break all ties into current chord
// these will exist only if user explicitly moved cursor to a tied-into note
// in ordinary use, cursor will autoamtically skip past these during note entry
for (Note* n : notes) {
if (n->tieBack())
undoRemoveElement(n->tieBack());
}
// for single note chords only, preserve ties by changing pitch of all forward notes
// the tie forward itself will be added later
// multi-note chords get reduced to single note chords anyhow since we remove the old notes below
// so there will be no way to preserve those ties
if (notes.size() == 1 && notes.front()->tieFor()) {
Note* tn = notes.front()->tieFor()->endNote();
while (tn) {
Chord* tc = tn->chord();
if (tc->notes().size() != 1) {
undoRemoveElement(tn->tieBack());
break;
}
if (!firstTiedNote)
firstTiedNote = tn;
lastTiedNote = tn;
undoChangePitch(tn, note->pitch(), note->tpc1(), note->tpc2());
if (tn->tieFor())
tn = tn->tieFor()->endNote();
else
break;
}
}
// remove all notes from chord
// the new note will be added below
while (!chord->notes().empty())
undoRemoveElement(chord->notes().front());
}
// add new note to chord
undoAddElement(note);
setPlayNote(true);
// recreate tie forward if there is a note to tie to
// one-sided ties will not be recreated
if (firstTiedNote) {
Tie* tie = new Tie(this);
tie->setStartNote(note);
tie->setEndNote(firstTiedNote);
tie->setTrack(note->track());
undoAddElement(tie);
}
select(lastTiedNote);
}
else if (!_is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
Segment* seg = setNoteRest(_is.segment(), track, nval, duration, stemDirection);
if (seg) {
note = toChord(seg->element(track))->upNote();
}
}
if (_is.slur()) {
//
// extend slur
//
ChordRest* e = searchNote(_is.tick(), _is.track());
if (e) {
int stick = 0;
Element* ee = _is.slur()->startElement();
if (ee->isChordRest())
stick = toChordRest(ee)->tick();
else if (ee->isNote())
stick = toNote(ee)->chord()->tick();
if (stick == e->tick()) {
_is.slur()->setTick(stick);
_is.slur()->setStartElement(e);
}
else {
_is.slur()->setTick2(e->tick());
_is.slur()->setEndElement(e);
}
}
else
qDebug("addPitch: cannot find slur note");
}
if (_is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
// move cursor to next note, but skip tied notes (they were already repitched above)
ChordRest* next = lastTiedNote ? nextChordRest(lastTiedNote->chord()) : nextChordRest(_is.cr());
while (next && !next->isChord())
next = nextChordRest(next);
if (next)
_is.moveInputPos(next->segment());
}
else {
NoteEntryMethod entryMethod = _is.noteEntryMethod();
if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL)
_is.moveToNextInputPos();
}
return note;
}
//---------------------------------------------------------
// putNote
// mouse click in state NoteType::ENTRY
//---------------------------------------------------------
void Score::putNote(const QPointF& pos, bool replace, bool insert)
{
Position p;
if (!getPosition(&p, pos, _is.voice())) {
qDebug("cannot put note here, get position failed");
return;
}
Score* score = p.segment->score();
if (score->inputState().usingNoteEntryMethod(NoteEntryMethod::REPITCH))
score->repitchNote(p, replace);
else {
if (insert)
score->insertChord(p);
else
score->putNote(p, replace);
}
}
void Score::putNote(const Position& p, bool replace)
{
Staff* st = staff(p.staffIdx);
Segment* s = p.segment;
_is.setTrack(p.staffIdx * VOICES + _is.voice());
_is.setSegment(s);
if (score()->excerpt() && !score()->excerpt()->tracks().isEmpty() && score()->excerpt()->tracks().key(_is.track(), -1) == -1)
return;
Direction stemDirection = Direction::AUTO;
bool error;
NoteVal nval = noteValForPosition(p, error);
if (error)
return;
const StringData* stringData = 0;
switch (st->staffType(s->tick())->group()) {
case StaffGroup::PERCUSSION: {
const Drumset* ds = st->part()->instrument(s->tick())->drumset();
stemDirection = ds->stemDirection(nval.pitch);
break;
}
case StaffGroup::TAB:
stringData = st->part()->instrument(s->tick())->stringData();
_is.setDrumNote(-1);
break;
case StaffGroup::STANDARD:
_is.setDrumNote(-1);
break;
}
expandVoice();
ChordRest* cr = _is.cr();
bool addToChord = false;
if (cr) {
// retrieve total duration of current chord
TDuration d = cr->durationType();
// if not in replace mode AND chord duration == input duration AND not rest input
// we need to add to current chord (otherwise, we will need to replace it or create a new one)
if (!replace
&& (d == _is.duration())
&& cr->isChord()
&& !_is.rest())
{
if (st->isTabStaff(cr->tick())) { // TAB
// if a note on same string already exists, update to new pitch/fret
foreach (Note* note, toChord(cr)->notes())
if (note->string() == nval.string) { // if string is the same
// if adding a new digit will keep fret number within fret limit,
// add a digit to existing fret number
if (stringData) {
int fret = note->fret() * 10 + nval.fret;
if (fret <= stringData->frets() ) {
nval.fret = fret;
nval.pitch = stringData->getPitch(nval.string, nval.fret, st, s->tick());
}
else
qDebug("can't increase fret to %d", fret);
}
// set fret number (original or combined) in all linked notes
int tpc1 = note->tpc1default(nval.pitch);
int tpc2 = note->tpc2default(nval.pitch);
undoChangeFretting(note, nval.pitch, nval.string, nval.fret, tpc1, tpc2);
return;
}
}
else { // not TAB
// if a note with the same pitch already exists in the chord, remove it
Chord* chord = toChord(cr);
Note* note = chord->findNote(nval.pitch);
if (note) {
if (chord->notes().size() > 1)
undoRemoveElement(note);
return;
}
}
addToChord = true; // if no special case, add note to chord
}
}
if (addToChord && cr->isChord()) {
// if adding, add!
addNote(toChord(cr), nval);
return;
}
else {
// if not adding, replace current chord (or create a new one)
if (_is.rest())
nval.pitch = -1;
setNoteRest(_is.segment(), _is.track(), nval, _is.duration().fraction(), stemDirection);
}
if (!st->isTabStaff(cr->tick()))
_is.moveToNextInputPos();
}
//---------------------------------------------------------
// repitchNote
//---------------------------------------------------------
void Score::repitchNote(const Position& p, bool replace)
{
Segment* s = p.segment;
int tick = s->tick();
Staff* st = staff(p.staffIdx);
ClefType clef = st->clef(tick);
NoteVal nval;
bool error = false;
AccidentalVal acci = s->measure()->findAccidental(s, p.staffIdx, p.line, error);
if (error)
return;
int step = absStep(p.line, clef);
int octave = step / 7;
nval.pitch = step2pitch(step) + octave * 12 + int(acci);
if (styleB(StyleIdx::concertPitch))
nval.tpc1 = step2tpc(step % 7, acci);
else {
nval.pitch += st->part()->instrument(s->tick())->transpose().chromatic;
nval.tpc2 = step2tpc(step % 7, acci);
}
if (!_is.segment())
return;
Chord* chord;
ChordRest* cr = _is.cr();
if (!cr) {
cr = _is.segment()->nextChordRest(_is.track());
if (!cr)
return;
}
if (cr->isRest()) { //skip rests
ChordRest* next = nextChordRest(cr);
while(next && !next->isChord())
next = nextChordRest(next);
if (next)
_is.moveInputPos(next->segment());
return;
}
else {
chord = toChord(cr);
}
Note* note = new Note(this);
note->setParent(chord);
note->setTrack(chord->track());
note->setNval(nval);
Note* firstTiedNote = 0;
Note* lastTiedNote = note;
if (replace) {
std::vector<Note*> notes = chord->notes();
// break all ties into current chord
// these will exist only if user explicitly moved cursor to a tied-into note
// in ordinary use, cursor will autoamtically skip past these during note entry
for (Note* n : notes) {
if (n->tieBack())
undoRemoveElement(n->tieBack());
}
// for single note chords only, preserve ties by changing pitch of all forward notes
// the tie forward itself will be added later
// multi-note chords get reduced to single note chords anyhow since we remove the old notes below
// so there will be no way to preserve those ties
if (notes.size() == 1 && notes.front()->tieFor()) {
Note* tn = notes.front()->tieFor()->endNote();
while (tn) {
Chord* tc = tn->chord();
if (tc->notes().size() != 1) {
undoRemoveElement(tn->tieBack());
break;
}
if (!firstTiedNote)
firstTiedNote = tn;
lastTiedNote = tn;
undoChangePitch(tn, note->pitch(), note->tpc1(), note->tpc2());
if (tn->tieFor())
tn = tn->tieFor()->endNote();
else
break;
}
}
// remove all notes from chord
// the new note will be added below
while (!chord->notes().empty())
undoRemoveElement(chord->notes().front());
}
// add new note to chord
undoAddElement(note);
setPlayNote(true);
setPlayChord(true);
// recreate tie forward if there is a note to tie to
// one-sided ties will not be recreated
if (firstTiedNote) {
Tie* tie = new Tie(this);
tie->setStartNote(note);
tie->setEndNote(firstTiedNote);
tie->setTrack(note->track());
undoAddElement(tie);
}
select(lastTiedNote);
// move to next Chord
ChordRest* next = nextChordRest(lastTiedNote->chord());
while (next && !next->isChord())
next = nextChordRest(next);
if (next)
_is.moveInputPos(next->segment());
}
//---------------------------------------------------------
// insertChord
//---------------------------------------------------------
void Score::insertChord(const Position& pos)
{
// insert
// TODO:
// - check voices
// - split chord/rest
Element* el = selection().element();
if (!el || !(el->isNote() || el->isRest()))
return;
Segment* seg = pos.segment;
if (seg->splitsTuplet()) {
MScore::setError(CANNOT_INSERT_TUPLET);
return;
}
if (_is.insertMode())
globalInsertChord(pos);
else
localInsertChord(pos);
}
//---------------------------------------------------------
// localInsertChord
//---------------------------------------------------------
void Score::localInsertChord(const Position& pos)
{
TDuration duration = _is.duration();
Fraction fraction = duration.fraction();
int len = fraction.ticks();
Segment* seg = pos.segment;
int tick = seg->tick();
Measure* m = seg->measure();
undoInsertTime(tick, len);
undo(new InsertTime(this, tick, len));
for (Segment* s = pos.segment; s; s = s-> next())
s->undoChangeProperty(P_ID::TICK, s->rtick() + len);
undo(new ChangeMeasureLen(m, m->len() + fraction));
Segment* s = m->undoGetSegment(SegmentType::ChordRest, tick);
Position p(pos);
p.segment = s;
int trackI = p.staffIdx * VOICES + _is.voice();
for (int track = 0; track < _staves.size() * VOICES; ++track) {
if (track == trackI)
putNote(p, true);
else {
Segment* fs = m->first(SegmentType::ChordRest);
if (fs->tick() == tick && m->hasVoice(track)) {
setRest(fs->tick(), track, fraction, false, nullptr, false);
continue;
}
Segment* seg1 = 0;
for (Segment* s = fs; s; s = s->next(SegmentType::ChordRest)) {
if (s->element(track)) {
ChordRest* cr = toChordRest(s->element(track));
if (s->tick() > tick)
break;
if (s->tick() + cr->duration().ticks() < tick)
continue;
seg1 = s;
break;
}
}
if (seg1) {
ChordRest* cr = toChordRest(seg1->element(track));
if (seg1->tick() + cr->duration().ticks() == tick) {
addRest(s, track, duration, nullptr);
}
else if (cr->isFullMeasureRest()) {
// do nothing
}
else
changeCRlen(cr, fraction + cr->duration());
}
}
}
}
//---------------------------------------------------------
// globalInsertChord
//---------------------------------------------------------
void Score::globalInsertChord(const Position& pos)
{
ChordRest* cr = selection().cr();
int track = cr ? cr->track() : -1;
deselectAll();
Segment* s1 = pos.segment;
Segment* s2 = lastSegment();
TDuration duration = _is.duration();
Fraction fraction = duration.fraction();
ScoreRange r;
r.read(s1, s2, false);
int strack = 0; // for now for all tracks
int etrack = nstaves() * VOICES;
int stick = s1->tick();
int etick = s2->tick();
int ticks = fraction.ticks();
Fraction len = r.duration();
if (!r.truncate(fraction))
appendMeasures(1);
putNote(pos, true);
int dtick = s1->tick() + ticks;
int voiceOffsets[VOICES] { 0, 0, 0, 0 };
len = r.duration();
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx)
makeGap1(dtick, staffIdx, r.duration(), voiceOffsets);
r.write(this, dtick);
for (auto i : spanner()) {
Spanner* s = i.second;
if (s->track() >= strack && s->track() < etrack) {
if (s->tick() >= stick && s->tick() < etick)
s->undoChangeProperty(P_ID::SPANNER_TICK, s->tick() + ticks);
else if (s->tick2() >= stick && s->tick2() < etick)
s->undoChangeProperty(P_ID::SPANNER_TICKS, s->ticks() + ticks);
}
}
if (track != -1) {
Measure* m = tick2measure(dtick);
Segment* s = m->findSegment(SegmentType::ChordRest, dtick);
Element* e = s->element(track);
if (e)
select(e->isChord() ? toChord(e)->notes().front() : e);
}
}
} // namespace Ms