summaryrefslogtreecommitdiffstats
path: root/src/commands/notation/InterpretCommand.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
commit145364a8af6a1fec06556221e66d4b724a62fc9a (patch)
tree53bd71a544008c518034f208d64c932dc2883f50 /src/commands/notation/InterpretCommand.cpp
downloadrosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz
rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/commands/notation/InterpretCommand.cpp')
-rw-r--r--src/commands/notation/InterpretCommand.cpp602
1 files changed, 602 insertions, 0 deletions
diff --git a/src/commands/notation/InterpretCommand.cpp b/src/commands/notation/InterpretCommand.cpp
new file mode 100644
index 0000000..d8a82cd
--- /dev/null
+++ b/src/commands/notation/InterpretCommand.cpp
@@ -0,0 +1,602 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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 <qstring.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();
+}
+
+}