You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
601 lines
20 KiB
601 lines
20 KiB
/*
|
|
Rosegarden
|
|
A MIDI and audio sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <richard.bown@ferventsoftware.com>
|
|
|
|
The moral rights of Guillaume Laurent, Chris Cannam, and Richard
|
|
Bown to claim authorship of this work have been asserted.
|
|
|
|
Other copyrights also apply to some parts of this work. Please
|
|
see the AUTHORS file and individual file headers for details.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
|
|
#include "InterpretCommand.h"
|
|
|
|
#include "base/Composition.h"
|
|
#include "base/Event.h"
|
|
#include "base/NotationTypes.h"
|
|
#include "misc/Debug.h"
|
|
#include "base/Quantizer.h"
|
|
#include "base/Segment.h"
|
|
#include "base/Sets.h"
|
|
#include "base/BaseProperties.h"
|
|
#include "base/Selection.h"
|
|
#include "document/BasicSelectionCommand.h"
|
|
#include <tqstring.h>
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
using namespace BaseProperties;
|
|
|
|
const int InterpretCommand::NoInterpretation = 0;
|
|
const int InterpretCommand::GuessDirections = (1<<0);
|
|
const int InterpretCommand::ApplyTextDynamics = (1<<1);
|
|
const int InterpretCommand::ApplyHairpins = (1<<2);
|
|
const int InterpretCommand::StressBeats = (1<<3);
|
|
const int InterpretCommand::Articulate = (1<<4);
|
|
const int InterpretCommand::AllInterpretations= (1<<5) - 1;
|
|
|
|
InterpretCommand::~InterpretCommand()
|
|
{
|
|
for (IndicationMap::iterator i = m_indications.begin();
|
|
i != m_indications.end(); ++i) {
|
|
delete i->second;
|
|
}
|
|
}
|
|
|
|
void
|
|
InterpretCommand::modifySegment()
|
|
{
|
|
// Of all the interpretations, Articulate is the only one that
|
|
// changes event times or durations. This means we must apply it
|
|
// last, as the selection cannot be used after it's been applied,
|
|
// because the events in the selection will have been recreated
|
|
// with their new timings.
|
|
|
|
// The default velocity for new notes is 100, and the range is
|
|
// 0-127 (in practice this seems to be roughly logarithmic rather
|
|
// than linear, though perhaps that's an illusion).
|
|
|
|
// We should only apply interpretation to those events actually
|
|
// selected, but when applying things like hairpins and text
|
|
// dynamics we need to take into account all dynamics that may
|
|
// cover our events even if they're not selected or are not within
|
|
// the time range of the selection at all. So first we'd better
|
|
// find all the likely indications, starting at (for the sake of
|
|
// argument) three bars before the start of the selection:
|
|
|
|
Segment &segment(getSegment());
|
|
|
|
timeT t = m_selection->getStartTime();
|
|
for (int i = 0; i < 3; ++i)
|
|
t = segment.getBarStartForTime(t);
|
|
|
|
Segment::iterator itr = segment.findTime(t);
|
|
|
|
while (itr != segment.end()) {
|
|
timeT eventTime = (*itr)->getAbsoluteTime();
|
|
if (eventTime > m_selection->getEndTime())
|
|
break;
|
|
if ((*itr)->isa(Indication::EventType)) {
|
|
m_indications[eventTime] = new Indication(**itr);
|
|
}
|
|
++itr;
|
|
}
|
|
|
|
//!!! need the option of ignoring current velocities or adjusting
|
|
//them: at the moment ApplyTextDynamics ignores them and the
|
|
//others adjust them
|
|
|
|
if (m_interpretations & GuessDirections)
|
|
guessDirections();
|
|
if (m_interpretations & ApplyTextDynamics)
|
|
applyTextDynamics();
|
|
if (m_interpretations & ApplyHairpins)
|
|
applyHairpins();
|
|
if (m_interpretations & StressBeats)
|
|
stressBeats();
|
|
if (m_interpretations & Articulate)
|
|
articulate();
|
|
|
|
//!!! Finally, in future we should extend this to allow
|
|
// indications on one segment (e.g. top line of piano staff) to
|
|
// affect another (e.g. bottom line). All together now: "Even
|
|
// Rosegarden 2.1 could do that!"
|
|
}
|
|
|
|
void
|
|
InterpretCommand::guessDirections()
|
|
{
|
|
//...
|
|
}
|
|
|
|
void
|
|
InterpretCommand::applyTextDynamics()
|
|
{
|
|
// laborious
|
|
|
|
Segment &segment(getSegment());
|
|
int velocity = 100;
|
|
|
|
timeT startTime = m_selection->getStartTime();
|
|
timeT endTime = m_selection->getEndTime();
|
|
|
|
for (Segment::iterator i = segment.begin();
|
|
segment.isBeforeEndMarker(i); ++i) {
|
|
|
|
timeT t = (*i)->getAbsoluteTime();
|
|
|
|
if (t > endTime)
|
|
break;
|
|
|
|
if (Text::isTextOfType(*i, Text::Dynamic)) {
|
|
|
|
std::string text;
|
|
if ((*i)->get
|
|
<String>(Text::TextPropertyName, text)) {
|
|
velocity = getVelocityForDynamic(text);
|
|
}
|
|
}
|
|
|
|
if (t >= startTime &&
|
|
(*i)->isa(Note::EventType) && m_selection->contains(*i)) {
|
|
(*i)->set
|
|
<Int>(VELOCITY, velocity);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
InterpretCommand::getVelocityForDynamic(std::string text)
|
|
{
|
|
int velocity = 100;
|
|
|
|
// should do case-insensitive matching with whitespace
|
|
// removed. can surely be cleverer about this too!
|
|
|
|
if (text == "ppppp")
|
|
velocity = 10;
|
|
else if (text == "pppp")
|
|
velocity = 20;
|
|
else if (text == "ppp")
|
|
velocity = 30;
|
|
else if (text == "pp")
|
|
velocity = 40;
|
|
else if (text == "p")
|
|
velocity = 60;
|
|
else if (text == "mp")
|
|
velocity = 80;
|
|
else if (text == "mf")
|
|
velocity = 90;
|
|
else if (text == "f")
|
|
velocity = 105;
|
|
else if (text == "ff")
|
|
velocity = 110;
|
|
else if (text == "fff")
|
|
velocity = 115;
|
|
else if (text == "ffff")
|
|
velocity = 120;
|
|
else if (text == "fffff")
|
|
velocity = 125;
|
|
|
|
NOTATION_DEBUG << "InterpretCommand::getVelocityForDynamic: unrecognised dynamic " << text << endl;
|
|
|
|
return velocity;
|
|
}
|
|
|
|
void
|
|
InterpretCommand::applyHairpins()
|
|
{
|
|
Segment &segment(getSegment());
|
|
int velocityToApply = -1;
|
|
|
|
for (EventSelection::eventcontainer::iterator ecitr =
|
|
m_selection->getSegmentEvents().begin();
|
|
ecitr != m_selection->getSegmentEvents().end(); ++ecitr) {
|
|
|
|
Event *e = *ecitr;
|
|
if (Text::isTextOfType(e, Text::Dynamic)) {
|
|
velocityToApply = -1;
|
|
}
|
|
if (!e->isa(Note::EventType))
|
|
continue;
|
|
bool crescendo = true;
|
|
|
|
IndicationMap::iterator inditr =
|
|
findEnclosingIndication(e, Indication::Crescendo);
|
|
|
|
// we can't be in both crescendo and decrescendo -- at least,
|
|
// not meaningfully
|
|
|
|
if (inditr == m_indications.end()) {
|
|
inditr = findEnclosingIndication(e, Indication::Decrescendo);
|
|
if (inditr == m_indications.end()) {
|
|
if (velocityToApply > 0) {
|
|
e->set
|
|
<Int>(VELOCITY, velocityToApply);
|
|
}
|
|
continue;
|
|
}
|
|
crescendo = false;
|
|
}
|
|
|
|
// The starting velocity for the indication is easy -- it's
|
|
// just the velocity of the last note at or before the
|
|
// indication begins that has a velocity
|
|
|
|
timeT hairpinStartTime = inditr->first;
|
|
// ensure we scan all of the events at this time:
|
|
Segment::iterator itr(segment.findTime(hairpinStartTime + 1));
|
|
while (itr == segment.end() ||
|
|
(*itr)->getAbsoluteTime() > hairpinStartTime ||
|
|
!(*itr)->isa(Note::EventType) ||
|
|
!(*itr)->has(VELOCITY)) {
|
|
if (itr == segment.begin()) {
|
|
itr = segment.end();
|
|
break;
|
|
}
|
|
--itr;
|
|
}
|
|
|
|
long startingVelocity = 100;
|
|
if (itr != segment.end()) {
|
|
(*itr)->get
|
|
<Int>(VELOCITY, startingVelocity);
|
|
}
|
|
|
|
// The ending velocity is harder. If there's a dynamic change
|
|
// directly after the hairpin, then we want to use that
|
|
// dynamic's velocity unless it opposes the hairpin's
|
|
// direction. If there isn't, or it does oppose the hairpin,
|
|
// we should probably make the degree of change caused by the
|
|
// hairpin depend on its total duration.
|
|
|
|
long endingVelocity = startingVelocity;
|
|
timeT hairpinEndTime = inditr->first +
|
|
inditr->second->getIndicationDuration();
|
|
itr = segment.findTime(hairpinEndTime);
|
|
while (itr != segment.end()) {
|
|
if (Text::isTextOfType(*itr, Text::Dynamic)) {
|
|
std::string text;
|
|
if ((*itr)->get
|
|
<String>(Text::TextPropertyName, text)) {
|
|
endingVelocity = getVelocityForDynamic(text);
|
|
break;
|
|
}
|
|
}
|
|
if ((*itr)->getAbsoluteTime() >
|
|
(hairpinEndTime + Note(Note::Crotchet).getDuration()))
|
|
break;
|
|
++itr;
|
|
}
|
|
|
|
if (( crescendo && (endingVelocity < startingVelocity)) ||
|
|
(!crescendo && (endingVelocity > startingVelocity))) {
|
|
// we've got it wrong; prefer following the hairpin to
|
|
// following whatever direction we got the dynamic from
|
|
endingVelocity = startingVelocity;
|
|
// and then fall into the next conditional to set up the
|
|
// velocities
|
|
}
|
|
|
|
if (endingVelocity == startingVelocity) {
|
|
// calculate an ending velocity based on starting velocity
|
|
// and hairpin duration (okay, we'll leave that bit for later)
|
|
endingVelocity = startingVelocity * (crescendo ? 120 : 80) / 100;
|
|
}
|
|
|
|
double proportion =
|
|
(double(e->getAbsoluteTime() - hairpinStartTime) /
|
|
double(hairpinEndTime - hairpinStartTime));
|
|
long velocity =
|
|
int((endingVelocity - startingVelocity) * proportion +
|
|
startingVelocity);
|
|
|
|
NOTATION_DEBUG << "InterpretCommand::applyHairpins: velocity of note at " << e->getAbsoluteTime() << " is " << velocity << " (" << proportion << " through hairpin from " << startingVelocity << " to " << endingVelocity << ")" << endl;
|
|
if (velocity < 10)
|
|
velocity = 10;
|
|
if (velocity > 127)
|
|
velocity = 127;
|
|
e->set
|
|
<Int>(VELOCITY, velocity);
|
|
velocityToApply = velocity;
|
|
}
|
|
}
|
|
|
|
void
|
|
InterpretCommand::stressBeats()
|
|
{
|
|
Composition *c = getSegment().getComposition();
|
|
|
|
for (EventSelection::eventcontainer::iterator itr =
|
|
m_selection->getSegmentEvents().begin();
|
|
itr != m_selection->getSegmentEvents().end(); ++itr) {
|
|
|
|
Event *e = *itr;
|
|
if (!e->isa(Note::EventType))
|
|
continue;
|
|
|
|
timeT t = e->getNotationAbsoluteTime();
|
|
TimeSignature timeSig = c->getTimeSignatureAt(t);
|
|
timeT barStart = getSegment().getBarStartForTime(t);
|
|
int stress = timeSig.getEmphasisForTime(t - barStart);
|
|
|
|
// stresses are from 0 to 4, so we add 12% to the velocity
|
|
// at the maximum stress, subtract 4% at the minimum
|
|
int velocityChange = stress * 4 - 4;
|
|
|
|
// do this even if velocityChange == 0, in case the event
|
|
// has no velocity yet
|
|
long velocity = 100;
|
|
e->get
|
|
<Int>(VELOCITY, velocity);
|
|
velocity += velocity * velocityChange / 100;
|
|
if (velocity < 10)
|
|
velocity = 10;
|
|
if (velocity > 127)
|
|
velocity = 127;
|
|
e->set
|
|
<Int>(VELOCITY, velocity);
|
|
}
|
|
}
|
|
|
|
void
|
|
InterpretCommand::articulate()
|
|
{
|
|
// Basic articulations:
|
|
//
|
|
// -- Anything marked tenuto or within a slur gets 100% of its
|
|
// nominal duration (that's what we need the quantizer for,
|
|
// to get the display nominal duration), and its velocity
|
|
// is unchanged.
|
|
//
|
|
// -- Anything marked marcato gets 60%, or 70% if slurred (!),
|
|
// and gets an extra 15% of velocity.
|
|
//
|
|
// -- Anything marked staccato gets 55%, or 70% if slurred,
|
|
// and unchanged velocity.
|
|
//
|
|
// -- Anything marked staccatissimo gets 30%, or 50% if slurred (!),
|
|
// and loses 5% of velocity.
|
|
//
|
|
// -- Anything marked sforzando gains 35% of velocity.
|
|
//
|
|
// -- Anything marked with an accent gains 30% of velocity.
|
|
//
|
|
// -- Anything marked rinforzando gains 15% of velocity and has
|
|
// its full duration. Guess we really need to use some proper
|
|
// controllers here.
|
|
//
|
|
// -- Anything marked down-bow gains 5% of velocity, anything
|
|
// marked up-bow loses 5%.
|
|
//
|
|
// -- Anything unmarked and unslurred, or marked tenuto and
|
|
// slurred, gets 90% of duration.
|
|
|
|
std::set
|
|
<Event *> toErase;
|
|
std::set
|
|
<Event *> toInsert;
|
|
Segment &segment(getSegment());
|
|
|
|
for (EventSelection::eventcontainer::iterator ecitr =
|
|
m_selection->getSegmentEvents().begin();
|
|
ecitr != m_selection->getSegmentEvents().end(); ++ecitr) {
|
|
|
|
Event *e = *ecitr;
|
|
if (!e->isa(Note::EventType))
|
|
continue;
|
|
Segment::iterator itr = segment.findSingle(e);
|
|
Chord chord(segment, itr, m_quantizer);
|
|
|
|
// the things that affect duration
|
|
bool staccato = false;
|
|
bool staccatissimo = false;
|
|
bool marcato = false;
|
|
bool tenuto = false;
|
|
bool rinforzando = false;
|
|
bool slurred = false;
|
|
|
|
int velocityChange = 0;
|
|
|
|
std::vector<Mark> marks(chord.getMarksForChord());
|
|
|
|
for (std::vector<Mark>::iterator i = marks.begin();
|
|
i != marks.end(); ++i) {
|
|
|
|
if (*i == Marks::Accent) {
|
|
velocityChange += 30;
|
|
} else if (*i == Marks::Tenuto) {
|
|
tenuto = true;
|
|
} else if (*i == Marks::Staccato) {
|
|
staccato = true;
|
|
} else if (*i == Marks::Staccatissimo) {
|
|
staccatissimo = true;
|
|
velocityChange -= 5;
|
|
} else if (*i == Marks::Marcato) {
|
|
marcato = true;
|
|
velocityChange += 15;
|
|
} else if (*i == Marks::Sforzando) {
|
|
velocityChange += 35;
|
|
} else if (*i == Marks::Rinforzando) {
|
|
rinforzando = true;
|
|
velocityChange += 15;
|
|
} else if (*i == Marks::DownBow) {
|
|
velocityChange += 5;
|
|
} else if (*i == Marks::UpBow) {
|
|
velocityChange -= 5;
|
|
}
|
|
}
|
|
|
|
IndicationMap::iterator inditr =
|
|
findEnclosingIndication(e, Indication::Slur);
|
|
|
|
if (inditr != m_indications.end())
|
|
slurred = true;
|
|
if (slurred) {
|
|
// last note in a slur should be treated as if unslurred
|
|
timeT slurEnd =
|
|
inditr->first + inditr->second->getIndicationDuration();
|
|
if (slurEnd == e->getNotationAbsoluteTime() + e->getNotationDuration() ||
|
|
slurEnd == e->getAbsoluteTime() + e->getDuration()) {
|
|
slurred = false;
|
|
}
|
|
/*!!!
|
|
Segment::iterator slurEndItr = segment.findTime(slurEnd);
|
|
if (slurEndItr != segment.end() &&
|
|
(*slurEndItr)->getNotationAbsoluteTime() <=
|
|
e->getNotationAbsoluteTime()) {
|
|
slurred = false;
|
|
}
|
|
*/
|
|
}
|
|
|
|
int durationChange = 0;
|
|
|
|
if (slurred) {
|
|
//!!! doesn't seem to be picking up slurs correctly
|
|
if (tenuto)
|
|
durationChange = -10;
|
|
else if (marcato || staccato)
|
|
durationChange = -30;
|
|
else if (staccatissimo)
|
|
durationChange = -50;
|
|
else
|
|
durationChange = 0;
|
|
} else {
|
|
if (tenuto)
|
|
durationChange = 0;
|
|
else if (marcato)
|
|
durationChange = -40;
|
|
else if (staccato)
|
|
durationChange = -45;
|
|
else if (staccatissimo)
|
|
durationChange = -70;
|
|
else if (rinforzando)
|
|
durationChange = 0;
|
|
else
|
|
durationChange = -10;
|
|
}
|
|
|
|
NOTATION_DEBUG << "InterpretCommand::modifySegment: chord has " << chord.size() << " notes in it" << endl;
|
|
|
|
for (Chord::iterator ci = chord.begin();
|
|
ci != chord.end(); ++ci) {
|
|
|
|
e = **ci;
|
|
|
|
NOTATION_DEBUG << "InterpretCommand::modifySegment: For note at " << e->getAbsoluteTime() << ", velocityChange is " << velocityChange << " and durationChange is " << durationChange << endl;
|
|
|
|
// do this even if velocityChange == 0, in case the event
|
|
// has no velocity yet
|
|
long velocity = 100;
|
|
e->get
|
|
<Int>(VELOCITY, velocity);
|
|
velocity += velocity * velocityChange / 100;
|
|
if (velocity < 10)
|
|
velocity = 10;
|
|
if (velocity > 127)
|
|
velocity = 127;
|
|
e->set
|
|
<Int>(VELOCITY, velocity);
|
|
|
|
timeT duration = e->getNotationDuration();
|
|
|
|
// don't mess with the duration of a tied note
|
|
bool tiedForward = false;
|
|
if (e->get
|
|
<Bool>(TIED_FORWARD, tiedForward) && tiedForward) {
|
|
durationChange = 0;
|
|
}
|
|
|
|
timeT newDuration = duration + duration * durationChange / 100;
|
|
|
|
// this comparison instead of "durationChange != 0"
|
|
// because we want to permit the possibility of resetting
|
|
// the performance duration of a note (that's perhaps been
|
|
// articulated wrongly) based on the notation duration:
|
|
|
|
if (e->getDuration() != newDuration) {
|
|
|
|
if (toErase.find(e) == toErase.end()) {
|
|
|
|
//!!! deal with tuplets
|
|
|
|
Event *newEvent = new Event(*e,
|
|
e->getAbsoluteTime(),
|
|
newDuration,
|
|
e->getSubOrdering(),
|
|
e->getNotationAbsoluteTime(),
|
|
duration);
|
|
toInsert.insert(newEvent);
|
|
toErase.insert(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// what we want to do here is jump our iterator to the final
|
|
// element in the chord -- but that doesn't work because we're
|
|
// iterating through the selection, not the segment. So for
|
|
// now we just accept the fact that notes in chords might be
|
|
// processed multiple times (slow) and added into the toErase
|
|
// set more than once (hence the nasty tests in the loop just
|
|
// after the close of this loop).
|
|
}
|
|
|
|
for (std::set
|
|
<Event *>::iterator j = toErase.begin(); j != toErase.end(); ++j) {
|
|
Segment::iterator jtr(segment.findSingle(*j));
|
|
if (jtr != segment.end())
|
|
segment.erase(jtr);
|
|
}
|
|
|
|
for (std::set
|
|
<Event *>::iterator j = toInsert.begin(); j != toInsert.end(); ++j) {
|
|
segment.insert(*j);
|
|
}
|
|
}
|
|
|
|
InterpretCommand::IndicationMap::iterator
|
|
|
|
InterpretCommand::findEnclosingIndication(Event *e,
|
|
std::string type)
|
|
{
|
|
// a bit slow, but let's wait and see whether it's a bottleneck
|
|
// before we worry about that
|
|
|
|
timeT t = e->getAbsoluteTime();
|
|
IndicationMap::iterator itr = m_indications.lower_bound(t);
|
|
|
|
while (1) {
|
|
if (itr != m_indications.end()) {
|
|
|
|
if (itr->second->getIndicationType() == type &&
|
|
itr->first <= t &&
|
|
itr->first + itr->second->getIndicationDuration() > t) {
|
|
return itr;
|
|
}
|
|
}
|
|
if (itr == m_indications.begin())
|
|
break;
|
|
--itr;
|
|
}
|
|
|
|
return m_indications.end();
|
|
}
|
|
|
|
}
|