f31624d746
* Found via `codespell -q 3 -S ./share/locale,./thirdparty -L ba,cann,clas,dur,foto,iff,nd,ois,ot,pres,possibile,snaped,strack,tage,te,uint,thru,valu` * Some revisions made per feedback given during review. * Follow-up typos for review * Add revisions per feedback
2717 lines
119 KiB
C++
2717 lines
119 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2002-2012 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
|
|
//=============================================================================
|
|
|
|
/**
|
|
\file
|
|
render score into event list
|
|
*/
|
|
|
|
#include <set>
|
|
|
|
#include "rendermidi.h"
|
|
#include "score.h"
|
|
#include "volta.h"
|
|
#include "note.h"
|
|
#include "glissando.h"
|
|
#include "instrument.h"
|
|
#include "part.h"
|
|
#include "chord.h"
|
|
#include "trill.h"
|
|
#include "vibrato.h"
|
|
#include "style.h"
|
|
#include "slur.h"
|
|
#include "tie.h"
|
|
#include "stafftext.h"
|
|
#include "repeat.h"
|
|
#include "articulation.h"
|
|
#include "arpeggio.h"
|
|
#include "durationtype.h"
|
|
#include "measure.h"
|
|
#include "tempo.h"
|
|
#include "repeatlist.h"
|
|
#include "velo.h"
|
|
#include "dynamic.h"
|
|
#include "navigate.h"
|
|
#include "pedal.h"
|
|
#include "staff.h"
|
|
#include "hairpin.h"
|
|
#include "bend.h"
|
|
#include "tremolo.h"
|
|
#include "noteevent.h"
|
|
#include "synthesizer/event.h"
|
|
#include "segment.h"
|
|
#include "undo.h"
|
|
#include "utils.h"
|
|
#include "sym.h"
|
|
#include "synthesizerstate.h"
|
|
|
|
namespace Ms {
|
|
|
|
//int printNoteEventLists(NoteEventList el, int prefix, int j){
|
|
// int k=0;
|
|
// for (NoteEvent event : el) {
|
|
// qDebug("%d: %d: %d pitch=%d ontime=%d duration=%d",prefix, j, k, event.pitch(), event.ontime(), event.len());
|
|
// k++;
|
|
// }
|
|
// return 0;
|
|
//}
|
|
//int printNoteEventLists(QList<NoteEventList> ell, int prefix){
|
|
// int j=0;
|
|
// for (NoteEventList el : ell) {
|
|
// printNoteEventLists(el,prefix,j);
|
|
// j++;
|
|
// }
|
|
// return 0;
|
|
//}
|
|
|
|
struct StaffRenderData {
|
|
Fraction lastHairpinStart = Fraction(-1, 1);
|
|
Fraction lastDynamicEnd = Fraction(-1, 1);
|
|
std::map<int, NPlayEvent> tempPlayEvents;
|
|
};
|
|
|
|
bool graceNotesMerged(Chord *chord);
|
|
|
|
//---------------------------------------------------------
|
|
// updateSwing
|
|
//---------------------------------------------------------
|
|
|
|
void Score::updateSwing()
|
|
{
|
|
for (Staff* s : _staves) {
|
|
s->clearSwingList();
|
|
}
|
|
Measure* fm = firstMeasure();
|
|
if (!fm)
|
|
return;
|
|
for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
|
|
for (const Element* e : s->annotations()) {
|
|
if (!e->isStaffTextBase())
|
|
continue;
|
|
const StaffTextBase* st = toStaffTextBase(e);
|
|
if (st->xmlText().isEmpty())
|
|
continue;
|
|
Staff* staff = st->staff();
|
|
if (!st->swing())
|
|
continue;
|
|
SwingParameters sp;
|
|
sp.swingRatio = st->swingParameters()->swingRatio;
|
|
sp.swingUnit = st->swingParameters()->swingUnit;
|
|
if (st->systemFlag()) {
|
|
for (Staff* sta : _staves) {
|
|
sta->insertIntoSwingList(s->tick(),sp);
|
|
}
|
|
}
|
|
else
|
|
staff->insertIntoSwingList(s->tick(),sp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateCapo
|
|
//---------------------------------------------------------
|
|
|
|
void Score::updateCapo()
|
|
{
|
|
for (Staff* s : _staves) {
|
|
s->clearCapoList();
|
|
}
|
|
Measure* fm = firstMeasure();
|
|
if (!fm)
|
|
return;
|
|
for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
|
|
for (const Element* e : s->annotations()) {
|
|
if (!e->isStaffTextBase())
|
|
continue;
|
|
const StaffTextBase* st = toStaffTextBase(e);
|
|
if (st->xmlText().isEmpty())
|
|
continue;
|
|
Staff* staff = st->staff();
|
|
if (st->capo() == 0)
|
|
continue;
|
|
staff->insertIntoCapoList(s->tick(),st->capo());
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateChannel
|
|
//---------------------------------------------------------
|
|
|
|
void Score::updateChannel()
|
|
{
|
|
for (Staff* s : staves()) {
|
|
for (int i = 0; i < VOICES; ++i)
|
|
s->clearChannelList(i);
|
|
}
|
|
Measure* fm = firstMeasure();
|
|
if (!fm)
|
|
return;
|
|
for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
|
|
for (const Element* e : s->annotations()) {
|
|
if (e->isInstrumentChange()) {
|
|
Staff* staff = Score::staff(e->staffIdx());
|
|
for (int voice = 0; voice < VOICES; ++voice)
|
|
staff->insertIntoChannelList(voice, s->tick(), 0);
|
|
continue;
|
|
}
|
|
if (!e->isStaffTextBase())
|
|
continue;
|
|
const StaffTextBase* st = toStaffTextBase(e);
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
|
QString an(st->channelName(voice));
|
|
if (an.isEmpty())
|
|
continue;
|
|
Staff* staff = Score::staff(st->staffIdx());
|
|
int a = staff->part()->instrument(s->tick())->channelIdx(an);
|
|
if (a != -1)
|
|
staff->insertIntoChannelList(voice, s->tick(), a);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto it = spanner().cbegin(); it != spanner().cend(); ++it) {
|
|
Spanner* spanner = (*it).second;
|
|
if (!spanner->isVolta())
|
|
continue;
|
|
Volta* volta = toVolta(spanner);
|
|
volta->setChannel();
|
|
}
|
|
|
|
for (Segment* s = fm->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
|
|
for (Staff* st : staves()) {
|
|
int strack = st->idx() * VOICES;
|
|
int etrack = strack + VOICES;
|
|
for (int track = strack; track < etrack; ++track) {
|
|
if (!s->element(track))
|
|
continue;
|
|
Element* e = s->element(track);
|
|
if (e->type() != ElementType::CHORD)
|
|
continue;
|
|
Chord* c = toChord(e);
|
|
int channel = st->channel(c->tick(), c->voice());
|
|
Instrument* instr = c->part()->instrument(c->tick());
|
|
if (channel >= instr->channel().size()) {
|
|
qDebug() << "Channel " << channel << " too high. Max " << instr->channel().size();
|
|
channel = 0;
|
|
}
|
|
for (Note* note : c->notes()) {
|
|
if (note->hidden())
|
|
continue;
|
|
if (note->tieBack())
|
|
continue;
|
|
note->setSubchannel(channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// playNote
|
|
//---------------------------------------------------------
|
|
|
|
static void playNote(EventMap* events, const Note* note, int channel, int pitch,
|
|
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);
|
|
if (offTime < onTime)
|
|
offTime = onTime;
|
|
events->insert(std::pair<int, NPlayEvent>(onTime, ev));
|
|
ev.setVelo(0);
|
|
events->insert(std::pair<int, NPlayEvent>(offTime, ev));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// collectNote
|
|
//---------------------------------------------------------
|
|
|
|
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;
|
|
Chord* chord = note->chord();
|
|
|
|
int ticks;
|
|
int tieLen = 0;
|
|
if (chord->isGrace()) {
|
|
Q_ASSERT( !graceNotesMerged(chord)); // this function should not be called on a grace note if grace notes are merged
|
|
chord = toChord(chord->parent());
|
|
}
|
|
ticks = chord->actualTicks().ticks();
|
|
// calculate additional length due to ties forward
|
|
// taking NoteEvent length adjustments into account
|
|
// but stopping at any note with multiple NoteEvents
|
|
// and processing those notes recursively
|
|
if (note->tieFor()) {
|
|
Note* n = note->tieFor()->endNote();
|
|
while (n) {
|
|
NoteEventList nel = n->playEvents();
|
|
if (nel.size() == 1) {
|
|
// add value of this note to main note
|
|
// if we wish to suppress first note of ornament,
|
|
// then do this regardless of number of NoteEvents
|
|
tieLen += (n->chord()->actualTicks().ticks() * (nel[0].len())) / 1000;
|
|
}
|
|
else {
|
|
// recurse
|
|
collectNote(events, channel, n, velo, tickOffset, staffIdx);
|
|
break;
|
|
}
|
|
if (n->tieFor() && n != n->tieFor()->endNote())
|
|
n = n->tieFor()->endNote();
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
int tick1 = chord->tick().ticks() + tickOffset;
|
|
bool tieFor = note->tieFor();
|
|
bool tieBack = note->tieBack();
|
|
|
|
NoteEventList nel = note->playEvents();
|
|
int nels = nel.size();
|
|
for (int i = 0, pitch = note->ppitch(); i < nels; ++i) {
|
|
const NoteEvent& e = nel[i]; // we make an explicit const ref, not a const copy. no need to copy as we won't change the original object.
|
|
|
|
// skip if note has a tie into it and only one NoteEvent
|
|
// its length was already added to previous note
|
|
// if we wish to suppress first note of ornament
|
|
// then change "nels == 1" to "i == 0", and change "break" to "continue"
|
|
if (tieBack && nels == 1)
|
|
break;
|
|
int p = pitch + e.pitch();
|
|
if (p < 0)
|
|
p = 0;
|
|
else if (p > 127)
|
|
p = 127;
|
|
int on = tick1 + (ticks * e.ontime())/1000;
|
|
int off = on + (ticks * e.len())/1000 - 1;
|
|
if (tieFor && i == nels - 1)
|
|
off += tieLen;
|
|
playNote(events, note, channel, p, velo, on, off, staffIdx);
|
|
}
|
|
|
|
// Bends
|
|
for (Element* e : note->el()) {
|
|
if (e == 0 || e->type() != ElementType::BEND)
|
|
continue;
|
|
Bend* bend = toBend(e);
|
|
if (!bend->playBend())
|
|
break;
|
|
const QList<PitchValue>& points = bend->points();
|
|
int pitchSize = points.size();
|
|
|
|
double noteLen = note->playTicks();
|
|
int lastPointTick = tick1;
|
|
for (int pitchIndex = 0; pitchIndex < pitchSize-1; pitchIndex++) {
|
|
PitchValue pitchValue = points[pitchIndex];
|
|
PitchValue nextPitch = points[pitchIndex+1];
|
|
int nextPointTick = tick1 + nextPitch.time / 60.0 * noteLen;
|
|
int pitch = pitchValue.pitch;
|
|
|
|
if (pitchIndex == 0 && (pitch == nextPitch.pitch)) {
|
|
int midiPitch = (pitch * 16384) / 1200 + 8192;
|
|
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;
|
|
}
|
|
if (pitch == nextPitch.pitch && !(pitchIndex == 0 && pitch != 0)) {
|
|
lastPointTick = nextPointTick;
|
|
continue;
|
|
}
|
|
|
|
double pitchDelta = nextPitch.pitch - pitch;
|
|
double tickDelta = nextPitch.time - pitchValue.time;
|
|
/* B
|
|
/. pitch is 1/100 semitones
|
|
bend / . pitchDelta time is in noteDuration/60
|
|
/ . midi pitch is 12/16384 semitones
|
|
A....
|
|
tickDelta */
|
|
for (int i = lastPointTick; i <= nextPointTick; i += 16) {
|
|
double dx = ((i-lastPointTick) * 60) / noteLen;
|
|
int p = pitch + dx * pitchDelta / tickDelta;
|
|
|
|
// We don't support negative pitch, but Midi does. Let's center by adding 8192.
|
|
int midiPitch = (p * 16384) / 1200 + 8192;
|
|
// Representing pitch as two bytes
|
|
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+int(noteLen), ev));
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// aeolusSetStop
|
|
//---------------------------------------------------------
|
|
|
|
static void aeolusSetStop(int tick, int channel, int i, int k, bool val, EventMap* events)
|
|
{
|
|
NPlayEvent event;
|
|
event.setType(ME_CONTROLLER);
|
|
event.setController(98);
|
|
if (val)
|
|
event.setValue(0x40 + 0x20 + i);
|
|
else
|
|
event.setValue(0x40 + 0x10 + i);
|
|
|
|
event.setChannel(channel);
|
|
events->insert(std::pair<int,NPlayEvent>(tick, event));
|
|
|
|
event.setValue(k);
|
|
events->insert(std::pair<int,NPlayEvent>(tick, event));
|
|
// event.setValue(0x40 + i);
|
|
// events->insert(std::pair<int,NPlayEvent>(tick, event));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// collectProgramChanges
|
|
//---------------------------------------------------------
|
|
|
|
static void collectProgramChanges(EventMap* events, Measure* m, Staff* staff, int tickOffset)
|
|
{
|
|
int firstStaffIdx = staff->idx();
|
|
int nextStaffIdx = firstStaffIdx + 1;
|
|
|
|
//
|
|
// collect program changes and controller
|
|
//
|
|
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
|
|
for (Element* e : s->annotations()) {
|
|
if (!e->isStaffTextBase() || e->staffIdx() < firstStaffIdx || e->staffIdx() >= nextStaffIdx)
|
|
continue;
|
|
const StaffTextBase* st1 = toStaffTextBase(e);
|
|
Fraction tick = s->tick() + Fraction::fromTicks(tickOffset);
|
|
|
|
Instrument* instr = e->part()->instrument(tick);
|
|
for (const ChannelActions& ca : *st1->channelActions()) {
|
|
int channel = instr->channel().at(ca.channel)->channel();
|
|
for (const QString& ma : ca.midiActionNames) {
|
|
NamedEventList* nel = instr->midiAction(ma, ca.channel);
|
|
if (!nel)
|
|
continue;
|
|
for (MidiCoreEvent event : nel->events) {
|
|
event.setChannel(channel);
|
|
NPlayEvent e1(event);
|
|
e1.setOriginatingStaff(firstStaffIdx);
|
|
if (e1.dataA() == CTRL_PROGRAM)
|
|
events->insert(std::pair<int, NPlayEvent>(tick.ticks()-1, e1));
|
|
else
|
|
events->insert(std::pair<int, NPlayEvent>(tick.ticks(), e1));
|
|
}
|
|
}
|
|
}
|
|
if (st1->setAeolusStops()) {
|
|
Staff* s1 = st1->staff();
|
|
int voice = 0;
|
|
int channel = s1->channel(tick, voice);
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
static int num[4] = { 12, 13, 16, 16 };
|
|
for (int k = 0; k < num[i]; ++k)
|
|
aeolusSetStop(tick.ticks(), channel, i, k, st1->getAeolusStop(i, k), events);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getControllerFromCC
|
|
//---------------------------------------------------------
|
|
|
|
static int getControllerFromCC(int cc)
|
|
{
|
|
int controller = -1;
|
|
|
|
switch (cc) {
|
|
case 1:
|
|
controller = CTRL_MODULATION;
|
|
break;
|
|
case 2:
|
|
controller = CTRL_BREATH;
|
|
break;
|
|
case 4:
|
|
controller = CTRL_FOOT;
|
|
break;
|
|
case 11:
|
|
controller = CTRL_EXPRESSION;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return controller;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// collectMeasureEventsSimple
|
|
// the original, velocity-only method of collecting events.
|
|
//---------------------------------------------------------
|
|
|
|
static void collectMeasureEventsSimple(EventMap* events, Measure* m, Staff* staff, int tickOffset)
|
|
{
|
|
int firstStaffIdx = staff->idx();
|
|
int nextStaffIdx = firstStaffIdx + 1;
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
int strack = firstStaffIdx * VOICES;
|
|
int etrack = nextStaffIdx * VOICES;
|
|
|
|
for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
|
|
int tick = seg->tick().ticks();
|
|
for (int track = strack; track < etrack; ++track) {
|
|
// skip linked staves, except primary
|
|
if (!m->score()->staff(track / VOICES)->primaryStaff()) {
|
|
track += VOICES-1;
|
|
continue;
|
|
}
|
|
Element* cr = seg->element(track);
|
|
if (cr == 0 || cr->type() != ElementType::CHORD)
|
|
continue;
|
|
|
|
Chord* chord = toChord(cr);
|
|
Staff* st1 = chord->staff();
|
|
int staffIdx = st1->idx();
|
|
int velocity = st1->velocities().velo(seg->tick().ticks());
|
|
Instrument* instr = chord->part()->instrument(Fraction::fromTicks(tick));
|
|
int channel = instr->channel(chord->upNote()->subchannel())->channel();
|
|
events->registerChannel(channel);
|
|
|
|
for (Articulation* a : chord->articulations()) {
|
|
if (a->playArticulation())
|
|
instr->updateVelocity(&velocity,channel, a->articulationName());
|
|
}
|
|
|
|
if ( !graceNotesMerged(chord))
|
|
for (Chord* c : chord->graceNotesBefore())
|
|
for (const Note* note : c->notes())
|
|
collectNote(events, channel, note, velocity, tickOffset, staffIdx);
|
|
|
|
for (const Note* note : chord->notes())
|
|
collectNote(events, channel, note, velocity, tickOffset, staffIdx);
|
|
|
|
if ( !graceNotesMerged(chord))
|
|
for (Chord* c : chord->graceNotesAfter())
|
|
for (const Note* note : c->notes())
|
|
collectNote(events, channel, note, velocity, tickOffset, staffIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// changeCCBetween
|
|
// since we're adding events, pass ticks as ints rather than fractions
|
|
//---------------------------------------------------------
|
|
|
|
static void changeCCBetween(std::map<int, NPlayEvent>& tempEvents, int stick, int etick, int startExpr, int endExpr, int channel, int controller, VeloChangeMethod changeMethod, int tickOffset, int originatingStaff)
|
|
{
|
|
// Prevent zero-division error, but add single event
|
|
if (startExpr == endExpr || stick == etick) {
|
|
int tickToUse = stick + tickOffset;
|
|
if (tempEvents.find(tickToUse) != tempEvents.end()) {
|
|
// Don't add a play event if it would be quieter than the current one at
|
|
// this tick
|
|
if (tempEvents[tickToUse].velo() >= abs(startExpr))
|
|
return;
|
|
}
|
|
|
|
tempEvents[tickToUse] = NPlayEvent(ME_CONTROLLER, channel, controller, abs(startExpr));
|
|
return;
|
|
}
|
|
|
|
// Ticks to change expression over
|
|
int exprTicks = etick - stick;
|
|
int exprDiff = endExpr - startExpr;
|
|
|
|
// The lambdas are in the format:
|
|
// func(precalculated values, current tick)
|
|
// The precalculated values are for values that stay constant, involving
|
|
// total ticks or total expression. Although it seems messy,
|
|
// it saves some 13% processing time.
|
|
|
|
// NOTE:JT - hardcoded, todo change?
|
|
int tickInc = 1;
|
|
|
|
// See these functions graphically at: https://www.desmos.com/calculator/kk89ficmjk
|
|
std::function<int(std::vector<double>&, int)> valueFunction;
|
|
std::vector<double> preCalculated;
|
|
switch (changeMethod) {
|
|
case VeloChangeMethod::EXPONENTIAL:
|
|
// Due to the nth-root, exponential functions do not flip with negative values, and cause errors,
|
|
// so treat it as a piecewise function.
|
|
if (exprDiff > 0) {
|
|
preCalculated.push_back(
|
|
pow((exprDiff + 1), 1.0 / double(exprTicks)) // the exprTicks root of d+1
|
|
);
|
|
valueFunction = [](std::vector<double>& pc, int ct) { return int(
|
|
pow(
|
|
pc[0],
|
|
double(ct) // to the power of the current tick (exponential)
|
|
) - 1
|
|
); };
|
|
}
|
|
else {
|
|
preCalculated.push_back(
|
|
pow((-exprDiff + 1), 1.0 / double(exprTicks)) // the exprTicks root of 1-d
|
|
);
|
|
valueFunction = [](std::vector<double>& pc, int ct) { return -int(
|
|
pow(
|
|
pc[0],
|
|
double(ct) // again to the power of ct
|
|
) + 1
|
|
); };
|
|
}
|
|
break;
|
|
// Uses sin x transformed, which _does_ flip with negative numbers
|
|
case VeloChangeMethod::EASE_IN_OUT:
|
|
preCalculated.push_back(double(exprDiff) / 2.0);
|
|
preCalculated.push_back(double(M_PI / double(exprTicks)));
|
|
preCalculated.push_back(double(M_PI / 2.0));
|
|
valueFunction = [](std::vector<double>& pc, int ct) { return int(
|
|
pc[0] * (
|
|
sin(
|
|
double(ct) * (
|
|
pc[1]
|
|
) - pc[2]
|
|
) + 1
|
|
)
|
|
); };
|
|
break;
|
|
case VeloChangeMethod::EASE_IN:
|
|
preCalculated.push_back(double(exprDiff));
|
|
preCalculated.push_back(double(exprTicks));
|
|
preCalculated.push_back(double(M_PI / double(2 * exprTicks)));
|
|
valueFunction = [](std::vector<double>& pc, int ct) { return int(
|
|
pc[0] * (
|
|
sin(
|
|
double(ct - pc[1]) * (
|
|
pc[2]
|
|
)
|
|
) + 1
|
|
)
|
|
); };
|
|
break;
|
|
case VeloChangeMethod::EASE_OUT:
|
|
preCalculated.push_back(double(exprDiff));
|
|
preCalculated.push_back(double(M_PI / double(2 * exprTicks)));
|
|
valueFunction = [](std::vector<double>& pc, int ct) { return int(
|
|
pc[0] * sin(
|
|
double(ct) * (
|
|
pc[1]
|
|
)
|
|
)
|
|
); };
|
|
break;
|
|
case VeloChangeMethod::NORMAL:
|
|
default:
|
|
// We can calculate how to increase the ticks, since it is linear
|
|
tickInc = exprTicks / abs(exprDiff);
|
|
preCalculated.push_back(double(exprDiff));
|
|
preCalculated.push_back(double(exprTicks));
|
|
valueFunction = [](std::vector<double>& pc, int ct) { return int(pc[0] * (double(ct) / pc[1])); };
|
|
break;
|
|
}
|
|
|
|
// prevent possible infinite loop
|
|
if (tickInc < 1)
|
|
tickInc = 1;
|
|
|
|
int lastVal = -1;
|
|
for (int i = stick; i < etick; i += tickInc) {
|
|
int valueToAdd = valueFunction(preCalculated, i - stick);
|
|
if (lastVal == valueToAdd)
|
|
continue;
|
|
|
|
int exprVal = startExpr + valueToAdd;
|
|
if (tempEvents.find(i + tickOffset) != tempEvents.end()) {
|
|
// Don't add a play event if it would be quieter than the current one at this tick
|
|
if (tempEvents[i + tickOffset].velo() >= abs(exprVal))
|
|
continue;
|
|
}
|
|
|
|
lastVal = valueToAdd;
|
|
NPlayEvent event = NPlayEvent(ME_CONTROLLER, channel, controller, abs(exprVal));
|
|
event.setOriginatingStaff(originatingStaff);
|
|
tempEvents[i + tickOffset] = event;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// collectMeasureEventsDefault
|
|
// this uses only CC events to control note velocity, and sets the
|
|
// note-on velocity to always be 127 (max). This is the method that allows
|
|
// single note dynamics, but only works if the soundfont supports it.
|
|
// Method is one of:
|
|
// FIXED_MAX - default: velocity is fixed at 127
|
|
// SEG_START - note-on velocity is the same as the start velocity of the seg
|
|
//---------------------------------------------------------
|
|
|
|
static void collectMeasureEventsDefault(EventMap* events, Measure* m, Staff* staff, StaffRenderData& renderData, int tickOffset, DynamicsRenderMethod method, int cc)
|
|
{
|
|
int controller = getControllerFromCC(cc);
|
|
|
|
if (controller == -1) {
|
|
qWarning("controller for CC %d not valid", cc);
|
|
return;
|
|
}
|
|
|
|
int firstStaffIdx = staff->idx();
|
|
int nextStaffIdx = firstStaffIdx + 1;
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
int strack = firstStaffIdx * VOICES;
|
|
int etrack = nextStaffIdx * VOICES;
|
|
|
|
static const VeloChangeMethod defaultChangeMethod = VeloChangeMethod::NORMAL;
|
|
|
|
int lastSubchannel = -1;
|
|
for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
|
|
Fraction tick = seg->tick();
|
|
Fraction tick2 = tick + seg->ticks();
|
|
|
|
for (int track = strack; track < etrack; ++track) {
|
|
// Skip linked staves, except primary
|
|
Staff* st1 = m->score()->staff(track / VOICES);
|
|
if (!st1->primaryStaff()) {
|
|
track += VOICES - 1;
|
|
continue;
|
|
}
|
|
|
|
Chord* chord = nullptr;
|
|
Element* cr = seg->element(track);
|
|
if (cr != 0) {
|
|
if (cr->isChord())
|
|
chord = toChord(cr);
|
|
}
|
|
|
|
// We need to be able to add CC events even if there isn't a note,
|
|
// since in multi-stave scores, some segments will have no notes in
|
|
// them for a stave, despite a note being held. So, if there isn't
|
|
// a chord, at least try to add CC events.
|
|
if (!chord && !seg->isChordRestType())
|
|
continue;
|
|
|
|
int staffIdx = st1->idx();
|
|
int velocity = st1->velocities().velo(tick.ticks());
|
|
|
|
Instrument* instr = st1->part()->instrument(tick);
|
|
int channel;
|
|
if (chord != 0)
|
|
lastSubchannel = chord->upNote()->subchannel();
|
|
|
|
// This is a slightly hacky way of always getting a channel, no matter
|
|
// if there are notes or not.
|
|
if (lastSubchannel != -1)
|
|
channel = instr->channel(lastSubchannel)->channel();
|
|
else
|
|
continue;
|
|
|
|
events->registerChannel(channel);
|
|
|
|
//
|
|
// Decide whether to add CC events or not
|
|
//
|
|
|
|
if (instr->singleNoteDynamics()) {
|
|
Fraction hairpinStartTick;
|
|
Fraction hairpinStopTick;
|
|
bool hasHairpin = false;
|
|
|
|
bool singleNoteDynamics = false;
|
|
Hairpin* h = nullptr;
|
|
VeloChangeMethod changeMethod = VeloChangeMethod::NORMAL;
|
|
|
|
// This flag is used to decide whether to add a static velocity event or none at all,
|
|
// depending on whether we're in a hairpin/changing dynamic or not.
|
|
bool doAddStaticVel = true;
|
|
|
|
// Check for hairpin crossing segment
|
|
for (auto it : staff->score()->spannerMap().findOverlapping(tick.ticks(), tick2.ticks()-1)) {
|
|
Spanner* s = it.value;
|
|
if (it.stop == tick.ticks())
|
|
continue;
|
|
|
|
// Don't playback dynamic if this hairpin started before or at the same time as the last one
|
|
// processed (i.e. if it is the same hairpin)
|
|
if (it.start + tickOffset <= renderData.lastHairpinStart.ticks()) {
|
|
doAddStaticVel = false;
|
|
continue;
|
|
}
|
|
|
|
if (s->isHairpin()) {
|
|
h = toHairpin(s);
|
|
switch (h->dynRange()) {
|
|
case Dynamic::Range::STAFF:
|
|
if (h->staff() != st1)
|
|
continue;
|
|
break;
|
|
case Dynamic::Range::PART:
|
|
if (h->part() != st1->part())
|
|
continue;
|
|
break;
|
|
case Dynamic::Range::SYSTEM:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
singleNoteDynamics = h->singleNoteDynamics() || singleNoteDynamics;
|
|
if (singleNoteDynamics) {
|
|
hairpinStartTick = Fraction::fromTicks(it.start);
|
|
hairpinStopTick = Fraction::fromTicks(it.stop);
|
|
hasHairpin = true;
|
|
changeMethod = h->veloChangeMethod();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// From this, work out a start and end tick to apply CC events for
|
|
Fraction stick;
|
|
Fraction etick;
|
|
Fraction fracTickOffset = Fraction::fromTicks(tickOffset);
|
|
if (hasHairpin) {
|
|
stick = hairpinStartTick;
|
|
etick = hairpinStopTick;
|
|
|
|
// Correct for a changing dynamic that may still be finishing
|
|
if (renderData.lastDynamicEnd >= etick + fracTickOffset) {
|
|
singleNoteDynamics = false;
|
|
}
|
|
else if (renderData.lastDynamicEnd > stick + fracTickOffset) {
|
|
stick = renderData.lastDynamicEnd;
|
|
}
|
|
}
|
|
else {
|
|
stick = Fraction(0, 1);
|
|
etick = seg->tick() + seg->ticks();
|
|
}
|
|
|
|
if (stick < seg->tick())
|
|
stick = seg->tick();
|
|
|
|
// Make sure we don't add a static dynamic event in the middle of a changing dynamic
|
|
if (renderData.lastDynamicEnd >= stick + fracTickOffset)
|
|
doAddStaticVel = false;
|
|
|
|
// Check if there is a fortepiano / similar dynamic
|
|
bool hasChangingDynamic = false;
|
|
Dynamic* changingDyn = nullptr;
|
|
if (chord != 0) {
|
|
for (Element* e : seg->annotations()) {
|
|
if (!e)
|
|
continue;
|
|
if (!e->isDynamic())
|
|
continue;
|
|
Dynamic* d = toDynamic(e);
|
|
if (d->changeInVelocity() == 0)
|
|
continue;
|
|
|
|
switch (d->dynRange()) {
|
|
case Dynamic::Range::STAFF:
|
|
if (d->staff()->idx() != staffIdx)
|
|
continue;
|
|
break;
|
|
case Dynamic::Range::PART:
|
|
if (d->part() != chord->part())
|
|
continue;
|
|
break;
|
|
case Dynamic::Range::SYSTEM:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
hasChangingDynamic = true;
|
|
changingDyn = d;
|
|
}
|
|
}
|
|
|
|
// We have a start and end tick, so get the velocities at these points
|
|
int velocityStart = staff->velocities().velo(stick.ticks());
|
|
int velocityMiddle = hasChangingDynamic ? velocityStart + changingDyn->changeInVelocity() : -1;
|
|
int velocityEnd = staff->velocities().velo(etick.ticks() - 1);
|
|
|
|
// Attempt to fix invalid hairpin
|
|
if (hasHairpin) {
|
|
int hairpinStartVel = (velocityMiddle == -1) ? velocityStart : velocityMiddle;
|
|
if (h->isCrescendo() && hairpinStartVel > velocityEnd)
|
|
singleNoteDynamics = false;
|
|
else if (h->isDecrescendo() && hairpinStartVel < velocityEnd)
|
|
singleNoteDynamics = false;
|
|
}
|
|
|
|
// Check for articulations to be rendered for playback
|
|
bool hasArticulations = false;
|
|
if (chord) {
|
|
for (const Articulation* a : chord->articulations()) {
|
|
if (a->playArticulation()) {
|
|
hasArticulations = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add CC events
|
|
//
|
|
|
|
if (singleNoteDynamics || hasArticulations || hasChangingDynamic) {
|
|
if (chord != 0 && hasArticulations) {
|
|
for (Articulation* a : chord->articulations()) {
|
|
if (!a->playArticulation())
|
|
continue;
|
|
if (velocityMiddle == -1)
|
|
velocityMiddle = velocityStart;
|
|
instr->updateVelocity(&velocityStart, channel, a->articulationName());
|
|
}
|
|
}
|
|
|
|
if (hasHairpin && singleNoteDynamics)
|
|
renderData.lastHairpinStart = hairpinStartTick + Fraction::fromTicks(tickOffset);
|
|
|
|
if (hasArticulations || hasChangingDynamic) {
|
|
int startExpr = velocityStart;
|
|
int endExpr = velocityMiddle;
|
|
|
|
Fraction accentTicks = Fraction(1, 16);
|
|
if (hasChangingDynamic) {
|
|
accentTicks = changingDyn->velocityChangeLength();
|
|
renderData.lastDynamicEnd = stick + accentTicks + fracTickOffset;
|
|
}
|
|
|
|
// Determine how long to 'hold' the initial velocity
|
|
// This is shorter with a dynamic than an articulation
|
|
// Also, since we're about to add CC events, we can use int ticks instead of fractions
|
|
int stickToUse = stick.ticks() + accentTicks.ticks() / (hasChangingDynamic ? 4 : 2);
|
|
int etickToUse = stick.ticks() + accentTicks.ticks();
|
|
|
|
// First, add an initial accent velocity
|
|
// stick is the seg start tick, stickToUse is where we should dim to the rest velocity
|
|
changeCCBetween(renderData.tempPlayEvents, stick.ticks(), stickToUse, startExpr, startExpr, channel, controller, defaultChangeMethod, tickOffset, staffIdx);
|
|
|
|
// Then dimenuendo back down to normal
|
|
// eticktouse is the end of the dim back to normal for an accent,
|
|
// but etick is the segment end tick.
|
|
changeCCBetween(renderData.tempPlayEvents, stickToUse, etickToUse, startExpr, endExpr, channel, controller, defaultChangeMethod, tickOffset, staffIdx);
|
|
|
|
// if there's a cresc or dim after the dynamic, apply it
|
|
if (singleNoteDynamics && hasHairpin) {
|
|
startExpr = velocityMiddle;
|
|
endExpr = velocityEnd;
|
|
|
|
stickToUse = qMin(stick.ticks() + accentTicks.ticks() + 1, etick.ticks());
|
|
|
|
changeCCBetween(renderData.tempPlayEvents, stickToUse, etick.ticks(), startExpr, endExpr, channel, controller, changeMethod, tickOffset, staffIdx);
|
|
}
|
|
}
|
|
else {
|
|
int startExpr = velocityStart;
|
|
int endExpr = velocityEnd;
|
|
changeCCBetween(renderData.tempPlayEvents, stick.ticks(), etick.ticks(), startExpr, endExpr, channel, controller, changeMethod, tickOffset, staffIdx);
|
|
}
|
|
}
|
|
else if (doAddStaticVel) {
|
|
// Add a single expression value to match the velocity, since there is no hairpin
|
|
int exprVal = velocityStart;
|
|
int staticTick = seg->tick().ticks();
|
|
changeCCBetween(renderData.tempPlayEvents, staticTick, staticTick, exprVal, exprVal, channel, controller, defaultChangeMethod, tickOffset, staffIdx);
|
|
}
|
|
velocity = velocityStart; // update the velocity value that will be used in note events
|
|
} // if instr->singleNoteDynamics()
|
|
else {
|
|
if (chord != 0) {
|
|
for (Articulation* a : chord->articulations()) {
|
|
if (a->playArticulation())
|
|
instr->updateVelocity(&velocity, channel, a->articulationName());
|
|
}
|
|
}
|
|
// Add a single expression value to match the velocity, since this instrument should
|
|
// not use single note dynamics.
|
|
int staticTick = seg->tick().ticks();
|
|
changeCCBetween(renderData.tempPlayEvents, staticTick, staticTick, velocity, velocity, channel, controller, defaultChangeMethod, tickOffset, staffIdx);
|
|
}
|
|
|
|
//
|
|
// Add normal note events
|
|
//
|
|
|
|
if (chord != 0) {
|
|
int velocityToUse = 0;
|
|
switch (method) {
|
|
case DynamicsRenderMethod::FIXED_MAX:
|
|
velocityToUse = 127;
|
|
break;
|
|
case DynamicsRenderMethod::SEG_START:
|
|
default:
|
|
velocityToUse = velocity;
|
|
break;
|
|
}
|
|
|
|
if (!graceNotesMerged(chord))
|
|
for (Chord* c : chord->graceNotesBefore())
|
|
for (const Note* note : c->notes())
|
|
collectNote(events, channel, note, velocityToUse, tickOffset, staffIdx);
|
|
|
|
for (const Note* note : chord->notes())
|
|
collectNote(events, channel, note, velocityToUse, tickOffset, staffIdx);
|
|
|
|
if (!graceNotesMerged(chord))
|
|
for (Chord* c : chord->graceNotesAfter())
|
|
for (const Note* note : c->notes())
|
|
collectNote(events, channel, note, velocityToUse, tickOffset, staffIdx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// collectMeasureEvents
|
|
// redirects to the correct function based on the passed method
|
|
//---------------------------------------------------------
|
|
|
|
static void collectMeasureEvents(EventMap* events, Measure* m, Staff* staff, StaffRenderData& renderData, int tickOffset, DynamicsRenderMethod method, int cc)
|
|
{
|
|
switch (method) {
|
|
case DynamicsRenderMethod::SIMPLE:
|
|
collectMeasureEventsSimple(events, m, staff, tickOffset);
|
|
break;
|
|
case DynamicsRenderMethod::SEG_START:
|
|
case DynamicsRenderMethod::FIXED_MAX:
|
|
collectMeasureEventsDefault(events, m, staff, renderData, tickOffset, method, cc);
|
|
break;
|
|
default:
|
|
qWarning("Unrecognized dynamics method: %d", int(method));
|
|
break;
|
|
}
|
|
|
|
collectProgramChanges(events, m, staff, tickOffset);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateHairpin
|
|
//---------------------------------------------------------
|
|
|
|
void Score::updateHairpin(Hairpin* h)
|
|
{
|
|
Staff* st = h->staff();
|
|
Fraction tick = h->tick();
|
|
|
|
// Find any changing dynamics
|
|
// If there are any, then start the hairpin from after them
|
|
Segment* seg = h->startSegment();
|
|
if (seg) {
|
|
for (Element* e : seg->annotations()) {
|
|
if (!e)
|
|
continue;
|
|
if (!e->isDynamic())
|
|
continue;
|
|
Dynamic* d = toDynamic(e);
|
|
if (d->changeInVelocity() == 0)
|
|
continue;
|
|
|
|
switch (d->dynRange()) {
|
|
case Dynamic::Range::STAFF:
|
|
if (d->staff()->idx() != st->idx())
|
|
continue;
|
|
break;
|
|
case Dynamic::Range::PART:
|
|
if (d->part() != h->part())
|
|
continue;
|
|
break;
|
|
case Dynamic::Range::SYSTEM:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// start hairpin after the dynamic stops
|
|
tick = seg->tick() + d->velocityChangeLength();
|
|
break;
|
|
}
|
|
}
|
|
|
|
int velo = st->velocities().velo(tick.ticks());
|
|
int incr = h->veloChange();
|
|
Fraction tick2 = h->tick2();
|
|
if (tick > tick2)
|
|
tick = tick2;
|
|
|
|
//
|
|
// If velocity increase/decrease is zero, then assume
|
|
// the end velocity is taken from the next velocity
|
|
// event (the next dynamics symbol after the hairpin).
|
|
//
|
|
|
|
int endVelo = velo;
|
|
if (h->isCrescendo()) {
|
|
if (incr == 0 && velo < st->velocities().nextVelo(tick2.ticks()-1))
|
|
endVelo = st->velocities().nextVelo(tick2.ticks()-1);
|
|
else
|
|
endVelo += incr;
|
|
}
|
|
else {
|
|
if (incr == 0 && velo > st->velocities().nextVelo(tick2.ticks()-1))
|
|
endVelo = st->velocities().nextVelo(tick2.ticks()-1);
|
|
else
|
|
endVelo -= incr;
|
|
}
|
|
|
|
if (endVelo > 127)
|
|
endVelo = 127;
|
|
else if (endVelo < 1)
|
|
endVelo = 1;
|
|
|
|
switch (h->dynRange()) {
|
|
case Dynamic::Range::STAFF:
|
|
st->velocities().setVelo(tick.ticks(), VeloEvent(VeloType::RAMP, velo));
|
|
st->velocities().setVelo(tick2.ticks()-1, VeloEvent(VeloType::FIX, endVelo));
|
|
break;
|
|
case Dynamic::Range::PART:
|
|
for (Staff* s : *st->part()->staves()) {
|
|
s->velocities().setVelo(tick.ticks(), VeloEvent(VeloType::RAMP, velo));
|
|
s->velocities().setVelo(tick2.ticks()-1, VeloEvent(VeloType::FIX, endVelo));
|
|
}
|
|
break;
|
|
case Dynamic::Range::SYSTEM:
|
|
for (Staff* s : _staves) {
|
|
s->velocities().setVelo(tick.ticks(), VeloEvent(VeloType::RAMP, velo));
|
|
s->velocities().setVelo(tick2.ticks()-1, VeloEvent(VeloType::FIX, endVelo));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// removeHairpin
|
|
//---------------------------------------------------------
|
|
|
|
void Score::removeHairpin(Hairpin* h)
|
|
{
|
|
Staff* st = h->staff();
|
|
int tick = h->tick().ticks();
|
|
int tick2 = h->tick2().ticks() - 1;
|
|
|
|
switch (h->dynRange()) {
|
|
case Dynamic::Range::STAFF:
|
|
st->velocities().remove(tick);
|
|
st->velocities().remove(tick2);
|
|
break;
|
|
case Dynamic::Range::PART:
|
|
for (Staff* s : *st->part()->staves()) {
|
|
s->velocities().remove(tick);
|
|
s->velocities().remove(tick2);
|
|
}
|
|
break;
|
|
case Dynamic::Range::SYSTEM:
|
|
for (Staff* s : _staves) {
|
|
s->velocities().remove(tick);
|
|
s->velocities().remove(tick2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateVelo
|
|
// calculate velocity for all notes
|
|
//---------------------------------------------------------
|
|
|
|
void Score::updateVelo()
|
|
{
|
|
//
|
|
// collect Dynamics
|
|
//
|
|
if (!firstMeasure())
|
|
return;
|
|
|
|
for (Staff* st : _staves) {
|
|
VeloList& velo = st->velocities();
|
|
velo.clear();
|
|
velo.setVelo(0, 80);
|
|
}
|
|
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
|
|
Staff* st = staff(staffIdx);
|
|
VeloList& velo = st->velocities();
|
|
Part* prt = st->part();
|
|
int partStaves = prt->nstaves();
|
|
int partStaff = Score::staffIdx(prt);
|
|
|
|
for (Segment* s = firstMeasure()->first(); s; s = s->next1()) {
|
|
Fraction tick = s->tick();
|
|
for (const Element* e : s->annotations()) {
|
|
if (e->staffIdx() != staffIdx)
|
|
continue;
|
|
if (e->type() != ElementType::DYNAMIC)
|
|
continue;
|
|
const Dynamic* d = toDynamic(e);
|
|
int v = d->velocity();
|
|
|
|
// treat an invalid dynamic as no change, i.e. a dynamic set to 0
|
|
if (v < 1)
|
|
continue;
|
|
|
|
v = qBound(1, v, 127); // illegal values
|
|
|
|
// If a dynamic has 'velocity change' update its ending
|
|
int v2 = 0;
|
|
if (d->changeInVelocity() != 0) {
|
|
v2 = d->velocity() + d->changeInVelocity();
|
|
v2 = qBound(1, v2, 127); // illegal values
|
|
}
|
|
|
|
int dStaffIdx = d->staffIdx();
|
|
switch(d->dynRange()) {
|
|
case Dynamic::Range::STAFF:
|
|
if (dStaffIdx == staffIdx) {
|
|
velo.setVelo(tick.ticks(), v);
|
|
|
|
// Set the second dynamic point of changing dynamic immediately after
|
|
// to make sure that hairpins start from the correct dynamic if they
|
|
// overlap the dynamic velocity change
|
|
if (v2 > 0)
|
|
velo.setVelo(tick.ticks() + 1, v2);
|
|
}
|
|
break;
|
|
case Dynamic::Range::PART:
|
|
if (dStaffIdx >= partStaff && dStaffIdx < partStaff+partStaves) {
|
|
for (int i = partStaff; i < partStaff+partStaves; ++i) {
|
|
staff(i)->velocities().setVelo(tick.ticks(), v);
|
|
if (v2 > 0)
|
|
staff(i)->velocities().setVelo(tick.ticks() + 1, v2);
|
|
}
|
|
}
|
|
break;
|
|
case Dynamic::Range::SYSTEM:
|
|
for (int i = 0; i < nstaves(); ++i) {
|
|
staff(i)->velocities().setVelo(tick.ticks(), v);
|
|
if (v2 > 0)
|
|
staff(i)->velocities().setVelo(tick.ticks() + 1, v2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (const auto& sp : _spanner.map()) {
|
|
Spanner* s = sp.second;
|
|
if (s->type() != ElementType::HAIRPIN || sp.second->staffIdx() != staffIdx)
|
|
continue;
|
|
Hairpin* h = toHairpin(s);
|
|
updateHairpin(h);
|
|
}
|
|
}
|
|
|
|
for (auto it = spanner().cbegin(); it != spanner().cend(); ++it) {
|
|
Spanner* spanner = (*it).second;
|
|
if (!spanner->isVolta())
|
|
continue;
|
|
Volta* volta = toVolta(spanner);
|
|
volta->setVelocity();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderStaffSegment
|
|
//---------------------------------------------------------
|
|
|
|
void MidiRenderer::renderStaffChunk(const Chunk& chunk, EventMap* events, Staff* staff, DynamicsRenderMethod method, int cc)
|
|
{
|
|
Measure* start = chunk.startMeasure();
|
|
Measure* end = chunk.endMeasure();
|
|
const int tickOffset = chunk.tickOffset();
|
|
|
|
Measure* lastMeasure = start->prevMeasure();
|
|
StaffRenderData renderData;
|
|
|
|
for (Measure* m = start; m != end; m = m->nextMeasure()) {
|
|
if (lastMeasure && m->isRepeatMeasure(staff)) {
|
|
int offset = (m->tick() - lastMeasure->tick()).ticks();
|
|
collectMeasureEvents(events, lastMeasure, staff, renderData, tickOffset + offset, method, cc);
|
|
}
|
|
else {
|
|
lastMeasure = m;
|
|
collectMeasureEvents(events, lastMeasure, staff, renderData, tickOffset, method, cc);
|
|
}
|
|
}
|
|
|
|
events->insert(renderData.tempPlayEvents.begin(), renderData.tempPlayEvents.end());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderSpanners
|
|
//---------------------------------------------------------
|
|
|
|
void MidiRenderer::renderSpanners(const Chunk& chunk, EventMap* events)
|
|
{
|
|
const int tickOffset = chunk.tickOffset();
|
|
const int tick1 = chunk.tick1();
|
|
const int tick2 = chunk.tick2();
|
|
|
|
std::map<int, std::vector<std::pair<int, std::pair<bool, int>>>> channelPedalEvents;
|
|
for (const auto& sp : score->spannerMap().map()) {
|
|
Spanner* s = sp.second;
|
|
|
|
int staff = s->staffIdx();
|
|
int idx = s->staff()->channel(s->tick(), 0);
|
|
int channel = s->part()->instrument(s->tick())->channel(idx)->channel();
|
|
|
|
if (s->isPedal() || s->isLetRing()) {
|
|
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, std::pair<bool, int>>(0, std::pair<bool, int>(true, staff));
|
|
|
|
int st = s->tick().ticks();
|
|
if (st >= tick1 && st < tick2) {
|
|
// Handle "overlapping" pedal segments (usual case for connected pedal line)
|
|
if (lastEvent.second.first == false && lastEvent.first >= (st + tickOffset + 2)) {
|
|
channelPedalEvents.at(channel).pop_back();
|
|
channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(st + tickOffset + 1, std::pair<bool, int>(false, staff)));
|
|
}
|
|
int a = st + tickOffset + 2;
|
|
channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(a, std::pair<bool, int>(true, staff)));
|
|
}
|
|
if (s->tick2().ticks() >= tick1 && s->tick2().ticks() <= tick2) {
|
|
int t = s->tick2().ticks() + tickOffset + 1;
|
|
const RepeatSegment& lastRepeat = *score->repeatList().back();
|
|
if (t > lastRepeat.utick + lastRepeat.len())
|
|
t = lastRepeat.utick + lastRepeat.len();
|
|
channelPedalEvents.at(channel).push_back(std::pair<int, std::pair<bool, int>>(t, std::pair<bool, int>(false, staff)));
|
|
}
|
|
}
|
|
else if (s->isVibrato()) {
|
|
int stick = s->tick().ticks();
|
|
int etick = s->tick2().ticks();
|
|
if (stick >= tick2 || etick < tick1)
|
|
continue;
|
|
|
|
if (stick < tick1)
|
|
stick = tick1;
|
|
if (etick > tick2)
|
|
etick = tick2;
|
|
|
|
// from start to end of trill, send bend events at regular interval
|
|
Vibrato* t = toVibrato(s);
|
|
// guitar vibrato, up only
|
|
int spitch = 0; // 1/8 (100 is a semitone)
|
|
int epitch = 12;
|
|
if (t->vibratoType() == Vibrato::Type::GUITAR_VIBRATO_WIDE) {
|
|
spitch = 0; // 1/4
|
|
epitch = 25;
|
|
}
|
|
// vibrato with whammy bar up and down
|
|
else if (t->vibratoType() == Vibrato::Type::VIBRATO_SAWTOOTH_WIDE) {
|
|
spitch = 25; // 1/16
|
|
epitch = -25;
|
|
}
|
|
else if (t->vibratoType() == Vibrato::Type::VIBRATO_SAWTOOTH) {
|
|
spitch = 12;
|
|
epitch = -12;
|
|
}
|
|
|
|
int j = 0;
|
|
int delta = MScore::division / 8; // 1/8 note
|
|
int lastPointTick = stick;
|
|
while (lastPointTick < etick) {
|
|
int pitch = (j % 4 < 2) ? spitch : epitch;
|
|
int nextPitch = ((j+1) % 4 < 2) ? spitch : epitch;
|
|
int nextPointTick = lastPointTick + delta;
|
|
for (int i = lastPointTick; i <= nextPointTick; i += 16) {
|
|
double dx = ((i - lastPointTick) * 60) / delta;
|
|
int p = pitch + dx * (nextPitch - pitch) / delta;
|
|
int midiPitch = (p * 16384) / 1200 + 8192;
|
|
int msb = midiPitch / 128;
|
|
int lsb = midiPitch % 128;
|
|
NPlayEvent ev(ME_PITCHBEND, channel, lsb, msb);
|
|
ev.setOriginatingStaff(staff);
|
|
events->insert(std::pair<int, NPlayEvent>(i + tickOffset, ev));
|
|
}
|
|
lastPointTick = nextPointTick;
|
|
j++;
|
|
}
|
|
NPlayEvent ev(ME_PITCHBEND, channel, 0, 64); // no pitch bend
|
|
ev.setOriginatingStaff(staff);
|
|
events->insert(std::pair<int, NPlayEvent>(etick + tickOffset, ev));
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
for (const auto& pedalEvents : channelPedalEvents) {
|
|
int channel = pedalEvents.first;
|
|
for (const auto& pe : pedalEvents.second) {
|
|
NPlayEvent event;
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// swingAdjustParams
|
|
//--------------------------------------------------------
|
|
|
|
void Score::swingAdjustParams(Chord* chord, int& gateTime, int& ontime, int swingUnit, int swingRatio)
|
|
{
|
|
Fraction tick = chord->rtick();
|
|
// adjust for anacrusis
|
|
Measure* cm = chord->measure();
|
|
MeasureBase* pm = cm->prev();
|
|
ElementType pt = pm ? pm->type() : ElementType::INVALID;
|
|
if (!pm || pm->lineBreak() || pm->pageBreak() || pm->sectionBreak()
|
|
|| pt == ElementType::VBOX || pt == ElementType::HBOX
|
|
|| pt == ElementType::FBOX || pt == ElementType::TBOX) {
|
|
Fraction offset = cm->timesig() - cm->ticks();
|
|
if (offset > Fraction(0,1)) {
|
|
tick += offset;
|
|
}
|
|
}
|
|
|
|
int swingBeat = swingUnit * 2;
|
|
qreal ticksDuration = (qreal)chord->actualTicks().ticks();
|
|
qreal swingTickAdjust = ((qreal)swingBeat) * (((qreal)(swingRatio-50))/100.0);
|
|
qreal swingActualAdjust = (swingTickAdjust/ticksDuration) * 1000.0;
|
|
ChordRest *ncr = nextChordRest(chord);
|
|
|
|
//Check the position of the chord to apply changes accordingly
|
|
if (tick.ticks() % swingBeat == swingUnit) {
|
|
if (!isSubdivided(chord,swingUnit)) {
|
|
ontime = ontime + swingActualAdjust;
|
|
}
|
|
}
|
|
int endTick = tick.ticks() + ticksDuration;
|
|
if ((endTick % swingBeat == swingUnit) && (!isSubdivided(ncr,swingUnit))) {
|
|
gateTime = gateTime + (swingActualAdjust/10);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// isSubdivided
|
|
// Check for subdivided beat
|
|
//---------------------------------------------------------
|
|
|
|
bool Score::isSubdivided(ChordRest* chord, int swingUnit)
|
|
{
|
|
if (!chord)
|
|
return false;
|
|
ChordRest* prev = prevChordRest(chord);
|
|
if (chord->actualTicks().ticks() < swingUnit || (prev && prev->actualTicks().ticks() < swingUnit))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
const Drumset* getDrumset(const Chord* chord)
|
|
{
|
|
if (chord->staff() && chord->staff()->isDrumStaff(chord->tick())) {
|
|
const Drumset* ds = chord->staff()->part()->instrument(chord->tick())->drumset();
|
|
return ds;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderTremolo
|
|
//---------------------------------------------------------
|
|
|
|
void renderTremolo(Chord* chord, QList<NoteEventList>& ell)
|
|
{
|
|
Segment* seg = chord->segment();
|
|
Tremolo* tremolo = chord->tremolo();
|
|
int notes = int(chord->notes().size());
|
|
|
|
// check if tremolo was rendered before for drum staff
|
|
const Drumset* ds = getDrumset(chord);
|
|
if (ds) {
|
|
for (Note* n : chord->notes()) {
|
|
DrumInstrumentVariant div = ds->findVariant(n->pitch(), chord->articulations(), chord->tremolo());
|
|
if (div.pitch != INVALID_PITCH && div.tremolo == tremolo->tremoloType())
|
|
return; // already rendered
|
|
}
|
|
}
|
|
|
|
// we cannot render buzz roll with MIDI events only
|
|
if (tremolo->tremoloType() == TremoloType::BUZZ_ROLL)
|
|
return;
|
|
|
|
// render tremolo with multiple events
|
|
if (chord->tremoloChordType() == TremoloChordType::TremoloFirstNote) {
|
|
int t = MScore::division / (1 << (tremolo->lines() + chord->durationType().hooks()));
|
|
SegmentType st = SegmentType::ChordRest;
|
|
Segment* seg2 = seg->next(st);
|
|
int track = chord->track();
|
|
while (seg2 && !seg2->element(track))
|
|
seg2 = seg2->next(st);
|
|
|
|
if (!seg2)
|
|
return;
|
|
|
|
Element* s2El = seg2->element(track);
|
|
if (s2El) {
|
|
if (!s2El->isChord())
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
|
|
Chord* c2 = toChord(s2El);
|
|
if (c2->type() == ElementType::CHORD) {
|
|
int notes2 = int(c2->notes().size());
|
|
int tnotes = qMax(notes, notes2);
|
|
int tticks = chord->actualTicks().ticks() * 2; // use twice the size
|
|
int n = tticks / t;
|
|
n /= 2;
|
|
int l = 2000 * t / tticks;
|
|
for (int k = 0; k < tnotes; ++k) {
|
|
NoteEventList* events;
|
|
if (k < notes) {
|
|
// first chord has note
|
|
events = &ell[k];
|
|
events->clear();
|
|
}
|
|
else {
|
|
// otherwise reuse note 0
|
|
events = &ell[0];
|
|
}
|
|
if (k < notes && k < notes2) {
|
|
// both chords have note
|
|
int p1 = chord->notes()[k]->pitch();
|
|
int p2 = c2->notes()[k]->pitch();
|
|
int dpitch = p2 - p1;
|
|
for (int i = 0; i < n; ++i) {
|
|
events->append(NoteEvent(0, l * i * 2, l));
|
|
events->append(NoteEvent(dpitch, l * i * 2 + l, l));
|
|
}
|
|
}
|
|
else if (k < notes) {
|
|
// only first chord has note
|
|
for (int i = 0; i < n; ++i)
|
|
events->append(NoteEvent(0, l * i * 2, l));
|
|
}
|
|
else {
|
|
// only second chord has note
|
|
// reuse note 0 of first chord
|
|
int p1 = chord->notes()[0]->pitch();
|
|
int p2 = c2->notes()[k]->pitch();
|
|
int dpitch = p2-p1;
|
|
for (int i = 0; i < n; ++i)
|
|
events->append(NoteEvent(dpitch, l * i * 2 + l, l));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
qDebug("Chord::renderTremolo: cannot find 2. chord");
|
|
}
|
|
else if (chord->tremoloChordType() == TremoloChordType::TremoloSecondNote) {
|
|
for (int k = 0; k < notes; ++k) {
|
|
NoteEventList* events = &(ell)[k];
|
|
events->clear();
|
|
}
|
|
}
|
|
else if (chord->tremoloChordType() == TremoloChordType::TremoloSingle) {
|
|
int t = MScore::division / (1 << (tremolo->lines() + chord->durationType().hooks()));
|
|
if (t == 0) // avoid crash on very short tremolo
|
|
t = 1;
|
|
int n = chord->ticks().ticks() / t;
|
|
int l = 1000 / n;
|
|
for (int k = 0; k < notes; ++k) {
|
|
NoteEventList* events = &(ell)[k];
|
|
events->clear();
|
|
for (int i = 0; i < n; ++i)
|
|
events->append(NoteEvent(0, l * i, l));
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderArpeggio
|
|
//---------------------------------------------------------
|
|
|
|
void renderArpeggio(Chord *chord, QList<NoteEventList> & ell)
|
|
{
|
|
int notes = int(chord->notes().size());
|
|
int l = 64;
|
|
while (l && (l * notes > chord->upNote()->playTicks()))
|
|
l = 2*l / 3;
|
|
int start, end, step;
|
|
bool up = chord->arpeggio()->arpeggioType() != ArpeggioType::DOWN && chord->arpeggio()->arpeggioType() != ArpeggioType::DOWN_STRAIGHT;
|
|
if (up) {
|
|
start = 0;
|
|
end = notes;
|
|
step = 1;
|
|
}
|
|
else {
|
|
start = notes - 1;
|
|
end = -1;
|
|
step = -1;
|
|
}
|
|
int j = 0;
|
|
for (int i = start; i != end; i += step) {
|
|
NoteEventList* events = &(ell)[i];
|
|
events->clear();
|
|
|
|
auto tempoRatio = chord->score()->tempomap()->tempo(chord->tick().ticks()) / Score::defaultTempo();
|
|
int ot = (l * j * 1000) / chord->upNote()->playTicks() *
|
|
tempoRatio * chord->arpeggio()->Stretch();
|
|
|
|
events->append(NoteEvent(0, ot, 1000 - ot));
|
|
j++;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// convertLine
|
|
// find the line in clefF corresponding to lineL2 in clefR
|
|
//---------------------------------------------------------
|
|
|
|
int convertLine (int lineL2, ClefType clefL, ClefType clefR) {
|
|
int lineR2 = lineL2;
|
|
int goalpitch = line2pitch(lineL2, clefL, Key::C);
|
|
int p;
|
|
while ( (p = line2pitch(lineR2, clefR, Key::C)) > goalpitch && p < 127)
|
|
lineR2++;
|
|
while ( (p = line2pitch(lineR2, clefR, Key::C)) < goalpitch && p > 0)
|
|
lineR2--;
|
|
return lineR2;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// convertLine
|
|
// find the line in clef for NoteL corresponding to lineL2 in clef for noteR
|
|
// for example middle C is line 10 in Treble clef, but is line -2 in Bass clef.
|
|
//---------------------------------------------------------
|
|
|
|
int convertLine(int lineL2, Note *noteL, Note *noteR)
|
|
{
|
|
return convertLine(lineL2,
|
|
noteL->chord()->staff()->clef(noteL->chord()->tick()),
|
|
noteR->chord()->staff()->clef(noteR->chord()->tick()));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// articulationExcursion -- an articulation such as a trill, or modant consists of several notes
|
|
// played in succession. The pitch offsets of each such note in the sequence can be represented either
|
|
// as a number of steps in the diatonic scale, or in half steps as on a piano keyboard.
|
|
// this function, articulationExcursion, takes deltastep indicating the number of steps in the
|
|
// diatonic scale, and calculates (and returns) the number of half steps, taking several things into account.
|
|
// E.g., the key signature, a trill from e to f, is to be understood as a trill between E and F# if we are
|
|
// in the key of G.
|
|
// E.g., if previously (looking backward in time) in the same measure there is another note on the same
|
|
// staff line/space, and that note has an accidental (sharp,flat,natural,etc), then we want to match that
|
|
// tone exactly.
|
|
// E.g., If there are multiple notes on the same line/space, then we only consider the most
|
|
// recent one, but avoid looking forward in time after the current note.
|
|
// E.g., Also if there is an accidental // on a note one (or more) octaves above or below we
|
|
// observe its accidental as well.
|
|
// E.g., Still another case is that if two staffs are involved (such as a glissando between two
|
|
// notes on different staffs) then we have to search both staffs for the most recent accidental.
|
|
//
|
|
// noteL is the note to measure the deltastep from, i.e., ornaments are w.r.t. this note
|
|
// noteR is the note to search backward from to find accidentals.
|
|
// for ornament calculation noteL and noteR are the same, but for glissando they are
|
|
// the start end end note of glissando.
|
|
// deltastep is the desired number of diatonic steps between the base note and this articulation step.
|
|
//---------------------------------------------------------
|
|
|
|
int articulationExcursion(Note *noteL, Note *noteR, int deltastep)
|
|
{
|
|
if (0 == deltastep)
|
|
return 0;
|
|
Chord *chordL = noteL->chord();
|
|
Chord *chordR = noteR->chord();
|
|
int epitchL = noteL->epitch();
|
|
Fraction tickL = chordL->tick();
|
|
// we cannot use staffL = chord->staff() because that won't correspond to the noteL->line()
|
|
// in the case the user has pressed Shift-Cmd->Up or Shift-Cmd-Down.
|
|
// Therefore we have to take staffMove() into account using vStaffIdx().
|
|
Staff * staffL = noteL->score()->staff(chordL->vStaffIdx());
|
|
ClefType clefL = staffL->clef(tickL);
|
|
// line represents the ledger line of the staff. 0 is the top line, 1, is the space between the top 2 lines,
|
|
// ... 8 is the bottom line.
|
|
int lineL = noteL->line();
|
|
// we use line - deltastep, because lines are oriented from top to bottom, while step is oriented from bottom to top.
|
|
int lineL2 = lineL - deltastep;
|
|
Measure* measureR = chordR->segment()->measure();
|
|
|
|
Segment* segment = noteL->chord()->segment();
|
|
int lineR2 = convertLine(lineL2, noteL, noteR);
|
|
// is there another note in this segment on the same line?
|
|
// if so, use its pitch exactly.
|
|
int halfsteps = 0;
|
|
int staffIdx = noteL->chord()->staff()->idx(); // cannot use staffL->idx() because of staffMove()
|
|
int startTrack = staffIdx * VOICES;
|
|
int endTrack = startTrack + VOICES;
|
|
bool done = false;
|
|
for (int track = startTrack; track < endTrack; ++track) {
|
|
Element *e = segment->element(track);
|
|
if (!e || e->type() != ElementType::CHORD)
|
|
continue;
|
|
Chord* chord = toChord(e);
|
|
if (chord->vStaffIdx() != chordL->vStaffIdx())
|
|
continue;
|
|
for (Note* note : chord->notes()) {
|
|
if (note->tieBack())
|
|
continue;
|
|
int pc = (note->line() + 700) % 7;
|
|
int pc2 = (lineL2 + 700) % 7;
|
|
if (pc2 == pc) {
|
|
// e.g., if there is an F# note at this staff/tick, then force every F to be F#.
|
|
int octaves = (note->line() - lineL2) / 7;
|
|
halfsteps = note->epitch() + 12 * octaves - epitchL;
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!done) {
|
|
if (staffL->isPitchedStaff(segment->tick())) {
|
|
bool error = false;
|
|
AccidentalVal acciv2 = measureR->findAccidental(chordR->segment(), chordR->vStaffIdx(), lineR2, error);
|
|
int acci2 = int(acciv2);
|
|
// epitch (effective pitch) is a visible pitch so line2pitch returns exactly that.
|
|
halfsteps = line2pitch(lineL-deltastep, clefL, Key::C) + acci2 - epitchL;
|
|
}
|
|
else {
|
|
// cannot rely on accidentals or key signatures
|
|
halfsteps = deltastep;
|
|
}
|
|
}
|
|
}
|
|
return halfsteps;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// totalTiedNoteTicks
|
|
// return the total of the actualTicks of the given note plus
|
|
// the chain of zero or more notes tied to it to the right.
|
|
//---------------------------------------------------------
|
|
|
|
int totalTiedNoteTicks(Note* note)
|
|
{
|
|
Fraction total = note->chord()->actualTicks();
|
|
while (note->tieFor() && note->tieFor()->endNote() && (note->chord()->tick() < note->tieFor()->endNote()->chord()->tick())) {
|
|
note = note->tieFor()->endNote();
|
|
total += note->chord()->actualTicks();
|
|
}
|
|
return total.ticks();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderNoteArticulation
|
|
// prefix, vector of int, normally something like {0,-1,0,1} modeling the prefix of tremblement relative to the base note
|
|
// body, vector of int, normally something like {0,-1,0,1} modeling the possibly repeated tremblement relative to the base note
|
|
// tickspernote, number of ticks, either _16h or _32nd, i.e., MScore::division/4 or MScore::division/8
|
|
// repeatp, true means repeat the body as many times as possible to fill the time slice.
|
|
// sustainp, true means the last note of the body is sustained to fill remaining time slice
|
|
//---------------------------------------------------------
|
|
|
|
bool renderNoteArticulation(NoteEventList* events, Note* note, bool chromatic, int requestedTicksPerNote,
|
|
const vector<int>& prefix, const vector<int>& body,
|
|
bool repeatp, bool sustainp, const vector<int>& suffix,
|
|
int fastestFreq=64, int slowestFreq=8 // 64 Hz and 8 Hz
|
|
)
|
|
{
|
|
events->clear();
|
|
Chord *chord = note->chord();
|
|
int maxticks = totalTiedNoteTicks(note);
|
|
int space = 1000 * maxticks;
|
|
int numrepeat = 1;
|
|
int sustain = 0;
|
|
int ontime = 0;
|
|
|
|
int gnb = note->chord()->graceNotesBefore().size();
|
|
int p = int(prefix.size());
|
|
int b = int(body.size());
|
|
int s = int(suffix.size());
|
|
int gna = note->chord()->graceNotesAfter().size();
|
|
|
|
int ticksPerNote = 0;
|
|
|
|
if (gnb + p + b + s + gna <= 0 )
|
|
return false;
|
|
|
|
Fraction tick = chord->tick();
|
|
qreal tempo = chord->score()->tempo(tick);
|
|
int ticksPerSecond = tempo * MScore::division;
|
|
|
|
int minTicksPerNote = int(ticksPerSecond / fastestFreq);
|
|
int maxTicksPerNote = (0 == slowestFreq) ? 0 : int(ticksPerSecond / slowestFreq);
|
|
|
|
// for fast tempos, we have to slow down the tremblement frequency, i.e., increase the ticks per note
|
|
if (requestedTicksPerNote >= minTicksPerNote)
|
|
;
|
|
else { // try to divide the requested frequency by a power of 2 if possible, if not, use the maximum frequency, ie., minTicksPerNote
|
|
ticksPerNote = requestedTicksPerNote;
|
|
while (ticksPerNote < minTicksPerNote) {
|
|
ticksPerNote *= 2; // decrease the tremblement frequency
|
|
}
|
|
if (ticksPerNote > maxTicksPerNote)
|
|
ticksPerNote = minTicksPerNote;
|
|
}
|
|
|
|
ticksPerNote = max(requestedTicksPerNote, minTicksPerNote);
|
|
|
|
if (slowestFreq <= 0) // no slowest freq given such as something silly like glissando with 4 notes over 8 counts.
|
|
;
|
|
else if (ticksPerNote <= maxTicksPerNote) // in a good range, so we don't need to adjust ticksPerNote
|
|
;
|
|
else {
|
|
// for slow tempos, such as adagio, we may need to speed up the tremblement frequency, i.e., decrease the ticks per note, to make it sound reasonable.
|
|
ticksPerNote = requestedTicksPerNote ;
|
|
while (ticksPerNote > maxTicksPerNote) {
|
|
ticksPerNote /= 2;
|
|
}
|
|
if (ticksPerNote < minTicksPerNote)
|
|
ticksPerNote = minTicksPerNote;
|
|
}
|
|
// calculate whether to shorten the duration value.
|
|
if ( ticksPerNote*(gnb + p + b + s + gna) <= maxticks )
|
|
; // plenty of space to play the notes without changing the requested trill note duration
|
|
else if ( ticksPerNote == minTicksPerNote )
|
|
return false; // the ornament is impossible to implement respecting the minimum duration and all the notes it contains
|
|
else {
|
|
ticksPerNote = maxticks / (gnb + p + b + s + gna); // integer division ignoring remainder
|
|
if ( slowestFreq <= 0 )
|
|
;
|
|
else if ( ticksPerNote < minTicksPerNote )
|
|
return false;
|
|
}
|
|
|
|
int millespernote = space * ticksPerNote / maxticks; // rescale duration into per mille
|
|
|
|
// local function:
|
|
// look ahead in the given vector to see if the current note is the same pitch as the next note or next several notes.
|
|
// If so, increment the duration by the appropriate note duration, and increment the index, j, to the next note index
|
|
// of a different pitch.
|
|
// The total duration of the tied note is returned, and the index is modified.
|
|
auto tieForward = [millespernote] (int & j, const vector<int> & vec) {
|
|
int size = int(vec.size());
|
|
int duration = millespernote;
|
|
while ( j < size-1 && vec[j] == vec[j+1] ) {
|
|
duration += millespernote;
|
|
j++;
|
|
}
|
|
return duration;
|
|
};
|
|
|
|
// local function:
|
|
// append a NoteEvent either by calculating an articulationExcursion or by
|
|
// the given chromatic relative pitch.
|
|
// RETURNS the new ontime value. The caller is expected to assign this value.
|
|
auto makeEvent = [note,chord,chromatic,events] (int pitch, int ontime, int duration) {
|
|
events->append( NoteEvent(chromatic ? pitch : articulationExcursion(note,note,pitch),
|
|
ontime/chord->actualTicks().ticks(),
|
|
duration/chord->actualTicks().ticks()));
|
|
return ontime + duration;
|
|
};
|
|
|
|
// local function:
|
|
// Given a chord from a grace note, (normally the chord contains a single note) and create
|
|
// a NoteEvent as if the grace note were part of the articulation (such as trill). This
|
|
// local function works for the graceNotesBefore() and also graceNotesAfter().
|
|
// If the grace note has play=false, then it will sound as a rest, but the other grace
|
|
// notes will still play. This means graceExtend simply omits the call to append( NoteEvent(...))
|
|
// but still updates ontime +=millespernote.
|
|
// RETURNS the new value of ontime, so caller must make an assignment to the return value.
|
|
auto graceExtend = [millespernote,chord,events] (int notePitch, QVector<Chord*> graceNotes, int ontime) {
|
|
for (Chord* c : graceNotes) {
|
|
for (Note* n : c->notes()) {
|
|
// NoteEvent takes relative pitch as first argument.
|
|
// The pitch is relative to the pitch of the note, the event is rendering
|
|
if (n->play())
|
|
events->append( NoteEvent(n->pitch() - notePitch,
|
|
ontime/chord->actualTicks().ticks(),
|
|
millespernote/chord->actualTicks().ticks()));
|
|
}
|
|
ontime += millespernote;
|
|
}
|
|
return ontime;
|
|
};
|
|
|
|
// calculate the number of times to repeat the body, and sustain the last note of the body
|
|
// 1000 = P + numrepeat*B+sustain + S
|
|
if (repeatp)
|
|
numrepeat = (space - millespernote*(gnb + p + s + gna)) / (millespernote * b);
|
|
if (sustainp)
|
|
sustain = space - millespernote*(gnb + p + numrepeat * b + s + gna);
|
|
// render the graceNotesBefore
|
|
ontime = graceExtend(note->pitch(),note->chord()->graceNotesBefore(), ontime);
|
|
|
|
// render the prefix
|
|
for (int j=0; j < p; j++)
|
|
ontime = makeEvent(prefix[j], ontime, tieForward(j,prefix));
|
|
|
|
if (b > 0) {
|
|
// render the body, but not the final repetition
|
|
for (int r = 0; r < numrepeat-1; r++) {
|
|
for (int j=0; j < b; j++)
|
|
ontime = makeEvent(body[j], ontime, millespernote);
|
|
}
|
|
// render the final repetition of body, but not the final note of the repition
|
|
for (int j = 0; j < b - 1; j++)
|
|
ontime = makeEvent(body[j], ontime, millespernote);
|
|
// render the final note of the final repeat of body
|
|
ontime = makeEvent(body[b-1], ontime, millespernote+sustain);
|
|
}
|
|
// render the suffix
|
|
for (int j = 0; j < s; j++)
|
|
ontime = makeEvent(suffix[j], ontime, tieForward(j,suffix));
|
|
// render graceNotesAfter
|
|
graceExtend(note->pitch(), note->chord()->graceNotesAfter(), ontime);
|
|
return true;
|
|
}
|
|
|
|
// This struct specifies how to render an articulation.
|
|
// atype - the articulation type to implement, such as SymId::ornamentTurn
|
|
// ostyles - the actual ornament has a property called ornamentStyle whose value is
|
|
// a value of type MScore::OrnamentStyle. This ostyles field indicates the
|
|
// the set of ornamentStyles which apply to this rendition.
|
|
// duration - the default duration for each note in the rendition, the final duration
|
|
// rendered might be less than this if an articulation is attached to a note of
|
|
// short duration.
|
|
// prefix - vector of integers. indicating which notes to play at the beginning of rendering the
|
|
// articulation. 0 represents the principle note, 1==> the note diatonically 1 above
|
|
// -1 ==> the note diatonically 1 below. E.g., in the key of G, if a turn articulation
|
|
// occurs above the note F#, then 0==>F#, 1==>G, -1==>E.
|
|
// These integers indicate which notes actual notes to play when rendering the ornamented
|
|
// note. However, if the same integer appears several times adjacently such as {0,0,0,1}
|
|
// That means play the notes tied. e.g., F# followed by G, but the duration of F# is 3x the
|
|
// duration of the G.
|
|
// body - notes to play comprising the body of the rendered ornament.
|
|
// The body differs from the prefix and suffix in several ways.
|
|
// * body does not support tied notes: {0,0,0,1} means play 4 distinct notes (not tied).
|
|
// * if there is sufficient duration in the principle note, AND repeatep is true, then body
|
|
// will be rendered multiple times, as the duration allows.
|
|
// * to avoid a time gap (or rest) in rendering the articulation, if sustainp is true,
|
|
// then the final note of the body will be sustained to fill the left-over time.
|
|
// suffix - similar to prefix but played once at the end of the rendered ornament.
|
|
// repeatp - whether the body is repeatable in its entirety.
|
|
// sustainp - whether the final note of the body should be sustained to fill the remaining duration.
|
|
|
|
struct OrnamentExcursion {
|
|
SymId atype;
|
|
set<MScore::OrnamentStyle> ostyles;
|
|
int duration;
|
|
vector<int> prefix;
|
|
vector<int> body;
|
|
bool repeatp;
|
|
bool sustainp;
|
|
vector<int> suffix;
|
|
};
|
|
|
|
set<MScore::OrnamentStyle> baroque = {MScore::OrnamentStyle::BAROQUE};
|
|
set<MScore::OrnamentStyle> defstyle = {MScore::OrnamentStyle::DEFAULT};
|
|
set<MScore::OrnamentStyle> any; // empty set has the special meaning of any-style, rather than no-styles.
|
|
int _16th = MScore::division / 4;
|
|
int _32nd = _16th / 2;
|
|
|
|
vector<OrnamentExcursion> excursions = {
|
|
// articulation type set of duration body repeatp suffix
|
|
// styles prefix sustainp
|
|
{ SymId::ornamentTurn, any, _32nd, {}, {1,0,-1,0}, false, true, {}}
|
|
,{SymId::ornamentTurnInverted, any, _32nd, {}, {-1,0,1,0}, false, true, {}}
|
|
,{SymId::ornamentTrill, baroque, _32nd, {1,0}, {1,0}, true, true, {}}
|
|
,{SymId::ornamentTrill, defstyle, _32nd, {0,1}, {0,1}, true, true, {}}
|
|
,{SymId::brassMuteClosed, baroque, _32nd, {0,-1},{0, -1}, true, true, {}}
|
|
,{SymId::ornamentMordentInverted, any, _32nd, {}, {0,-1,0}, false, true, {}}
|
|
,{SymId::ornamentMordent, defstyle, _32nd, {}, {0,1,0}, false, true, {}} // inverted mordent
|
|
,{SymId::ornamentMordent, baroque, _32nd, {1,0,1},{0}, false, true, {}} // short trill
|
|
,{SymId::ornamentTremblement, any, _32nd, {1,0}, {1,0}, false, true, {}}
|
|
,{SymId::ornamentPrallMordent, any, _32nd, {}, {1,0,-1,0}, false, true, {}}
|
|
,{SymId::ornamentLinePrall, any, _32nd, {2,2,2},{1,0}, true, true, {}}
|
|
,{SymId::ornamentUpPrall, any, _16th, {-1,0},{1,0}, true, true, {1,0}} // p 144 Ex 152 [1]
|
|
,{SymId::ornamentUpMordent, any, _16th, {-1,0},{1,0}, true, true, {-1,0}} // p 144 Ex 152 [1]
|
|
|
|
,{SymId::ornamentPrecompMordentUpperPrefix, any, _16th, {1,1,1,0}, {1,0}, true, true, {}} // p136 Cadence Appuyee [1] [2]
|
|
,{SymId::ornamentDownMordent, any, _16th, {1,1,1,0}, {1,0}, true, true, {-1, 0}} // p136 Cadence Appuyee + mordent [1] [2]
|
|
,{SymId::ornamentPrallUp, any, _16th, {1,0}, {1,0}, true, true, {-1,0}} // p136 Double Cadence [1]
|
|
,{SymId::ornamentPrallDown, any, _16th, {1,0}, {1,0}, true, true, {-1,0,0,0}} // p144 ex 153 [1]
|
|
,{SymId::ornamentPrecompSlide, any, _32nd, {}, {0}, false, true, {}}
|
|
|
|
// [1] Some of the articulations/ornaments in the excursions table above come from
|
|
// Baroque Music, Style and Performance A Handbook, by Robert Donington,(c) 1982
|
|
// ISBN 0-393-30052-8, W. W. Norton & Company, Inc.
|
|
|
|
// [2] In some cases, the example from [1] does not preserve the timing.
|
|
// For example, illustrates 2+1/4 counts per half note.
|
|
};
|
|
|
|
//---------------------------------------------------------
|
|
// renderNoteArticulation
|
|
//---------------------------------------------------------
|
|
|
|
bool renderNoteArticulation(NoteEventList* events, Note * note, bool chromatic, SymId articulationType, MScore::OrnamentStyle ornamentStyle)
|
|
{
|
|
if (!note->staff()->isPitchedStaff(note->tick())) // not enough info in tab staff
|
|
return false;
|
|
|
|
vector<int> emptypattern = {};
|
|
for (auto& oe : excursions) {
|
|
if (oe.atype == articulationType && ( 0 == oe.ostyles.size()
|
|
|| oe.ostyles.end() != oe.ostyles.find(ornamentStyle))) {
|
|
return renderNoteArticulation(events, note, chromatic, oe.duration,
|
|
oe.prefix, oe.body, oe.repeatp, oe.sustainp, oe.suffix);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderNoteArticulation
|
|
//---------------------------------------------------------
|
|
|
|
bool renderNoteArticulation(NoteEventList* events, Note * note, bool chromatic, Trill::Type trillType, MScore::OrnamentStyle ornamentStyle)
|
|
{
|
|
map<Trill::Type,SymId> articulationMap = {
|
|
{Trill::Type::TRILL_LINE, SymId::ornamentTrill }
|
|
,{Trill::Type::UPPRALL_LINE, SymId::ornamentUpPrall }
|
|
,{Trill::Type::DOWNPRALL_LINE, SymId::ornamentPrecompMordentUpperPrefix }
|
|
,{Trill::Type::PRALLPRALL_LINE, SymId::ornamentTrill }
|
|
};
|
|
auto it = articulationMap.find(trillType);
|
|
if (it == articulationMap.cend())
|
|
return false;
|
|
else
|
|
return renderNoteArticulation(events, note, chromatic, it->second, ornamentStyle);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// noteHasGlissando
|
|
// true if note is the end of a glissando
|
|
//---------------------------------------------------------
|
|
|
|
bool noteHasGlissando(Note *note)
|
|
{
|
|
for (Spanner* spanner : note->spannerFor()) {
|
|
if ((spanner->type() == ElementType::GLISSANDO)
|
|
&& spanner->endElement()
|
|
&& (ElementType::NOTE == spanner->endElement()->type()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderGlissando
|
|
//---------------------------------------------------------
|
|
|
|
void renderGlissando(NoteEventList* events, Note *notestart)
|
|
{
|
|
vector<int> empty = {};
|
|
int Cnote = 60; // pitch of middle C
|
|
int pitchstart = notestart->ppitch();
|
|
int linestart = notestart->line();
|
|
|
|
set<int> blacknotes = { 1, 3, 6, 8, 10};
|
|
set<int> whitenotes = {0, 2, 4, 5, 7, 9, 11};
|
|
|
|
for (Spanner* spanner : notestart->spannerFor()) {
|
|
if (spanner->type() == ElementType::GLISSANDO) {
|
|
Glissando *glissando = toGlissando(spanner);
|
|
GlissandoStyle glissandoStyle = glissando->glissandoStyle();
|
|
Element* ee = spanner->endElement();
|
|
// only consider glissando connected to NOTE.
|
|
if (glissando->playGlissando() && ElementType::NOTE == ee->type()) {
|
|
vector<int> body;
|
|
Note *noteend = toNote(ee);
|
|
int pitchend = noteend->ppitch();
|
|
bool direction = pitchend > pitchstart;
|
|
if (pitchend == pitchstart)
|
|
continue; // next spanner
|
|
if (glissandoStyle == GlissandoStyle::DIATONIC) { // scale obeying accidentals
|
|
int line;
|
|
int p = pitchstart;
|
|
// iterate as long as we haven't past the pitchend.
|
|
for (line = linestart; (direction) ? (p<pitchend) : (p>pitchend);
|
|
(direction) ? line-- : line++) {
|
|
int halfsteps = articulationExcursion(notestart, noteend, linestart - line);
|
|
p = pitchstart + halfsteps;
|
|
if (direction ? p < pitchend : p > pitchend)
|
|
body.push_back(halfsteps);
|
|
}
|
|
}
|
|
else {
|
|
for (int p = pitchstart; direction ? p < pitchend : p > pitchend; p += (direction ? 1 : -1)) {
|
|
bool choose = false;
|
|
int mod = ((p - Cnote) + 1200) % 12;
|
|
switch (glissandoStyle) {
|
|
case GlissandoStyle::CHROMATIC:
|
|
choose = true;
|
|
break;
|
|
case GlissandoStyle::WHITE_KEYS: // white note
|
|
choose = (whitenotes.find(mod) != whitenotes.end());
|
|
break;
|
|
case GlissandoStyle::BLACK_KEYS: // black note
|
|
choose = (blacknotes.find(mod) != blacknotes.end());
|
|
break;
|
|
default:
|
|
choose = false;
|
|
}
|
|
if (choose)
|
|
body.push_back(p - pitchstart);
|
|
}
|
|
}
|
|
renderNoteArticulation(events, notestart, true, MScore::division, empty, body, false, true, empty, 16, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// findFirstTrill
|
|
// search the spanners in the score, finding the first one
|
|
// which overlaps this chord and is of type ElementType::TRILL
|
|
//---------------------------------------------------------
|
|
|
|
Trill* findFirstTrill(Chord *chord)
|
|
{
|
|
auto spanners = chord->score()->spannerMap().findOverlapping(1+chord->tick().ticks(),
|
|
chord->tick().ticks() + chord->actualTicks().ticks() - 1);
|
|
for (auto i : spanners) {
|
|
if (i.value->type() != ElementType::TRILL)
|
|
continue;
|
|
if (i.value->track() != chord->track())
|
|
continue;
|
|
Trill *trill = toTrill (i.value);
|
|
if (trill->playArticulation() == false)
|
|
continue;
|
|
return trill;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// In the case that graceNotesBefore or graceNotesAfter are attached to a note
|
|
// with an articulation such as a trill, then the grace notes are/will-be/have-been
|
|
// already merged into the articulation.
|
|
// So this predicate, graceNotesMerged, checks for this condition to avoid calling
|
|
// functions which would re-emit the grace notes by a different algorithm.
|
|
|
|
bool graceNotesMerged(Chord* chord)
|
|
{
|
|
if (findFirstTrill(chord))
|
|
return true;
|
|
for (Articulation* a : chord->articulations())
|
|
for (auto& oe : excursions)
|
|
if ( oe.atype == a->symId() )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderChordArticulation
|
|
//---------------------------------------------------------
|
|
|
|
void renderChordArticulation(Chord* chord, QList<NoteEventList> & ell, int & gateTime)
|
|
{
|
|
Segment* seg = chord->segment();
|
|
Instrument* instr = chord->part()->instrument(seg->tick());
|
|
int channel = 0; // note->subchannel();
|
|
|
|
for (unsigned k = 0; k < chord->notes().size(); ++k) {
|
|
NoteEventList* events = &ell[k];
|
|
Note *note = chord->notes()[k];
|
|
Trill *trill;
|
|
|
|
if (noteHasGlissando(note))
|
|
renderGlissando(events, note);
|
|
else if (chord->staff()->isPitchedStaff(chord->tick()) && (trill = findFirstTrill(chord)) != nullptr) {
|
|
renderNoteArticulation(events, note, false, trill->trillType(), trill->ornamentStyle());
|
|
}
|
|
else {
|
|
for (Articulation* a : chord->articulations()) {
|
|
if (!a->playArticulation())
|
|
continue;
|
|
if (!renderNoteArticulation(events, note, false, a->symId(), a->ornamentStyle()))
|
|
instr->updateGateTime(&gateTime, channel, a->articulationName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// shouldRenderNote
|
|
//---------------------------------------------------------
|
|
|
|
static bool shouldRenderNote(Note* n)
|
|
{
|
|
while (n->tieBack()) {
|
|
n = n->tieBack()->startNote();
|
|
if (findFirstTrill(n->chord()))
|
|
// The previous tied note probably has events for this note too.
|
|
// That is, we don't need to render this note separately.
|
|
return false;
|
|
for (Articulation* a : n->chord()->articulations()) {
|
|
if (a->isOrnament()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderChord
|
|
// ontime and trailtime in 1/1000 of duration
|
|
// ontime signifies how much gap to leave, i.e., how late the note should start because of graceNotesBefore which have already been rendered
|
|
// trailtime signifies how much gap to leave after the note to allow for graceNotesAfter to be rendered
|
|
//---------------------------------------------------------
|
|
|
|
static QList<NoteEventList> renderChord(Chord* chord, int gateTime, int ontime, int trailtime)
|
|
{
|
|
QList<NoteEventList> ell;
|
|
if (chord->notes().empty())
|
|
return ell;
|
|
|
|
size_t notes = chord->notes().size();
|
|
for (size_t i = 0; i < notes; ++i)
|
|
ell.append(NoteEventList());
|
|
|
|
bool arpeggio = false;
|
|
if (chord->tremolo()) {
|
|
renderTremolo(chord, ell);
|
|
}
|
|
else if (chord->arpeggio() && chord->arpeggio()->playArpeggio()) {
|
|
renderArpeggio(chord, ell);
|
|
arpeggio = true;
|
|
}
|
|
else
|
|
renderChordArticulation(chord, ell, gateTime);
|
|
|
|
// Check each note and apply gateTime
|
|
for (int i = 0; i < int(notes); ++i) {
|
|
NoteEventList* el = &ell[i];
|
|
if (!shouldRenderNote(chord->notes()[i])) {
|
|
el->clear();
|
|
continue;
|
|
}
|
|
if (arpeggio)
|
|
continue; // don't add extra events and apply gateTime to arpeggio
|
|
|
|
// If we are here then we still need to render the note.
|
|
// Render its body if necessary and apply gateTime.
|
|
if (el->size() == 0 && chord->tremoloChordType() != TremoloChordType::TremoloSecondNote) {
|
|
el->append(NoteEvent(0, ontime, 1000 - ontime - trailtime));
|
|
}
|
|
if (trailtime == 0) // if trailtime is non-zero that means we have graceNotesAfter, so we don't need additional gate time.
|
|
for (NoteEvent& e : ell[i])
|
|
e.setLen(e.len() * gateTime / 100);
|
|
}
|
|
return ell;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// createGraceNotesPlayEvent
|
|
// as a side effect of createGraceNotesPlayEvents, ontime and trailtime (passed by ref)
|
|
// are modified. ontime reflects the time needed to play the grace-notes-before, and
|
|
// trailtime reflects the time for the grace-notes-after. These are used by the caller
|
|
// to effect the on/off time of the main note
|
|
//---------------------------------------------------------
|
|
|
|
void Score::createGraceNotesPlayEvents(const Fraction& tick, Chord* chord, int& ontime, int& trailtime)
|
|
{
|
|
QVector<Chord*> gnb = chord->graceNotesBefore();
|
|
QVector<Chord*> gna = chord->graceNotesAfter();
|
|
int nb = gnb.size();
|
|
int na = gna.size();
|
|
if (0 == nb + na){
|
|
return; // return immediately if no grace notes to deal with
|
|
}
|
|
// return immediately if the chord has a trill or articulation which effectively plays the graces notes.
|
|
if (graceNotesMerged(chord)) {
|
|
return;
|
|
}
|
|
// if there are graceNotesBefore and also graceNotesAfter, and the before grace notes are
|
|
// not ACCIACCATURA, then the total time of all of them will be 50% of the time of the main note.
|
|
// if the before grace notes are ACCIACCATURA then the grace notes after (if there are any).
|
|
// get 50% of the time of the main note.
|
|
// this is achieved by the two floating point weights: weighta and weightb whose total is 1.0
|
|
// assuring that all the grace notes get the same duration, and their total is 50%.
|
|
// exception is if the note is dotted or double-dotted; see below.
|
|
float weighta = float(na) / (nb+na);
|
|
float weightb = float(nb) / (nb+na);
|
|
|
|
int graceDuration = 0;
|
|
bool drumset = (getDrumset(chord) != nullptr);
|
|
const qreal ticksPerSecond = tempo(tick) * MScore::division;
|
|
const qreal chordTimeMS = (chord->actualTicks().ticks() / ticksPerSecond) * 1000;
|
|
if (drumset) {
|
|
int flamDuration = 15; //ms
|
|
graceDuration = flamDuration / chordTimeMS * 1000; //ratio 1/1000 from the main note length
|
|
ontime = graceDuration * nb;
|
|
}
|
|
else if (nb) {
|
|
//
|
|
// render grace notes:
|
|
// simplified implementation:
|
|
// - grace notes start on the beat of the main note
|
|
// - duration: appoggiatura: 0.5 * duration of main note (2/3 for dotted notes, 4/7 for double-dotted)
|
|
// acciacatura: min of 0.5 * duration or 65ms fixed (independent of duration or tempo)
|
|
// - for appoggiaturas, the duration is divided by the number of grace notes
|
|
// - the grace note duration as notated does not matter
|
|
//
|
|
Chord* graceChord = gnb[0];
|
|
if (graceChord->noteType() == NoteType::ACCIACCATURA) {
|
|
int graceTimeMS = 65 * nb; // value determined empirically (TODO: make instrument-specific, like articulations)
|
|
// 1000 occurs below as a unit for ontime
|
|
ontime = qMin(500, static_cast<int>((graceTimeMS / chordTimeMS) * 1000));
|
|
weightb = 0.0;
|
|
weighta = 1.0;
|
|
}
|
|
else if (chord->dots() == 1)
|
|
ontime = floor(667 * weightb);
|
|
else if (chord->dots() == 2)
|
|
ontime = floor(571 * weightb);
|
|
else
|
|
ontime = floor(500 * weightb);
|
|
|
|
graceDuration = ontime / nb;
|
|
}
|
|
|
|
for (int i = 0, on = 0; i < nb; ++i) {
|
|
QList<NoteEventList> el;
|
|
Chord* gc = gnb.at(i);
|
|
size_t nn = gc->notes().size();
|
|
for (size_t ii = 0; ii < nn; ++ii) {
|
|
NoteEventList nel;
|
|
nel.append(NoteEvent(0, on, graceDuration));
|
|
el.append(nel);
|
|
}
|
|
|
|
if (gc->playEventType() == PlayEventType::Auto)
|
|
gc->setNoteEventLists(el);
|
|
on += graceDuration;
|
|
}
|
|
if (na) {
|
|
if (chord->dots() == 1)
|
|
trailtime = floor(667 * weighta);
|
|
else if (chord->dots() == 2)
|
|
trailtime = floor(571 * weighta);
|
|
else
|
|
trailtime = floor(500 * weighta);
|
|
int graceDuration1 = trailtime / na;
|
|
int on = 1000 - trailtime;
|
|
for (int i = 0; i < na; ++i) {
|
|
QList<NoteEventList> el;
|
|
Chord* gc = gna.at(i);
|
|
size_t nn = gc->notes().size();
|
|
for (size_t ii = 0; ii < nn; ++ii) {
|
|
NoteEventList nel;
|
|
nel.append(NoteEvent(0, on, graceDuration1)); // NoteEvent(pitch,ontime,len)
|
|
el.append(nel);
|
|
}
|
|
|
|
if (gc->playEventType() == PlayEventType::Auto)
|
|
gc->setNoteEventLists(el);
|
|
on += graceDuration1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// createPlayEvents
|
|
// create default play events
|
|
//---------------------------------------------------------
|
|
|
|
void Score::createPlayEvents(Chord* chord)
|
|
{
|
|
int gateTime = 100;
|
|
|
|
Fraction tick = chord->tick();
|
|
Slur* slur = 0;
|
|
for (auto sp : _spanner.map()) {
|
|
if (!sp.second->isSlur() || sp.second->staffIdx() != chord->staffIdx())
|
|
continue;
|
|
Slur* s = toSlur(sp.second);
|
|
if (tick >= s->tick() && tick < s->tick2()) {
|
|
slur = s;
|
|
break;
|
|
}
|
|
}
|
|
// gateTime is 100% for slured notes
|
|
if (!slur) {
|
|
Instrument* instr = chord->part()->instrument(tick);
|
|
instr->updateGateTime(&gateTime, 0, "");
|
|
}
|
|
|
|
int ontime = 0;
|
|
int trailtime = 0;
|
|
createGraceNotesPlayEvents(tick, chord, ontime, trailtime); // ontime and trailtime are modified by this call depending on grace notes before and after
|
|
|
|
SwingParameters st = chord->staff()->swing(tick);
|
|
int unit = st.swingUnit;
|
|
int ratio = st.swingRatio;
|
|
// Check if swing needs to be applied
|
|
if (unit && !chord->tuplet()) {
|
|
swingAdjustParams(chord, gateTime, ontime, unit, ratio);
|
|
}
|
|
//
|
|
// render normal (and articulated) chords
|
|
//
|
|
QList<NoteEventList> el = renderChord(chord, gateTime, ontime, trailtime);
|
|
if (chord->playEventType() == PlayEventType::Auto)
|
|
chord->setNoteEventLists(el);
|
|
// don't change event list if type is PlayEventType::User
|
|
}
|
|
|
|
void Score::createPlayEvents(Measure* start, Measure* end)
|
|
{
|
|
if (!start)
|
|
start = firstMeasure();
|
|
|
|
int etrack = nstaves() * VOICES;
|
|
for (int track = 0; track < etrack; ++track) {
|
|
bool rangeEnded = false;
|
|
for (Measure* m = start; m; m = m->nextMeasure()) {
|
|
constexpr SegmentType st = SegmentType::ChordRest;
|
|
|
|
if (m == end)
|
|
rangeEnded = true;
|
|
if (rangeEnded) {
|
|
// The range has ended, but we should collect events
|
|
// for tied notes. So we'll check if this is the case.
|
|
const Segment* seg = m->first(st);
|
|
const Element* e = seg->element(track);
|
|
bool tie = false;
|
|
if (e && e->isChord()) {
|
|
for (const Note* n : toChord(e)->notes()) {
|
|
if (n->tieBack()) {
|
|
tie = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!tie)
|
|
break;
|
|
}
|
|
|
|
// skip linked staves, except primary
|
|
if (!m->score()->staff(track / VOICES)->primaryStaff())
|
|
continue;
|
|
for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
|
|
Element* e = seg->element(track);
|
|
if (e == 0 || !e->isChord())
|
|
continue;
|
|
createPlayEvents(toChord(e));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderMetronome
|
|
/// add metronome tick events
|
|
//---------------------------------------------------------
|
|
|
|
void MidiRenderer::renderMetronome(const Chunk& chunk, EventMap* events)
|
|
{
|
|
const int tickOffset = chunk.tickOffset();
|
|
Measure* start = chunk.startMeasure();
|
|
Measure* end = chunk.endMeasure();
|
|
|
|
for (Measure* m = start; m != end; m = m->nextMeasure())
|
|
renderMetronome(events, m, Fraction::fromTicks(tickOffset));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderMetronome
|
|
/// add metronome tick events
|
|
//---------------------------------------------------------
|
|
|
|
void MidiRenderer::renderMetronome(EventMap* events, Measure* m, const Fraction& tickOffset)
|
|
{
|
|
int msrTick = m->tick().ticks();
|
|
qreal tempo = score->tempomap()->tempo(msrTick);
|
|
TimeSigFrac timeSig = score->sigmap()->timesig(msrTick).nominal();
|
|
|
|
int clickTicks = timeSig.isBeatedCompound(tempo) ? timeSig.beatTicks() : timeSig.dUnitTicks();
|
|
int endTick = m->endTick().ticks();
|
|
|
|
int rtick;
|
|
|
|
if (m->isAnacrusis()) {
|
|
int rem = m->ticks().ticks() % clickTicks;
|
|
msrTick += rem;
|
|
rtick = rem + timeSig.ticksPerMeasure() - m->ticks().ticks();
|
|
}
|
|
else
|
|
rtick = 0;
|
|
|
|
for (int tick = msrTick; tick < endTick; tick += clickTicks, rtick += clickTicks)
|
|
events->insert(std::pair<int,NPlayEvent>(tick + tickOffset.ticks(), NPlayEvent(timeSig.rtick2beatType(rtick))));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// renderMidi
|
|
// export score to event list
|
|
//---------------------------------------------------------
|
|
|
|
void Score::renderMidi(EventMap* events, const SynthesizerState& synthState)
|
|
{
|
|
renderMidi(events, true, MScore::playRepeats, synthState);
|
|
}
|
|
|
|
void Score::renderMidi(EventMap* events, bool metronome, bool expandRepeats, const SynthesizerState& synthState)
|
|
{
|
|
masterScore()->setExpandRepeats(expandRepeats);
|
|
MidiRenderer(this).renderScore(events, synthState, metronome);
|
|
}
|
|
|
|
void MidiRenderer::renderScore(EventMap* events, const SynthesizerState& synthState, bool metronome)
|
|
{
|
|
updateState();
|
|
for (const Chunk& chunk : chunks) {
|
|
renderChunk(chunk, events, synthState, metronome);
|
|
}
|
|
}
|
|
|
|
void MidiRenderer::renderChunk(const Chunk& chunk, EventMap* events, const SynthesizerState& synthState, bool metronome)
|
|
{
|
|
// TODO: avoid doing it multiple times for the same measures
|
|
score->createPlayEvents(chunk.startMeasure(), chunk.endMeasure());
|
|
|
|
score->updateChannel();
|
|
score->updateVelo();
|
|
|
|
SynthesizerState s = score->synthesizerState();
|
|
int method = s.method();
|
|
int cc = s.ccToUse();
|
|
|
|
// check if the score synth settings are actually set
|
|
// if not, use the global synth state
|
|
if (method == -1) {
|
|
method = synthState.method();
|
|
cc = synthState.ccToUse();
|
|
|
|
if (method == -1) {
|
|
// fall back to defaults - this may be needed to pass tests,
|
|
// since sometimes the synth state is not init
|
|
method = 1;
|
|
cc = 2;
|
|
qWarning("Had to fall back to defaults to render measure");
|
|
}
|
|
}
|
|
|
|
DynamicsRenderMethod renderMethod = DynamicsRenderMethod::SIMPLE;
|
|
switch (method) {
|
|
case 0:
|
|
renderMethod = DynamicsRenderMethod::SIMPLE;
|
|
break;
|
|
case 1:
|
|
renderMethod = DynamicsRenderMethod::SEG_START;
|
|
break;
|
|
case 2:
|
|
renderMethod = DynamicsRenderMethod::FIXED_MAX;
|
|
break;
|
|
default:
|
|
qWarning("Unrecognized dynamics method: %d", method);
|
|
break;
|
|
}
|
|
|
|
// create note & other events
|
|
for (Staff* st : score->staves())
|
|
renderStaffChunk(chunk, events, st, renderMethod, cc);
|
|
events->fixupMIDI();
|
|
|
|
// create sustain pedal events
|
|
renderSpanners(chunk, events);
|
|
|
|
if (metronome)
|
|
renderMetronome(chunk, events);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// MidiRenderer::updateState
|
|
//---------------------------------------------------------
|
|
|
|
void MidiRenderer::updateState()
|
|
{
|
|
if (needUpdate) {
|
|
// Update the related structures inside score
|
|
// to avoid doing it multiple times on chunks rendering
|
|
score->updateSwing();
|
|
score->updateCapo();
|
|
|
|
updateChunksPartition();
|
|
|
|
needUpdate = false;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// MidiRenderer::canBreakChunk
|
|
/// Helper function for updateChunksPartition
|
|
/// Determines whether it is allowed to break MIDI
|
|
/// rendering chunk at given measure.
|
|
//---------------------------------------------------------
|
|
|
|
bool MidiRenderer::canBreakChunk(const Measure* last)
|
|
{
|
|
Score* score = last->score();
|
|
|
|
// Check for hairpins that overlap measure end:
|
|
// hairpins should be inside one chunk, if possible
|
|
const int endTick = last->endTick().ticks();
|
|
const auto& spanners = score->spannerMap().findOverlapping(endTick - 1, endTick);
|
|
for (const auto& interval : spanners) {
|
|
const Spanner* sp = interval.value;
|
|
if (sp->isHairpin() && sp->tick2().ticks() > endTick)
|
|
return false;
|
|
}
|
|
|
|
// Repeat measures rely on the previous measure
|
|
// being properly rendered, disallow breaking
|
|
// chunk at repeat measure.
|
|
if (const Measure* next = last->nextMeasure())
|
|
for (const Staff* staff : score->staves()) {
|
|
if (next->isRepeatMeasure(staff))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// MidiRenderer::updateChunksPartition
|
|
//---------------------------------------------------------
|
|
|
|
void MidiRenderer::updateChunksPartition()
|
|
{
|
|
chunks.clear();
|
|
|
|
const RepeatList& repeatList = score->repeatList();
|
|
|
|
for (const RepeatSegment* rs : repeatList) {
|
|
const int tickOffset = rs->utick - rs->tick;
|
|
|
|
if (!minChunkSize) {
|
|
// just make chunks corresponding to repeat segments
|
|
chunks.emplace_back(tickOffset, rs->firstMeasure(), rs->lastMeasure());
|
|
continue;
|
|
}
|
|
|
|
Measure* end = rs->lastMeasure()->nextMeasure();
|
|
int count = 0;
|
|
bool needBreak = false;
|
|
Measure* chunkStart = nullptr;
|
|
for (Measure* m = rs->firstMeasure(); m != end; m = m->nextMeasure()) {
|
|
if (!chunkStart)
|
|
chunkStart = m;
|
|
if ((++count) >= minChunkSize)
|
|
needBreak = true;
|
|
if (needBreak && canBreakChunk(m)) {
|
|
chunks.emplace_back(tickOffset, chunkStart, m);
|
|
chunkStart = nullptr;
|
|
needBreak = false;
|
|
count = 0;
|
|
}
|
|
}
|
|
if (chunkStart) // last measures did not get added to chunk list
|
|
chunks.emplace_back(tickOffset, chunkStart, rs->lastMeasure());
|
|
}
|
|
|
|
if (score != repeatList.score()) {
|
|
// Repeat list may belong to another linked score (e.g. MasterScore).
|
|
// Update chunks to make them contain measures from the currently
|
|
// rendered score.
|
|
for (Chunk& ch : chunks) {
|
|
Measure* first = score->tick2measure(ch.startMeasure()->tick());
|
|
Measure* last = score->tick2measure(ch.lastMeasure()->tick());
|
|
ch = Chunk(ch.tickOffset(), first, last);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// MidiRenderer::getChunkAt
|
|
//---------------------------------------------------------
|
|
|
|
MidiRenderer::Chunk MidiRenderer::getChunkAt(int utick)
|
|
{
|
|
updateState();
|
|
|
|
auto it = std::upper_bound(chunks.begin(), chunks.end(), utick, [](int utick, const Chunk& ch) { return utick < ch.utick1(); });
|
|
if (it == chunks.begin())
|
|
return Chunk();
|
|
--it;
|
|
const Chunk& ch = *it;
|
|
if (ch.utick2() <= utick)
|
|
return Chunk();
|
|
return ch;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// RangeMap::setOccupied
|
|
//---------------------------------------------------------
|
|
|
|
void RangeMap::setOccupied(int tick1, int tick2)
|
|
{
|
|
auto it1 = status.upper_bound(tick1);
|
|
const bool beforeBegin = (it1 == status.begin());
|
|
if (beforeBegin || (--it1)->second != Range::BEGIN) {
|
|
if (!beforeBegin && it1->first == tick1)
|
|
status.erase(it1);
|
|
else
|
|
status.insert(std::make_pair(tick1, Range::BEGIN));
|
|
}
|
|
|
|
const auto it2 = status.lower_bound(tick2);
|
|
const bool afterEnd = (it2 == status.end());
|
|
if (afterEnd || it2->second != Range::END) {
|
|
if (!afterEnd && it2->first == tick2)
|
|
status.erase(it2);
|
|
else
|
|
status.insert(std::make_pair(tick2, Range::END));
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// RangeMap::occupiedRangeEnd
|
|
//---------------------------------------------------------
|
|
|
|
int RangeMap::occupiedRangeEnd(int tick) const
|
|
{
|
|
const auto it = status.upper_bound(tick);
|
|
if (it == status.begin())
|
|
return tick;
|
|
const int rangeEnd = (it == status.end()) ? tick : it->first;
|
|
if (it->second == Range::END)
|
|
return rangeEnd;
|
|
return tick;
|
|
}
|
|
}
|