Skip to content

Commit

Permalink
fix #12971: note collisions in playback
Browse files Browse the repository at this point in the history
Also adjust MIDI testcases: the reference output did
hardcode the old behaviour of not refcounting.
  • Loading branch information
mirabilos authored and lasconic committed Mar 15, 2018
1 parent 7afd656 commit c2f0b8b
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 62 deletions.
60 changes: 38 additions & 22 deletions libmscore/rendermidi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,13 @@ void Score::updateChannel()
//---------------------------------------------------------

static void playNote(EventMap* events, const Note* note, int channel, int pitch,
int velo, int onTime, int offTime)
int velo, int onTime, int offTime, int staffIdx)
{
if (!note->play())
return;
velo = note->customizeVelocity(velo);
NPlayEvent ev(ME_NOTEON, channel, pitch, velo);
ev.setOriginatingStaff(staffIdx);
ev.setTuning(note->tuning());
ev.setNote(note);
events->insert(std::pair<int, NPlayEvent>(onTime, ev));
Expand All @@ -175,7 +176,7 @@ static void playNote(EventMap* events, const Note* note, int channel, int pitch,
// collectNote
//---------------------------------------------------------

static void collectNote(EventMap* events, int channel, const Note* note, int velo, int tickOffset)
static void collectNote(EventMap* events, int channel, const Note* note, int velo, int tickOffset, int staffIdx)
{
if (!note->play() || note->hidden()) // do not play overlapping notes
return;
Expand Down Expand Up @@ -208,7 +209,7 @@ static void collectNote(EventMap* events, int channel, const Note* note, int vel
}
else {
// recurse
collectNote(events, channel, n, velo, tickOffset);
collectNote(events, channel, n, velo, tickOffset, staffIdx);
break;
}
if (n->tieFor() && n != n->tieFor()->endNote())
Expand Down Expand Up @@ -243,7 +244,7 @@ static void collectNote(EventMap* events, int channel, const Note* note, int vel
int off = on + (ticks * e.len())/1000 - 1;
if (tieFor && i == nels - 1)
off += tieLen;
playNote(events, note, channel, p, velo, on, off);
playNote(events, note, channel, p, velo, on, off, staffIdx);
}

// Bends
Expand All @@ -269,6 +270,7 @@ static void collectNote(EventMap* events, int channel, const Note* note, int vel
int msb = midiPitch / 128;
int lsb = midiPitch % 128;
NPlayEvent ev(ME_PITCHBEND, channel, lsb, msb);
ev.setOriginatingStaff(staffIdx);
events->insert(std::pair<int, NPlayEvent>(lastPointTick, ev));
lastPointTick = nextPointTick;
continue;
Expand Down Expand Up @@ -296,11 +298,13 @@ static void collectNote(EventMap* events, int channel, const Note* note, int vel
int msb = midiPitch / 128;
int lsb = midiPitch % 128;
NPlayEvent ev(ME_PITCHBEND, channel, lsb, msb);
ev.setOriginatingStaff(staffIdx);
events->insert(std::pair<int, NPlayEvent>(i, ev));
}
lastPointTick = nextPointTick;
}
NPlayEvent ev(ME_PITCHBEND, channel, 0, 64); // 0:64 is 8192 - no pitch bend
ev.setOriginatingStaff(staffIdx);
events->insert(std::pair<int, NPlayEvent>(tick1+noteLen, ev));
}
}
Expand Down Expand Up @@ -355,29 +359,31 @@ static void collectMeasureEvents(EventMap* events, Measure* m, Staff* staff, int

Chord* chord = static_cast<Chord*>(cr);
Staff* staff = chord->staff();
int staffIdx = staff->idx();
int velocity = staff->velocities().velo(seg->tick());
Instrument* instr = chord->part()->instrument(tick);
int channel = instr->channel(chord->upNote()->subchannel())->channel;
events->registerChannel(channel);

foreach (Articulation* a, chord->articulations()) {
instr->updateVelocity(&velocity,channel, a->subtypeName());
}

for (Chord* c : chord->graceNotesBefore()) {
for (const Note* note : c->notes())
collectNote(events, channel, note, velocity, tickOffset);
collectNote(events, channel, note, velocity, tickOffset, staffIdx);
}

foreach (const Note* note, chord->notes())
collectNote(events, channel, note, velocity, tickOffset);
collectNote(events, channel, note, velocity, tickOffset, staffIdx);

#if 0
// TODO: add support for grace notes after - see createPlayEvents()
QList<Chord*> gna;
chord->getGraceNotesAfter(&gna);
for (Chord* c : gna) {
for (const Note* note : c->notes())
collectNote(events, channel, note, velocity, tickOffset);
collectNote(events, channel, note, velocity, tickOffset, staffIdx);
}
#endif

Expand Down Expand Up @@ -407,6 +413,7 @@ static void collectMeasureEvents(EventMap* events, Measure* m, Staff* staff, int
for (MidiCoreEvent event : nel->events) {
event.setChannel(channel);
NPlayEvent e(event);
e.setOriginatingStaff(firstStaffIdx);
if (e.dataA() == CTRL_PROGRAM)
events->insert(std::pair<int, NPlayEvent>(tick-1, e));
else
Expand Down Expand Up @@ -643,54 +650,56 @@ void Score::renderStaff(EventMap* events, Staff* staff)
// renderSpanners
//---------------------------------------------------------

void Score::renderSpanners(EventMap* events, int staffIdx)
void Score::renderSpanners(EventMap* events)
{
foreach (const RepeatSegment* rs, *repeatList()) {
int tickOffset = rs->utick - rs->tick;
int utick1 = rs->utick;
int tick1 = repeatList()->utick2tick(utick1);
int tick2 = tick1 + rs->len();
std::map<int, std::vector<std::pair<int, bool>>> channelPedalEvents = std::map<int, std::vector<std::pair<int, bool>>>();
std::map<int, std::vector<std::pair<int, std::pair<bool, int>>>> channelPedalEvents;
for (const auto& sp : _spanner.map()) {
Spanner* s = sp.second;
if (s->type() != Element::Type::PEDAL || (staffIdx != -1 && s->staffIdx() != staffIdx))
if (s->type() != Element::Type::PEDAL)
continue;

int staff = s->staffIdx();
int idx = s->staff()->channel(s->tick(), 0);
int channel = s->part()->instrument(s->tick())->channel(idx)->channel;
channelPedalEvents.insert({channel, std::vector<std::pair<int, bool>>()});
std::vector<std::pair<int, bool>> pedalEventList = channelPedalEvents.at(channel);
std::pair<int, bool> lastEvent;
channelPedalEvents.insert({channel, std::vector<std::pair<int, std::pair<bool, int>>>()});
std::vector<std::pair<int, std::pair<bool, int>>> pedalEventList = channelPedalEvents.at(channel);
std::pair<int, std::pair<bool, int>> lastEvent;

if (!pedalEventList.empty())
lastEvent = pedalEventList.back();
else
lastEvent = std::pair<int, bool>(0, true);
lastEvent = std::pair<int, std::pair<bool, int>>(0, std::pair<bool, int>(true, staff));

if (s->tick() >= tick1 && s->tick() < tick2) {
// Handle "overlapping" pedal segments (usual case for connected pedal line)
if (lastEvent.second == false && lastEvent.first >= (s->tick() + tickOffset + 2)) {
if (lastEvent.second.first == false && lastEvent.first >= (s->tick() + tickOffset + 2)) {
channelPedalEvents.at(channel).pop_back();
channelPedalEvents.at(channel).push_back(std::pair<int, bool>(s->tick() + tickOffset + 1, false));
channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(s->tick() + tickOffset + 1, std::pair<bool, int>(false, staff)));
}
channelPedalEvents.at(channel).push_back(std::pair<int, bool>(s->tick() + tickOffset + 2, true));
channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(s->tick() + tickOffset + 2, std::pair<bool, int>(true, staff)));
}
if (s->tick2() >= tick1 && s->tick2() <= tick2) {
int t = s->tick2() + tickOffset + 1;
if (t > repeatList()->last()->utick + repeatList()->last()->len())
t = repeatList()->last()->utick + repeatList()->last()->len();
channelPedalEvents.at(channel).push_back(std::pair<int, bool>(t, false));
channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(t, std::pair<bool, int>(false, staff)));
}
}

for (const auto& pedalEvents : channelPedalEvents) {
int channel = pedalEvents.first;
for (const auto& pe : pedalEvents.second) {
NPlayEvent event;
if (pe.second == true)
if (pe.second.first == true)
event = NPlayEvent(ME_CONTROLLER, channel, CTRL_SUSTAIN, 127);
else
event = NPlayEvent(ME_CONTROLLER, channel, CTRL_SUSTAIN, 0);
event.setOriginatingStaff(pe.second.second);
events->insert(std::pair<int,NPlayEvent>(pe.first, event));
}
}
Expand Down Expand Up @@ -1562,22 +1571,29 @@ void Score::renderMetronome(EventMap* events, Measure* m, int tickOffset)
//---------------------------------------------------------

void Score::renderMidi(EventMap* events)
{
renderMidi(events, true, MScore::playRepeats);
}

void Score::renderMidi(EventMap* events, bool metronome, bool expandRepeats)
{
updateSwing();
createPlayEvents();

updateRepeatList(MScore::playRepeats);
_foundPlayPosAfterRepeats = false;
updateRepeatList(expandRepeats);
updateChannel();
updateVelo();

// create note & other events
foreach (Staff* part, _staves)
renderStaff(events, part);
events->fixupMIDI();

// create sustain pedal events
renderSpanners(events, -1);
renderSpanners(events);

if (!metronome)
return;
// add metronome ticks
foreach (const RepeatSegment* rs, *repeatList()) {
int startTick = rs->tick;
Expand Down
12 changes: 5 additions & 7 deletions libmscore/score.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,6 @@ class Score : public QObject, public ScoreElement {

int _pos[3]; ///< 0 - current, 1 - left loop, 2 - right loop

bool _foundPlayPosAfterRepeats; ///< Temporary used during playback rendering
///< indicating if playPos after expanded repeats
///< has been calculated.

int _fileDivision; ///< division of current loading *.mscx file
int _mscVersion; ///< version of .mscx file during file read, then changed to MSCVERSION for drag and drop
int _mscRealVersion; ///< keep the actual and initial version of current loaded *.mscx file
Expand Down Expand Up @@ -494,6 +490,10 @@ class Score : public QObject, public ScoreElement {
FileError read114(XmlReader&);
FileError read1(XmlReader&, bool ignoreVersionError);

void renderStaff(EventMap* events, Staff*);
void renderSpanners(EventMap* events);
void renderMetronome(EventMap* events, Measure* m, int tickOffset);

protected:
void createPlayEvents(Chord*);
void createGraceNotesPlayEvents(QList<Chord*> gnb, int tick, Chord* chord, int& ontime);
Expand Down Expand Up @@ -807,9 +807,7 @@ class Score : public QObject, public ScoreElement {
PasteStatus pasteStaff(XmlReader&, Segment* dst, int staffIdx);
void pasteSymbols(XmlReader& e, ChordRest* dst);
void renderMidi(EventMap* events);
void renderStaff(EventMap* events, Staff*);
void renderSpanners(EventMap* events, int staffIdx);
void renderMetronome(EventMap* events, Measure* m, int tickOffset);
void renderMidi(EventMap* events, bool metronome, bool expandRepeats);

BeatType tick2beatType(int tick);

Expand Down
16 changes: 7 additions & 9 deletions mscore/exportmidi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ bool ExportMidi::write(const QString& name, bool midiExpandRepeats)
for (int i = 0; i < cs->nstaves(); ++i)
tracks.append(MidiTrack());

cs->updateSwing();
cs->createPlayEvents();
cs->updateRepeatList(midiExpandRepeats);
EventMap events;
cs->renderMidi(&events, false, midiExpandRepeats);

pauseMap.calculate(cs);
writeHeader();

Expand All @@ -235,11 +235,6 @@ bool ExportMidi::write(const QString& name, bool midiExpandRepeats)
track.setOutPort(part->midiPort());
track.setOutChannel(part->midiChannel());

// Render each staff only once
EventMap events;
cs->renderStaff(&events, staff);
cs->renderSpanners(&events, staffIdx);

// Pass throught the all instruments in the part
const InstrumentList* il = part->instruments();
for(auto j = il->begin(); j!= il->end(); j++) {
Expand Down Expand Up @@ -292,7 +287,10 @@ bool ExportMidi::write(const QString& name, bool midiExpandRepeats)
}

for (auto i = events.begin(); i != events.end(); ++i) {
NPlayEvent event(i->second);
const NPlayEvent& event = i->second;
if (event.getOriginatingStaff() != staffIdx)
continue;

char eventPort = cs->midiPort(event.channel());
char eventChannel = cs->midiChannel(event.channel());
if (port != eventPort || channel != eventChannel)
Expand Down
Loading

0 comments on commit c2f0b8b

Please sign in to comment.