summaryrefslogtreecommitdiffstats
path: root/src/base
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/base
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/base')
-rw-r--r--src/base/AnalysisTypes.cpp1118
-rw-r--r--src/base/AnalysisTypes.h227
-rw-r--r--src/base/AudioDevice.cpp107
-rw-r--r--src/base/AudioDevice.h70
-rw-r--r--src/base/AudioLevel.cpp272
-rw-r--r--src/base/AudioLevel.h67
-rw-r--r--src/base/AudioPluginInstance.cpp256
-rw-r--r--src/base/AudioPluginInstance.h172
-rw-r--r--src/base/BaseProperties.cpp133
-rw-r--r--src/base/BaseProperties.h82
-rw-r--r--src/base/BasicQuantizer.cpp253
-rw-r--r--src/base/BasicQuantizer.h95
-rw-r--r--src/base/Clipboard.cpp387
-rw-r--r--src/base/Clipboard.h203
-rw-r--r--src/base/Colour.cpp175
-rw-r--r--src/base/Colour.h125
-rw-r--r--src/base/ColourMap.cpp266
-rw-r--r--src/base/ColourMap.h138
-rw-r--r--src/base/Composition.cpp2225
-rw-r--r--src/base/Composition.h1134
-rw-r--r--src/base/CompositionTimeSliceAdapter.cpp283
-rw-r--r--src/base/CompositionTimeSliceAdapter.h149
-rw-r--r--src/base/Configuration.cpp232
-rw-r--r--src/base/Configuration.h211
-rw-r--r--src/base/ControlParameter.cpp144
-rw-r--r--src/base/ControlParameter.h124
-rw-r--r--src/base/Controllable.h48
-rw-r--r--src/base/Device.cpp31
-rw-r--r--src/base/Device.h102
-rw-r--r--src/base/Equation.cpp69
-rw-r--r--src/base/Equation.h51
-rw-r--r--src/base/Event.cpp445
-rw-r--r--src/base/Event.h584
-rw-r--r--src/base/Exception.cpp46
-rw-r--r--src/base/Exception.h47
-rw-r--r--src/base/FastVector.h596
-rw-r--r--src/base/Instrument.cpp645
-rw-r--r--src/base/Instrument.h349
-rw-r--r--src/base/LayoutEngine.cpp63
-rw-r--r--src/base/LayoutEngine.h161
-rw-r--r--src/base/LegatoQuantizer.cpp141
-rw-r--r--src/base/LegatoQuantizer.h64
-rw-r--r--src/base/Marker.cpp55
-rw-r--r--src/base/Marker.h78
-rw-r--r--src/base/MidiDevice.cpp839
-rw-r--r--src/base/MidiDevice.h213
-rw-r--r--src/base/MidiProgram.cpp224
-rw-r--r--src/base/MidiProgram.h180
-rw-r--r--src/base/MidiTypes.cpp320
-rw-r--r--src/base/MidiTypes.h224
-rw-r--r--src/base/NotationQuantizer.cpp1205
-rw-r--r--src/base/NotationQuantizer.h93
-rw-r--r--src/base/NotationRules.h133
-rw-r--r--src/base/NotationTypes.cpp2436
-rw-r--r--src/base/NotationTypes.h1342
-rw-r--r--src/base/Profiler.cpp187
-rw-r--r--src/base/Profiler.h84
-rw-r--r--src/base/Property.cpp169
-rw-r--r--src/base/Property.h225
-rw-r--r--src/base/PropertyMap.cpp101
-rw-r--r--src/base/PropertyMap.h50
-rw-r--r--src/base/PropertyName.cpp86
-rw-r--r--src/base/PropertyName.h158
-rw-r--r--src/base/Quantizer.cpp496
-rw-r--r--src/base/Quantizer.h249
-rw-r--r--src/base/RealTime.cpp236
-rw-r--r--src/base/RealTime.h124
-rw-r--r--src/base/RefreshStatus.h76
-rw-r--r--src/base/RulerScale.cpp243
-rw-r--r--src/base/RulerScale.h166
-rw-r--r--src/base/ScriptAPI.cpp85
-rw-r--r--src/base/ScriptAPI.h128
-rw-r--r--src/base/Segment.cpp1294
-rw-r--r--src/base/Segment.h783
-rw-r--r--src/base/SegmentMatrixHelper.cpp56
-rw-r--r--src/base/SegmentMatrixHelper.h53
-rw-r--r--src/base/SegmentNotationHelper.cpp2129
-rw-r--r--src/base/SegmentNotationHelper.h591
-rw-r--r--src/base/SegmentPerformanceHelper.cpp472
-rw-r--r--src/base/SegmentPerformanceHelper.h126
-rw-r--r--src/base/Selection.cpp318
-rw-r--r--src/base/Selection.h263
-rw-r--r--src/base/Sets.cpp108
-rw-r--r--src/base/Sets.h698
-rw-r--r--src/base/SnapGrid.cpp192
-rw-r--r--src/base/SnapGrid.h183
-rw-r--r--src/base/SoftSynthDevice.cpp174
-rw-r--r--src/base/SoftSynthDevice.h70
-rw-r--r--src/base/Staff.cpp213
-rw-r--r--src/base/Staff.h149
-rw-r--r--src/base/StaffExportTypes.h75
-rw-r--r--src/base/Studio.cpp674
-rw-r--r--src/base/Studio.h208
-rw-r--r--src/base/Track.cpp201
-rw-r--r--src/base/Track.h162
-rw-r--r--src/base/TriggerSegment.cpp130
-rw-r--r--src/base/TriggerSegment.h100
-rw-r--r--src/base/ViewElement.cpp172
-rw-r--r--src/base/ViewElement.h164
-rw-r--r--src/base/XmlExportable.cpp197
-rw-r--r--src/base/XmlExportable.h55
-rw-r--r--src/base/test/Makefile57
-rw-r--r--src/base/test/accidentals.cpp60
-rw-r--r--src/base/test/colour.cpp222
-rw-r--r--src/base/test/colour.output76
-rw-r--r--src/base/test/pitch.cpp474
-rw-r--r--src/base/test/seq/Makefile6
-rw-r--r--src/base/test/seq/complainer.c74
-rw-r--r--src/base/test/seq/generator.c96
-rw-r--r--src/base/test/seq/queue-timer-jack.c166
-rw-r--r--src/base/test/seq/queue-timer.c123
-rw-r--r--src/base/test/test.cpp535
-rw-r--r--src/base/test/thread.cpp126
-rw-r--r--src/base/test/transpose.cpp83
-rw-r--r--src/base/test/utf8.cpp96
115 files changed, 34499 insertions, 0 deletions
diff --git a/src/base/AnalysisTypes.cpp b/src/base/AnalysisTypes.cpp
new file mode 100644
index 0000000..b2d8727
--- /dev/null
+++ b/src/base/AnalysisTypes.cpp
@@ -0,0 +1,1118 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2002
+ Randall Farmer <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <iostream>
+#include <string>
+#include <map>
+#include <algorithm>
+#include <cmath> // fabs, pow
+
+#include "NotationTypes.h"
+#include "AnalysisTypes.h"
+#include "Event.h"
+#include "Segment.h"
+#include "CompositionTimeSliceAdapter.h"
+#include "BaseProperties.h"
+#include "Composition.h"
+#include "Profiler.h"
+
+#include "Sets.h"
+#include "Quantizer.h"
+
+
+namespace Rosegarden
+{
+
+using std::string;
+using std::cerr;
+using std::endl;
+using std::multimap;
+using std::vector;
+using std::partial_sort_copy;
+
+///////////////////////////////////////////////////////////////////////////
+// Miscellany (doesn't analyze anything)
+///////////////////////////////////////////////////////////////////////////
+
+Key
+AnalysisHelper::getKeyForEvent(Event *e, Segment &s)
+{
+ Segment::iterator i =
+ e ? s.findNearestTime(e->getAbsoluteTime()) //cc
+ : s.begin();
+
+ if (i==s.end()) return Key();
+
+ // This is an ugly loop. Is there a better way to iterate backwards
+ // through an STL container?
+
+ while (true) {
+ if ((*i)->isa(Key::EventType)) {
+ return Key(**i);
+ }
+ if (i != s.begin()) --i;
+ else break;
+ }
+
+ return Key();
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Simple chord identification
+///////////////////////////////////////////////////////////////////////////
+
+void
+AnalysisHelper::labelChords(CompositionTimeSliceAdapter &c, Segment &s,
+ const Rosegarden::Quantizer *quantizer)
+{
+
+ Key key;
+ if (c.begin() != c.end()) key = getKeyForEvent(*c.begin(), s);
+ else key = getKeyForEvent(0, s);
+
+ Profiler profiler("AnalysisHelper::labelChords", true);
+
+ for (CompositionTimeSliceAdapter::iterator i = c.begin(); i != c.end(); ++i) {
+
+ timeT time = (*i)->getAbsoluteTime();
+
+// std::cerr << "AnalysisHelper::labelChords: time is " << time << ", type is " << (*i)->getType() << ", event is " << *i << " (itr is " << &i << ")" << std::endl;
+
+ if ((*i)->isa(Key::EventType)) {
+ key = Key(**i);
+ Text text(key.getName(), Text::KeyName);
+ s.insert(text.getAsEvent(time));
+ continue;
+ }
+
+ if ((*i)->isa(Note::EventType)) {
+
+ int bass = 999;
+ int mask = 0;
+
+ GlobalChord chord(c, i, quantizer);
+ if (chord.size() == 0) continue;
+
+ for (GlobalChord::iterator j = chord.begin(); j != chord.end(); ++j) {
+ long pitch = 999;
+ if ((**j)->get<Int>(BaseProperties::PITCH, pitch)) {
+ if (pitch < bass) {
+ assert(bass == 999); // should be in ascending order already
+ bass = pitch;
+ }
+ mask |= 1 << (pitch % 12);
+ }
+ }
+
+ i = chord.getFinalElement();
+
+ if (mask == 0) continue;
+
+ ChordLabel ch(key, mask, bass);
+
+ if (ch.isValid())
+ {
+ //std::cerr << ch.getName(key) << " at time " << time << std::endl;
+
+ Text text(ch.getName(key), Text::ChordName);
+ s.insert(text.getAsEvent(time));
+ }
+ }
+
+ }
+}
+
+
+// ChordLabel
+/////////////////////////////////////////////////
+
+ChordLabel::ChordMap ChordLabel::m_chordMap;
+
+ChordLabel::ChordLabel()
+{
+ checkMap();
+}
+
+ChordLabel::ChordLabel(Key key, int mask, int /* bass */) :
+ m_data()
+{
+ checkMap();
+
+ // Look for a chord built on an unaltered scale step of the current key.
+
+ for (ChordMap::iterator i = m_chordMap.find(mask);
+ i != m_chordMap.end() && i->first==mask; ++i)
+ {
+
+ if (Pitch(i->second.m_rootPitch).isDiatonicInKey(key))
+ {
+ m_data = i->second;
+ }
+
+ }
+
+ /*
+ int rootBassInterval = ((bass - m_data.m_rootPitch + 12) % 12);
+
+ // Pretend nobody cares about second and third inversions
+ // (i.e., bass must always be either root or third of chord)
+ if (rootBassInterval > 7) m_data.m_type=ChordTypes::NoChord;
+ else if (rootBassInterval > 4) m_data.m_type=ChordTypes::NoChord;
+ // Mark first-inversion and root-position chords as such
+ else if (rootBassInterval > 0) m_data.m_inversion=1;
+ else m_data.m_inversion=0;
+ */
+
+}
+
+std::string
+ChordLabel::getName(Key key) const
+{
+ return Pitch(m_data.m_rootPitch).getAsString(key.isSharp(), false) +
+ m_data.m_type;
+ // + (m_data.m_inversion>0 ? " in first inversion" : "");
+}
+
+int
+ChordLabel::rootPitch()
+{
+ return m_data.m_rootPitch;
+}
+
+bool
+ChordLabel::isValid() const
+{
+ return m_data.m_type != ChordTypes::NoChord;
+}
+
+bool
+ChordLabel::operator<(const ChordLabel& other) const
+{
+ if (!isValid()) return true;
+ return getName(Key()) < other.getName(Key());
+}
+
+bool
+ChordLabel::operator==(const ChordLabel& other) const
+{
+ return getName(Key()) == other.getName(Key());
+}
+
+void
+ChordLabel::checkMap()
+{
+ if (!m_chordMap.empty()) return;
+
+ const ChordType basicChordTypes[8] =
+ {ChordTypes::Major, ChordTypes::Minor, ChordTypes::Diminished,
+ ChordTypes::MajorSeventh, ChordTypes::DominantSeventh,
+ ChordTypes::MinorSeventh, ChordTypes::HalfDimSeventh,
+ ChordTypes::DimSeventh};
+
+ // What the basicChordMasks mean: each bit set in each one represents
+ // a pitch class (pitch%12) in a chord. C major has three pitch
+ // classes, C, E, and G natural; if you take the MIDI pitches
+ // of those notes modulo 12, you get 0, 4, and 7, so the mask for
+ // major triads is (1<<0)+(1<<4)+(1<<7). All the masks are for chords
+ // built on C.
+
+ const int basicChordMasks[8] =
+ {
+ 1 + (1<<4) + (1<<7), // major
+ 1 + (1<<3) + (1<<7), // minor
+ 1 + (1<<3) + (1<<6), // diminished
+ 1 + (1<<4) + (1<<7) + (1<<11), // major 7th
+ 1 + (1<<4) + (1<<7) + (1<<10), // dominant 7th
+ 1 + (1<<3) + (1<<7) + (1<<10), // minor 7th
+ 1 + (1<<3) + (1<<6) + (1<<10), // half-diminished 7th
+ 1 + (1<<3) + (1<<6) + (1<<9) // diminished 7th
+ };
+
+ // Each mask is inserted into the map rotated twelve ways; each
+ // rotation is a mask you would get by transposing the chord
+ // to have a new root (i.e., C, C#, D, D#, E, F...)
+
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 12; ++j)
+ {
+
+ m_chordMap.insert
+ (
+ std::pair<int, ChordData>
+ (
+ (basicChordMasks[i] << j | basicChordMasks[i] >> (12-j))
+ & ((1<<12) - 1),
+ ChordData(basicChordTypes[i], j)
+ )
+ );
+
+ }
+ }
+
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Harmony guessing
+///////////////////////////////////////////////////////////////////////////
+
+void
+AnalysisHelper::guessHarmonies(CompositionTimeSliceAdapter &c, Segment &s)
+{
+ HarmonyGuessList l;
+
+ // 1. Get the list of possible harmonies
+ makeHarmonyGuessList(c, l);
+
+ // 2. Refine the list of possible harmonies by preferring chords in the
+ // current key and looking for familiar progressions and
+ // tonicizations.
+ refineHarmonyGuessList(c, l, s);
+
+ // 3. Put labels in the Segment. For the moment we just do the
+ // really naive thing with the segment arg to refineHarmonyGuessList:
+ // could do much better here
+}
+
+// #### explain how this works:
+// in terms of other functions (simple chord labelling, key guessing)
+// in terms of basic concepts (pitch profile, harmony guess)
+// in terms of flow
+
+void
+AnalysisHelper::makeHarmonyGuessList(CompositionTimeSliceAdapter &c,
+ HarmonyGuessList &l)
+{
+ if (*c.begin() == *c.end()) return;
+
+ checkHarmonyTable();
+
+ PitchProfile p; // defaults to all zeroes
+ TimeSignature timeSig;
+ timeT timeSigTime = 0;
+ timeT nextSigTime = (*c.begin())->getAbsoluteTime();
+
+ // Walk through the piece labelChords style
+
+ // no increment (the first inner loop does the incrementing)
+ for (CompositionTimeSliceAdapter::iterator i = c.begin(); i != c.end(); )
+ {
+
+ // 2. Update the pitch profile
+
+ timeT time = (*i)->getAbsoluteTime();
+
+ if (time >= nextSigTime) {
+ Composition *comp = c.getComposition();
+ int sigNo = comp->getTimeSignatureNumberAt(time);
+ if (sigNo >= 0) {
+ std::pair<timeT, TimeSignature> sig = comp->getTimeSignatureChange(sigNo);
+ timeSigTime = sig.first;
+ timeSig = sig.second;
+ }
+ if (sigNo < comp->getTimeSignatureCount() - 1) {
+ nextSigTime = comp->getTimeSignatureChange(sigNo + 1).first;
+ } else {
+ nextSigTime = comp->getEndMarker();
+ }
+ }
+
+ double emphasis =
+ double(timeSig.getEmphasisForTime(time - timeSigTime));
+
+ // Scale all the components of the pitch profile down so that
+ // 1. notes that are a beat/bar away have less weight than notes
+ // from this beat/bar
+ // 2. the difference in weight depends on the metrical importance
+ // of the boundary between the notes: the previous beat should
+ // get less weight if this is the first beat of a new bar
+
+ // ### possibly too much fade here
+ // also, fade should happen w/reference to how many notes played?
+
+ PitchProfile delta;
+ int noteCount = 0;
+
+ // no initialization
+ for ( ; i != c.end() && (*i)->getAbsoluteTime() == time; ++i)
+ {
+ if ((*i)->isa(Note::EventType))
+ {
+ try {
+ int pitch = (*i)->get<Int>(BaseProperties::PITCH);
+ delta[pitch % 12] += 1 << int(emphasis);
+ ++noteCount;
+ } catch (...) {
+ std::cerr << "No pitch for note at " << time << "!" << std::endl;
+ }
+ }
+ }
+
+ p *= 1. / (pow(2, emphasis) + 1 + noteCount);
+ p += delta;
+
+ // 1. If there could have been a chord change, compare the current
+ // pitch profile with all of the profiles in the table to figure
+ // out which chords we are now nearest.
+
+ // (If these events weren't on a beat boundary, assume there was no
+ // chord change and continue -- ### will need this back)
+/* if ((!(i != c.end())) ||
+ timeSig.getEmphasisForTime((*i)->getAbsoluteTime() - timeSigTime) < 3)
+ {
+ continue;
+ }*/
+
+ // (If the pitch profile hasn't changed much, continue)
+
+ PitchProfile np = p.normalized();
+
+ HarmonyGuess possibleChords;
+
+ possibleChords.reserve(m_harmonyTable.size());
+
+ for (HarmonyTable::iterator j = m_harmonyTable.begin();
+ j != m_harmonyTable.end();
+ ++j)
+ {
+ double score = np.productScorer(j->first);
+ possibleChords.push_back(ChordPossibility(score, j->second));
+ }
+
+ // 3. Save a short list of the nearest chords in the
+ // HarmonyGuessList passed in from guessHarmonies()
+
+ l.push_back(std::pair<timeT, HarmonyGuess>(time, HarmonyGuess()));
+
+ HarmonyGuess& smallerGuess = l.back().second;
+
+ // Have to learn to love this:
+
+ smallerGuess.resize(10);
+
+ partial_sort_copy(possibleChords.begin(),
+ possibleChords.end(),
+ smallerGuess.begin(),
+ smallerGuess.end(),
+ cp_less());
+
+#ifdef GIVE_HARMONYGUESS_DETAILS
+ std::cerr << "Time: " << time << std::endl;
+
+ std::cerr << "Profile: ";
+ for (int k = 0; k < 12; ++k)
+ std::cerr << np[k] << " ";
+ std::cerr << std::endl;
+
+ std::cerr << "Best guesses: " << std::endl;
+ for (HarmonyGuess::iterator debugi = smallerGuess.begin();
+ debugi != smallerGuess.end();
+ ++debugi)
+ {
+ std::cerr << debugi->first << ": " << debugi->second.getName(Key()) << std::endl;
+ }
+#endif
+
+ }
+
+}
+
+// Comparison function object -- can't declare this in the headers because
+// this only works with pair<double, ChordLabel> instantiated,
+// pair<double, ChordLabel> can't be instantiated while ChordLabel is an
+// incomplete type, and ChordLabel is still an incomplete type at that
+// point in the headers.
+
+bool
+AnalysisHelper::cp_less::operator()(ChordPossibility l, ChordPossibility r)
+{
+ // Change name from "less?"
+ return l.first > r.first;
+}
+
+
+void
+AnalysisHelper::refineHarmonyGuessList(CompositionTimeSliceAdapter &/* c */,
+ HarmonyGuessList &l, Segment &segment)
+{
+ // (Fetch the piece's starting key from the key guesser)
+ Key key;
+
+ checkProgressionMap();
+
+ if (l.size() < 2)
+ {
+ l.clear();
+ return;
+ }
+
+ // Look at the list of harmony guesses two guesses at a time.
+
+ HarmonyGuessList::iterator i = l.begin();
+ // j stays ahead of i
+ HarmonyGuessList::iterator j = i + 1;
+
+ ChordLabel bestGuessForFirstChord, bestGuessForSecondChord;
+ while (j != l.end())
+ {
+
+ double highestScore = 0;
+
+ // For each possible pair of chords (i.e., two for loops here)
+ for (HarmonyGuess::iterator k = i->second.begin();
+ k != i->second.end();
+ ++k)
+ {
+ for (HarmonyGuess::iterator l = j->second.begin();
+ l != j->second.end();
+ ++l)
+ {
+ // Print the guess being processed:
+
+ // std::cerr << k->second.getName(Key()) << "->" << l->second.getName(Key()) << std::endl;
+
+ // For a first approximation, let's say the probability that
+ // a chord guess is correct is proportional to its score. Then
+ // the probability that a pair is correct is the product of
+ // its scores.
+
+ double currentScore;
+ currentScore = k->first * l->first;
+
+ // std::cerr << currentScore << std::endl;
+
+ // Is this a familiar progression? Bonus if so.
+
+ bool isFamiliar = false;
+
+ // #### my ways of breaking up long function calls are haphazard
+ // also, does this code belong here?
+
+ ProgressionMap::iterator pmi =
+ m_progressionMap.lower_bound(
+ ChordProgression(k->second, l->second)
+ );
+
+ // no initialization
+ for ( ;
+ pmi != m_progressionMap.end()
+ && pmi->first == k->second
+ && pmi->second == l->second;
+ ++pmi)
+ {
+ // key doesn't have operator== defined
+ if (key.getName() == pmi->homeKey.getName())
+ {
+// std::cerr << k->second.getName(Key()) << "->" << l->second.getName(Key()) << " is familiar" << std::endl;
+ isFamiliar = true;
+ break;
+ }
+ }
+
+ if (isFamiliar) currentScore *= 5; // #### arbitrary
+
+ // (Are voice-leading rules followed? Penalty if not)
+
+ // Is this better than any pair examined so far? If so, set
+ // some variables that should end up holding the best chord
+ // progression
+ if (currentScore > highestScore)
+ {
+ bestGuessForFirstChord = k->second;
+ bestGuessForSecondChord = l->second;
+ highestScore = currentScore;
+ }
+
+ }
+ }
+
+ // Since we're not returning any results right now, print them
+ std::cerr << "Time: " << j->first << std::endl;
+ std::cerr << "Best chords: "
+ << bestGuessForFirstChord.getName(Key()) << ", "
+ << bestGuessForSecondChord.getName(Key()) << std::endl;
+ std::cerr << "Best score: " << highestScore << std::endl;
+
+ // Using the best pair of chords:
+
+ // Is the first chord diatonic?
+
+ // If not, is it a secondary function?
+ // If so, change the current key
+ // If not, set an "implausible progression" flag
+
+ // (Is the score of the best pair of chords reasonable?
+ // If not, set the flag.)
+
+ // Was the progression plausible?
+
+ // If so, replace the ten or so chords in the first guess with the
+ // first chord of the best pair, set
+ // first-iterator=second-iterator, and ++second-iterator
+ // (and possibly do the real key-setting)
+
+ // If not, h.erase(second-iterator++)
+
+ // Temporary hack to get _something_ interesting out:
+ Event *e;
+ e = Text(bestGuessForFirstChord.getName(Key()), Text::ChordName).
+ getAsEvent(j->first);
+ segment.insert(new Event(*e, e->getAbsoluteTime(),
+ e->getDuration(), e->getSubOrdering()-1));
+ delete e;
+
+ e = Text(bestGuessForSecondChord.getName(Key()), Text::ChordName).
+ getAsEvent(j->first);
+ segment.insert(e);
+
+ // For now, just advance:
+ i = j;
+ ++j;
+ }
+}
+
+AnalysisHelper::HarmonyTable AnalysisHelper::m_harmonyTable;
+
+void
+AnalysisHelper::checkHarmonyTable()
+{
+ if (!m_harmonyTable.empty()) return;
+
+ // Identical to basicChordTypes in ChordLabel::checkMap
+ const ChordType basicChordTypes[8] =
+ {ChordTypes::Major, ChordTypes::Minor, ChordTypes::Diminished,
+ ChordTypes::MajorSeventh, ChordTypes::DominantSeventh,
+ ChordTypes::MinorSeventh, ChordTypes::HalfDimSeventh,
+ ChordTypes::DimSeventh};
+
+ // Like basicChordMasks in ChordLabel::checkMap(), only with
+ // ints instead of bits
+ const int basicChordProfiles[8][12] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 10 11
+ {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, // major
+ {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}, // minor
+ {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0}, // diminished
+ {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1}, // major 7th
+ {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}, // dominant 7th
+ {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}, // minor 7th
+ {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}, // half-diminished 7th
+ {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0} // diminished 7th
+ };
+
+ for (int i = 0; i < 8; ++i)
+ {
+ for (int j = 0; j < 12; ++j)
+ {
+ PitchProfile p;
+
+ for (int k = 0; k < 12; ++k)
+ p[(j + k) % 12] = (basicChordProfiles[i][k] == 1)
+ ? 1.
+ : -1.;
+
+ PitchProfile np = p.normalized();
+
+ ChordLabel c(basicChordTypes[i], j);
+
+ m_harmonyTable.push_back(std::pair<PitchProfile, ChordLabel>(np, c));
+ }
+ }
+
+}
+
+AnalysisHelper::ProgressionMap AnalysisHelper::m_progressionMap;
+
+void
+AnalysisHelper::checkProgressionMap()
+{
+ if (!m_progressionMap.empty()) return;
+ // majorProgressionFirsts[0] = 5, majorProgressionSeconds[0]=1, so 5->1 is
+ // a valid progression. Note that the chord numbers are 1-based, like the
+ // Roman numeral symbols
+ const int majorProgressionFirsts[9] =
+ {5, 2, 6, 3, 7, 4, 4, 3, 5};
+ const int majorProgressionSeconds[9] =
+ {1, 5, 2, 6, 1, 2, 5, 4, 6};
+
+ // For each major key
+ for (int i = 0; i < 12; ++i)
+ {
+ // Make the key
+ Key k(0, false); // tonicPitch = i, isMinor = false
+ // Add the common progressions
+ for (int j = 0; j < 9; ++j)
+ {
+ std::cerr << majorProgressionFirsts[j] << ", " << majorProgressionSeconds[j] << std::endl;
+ addProgressionToMap(k,
+ majorProgressionFirsts[j],
+ majorProgressionSeconds[j]);
+ }
+ // Add I->everything
+ for (int j = 1; j < 8; ++j)
+ {
+ addProgressionToMap(k, 1, j);
+ }
+ // (Add the progressions involving seventh chords)
+ // (Add I->seventh chords)
+ }
+ // (For each minor key)
+ // (Do what we just did for major keys)
+
+}
+
+void
+AnalysisHelper::addProgressionToMap(Key k,
+ int firstChordNumber,
+ int secondChordNumber)
+{
+ // majorScalePitches[1] should be the pitch of the first step of
+ // the scale, so there's padding at the beginning of both these
+ // arrays.
+ const int majorScalePitches[] = {0, 0, 2, 4, 5, 7, 9, 11};
+ const ChordType majorDiationicTriadTypes[] =
+ {ChordTypes::NoChord, ChordTypes::Major, ChordTypes::Minor,
+ ChordTypes::Minor, ChordTypes::Major, ChordTypes::Major,
+ ChordTypes::Minor, ChordTypes::Diminished};
+
+ int offset = k.getTonicPitch();
+
+ if (!k.isMinor())
+ {
+ ChordLabel firstChord
+ (
+ majorDiationicTriadTypes[firstChordNumber],
+ (majorScalePitches[firstChordNumber] + offset) % 12
+ );
+ ChordLabel secondChord
+ (
+ majorDiationicTriadTypes[secondChordNumber],
+ (majorScalePitches[secondChordNumber] + offset) % 12
+ );
+ ChordProgression p(firstChord, secondChord, k);
+ m_progressionMap.insert(p);
+ }
+ // else handle minor-key chords
+
+}
+
+// AnalysisHelper::ChordProgression
+/////////////////////////////////////////////////
+
+AnalysisHelper::ChordProgression::ChordProgression(ChordLabel first_,
+ ChordLabel second_,
+ Key key_) :
+ first(first_),
+ second(second_),
+ homeKey(key_)
+{
+ // nothing else
+}
+
+bool
+AnalysisHelper::ChordProgression::operator<(const AnalysisHelper::ChordProgression& other) const
+{
+ // no need for:
+ // if (first == other.first) return second < other.second;
+ return first < other.first;
+}
+
+// AnalysisHelper::PitchProfile
+/////////////////////////////////////////////////
+
+AnalysisHelper::PitchProfile::PitchProfile()
+{
+ for (int i = 0; i < 12; ++i) m_data[i] = 0;
+}
+
+double&
+AnalysisHelper::PitchProfile::operator[](int i)
+{
+ return m_data[i];
+}
+
+const double&
+AnalysisHelper::PitchProfile::operator[](int i) const
+{
+ return m_data[i];
+}
+
+double
+AnalysisHelper::PitchProfile::distance(const PitchProfile &other)
+{
+ double distance = 0;
+
+ for (int i = 0; i < 12; ++i)
+ {
+ distance += fabs(other[i] - m_data[i]);
+ }
+
+ return distance;
+}
+
+double
+AnalysisHelper::PitchProfile::dotProduct(const PitchProfile &other)
+{
+ double product = 0;
+
+ for (int i = 0; i < 12; ++i)
+ {
+ product += other[i] * m_data[i];
+ }
+
+ return product;
+}
+
+double
+AnalysisHelper::PitchProfile::productScorer(const PitchProfile &other)
+{
+ double cumulativeProduct = 1;
+ double numbersInProduct = 0;
+
+ for (int i = 0; i < 12; ++i)
+ {
+ if (other[i] > 0)
+ {
+ cumulativeProduct *= m_data[i];
+ ++numbersInProduct;
+ }
+ }
+
+ if (numbersInProduct > 0)
+ return pow(cumulativeProduct, 1/numbersInProduct);
+
+ return 0;
+}
+
+AnalysisHelper::PitchProfile
+AnalysisHelper::PitchProfile::normalized()
+{
+ double size = 0;
+ PitchProfile normedProfile;
+
+ for (int i = 0; i < 12; ++i)
+ {
+ size += fabs(m_data[i]);
+ }
+
+ if (size == 0) size = 1;
+
+ for (int i = 0; i < 12; ++i)
+ {
+ normedProfile[i] = m_data[i] / size;
+ }
+
+ return normedProfile;
+}
+
+AnalysisHelper::PitchProfile&
+AnalysisHelper::PitchProfile::operator*=(double d)
+{
+
+ for (int i = 0; i < 12; ++i)
+ {
+ m_data[i] *= d;
+ }
+
+ return *this;
+}
+
+AnalysisHelper::PitchProfile&
+AnalysisHelper::PitchProfile::operator+=(const PitchProfile& d)
+{
+
+ for (int i = 0; i < 12; ++i)
+ {
+ m_data[i] += d[i];
+ }
+
+ return *this;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Time signature guessing
+///////////////////////////////////////////////////////////////////////////
+
+// #### this is too long
+// should use constants for basic lengths, not numbers
+
+TimeSignature
+AnalysisHelper::guessTimeSignature(CompositionTimeSliceAdapter &c)
+{
+ bool haveNotes = false;
+
+ // 1. Guess the duration of the beat. The right beat length is going
+ // to be a common note length, and beat boundaries should be likely
+ // to have notes starting on them.
+
+ vector<int> beatScores(4, 0);
+
+ // durations of quaver, dotted quaver, crotchet, dotted crotchet:
+ static const int commonBeatDurations[4] = {48, 72, 96, 144};
+
+ int j = 0;
+ for (CompositionTimeSliceAdapter::iterator i = c.begin();
+ i != c.end() && j < 100;
+ ++i, ++j)
+ {
+
+ // Skip non-notes
+ if (!(*i)->isa(Note::EventType)) continue;
+ haveNotes = true;
+
+ for (int k = 0; k < 4; ++k)
+ {
+
+ // Points for any note of the right length
+ if ((*i)->getDuration() == commonBeatDurations[k])
+ beatScores[k]++;
+
+ // Score for the probability that a note starts on a beat
+ // boundary.
+
+ // Normally, to get the probability that a random beat boundary
+ // has a note on it, you'd add a constant for each note on a
+ // boundary and divide by the number of beat boundaries.
+ // Instead, this multiplies the constant (1/24) by
+ // commonBeatDurations[k], which is inversely proportional to
+ // the number of beat boundaries.
+
+ if ((*i)->getAbsoluteTime() % commonBeatDurations[k] == 0)
+ beatScores[k] += commonBeatDurations[k] / 24;
+
+ }
+
+ }
+
+ if (!haveNotes) return TimeSignature();
+
+ int beatDuration = 0,
+ bestScore = 0;
+
+ for (int j = 0; j < 4; ++j)
+ {
+ if (beatScores[j] >= bestScore)
+ {
+ bestScore = beatScores[j];
+ beatDuration = commonBeatDurations[j];
+ }
+ }
+
+ // 2. Guess whether the measure has two, three or four beats. The right
+ // measure length should make notes rarely cross barlines and have a
+ // high average length for notes at the start of bars.
+
+ vector<int> measureLengthScores(5, 0);
+
+ for (CompositionTimeSliceAdapter::iterator i = c.begin();
+ i != c.end() && j < 100;
+ ++i, ++j)
+ {
+
+ if (!(*i)->isa(Note::EventType)) continue;
+
+ // k is the guess at the number of beats in a measure
+ for (int k = 2; k < 5; ++k)
+ {
+
+ // Determine whether this note crosses a barline; points for the
+ // measure length if it does NOT.
+
+ int noteOffset = ((*i)->getAbsoluteTime() % (beatDuration * k));
+ int noteEnd = noteOffset + (*i)->getDuration();
+ if ( !(noteEnd > (beatDuration * k)) )
+ measureLengthScores[k] += 10;
+
+
+ // Average length of notes at measure starts
+
+ // Instead of dividing by the number of measure starts, this
+ // multiplies by the number of beats per measure, which is
+ // inversely proportional to the number of measure starts.
+
+ if ((*i)->getAbsoluteTime() % (beatDuration * k) == 0)
+ measureLengthScores[k] +=
+ (*i)->getDuration() * k / 24;
+
+ }
+
+ }
+
+ int measureLength = 0;
+
+ bestScore = 0; // reused from earlier
+
+ for (int j = 2; j < 5; ++j)
+ {
+ if (measureLengthScores[j] >= bestScore)
+ {
+ bestScore = measureLengthScores[j];
+ measureLength = j;
+ }
+ }
+
+ //
+ // 3. Put the result in a TimeSignature object.
+ //
+
+ int numerator = 0, denominator = 0;
+
+ if (beatDuration % 72 == 0)
+ {
+
+ numerator = 3 * measureLength;
+
+ // 144 means the beat is a dotted crotchet, so the beat division
+ // is a quaver, so you want 8 on bottom
+ denominator = (144 * 8) / beatDuration;
+
+ }
+ else
+ {
+
+ numerator = measureLength;
+
+ // 96 means that the beat is a crotchet, so you want 4 on bottom
+ denominator = (96 * 4) / beatDuration;
+
+ }
+
+ TimeSignature ts(numerator, denominator);
+
+ return ts;
+
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Key guessing
+///////////////////////////////////////////////////////////////////////////
+
+Key
+AnalysisHelper::guessKey(CompositionTimeSliceAdapter &c)
+{
+ if (c.begin() == c.end()) return Key();
+
+ // 1. Figure out the distribution of emphasis over the twelve
+ // pitch clases in the first few bars. Pitches that occur
+ // more often have greater emphasis, and pitches that occur
+ // at stronger points in the bar have greater emphasis.
+
+ vector<int> weightedNoteCount(12, 0);
+ TimeSignature timeSig;
+ timeT timeSigTime = 0;
+ timeT nextSigTime = (*c.begin())->getAbsoluteTime();
+
+ int j = 0;
+ for (CompositionTimeSliceAdapter::iterator i = c.begin();
+ i != c.end() && j < 100; ++i, ++j)
+ {
+ timeT time = (*i)->getAbsoluteTime();
+
+ if (time >= nextSigTime) {
+ Composition *comp = c.getComposition();
+ int sigNo = comp->getTimeSignatureNumberAt(time);
+ if (sigNo >= 0) {
+ std::pair<timeT, TimeSignature> sig = comp->getTimeSignatureChange(sigNo);
+ timeSigTime = sig.first;
+ timeSig = sig.second;
+ }
+ if (sigNo < comp->getTimeSignatureCount() - 1) {
+ nextSigTime = comp->getTimeSignatureChange(sigNo + 1).first;
+ } else {
+ nextSigTime = comp->getEndMarker();
+ }
+ }
+
+ // Skip any other non-notes
+ if (!(*i)->isa(Note::EventType)) continue;
+
+ try {
+ // Get pitch, metric strength of this event
+ int pitch = (*i)->get<Int>(BaseProperties::PITCH)%12;
+ int emphasis =
+ 1 << timeSig.getEmphasisForTime((*i)->getAbsoluteTime() - timeSigTime);
+
+ // Count notes
+ weightedNoteCount[pitch] += emphasis;
+
+ } catch (...) {
+ std::cerr << "No pitch for note at " << time << "!" << std::endl;
+ }
+ }
+
+ // 2. Figure out what key best fits the distribution of emphasis.
+ // Notes outside a piece's key are rarely heavily emphasized,
+ // and the tonic and dominant of the key are likely to appear.
+
+ // This part is longer than it should be.
+
+ int bestTonic = -1;
+ bool bestKeyIsMinor = false;
+ int lowestCost = 999999999;
+
+ for (int k = 0; k < 12; ++k)
+ {
+ int cost =
+ // accidentals are costly
+ weightedNoteCount[(k + 1 ) % 12]
+ + weightedNoteCount[(k + 3 ) % 12]
+ + weightedNoteCount[(k + 6 ) % 12]
+ + weightedNoteCount[(k + 8 ) % 12]
+ + weightedNoteCount[(k + 10) % 12]
+ // tonic is very good
+ - weightedNoteCount[ k ] * 5
+ // dominant is good
+ - weightedNoteCount[(k + 7 ) % 12];
+ if (cost < lowestCost)
+ {
+ bestTonic = k;
+ lowestCost = cost;
+ }
+ }
+
+ for (int k = 0; k < 12; ++k)
+ {
+ int cost =
+ // accidentals are costly
+ weightedNoteCount[(k + 1 ) % 12]
+ + weightedNoteCount[(k + 4 ) % 12]
+ + weightedNoteCount[(k + 6 ) % 12]
+ // no cost for raised steps 6/7 (k+9, k+11)
+ // tonic is very good
+ - weightedNoteCount[ k ] * 5
+ // dominant is good
+ - weightedNoteCount[(k + 7 ) % 12];
+ if (cost < lowestCost)
+ {
+ bestTonic = k;
+ bestKeyIsMinor = true;
+ lowestCost = cost;
+ }
+ }
+
+ return Key(bestTonic, bestKeyIsMinor);
+
+}
+
+}
diff --git a/src/base/AnalysisTypes.h b/src/base/AnalysisTypes.h
new file mode 100644
index 0000000..d7eabad
--- /dev/null
+++ b/src/base/AnalysisTypes.h
@@ -0,0 +1,227 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2002
+ Randall Farmer <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _ANALYSISTYPES_H_
+#define _ANALYSISTYPES_H_
+
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "NotationTypes.h"
+
+namespace Rosegarden
+{
+
+class Segment;
+class Event;
+class CompositionTimeSliceAdapter;
+class Quantizer;
+
+///////////////////////////////////////////////////////////////////////////
+
+typedef std::string ChordType;
+class ChordLabel;
+
+namespace ChordTypes
+{
+const ChordType
+NoChord = "no-chord",
+ Major = "",
+ Minor = "m",
+ Diminished = "dim",
+ MajorSeventh = "M7",
+ DominantSeventh = "7",
+ MinorSeventh = "m7",
+ HalfDimSeventh = "7b5",
+ DimSeventh = "dim7";
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * ChordLabel names chords and identifies them from their masks. See
+ * ChordLabel::checkMap() for details on what the masks are and
+ * AnalysisHelper::labelChords() for an example.
+ */
+
+class ChordLabel
+{
+public:
+ ChordLabel();
+ ChordLabel(Key key, int mask, int bass);
+ ChordLabel(ChordType type, int rootPitch, int inversion = 0) :
+ m_data(type, rootPitch, inversion) { };
+ int rootPitch();
+ /**
+ * Gives the name of the chord in lead-sheet notation: C, Dm,
+ * G#7b5...
+ */
+ std::string getName(Key key) const;
+ /**
+ * Gives the name of the chord in roman-numeral notation: I, ii,
+ * VMm7...
+ */
+// std::string getRomanNumeral(Key key);
+ bool isValid() const;
+ bool operator<(const ChordLabel& other) const;
+ // ### I can't believe this is necessary, but the compiler
+ // is asking for it
+ bool operator==(const ChordLabel& other) const;
+
+private:
+ // #### are m_* names appropriate for a struct?
+ // shouldn't I find a neater way to keep a ChordMap?
+ struct ChordData
+ {
+ ChordData(ChordType type, int rootPitch, int inversion = 0) :
+ m_type(type),
+ m_rootPitch(rootPitch),
+ m_inversion(inversion) { };
+
+ ChordData() :
+ m_type(ChordTypes::NoChord),
+ m_rootPitch(0),
+ m_inversion(0) { };
+
+ ChordType m_type;
+ int m_rootPitch;
+ int m_inversion;
+ };
+ ChordData m_data;
+ void checkMap();
+
+ typedef std::multimap<int, ChordData> ChordMap;
+ static ChordMap m_chordMap;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+class AnalysisHelper
+{
+public:
+ AnalysisHelper() {};
+
+ /**
+ * Returns the key in force during a given event.
+ */
+ Key getKeyForEvent(Event *e, Segment &s);
+
+ /**
+ * Inserts in the given Segment labels for all of the chords found in
+ * the timeslice in the given CompositionTimeSliceAdapter.
+ */
+ void labelChords(CompositionTimeSliceAdapter &c, Segment &s,
+ const Quantizer *quantizer);
+
+ /**
+ * Returns a time signature that is probably reasonable for the
+ * given timeslice.
+ */
+ TimeSignature guessTimeSignature(CompositionTimeSliceAdapter &c);
+
+ /**
+ * Returns a guess at the starting key of the given timeslice.
+ */
+ Key guessKey(CompositionTimeSliceAdapter &c);
+
+ /**
+ * Like labelChords, but the algorithm is more complicated. This tries
+ * to guess the chords that should go under a beat even when all of the
+ * chord members aren't played at once.
+ */
+ void guessHarmonies(CompositionTimeSliceAdapter &c, Segment &s);
+
+protected:
+ // ### THESE NAMES ARE AWFUL. MUST GREP THEM OUT OF EXISTENCE.
+ typedef std::pair<double, ChordLabel> ChordPossibility;
+ typedef std::vector<ChordPossibility> HarmonyGuess;
+ typedef std::vector<std::pair<timeT, HarmonyGuess> > HarmonyGuessList;
+ struct cp_less : public std::binary_function<ChordPossibility, ChordPossibility, bool>
+ {
+ bool operator()(ChordPossibility l, ChordPossibility r);
+ };
+
+ /// For use by guessHarmonies
+ void makeHarmonyGuessList(CompositionTimeSliceAdapter &c,
+ HarmonyGuessList &l);
+
+ /// For use by guessHarmonies
+ void refineHarmonyGuessList(CompositionTimeSliceAdapter &c,
+ HarmonyGuessList& l,
+ Segment &);
+
+ /// For use by guessHarmonies (makeHarmonyGuessList)
+ class PitchProfile
+ {
+ public:
+ PitchProfile();
+ double& operator[](int i);
+ const double& operator[](int i) const;
+ double distance(const PitchProfile &other);
+ double dotProduct(const PitchProfile &other);
+ double productScorer(const PitchProfile &other);
+ PitchProfile normalized();
+ PitchProfile& operator*=(double d);
+ PitchProfile& operator+=(const PitchProfile &d);
+ private:
+ double m_data[12];
+ };
+
+ /// For use by guessHarmonies (makeHarmonyGuessList)
+ typedef std::vector<std::pair<PitchProfile, ChordLabel> > HarmonyTable;
+ static HarmonyTable m_harmonyTable;
+
+ /// For use by guessHarmonies (makeHarmonyGuessList)
+ void checkHarmonyTable();
+
+ /// For use by guessHarmonies (refineHarmonyGuessList)
+ // #### grep ProgressionMap to something else
+ struct ChordProgression {
+ ChordProgression(ChordLabel first_,
+ ChordLabel second_ = ChordLabel(),
+ Key key_ = Key());
+ ChordLabel first;
+ ChordLabel second;
+ Key homeKey;
+ // double commonness...
+ bool operator<(const ChordProgression& other) const;
+ };
+ typedef std::set<ChordProgression> ProgressionMap;
+ static ProgressionMap m_progressionMap;
+
+ /// For use by guessHarmonies (refineHarmonyGuessList)
+ void checkProgressionMap();
+
+ /// For use by checkProgressionMap
+ void addProgressionToMap(Key k,
+ int firstChordNumber,
+ int secondChordNumber);
+
+};
+
+}
+
+#endif
diff --git a/src/base/AudioDevice.cpp b/src/base/AudioDevice.cpp
new file mode 100644
index 0000000..d9ff1f2
--- /dev/null
+++ b/src/base/AudioDevice.cpp
@@ -0,0 +1,107 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "AudioDevice.h"
+#include "Instrument.h"
+
+#include <cstdio>
+
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+
+namespace Rosegarden
+{
+
+AudioDevice::AudioDevice():Device(0, "Default Audio Device", Device::Audio)
+{
+}
+
+AudioDevice::AudioDevice(DeviceId id, const std::string &name):
+ Device(id, name, Device::Audio)
+{
+}
+
+
+AudioDevice::AudioDevice(const AudioDevice &dev):
+ Device(dev.getId(), dev.getName(), dev.getType())
+{
+ // Copy the instruments
+ //
+ InstrumentList insList = dev.getAllInstruments();
+ InstrumentList::iterator iIt = insList.begin();
+ for (; iIt != insList.end(); iIt++)
+ m_instruments.push_back(new Instrument(**iIt));
+}
+
+AudioDevice::~AudioDevice()
+{
+}
+
+
+std::string
+AudioDevice::toXmlString()
+{
+ std::stringstream audioDevice;
+ InstrumentList::iterator iit;
+
+ audioDevice << " <device id=\"" << m_id
+ << "\" name=\"" << m_name
+ << "\" type=\"audio\">" << std::endl;
+
+ for (iit = m_instruments.begin(); iit != m_instruments.end(); ++iit)
+ audioDevice << (*iit)->toXmlString();
+
+ audioDevice << " </device>"
+#if (__GNUC__ < 3)
+ << std::endl << std::ends;
+#else
+ << std::endl;
+#endif
+
+ return audioDevice.str();
+}
+
+
+// Add to instrument list
+//
+void
+AudioDevice::addInstrument(Instrument *instrument)
+{
+ m_instruments.push_back(instrument);
+}
+
+// For the moment just use the first audio Instrument
+//
+InstrumentId
+AudioDevice::getPreviewInstrument()
+{
+ return AudioInstrumentBase;
+}
+
+}
+
+
diff --git a/src/base/AudioDevice.h b/src/base/AudioDevice.h
new file mode 100644
index 0000000..671c781
--- /dev/null
+++ b/src/base/AudioDevice.h
@@ -0,0 +1,70 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <string>
+
+#include "Device.h"
+#include "Instrument.h"
+
+// An AudioDevice defines Instruments where we can play our
+// audio Segments.
+//
+//
+//
+#ifndef _AUDIODEVICE_H_
+#define _AUDIODEVICE_H_
+
+namespace Rosegarden
+{
+
+class AudioDevice : public Device
+{
+
+public:
+ AudioDevice();
+ AudioDevice(DeviceId id, const std::string &name);
+ virtual ~AudioDevice();
+
+ // Copy constructor
+ //
+ AudioDevice(const AudioDevice &);
+
+ virtual void addInstrument(Instrument*);
+
+ // An untainted Instrument we can use for playing previews
+ //
+ InstrumentId getPreviewInstrument();
+
+ // Turn into XML string
+ //
+ virtual std::string toXmlString();
+
+ virtual InstrumentList getAllInstruments() const { return m_instruments; }
+ virtual InstrumentList getPresentationInstruments() const
+ { return m_instruments; }
+
+private:
+
+};
+
+}
+
+#endif // _AUDIODEVICE_H_
diff --git a/src/base/AudioLevel.cpp b/src/base/AudioLevel.cpp
new file mode 100644
index 0000000..6772c97
--- /dev/null
+++ b/src/base/AudioLevel.cpp
@@ -0,0 +1,272 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "AudioLevel.h"
+#include <cmath>
+#include <iostream>
+#include <map>
+#include <vector>
+
+namespace Rosegarden {
+
+const float AudioLevel::DB_FLOOR = -1000.0;
+
+struct FaderDescription
+{
+ FaderDescription(float _minDb, float _maxDb, float _zeroPoint) :
+ minDb(_minDb), maxDb(_maxDb), zeroPoint(_zeroPoint) { }
+
+ float minDb;
+ float maxDb;
+ float zeroPoint; // as fraction of total throw
+};
+
+static const FaderDescription faderTypes[] = {
+ FaderDescription(-40.0, +6.0, 0.75), // short
+ FaderDescription(-70.0, +10.0, 0.80), // long
+ FaderDescription(-70.0, 0.0, 1.00), // IEC268
+ FaderDescription(-70.0, +10.0, 0.80), // IEC268 long
+ FaderDescription(-40.0, 0.0, 1.00), // preview
+};
+
+typedef std::vector<float> LevelList;
+static std::map<int, LevelList> previewLevelCache;
+static const LevelList &getPreviewLevelCache(int levels);
+
+float
+AudioLevel::multiplier_to_dB(float multiplier)
+{
+ if (multiplier == 0.0) return DB_FLOOR;
+ float dB = 10 * log10f(multiplier);
+ return dB;
+}
+
+float
+AudioLevel::dB_to_multiplier(float dB)
+{
+ if (dB == DB_FLOOR) return 0.0;
+ float m = powf(10.0, dB / 10.0);
+ return m;
+}
+
+/* IEC 60-268-18 fader levels. Thanks to Steve Harris. */
+
+static float iec_dB_to_fader(float db)
+{
+ float def = 0.0f; // Meter deflection %age
+
+ if (db < -70.0f) {
+ def = 0.0f;
+ } else if (db < -60.0f) {
+ def = (db + 70.0f) * 0.25f;
+ } else if (db < -50.0f) {
+ def = (db + 60.0f) * 0.5f + 5.0f;
+ } else if (db < -40.0f) {
+ def = (db + 50.0f) * 0.75f + 7.5f;
+ } else if (db < -30.0f) {
+ def = (db + 40.0f) * 1.5f + 15.0f;
+ } else if (db < -20.0f) {
+ def = (db + 30.0f) * 2.0f + 30.0f;
+ } else {
+ def = (db + 20.0f) * 2.5f + 50.0f;
+ }
+
+ return def;
+}
+
+static float iec_fader_to_dB(float def) // Meter deflection %age
+{
+ float db = 0.0f;
+
+ if (def >= 50.0f) {
+ db = (def - 50.0f) / 2.5f - 20.0f;
+ } else if (def >= 30.0f) {
+ db = (def - 30.0f) / 2.0f - 30.0f;
+ } else if (def >= 15.0f) {
+ db = (def - 15.0f) / 1.5f - 40.0f;
+ } else if (def >= 7.5f) {
+ db = (def - 7.5f) / 0.75f - 50.0f;
+ } else if (def >= 5.0f) {
+ db = (def - 5.0f) / 0.5f - 60.0f;
+ } else {
+ db = (def / 0.25f) - 70.0f;
+ }
+
+ return db;
+}
+
+float
+AudioLevel::fader_to_dB(int level, int maxLevel, FaderType type)
+{
+ if (level == 0) return DB_FLOOR;
+
+ if (type == IEC268Meter || type == IEC268LongMeter) {
+
+ float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb);
+ float percent = float(level) * maxPercent / float(maxLevel);
+ float dB = iec_fader_to_dB(percent);
+ return dB;
+
+ } else { // scale proportional to sqrt(fabs(dB))
+
+ int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint);
+
+ if (level >= zeroLevel) {
+
+ float value = level - zeroLevel;
+ float scale = float(maxLevel - zeroLevel) /
+ sqrtf(faderTypes[type].maxDb);
+ value /= scale;
+ float dB = powf(value, 2.0);
+ return dB;
+
+ } else {
+
+ float value = zeroLevel - level;
+ float scale = zeroLevel / sqrtf(0.0 - faderTypes[type].minDb);
+ value /= scale;
+ float dB = powf(value, 2.0);
+ return 0.0 - dB;
+ }
+ }
+}
+
+
+int
+AudioLevel::dB_to_fader(float dB, int maxLevel, FaderType type)
+{
+ if (dB == DB_FLOOR) return 0;
+
+ if (type == IEC268Meter || type == IEC268LongMeter) {
+
+ // The IEC scale gives a "percentage travel" for a given dB
+ // level, but it reaches 100% at 0dB. So we want to treat the
+ // result not as a percentage, but as a scale between 0 and
+ // whatever the "percentage" for our (possibly >0dB) max dB is.
+
+ float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb);
+ float percent = iec_dB_to_fader(dB);
+ int faderLevel = int((maxLevel * percent) / maxPercent + 0.01);
+
+ if (faderLevel < 0) faderLevel = 0;
+ if (faderLevel > maxLevel) faderLevel = maxLevel;
+ return faderLevel;
+
+ } else {
+
+ int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint);
+
+ if (dB >= 0.0) {
+
+ float value = sqrtf(dB);
+ float scale = (maxLevel - zeroLevel) / sqrtf(faderTypes[type].maxDb);
+ value *= scale;
+ int level = int(value + 0.01) + zeroLevel;
+ if (level > maxLevel) level = maxLevel;
+ return level;
+
+ } else {
+
+ dB = 0.0 - dB;
+ float value = sqrtf(dB);
+ float scale = zeroLevel / sqrtf(0.0 - faderTypes[type].minDb);
+ value *= scale;
+ int level = zeroLevel - int(value + 0.01);
+ if (level < 0) level = 0;
+ return level;
+ }
+ }
+}
+
+
+float
+AudioLevel::fader_to_multiplier(int level, int maxLevel, FaderType type)
+{
+ if (level == 0) return 0.0;
+ return dB_to_multiplier(fader_to_dB(level, maxLevel, type));
+}
+
+int
+AudioLevel::multiplier_to_fader(float multiplier, int maxLevel, FaderType type)
+{
+ if (multiplier == 0.0) return 0;
+ float dB = multiplier_to_dB(multiplier);
+ int fader = dB_to_fader(dB, maxLevel, type);
+ return fader;
+}
+
+
+const LevelList &
+getPreviewLevelCache(int levels)
+{
+ LevelList &ll = previewLevelCache[levels];
+ if (ll.empty()) {
+ for (int i = 0; i <= levels; ++i) {
+ float m = AudioLevel::fader_to_multiplier
+ (i, levels, AudioLevel::PreviewLevel);
+ if (levels == 1) m /= 100; // noise
+ ll.push_back(m);
+ }
+ }
+ return ll;
+}
+
+int
+AudioLevel::multiplier_to_preview(float m, int levels)
+{
+ const LevelList &ll = getPreviewLevelCache(levels);
+ int result = -1;
+
+ int lo = 0, hi = levels;
+
+ // binary search
+ int level = -1;
+ while (result < 0) {
+ int newlevel = (lo + hi) / 2;
+ if (newlevel == level ||
+ newlevel == 0 ||
+ newlevel == levels) {
+ result = newlevel;
+ break;
+ }
+ level = newlevel;
+ if (ll[level] >= m) {
+ hi = level;
+ } else if (ll[level+1] >= m) {
+ result = level;
+ } else {
+ lo = level;
+ }
+ }
+
+ return result;
+}
+
+float
+AudioLevel::preview_to_multiplier(int level, int levels)
+{
+ const LevelList &ll = getPreviewLevelCache(levels);
+ return ll[level];
+}
+
+
+}
+
diff --git a/src/base/AudioLevel.h b/src/base/AudioLevel.h
new file mode 100644
index 0000000..2dc742d
--- /dev/null
+++ b/src/base/AudioLevel.h
@@ -0,0 +1,67 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _AUDIO_LEVEL_H_
+#define _AUDIO_LEVEL_H_
+
+namespace Rosegarden {
+
+/**
+ * We need to represent audio levels in three different ways: as dB
+ * values; as a floating-point multiplier for gain; and as an integer
+ * on a scale for fader position and vu level. This class does the
+ * necessary conversions.
+ */
+
+class AudioLevel
+{
+public:
+
+ static const float DB_FLOOR;
+
+ enum FaderType {
+ ShortFader = 0, // -40 -> +6 dB
+ LongFader = 1, // -70 -> +10 dB
+ IEC268Meter = 2, // -70 -> 0 dB
+ IEC268LongMeter = 3, // -70 -> +10 dB (0dB aligns with LongFader)
+ PreviewLevel = 4
+ };
+
+ static float multiplier_to_dB(float multiplier);
+ static float dB_to_multiplier(float dB);
+
+ static float fader_to_dB(int level, int maxLevel, FaderType type);
+ static int dB_to_fader(float dB, int maxFaderLevel, FaderType type);
+
+ static float fader_to_multiplier(int level, int maxLevel, FaderType type);
+ static int multiplier_to_fader(float multiplier, int maxFaderLevel,
+ FaderType type);
+
+ // fast if "levels" doesn't change often -- for audio segment previews
+ static int multiplier_to_preview(float multiplier, int levels);
+ static float preview_to_multiplier(int level, int levels);
+};
+
+}
+
+#endif
+
+
diff --git a/src/base/AudioPluginInstance.cpp b/src/base/AudioPluginInstance.cpp
new file mode 100644
index 0000000..112687b
--- /dev/null
+++ b/src/base/AudioPluginInstance.cpp
@@ -0,0 +1,256 @@
+// -*- c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "AudioPluginInstance.h"
+#include "Instrument.h"
+
+#include <iostream>
+#include <cstring>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+namespace Rosegarden
+{
+
+// ------------------ PluginPort ---------------------
+//
+
+PluginPort::PluginPort(int number,
+ std::string name,
+ PluginPort::PortType type,
+ PluginPort::PortDisplayHint hint,
+ PortData lowerBound,
+ PortData upperBound,
+ PortData defaultValue):
+ m_number(number),
+ m_name(name),
+ m_type(type),
+ m_displayHint(hint),
+ m_lowerBound(lowerBound),
+ m_upperBound(upperBound),
+ m_default(defaultValue)
+{
+}
+
+AudioPluginInstance::AudioPluginInstance(unsigned int position):
+ m_mappedId(-1),
+ m_identifier(""),
+ m_position(position),
+ m_assigned(false),
+ m_bypass(false),
+ m_program("")
+{
+}
+
+AudioPluginInstance::AudioPluginInstance(std::string identifier,
+ unsigned int position):
+ m_mappedId(-1),
+ m_identifier(identifier),
+ m_position(position),
+ m_assigned(true)
+{
+}
+
+std::string
+AudioPluginInstance::toXmlString()
+{
+
+ std::stringstream plugin;
+
+ if (m_assigned == false)
+ {
+#if (__GNUC__ < 3)
+ plugin << std::ends;
+#endif
+ return plugin.str();
+ }
+
+ if (m_position == Instrument::SYNTH_PLUGIN_POSITION) {
+ plugin << " <synth ";
+ } else {
+ plugin << " <plugin"
+ << " position=\""
+ << m_position
+ << "\" ";
+ }
+
+ plugin << "identifier=\""
+ << encode(m_identifier)
+ << "\" bypassed=\"";
+
+ if (m_bypass)
+ plugin << "true\" ";
+ else
+ plugin << "false\" ";
+
+ if (m_program != "") {
+ plugin << "program=\"" << encode(m_program) << "\"";
+ }
+
+ plugin << ">" << std::endl;
+
+ for (unsigned int i = 0; i < m_ports.size(); i++)
+ {
+ plugin << " <port id=\""
+ << m_ports[i]->number
+ << "\" value=\""
+ << m_ports[i]->value
+ << "\" changed=\""
+ << (m_ports[i]->changedSinceProgramChange ? "true" : "false")
+ << "\"/>" << std::endl;
+ }
+
+ for (ConfigMap::iterator i = m_config.begin(); i != m_config.end(); ++i) {
+ plugin << " <configure key=\""
+ << encode(i->first) << "\" value=\""
+ << encode(i->second) << "\"/>" << std::endl;
+ }
+
+ if (m_position == Instrument::SYNTH_PLUGIN_POSITION) {
+ plugin << " </synth>";
+ } else {
+ plugin << " </plugin>";
+ }
+
+#if (__GNUC__ < 3)
+ plugin << std::endl << std::ends;
+#else
+ plugin << std::endl;
+#endif
+
+ return plugin.str();
+}
+
+
+void
+AudioPluginInstance::addPort(int number, PortData value)
+{
+ m_ports.push_back(new PluginPortInstance(number, value));
+}
+
+
+bool
+AudioPluginInstance::removePort(int number)
+{
+ PortInstanceIterator it = m_ports.begin();
+
+ for (; it != m_ports.end(); ++it)
+ {
+ if ((*it)->number == number)
+ {
+ delete (*it);
+ m_ports.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+PluginPortInstance*
+AudioPluginInstance::getPort(int number)
+{
+ PortInstanceIterator it = m_ports.begin();
+
+ for (; it != m_ports.end(); ++it)
+ {
+ if ((*it)->number == number)
+ return *it;
+ }
+
+ return 0;
+}
+
+void
+AudioPluginInstance::clearPorts()
+{
+ PortInstanceIterator it = m_ports.begin();
+ for (; it != m_ports.end(); ++it)
+ delete (*it);
+ m_ports.erase(m_ports.begin(), m_ports.end());
+}
+
+std::string
+AudioPluginInstance::getConfigurationValue(std::string k) const
+{
+ ConfigMap::const_iterator i = m_config.find(k);
+ if (i != m_config.end()) return i->second;
+ return "";
+}
+
+void
+AudioPluginInstance::setProgram(std::string program)
+{
+ m_program = program;
+
+ PortInstanceIterator it = m_ports.begin();
+ for (; it != m_ports.end(); ++it) {
+ (*it)->changedSinceProgramChange = false;
+ }
+}
+
+void
+AudioPluginInstance::setConfigurationValue(std::string k, std::string v)
+{
+ m_config[k] = v;
+}
+
+std::string
+AudioPluginInstance::getDistinctiveConfigurationText() const
+{
+ std::string base = getConfigurationValue("load");
+
+ if (base == "") {
+ for (ConfigMap::const_iterator i = m_config.begin();
+ i != m_config.end(); ++i) {
+
+ if (!strncmp(i->first.c_str(),
+ "__ROSEGARDEN__",
+ strlen("__ROSEGARDEN__"))) continue;
+
+ if (i->second != "" && i->second[0] == '/') {
+ base = i->second;
+ break;
+ } else if (base != "") {
+ base = i->second;
+ }
+ }
+ }
+
+ if (base == "") return "";
+
+ std::string::size_type s = base.rfind('/');
+ if (s < base.length() - 1) base = base.substr(s + 1);
+
+ std::string::size_type d = base.rfind('.');
+ if (d < base.length() - 1 && d > 0) base = base.substr(0, d);
+
+ return base;
+}
+
+
+}
+
diff --git a/src/base/AudioPluginInstance.h b/src/base/AudioPluginInstance.h
new file mode 100644
index 0000000..7641228
--- /dev/null
+++ b/src/base/AudioPluginInstance.h
@@ -0,0 +1,172 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <vector>
+#include <string>
+#include <map>
+
+#include "XmlExportable.h"
+
+// An Instrument on needs to implement these to render an instance
+// of the plugin at the sequencer.
+//
+
+#ifndef _AUDIOPLUGININSTANCE_H_
+#define _AUDIOPLUGININSTANCE_H_
+
+namespace Rosegarden
+{
+
+typedef float PortData;
+
+class PluginPort
+{
+public:
+ typedef enum
+ {
+ Input = 0x01,
+ Output = 0x02,
+ Control = 0x04,
+ Audio = 0x08
+ } PortType;
+
+ typedef enum
+ {
+ NoHint = 0x00,
+ Toggled = 0x01,
+ Integer = 0x02,
+ Logarithmic = 0x04
+ } PortDisplayHint;
+
+ PluginPort(int number,
+ std::string m_name,
+ PortType type,
+ PortDisplayHint displayHint,
+ PortData lowerBound,
+ PortData upperBound,
+ PortData defaultValue);
+
+ int getNumber() const { return m_number; }
+ std::string getName() const { return m_name; }
+ PortType getType() const { return m_type; }
+ PortDisplayHint getDisplayHint() const { return m_displayHint; }
+ PortData getLowerBound() const { return m_lowerBound; }
+ PortData getUpperBound() const { return m_upperBound; }
+ PortData getDefaultValue() const { return m_default; }
+
+protected:
+
+ int m_number;
+ std::string m_name;
+ PortType m_type;
+ PortDisplayHint m_displayHint;
+ PortData m_lowerBound;
+ PortData m_upperBound;
+ PortData m_default;
+};
+
+class PluginPortInstance
+{
+public:
+ PluginPortInstance(unsigned int n,
+ float v)
+ : number(n), value(v), changedSinceProgramChange(false) {;}
+
+ int number;
+ PortData value;
+ bool changedSinceProgramChange;
+
+ void setValue(PortData v) { value = v; changedSinceProgramChange = true; }
+};
+
+typedef std::vector<PluginPortInstance*>::iterator PortInstanceIterator;
+
+class AudioPluginInstance : public XmlExportable
+{
+public:
+ AudioPluginInstance(unsigned int position);
+
+ AudioPluginInstance(std::string identifier,
+ unsigned int position);
+
+ void setIdentifier(std::string identifier) { m_identifier = identifier; }
+ std::string getIdentifier() const { return m_identifier; }
+
+ void setPosition(unsigned int position) { m_position = position; }
+ unsigned int getPosition() const { return m_position; }
+
+ PortInstanceIterator begin() { return m_ports.begin(); }
+ PortInstanceIterator end() { return m_ports.end(); }
+
+ // Port management
+ //
+ void addPort(int number, PortData value);
+ bool removePort(int number);
+ PluginPortInstance* getPort(int number);
+ void clearPorts();
+
+ unsigned int getPortCount() const { return m_ports.size(); }
+
+ // export
+ std::string toXmlString();
+
+ // Is the instance assigned to a plugin?
+ //
+ void setAssigned(bool ass) { m_assigned = ass; }
+ bool isAssigned() const { return m_assigned; }
+
+ void setBypass(bool bypass) { m_bypass = bypass; }
+ bool isBypassed() const { return m_bypass; }
+
+ void setProgram(std::string program);
+ std::string getProgram() const { return m_program; }
+
+ int getMappedId() const { return m_mappedId; }
+ void setMappedId(int value) { m_mappedId = value; }
+
+ typedef std::map<std::string, std::string> ConfigMap;
+ void clearConfiguration() { m_config.clear(); }
+ const ConfigMap &getConfiguration() { return m_config; }
+ std::string getConfigurationValue(std::string k) const;
+ void setConfigurationValue(std::string k, std::string v);
+
+ std::string getDistinctiveConfigurationText() const;
+
+protected:
+
+ int m_mappedId;
+ std::string m_identifier;
+ std::vector<PluginPortInstance*> m_ports;
+ unsigned int m_position;
+
+ // Is the plugin actually assigned i.e. should we create
+ // a matching instance at the sequencer?
+ //
+ bool m_assigned;
+ bool m_bypass;
+
+ std::string m_program;
+
+ ConfigMap m_config;
+};
+
+}
+
+#endif // _AUDIOPLUGININSTANCE_H_
diff --git a/src/base/BaseProperties.cpp b/src/base/BaseProperties.cpp
new file mode 100644
index 0000000..adff519
--- /dev/null
+++ b/src/base/BaseProperties.cpp
@@ -0,0 +1,133 @@
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "BaseProperties.h"
+#include <vector>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+namespace Rosegarden
+{
+
+namespace BaseProperties
+{
+
+// Some of the most basic property names are defined in individual
+// classes in NotationTypes.h -- those are the ones that are used to
+// store the value of a clef/key/timesig event, whereas things like
+// notes have their values calculated from the duration property.
+
+// We mostly define persistent properties with lower-case names and
+// non-persistent ones with mixed-case. That's just because lower-
+// case looks nicer in XML, whereas mixed-case is friendlier for the
+// sorts of long names sometimes found in cached calculations.
+
+const PropertyName PITCH = "pitch";
+const PropertyName VELOCITY = "velocity";
+const PropertyName ACCIDENTAL = "accidental";
+
+const PropertyName NOTE_TYPE = "notetype";
+const PropertyName NOTE_DOTS = "notedots";
+
+const PropertyName MARK_COUNT = "marks";
+
+PropertyName getMarkPropertyName(int markNo)
+{
+ static std::vector<PropertyName> firstFive;
+
+ if (firstFive.size() == 0) {
+ firstFive.push_back(PropertyName("mark1"));
+ firstFive.push_back(PropertyName("mark2"));
+ firstFive.push_back(PropertyName("mark3"));
+ firstFive.push_back(PropertyName("mark4"));
+ firstFive.push_back(PropertyName("mark5"));
+ }
+
+ if (markNo < 5) return firstFive[markNo];
+
+ // This is slower than it looks, because it means we need to do
+ // the PropertyName interning process for each string -- hence the
+ // firstFive cache
+
+ std::stringstream markPropertyName;
+
+#if (__GNUC__ < 3)
+ markPropertyName << "mark" << (markNo + 1) << std::ends;
+#else
+ markPropertyName << "mark" << (markNo + 1);
+#endif
+
+ return markPropertyName.str();
+}
+
+const PropertyName TIED_BACKWARD = "tiedback";
+const PropertyName TIED_FORWARD = "tiedforward";
+const PropertyName TIE_IS_ABOVE = "tieabove";
+
+// capitalised for back-compatibility (used to be in NotationProperties)
+const PropertyName HEIGHT_ON_STAFF = "HeightOnStaff";
+const PropertyName NOTE_STYLE = "NoteStyle";
+const PropertyName BEAMED = "Beamed";
+
+const PropertyName BEAMED_GROUP_ID = "groupid";
+const PropertyName BEAMED_GROUP_TYPE = "grouptype";
+
+const PropertyName BEAMED_GROUP_TUPLET_BASE = "tupletbase";
+const PropertyName BEAMED_GROUP_TUPLED_COUNT = "tupledcount";
+const PropertyName BEAMED_GROUP_UNTUPLED_COUNT = "untupledcount";
+
+// persistent, but mixed-case anyway
+const PropertyName IS_GRACE_NOTE = "IsGraceNote";
+
+// obsolete
+const PropertyName HAS_GRACE_NOTES = "HasGraceNotes";
+
+// non-persistent
+const PropertyName MAY_HAVE_GRACE_NOTES = "MayHaveGraceNotes";
+
+const std::string GROUP_TYPE_BEAMED = "beamed";
+const std::string GROUP_TYPE_TUPLED = "tupled";
+const std::string GROUP_TYPE_GRACE = "grace";
+
+const PropertyName TRIGGER_SEGMENT_ID = "triggersegmentid";
+const PropertyName TRIGGER_SEGMENT_RETUNE = "triggersegmentretune";
+const PropertyName TRIGGER_SEGMENT_ADJUST_TIMES = "triggersegmentadjusttimes";
+
+const std::string TRIGGER_SEGMENT_ADJUST_NONE = "none";
+const std::string TRIGGER_SEGMENT_ADJUST_SQUISH = "squish";
+const std::string TRIGGER_SEGMENT_ADJUST_SYNC_START = "syncstart";
+const std::string TRIGGER_SEGMENT_ADJUST_SYNC_END = "syncend";
+
+const PropertyName RECORDED_CHANNEL = "recordedchannel";
+const PropertyName RECORDED_PORT = "recordedport";
+
+const PropertyName DISPLACED_X = "displacedx";
+const PropertyName DISPLACED_Y = "displacedy";
+
+const PropertyName INVISIBLE = "invisible";
+
+}
+
+}
+
diff --git a/src/base/BaseProperties.h b/src/base/BaseProperties.h
new file mode 100644
index 0000000..f83b2f7
--- /dev/null
+++ b/src/base/BaseProperties.h
@@ -0,0 +1,82 @@
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _BASE_PROPERTIES_H_
+#define _BASE_PROPERTIES_H_
+
+#include "PropertyName.h"
+
+namespace Rosegarden
+{
+
+namespace BaseProperties
+{
+
+extern const PropertyName PITCH;
+extern const PropertyName VELOCITY;
+extern const PropertyName ACCIDENTAL;
+
+extern const PropertyName NOTE_TYPE;
+extern const PropertyName NOTE_DOTS;
+
+extern const PropertyName MARK_COUNT;
+extern PropertyName getMarkPropertyName(int markNo);
+
+extern const PropertyName TIED_BACKWARD;
+extern const PropertyName TIED_FORWARD;
+extern const PropertyName TIE_IS_ABOVE; // optional; default position if absent
+
+extern const PropertyName BEAMED_GROUP_ID;
+extern const PropertyName BEAMED_GROUP_TYPE;
+
+extern const PropertyName BEAMED_GROUP_TUPLET_BASE;
+extern const PropertyName BEAMED_GROUP_TUPLED_COUNT;
+extern const PropertyName BEAMED_GROUP_UNTUPLED_COUNT;
+
+extern const PropertyName IS_GRACE_NOTE;
+extern const PropertyName HAS_GRACE_NOTES; // obsolete
+extern const PropertyName MAY_HAVE_GRACE_NOTES; // hint for use by performance helper
+
+extern const std::string GROUP_TYPE_BEAMED;
+extern const std::string GROUP_TYPE_TUPLED;
+extern const std::string GROUP_TYPE_GRACE; // obsolete
+
+extern const PropertyName TRIGGER_SEGMENT_ID;
+extern const PropertyName TRIGGER_SEGMENT_RETUNE;
+extern const PropertyName TRIGGER_SEGMENT_ADJUST_TIMES;
+
+extern const std::string TRIGGER_SEGMENT_ADJUST_NONE;
+extern const std::string TRIGGER_SEGMENT_ADJUST_SQUISH;
+extern const std::string TRIGGER_SEGMENT_ADJUST_SYNC_START;
+extern const std::string TRIGGER_SEGMENT_ADJUST_SYNC_END;
+
+extern const PropertyName RECORDED_CHANNEL;
+extern const PropertyName RECORDED_PORT;
+
+extern const PropertyName DISPLACED_X;
+extern const PropertyName DISPLACED_Y;
+
+extern const PropertyName INVISIBLE;
+
+}
+
+}
+
+#endif
+
diff --git a/src/base/BasicQuantizer.cpp b/src/base/BasicQuantizer.cpp
new file mode 100644
index 0000000..7cfc0db
--- /dev/null
+++ b/src/base/BasicQuantizer.cpp
@@ -0,0 +1,253 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "BasicQuantizer.h"
+#include "BaseProperties.h"
+#include "NotationTypes.h"
+#include "Selection.h"
+#include "Composition.h"
+#include "Profiler.h"
+
+#include <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+const std::string Quantizer::RawEventData = "";
+const std::string Quantizer::DefaultTarget = "DefaultQ";
+const std::string Quantizer::GlobalSource = "GlobalQ";
+const std::string Quantizer::NotationPrefix = "Notation";
+
+BasicQuantizer::BasicQuantizer(timeT unit, bool doDurations,
+ int swing, int iterate) :
+ Quantizer(RawEventData),
+ m_unit(unit),
+ m_durations(doDurations),
+ m_swing(swing),
+ m_iterate(iterate)
+{
+ if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration();
+}
+
+BasicQuantizer::BasicQuantizer(std::string source, std::string target,
+ timeT unit, bool doDurations,
+ int swing, int iterate) :
+ Quantizer(source, target),
+ m_unit(unit),
+ m_durations(doDurations),
+ m_swing(swing),
+ m_iterate(iterate)
+{
+ if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration();
+}
+
+BasicQuantizer::BasicQuantizer(const BasicQuantizer &q) :
+ Quantizer(q.m_target),
+ m_unit(q.m_unit),
+ m_durations(q.m_durations),
+ m_swing(q.m_swing),
+ m_iterate(q.m_iterate)
+{
+ // nothing else
+}
+
+BasicQuantizer::~BasicQuantizer()
+{
+ // nothing
+}
+
+void
+BasicQuantizer::quantizeSingle(Segment *s, Segment::iterator i) const
+{
+ timeT d = getFromSource(*i, DurationValue);
+
+ if (d == 0 && (*i)->isa(Note::EventType)) {
+ s->erase(i);
+ return;
+ }
+
+ if (m_unit == 0) return;
+
+ timeT t = getFromSource(*i, AbsoluteTimeValue);
+ timeT d0(d), t0(t);
+
+ timeT barStart = s->getBarStartForTime(t);
+
+ t -= barStart;
+
+ int n = t / m_unit;
+ timeT low = n * m_unit;
+ timeT high = low + m_unit;
+ timeT swingOffset = (m_unit * m_swing) / 300;
+
+ if (high - t > t - low) {
+ t = low;
+ } else {
+ t = high;
+ ++n;
+ }
+
+ if (n % 2 == 1) {
+ t += swingOffset;
+ }
+
+ if (m_durations && d != 0) {
+
+ low = (d / m_unit) * m_unit;
+ high = low + m_unit;
+
+ if (low > 0 && (high - d > d - low)) {
+ d = low;
+ } else {
+ d = high;
+ }
+
+ int n1 = n + d / m_unit;
+
+ if (n % 2 == 0) { // start not swung
+ if (n1 % 2 == 0) { // end not swung
+ // do nothing
+ } else { // end swung
+ d += swingOffset;
+ }
+ } else { // start swung
+ if (n1 % 2 == 0) { // end not swung
+ d -= swingOffset;
+ } else {
+ // do nothing
+ }
+ }
+ }
+
+ t += barStart;
+
+ timeT t1(t), d1(d);
+ t = (t - t0) * m_iterate / 100 + t0;
+ d = (d - d0) * m_iterate / 100 + d0;
+
+ // if an iterative quantize results in something much closer than
+ // the shortest actual note resolution we have, just snap it
+ if (m_iterate != 100) {
+ timeT close = Note(Note::Shortest).getDuration()/2;
+ if (t >= t1 - close && t <= t1 + close) t = t1;
+ if (d >= d1 - close && d <= d1 + close) d = d1;
+ }
+
+ if (t0 != t || d0 != d) setToTarget(s, i, t, d);
+}
+
+
+std::vector<timeT>
+BasicQuantizer::getStandardQuantizations()
+{
+ checkStandardQuantizations();
+ return m_standardQuantizations;
+}
+
+void
+BasicQuantizer::checkStandardQuantizations()
+{
+ if (m_standardQuantizations.size() > 0) return;
+
+ for (Note::Type nt = Note::Semibreve; nt >= Note::Shortest; --nt) {
+
+ int i1 = (nt < Note::Quaver ? 1 : 0);
+ for (int i = 0; i <= i1; ++i) {
+
+ int divisor = (1 << (Note::Semibreve - nt));
+ if (i) divisor = divisor * 3 / 2;
+
+ timeT unit = Note(Note::Semibreve).getDuration() / divisor;
+ m_standardQuantizations.push_back(unit);
+ }
+ }
+}
+
+timeT
+BasicQuantizer::getStandardQuantization(Segment *s)
+{
+ checkStandardQuantizations();
+ timeT unit = -1;
+
+ for (Segment::iterator i = s->begin(); s->isBeforeEndMarker(i); ++i) {
+
+ if (!(*i)->isa(Rosegarden::Note::EventType)) continue;
+ timeT myUnit = getUnitFor(*i);
+ if (unit < 0 || myUnit < unit) unit = myUnit;
+ }
+
+ return unit;
+}
+
+timeT
+BasicQuantizer::getStandardQuantization(EventSelection *s)
+{
+ checkStandardQuantizations();
+ timeT unit = -1;
+
+ if (!s) return 0;
+
+ for (EventSelection::eventcontainer::iterator i =
+ s->getSegmentEvents().begin();
+ i != s->getSegmentEvents().end(); ++i) {
+
+ if (!(*i)->isa(Rosegarden::Note::EventType)) continue;
+ timeT myUnit = getUnitFor(*i);
+ if (unit < 0 || myUnit < unit) unit = myUnit;
+ }
+
+ return unit;
+}
+
+timeT
+BasicQuantizer::getUnitFor(Event *e)
+{
+ timeT absTime = e->getAbsoluteTime();
+ timeT myQuantizeUnit = 0;
+
+ // m_quantizations is in descending order of duration;
+ // stop when we reach one that divides into the note's time
+
+ for (unsigned int i = 0; i < m_standardQuantizations.size(); ++i) {
+ if (absTime % m_standardQuantizations[i] == 0) {
+ myQuantizeUnit = m_standardQuantizations[i];
+ break;
+ }
+ }
+
+ return myQuantizeUnit;
+}
+
+std::vector<timeT>
+BasicQuantizer::m_standardQuantizations;
+
+
+}
diff --git a/src/base/BasicQuantizer.h b/src/base/BasicQuantizer.h
new file mode 100644
index 0000000..1a9a7b7
--- /dev/null
+++ b/src/base/BasicQuantizer.h
@@ -0,0 +1,95 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef BASIC_QUANTIZER_H
+#define BASIC_QUANTIZER_H
+
+#include "Quantizer.h"
+
+namespace Rosegarden {
+
+class BasicQuantizer : public Quantizer
+{
+public:
+ // The default unit is the shortest note type. A unit of
+ // zero means do no quantization (rather pointlessly).
+ BasicQuantizer(timeT unit = -1, bool doDurations = false,
+ int swingPercent = 0, int iteratePercent = 100);
+ BasicQuantizer(std::string source, std::string target,
+ timeT unit = -1, bool doDurations = false,
+ int swingPercent = 0, int iteratePercent = 100);
+ BasicQuantizer(const BasicQuantizer &);
+ virtual ~BasicQuantizer();
+
+ void setUnit(timeT unit) { m_unit = unit; }
+ timeT getUnit() const { return m_unit; }
+
+ void setDoDurations(bool doDurations) { m_durations = doDurations; }
+ bool getDoDurations() const { return m_durations; }
+
+ void setSwing(int percent) { m_swing = percent; }
+ int getSwing() const { return m_swing; }
+
+ void setIterative(int percent) { m_iterate = percent; }
+ int getIterative() const { return m_iterate; }
+
+ /**
+ * Return the standard quantization units in descending order of
+ * unit duration
+ */
+ static std::vector<timeT> getStandardQuantizations();
+
+ /**
+ * Study the given segment; if all the events in it have times
+ * that match one or more of the standard quantizations, return
+ * the longest standard quantization unit to match. Otherwise
+ * return 0.
+ */
+ static timeT getStandardQuantization(Segment *);
+
+ /**
+ * Study the given selection; if all the events in it have times
+ * that match one or more of the standard quantizations, return
+ * the longest standard quantization unit to match. Otherwise
+ * return 0.
+ */
+ static timeT getStandardQuantization(EventSelection *);
+
+protected:
+ virtual void quantizeSingle(Segment *,
+ Segment::iterator) const;
+
+private:
+ BasicQuantizer &operator=(const BasicQuantizer &); // not provided
+
+ timeT m_unit;
+ bool m_durations;
+ int m_swing;
+ int m_iterate;
+
+ static std::vector<timeT> m_standardQuantizations;
+ static void checkStandardQuantizations();
+ static timeT getUnitFor(Event *);
+};
+
+}
+
+#endif
diff --git a/src/base/Clipboard.cpp b/src/base/Clipboard.cpp
new file mode 100644
index 0000000..71ff03f
--- /dev/null
+++ b/src/base/Clipboard.cpp
@@ -0,0 +1,387 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Clipboard.h"
+#include "Selection.h"
+
+namespace Rosegarden
+{
+
+Clipboard::Clipboard() :
+ m_partial(false),
+ m_haveTimeSigSelection(false),
+ m_haveTempoSelection(false),
+ m_nominalStart(0),
+ m_nominalEnd(0)
+{
+ // nothing
+}
+
+Clipboard::Clipboard(const Clipboard &c) :
+ m_partial(false)
+{
+ copyFrom(&c);
+}
+
+Clipboard &
+Clipboard::operator=(const Clipboard &c)
+{
+ copyFrom(&c);
+ return *this;
+}
+
+Clipboard::~Clipboard()
+{
+ clear();
+}
+
+void
+Clipboard::clear()
+{
+ for (iterator i = begin(); i != end(); ++i) {
+ delete *i;
+ }
+ m_segments.clear();
+ clearTimeSignatureSelection();
+ clearTempoSelection();
+ clearNominalRange();
+ m_partial = false;
+}
+
+bool
+Clipboard::isEmpty() const
+{
+ return (m_segments.size() == 0 &&
+ !m_haveTimeSigSelection &&
+ !m_haveTempoSelection &&
+ m_nominalStart == m_nominalEnd);
+}
+
+bool
+Clipboard::isSingleSegment() const
+{
+ return (m_segments.size() == 1 &&
+ !m_haveTimeSigSelection &&
+ !m_haveTempoSelection);
+}
+
+Segment *
+Clipboard::getSingleSegment() const
+{
+ if (isSingleSegment()) return *begin();
+ else return 0;
+}
+
+bool
+Clipboard::isPartial() const
+{
+ return m_partial;
+}
+
+Segment *
+Clipboard::newSegment()
+{
+ Segment *s = new Segment();
+ m_segments.insert(s);
+ // don't change m_partial
+ return s;
+}
+
+Segment *
+Clipboard::newSegment(const Segment *copyFrom)
+{
+ Segment *s = new Segment(*copyFrom);
+ m_segments.insert(s);
+ // don't change m_partial
+ return s;
+}
+
+void
+Clipboard::newSegment(const Segment *copyFrom, timeT from, timeT to,
+ bool expandRepeats)
+{
+ // create with copy ctor so as to inherit track, instrument etc
+ Segment *s = new Segment(*copyFrom);
+
+ if (from <= s->getStartTime() && to >= s->getEndMarkerTime()) {
+ m_segments.insert(s);
+ s->setEndTime(s->getEndMarkerTime());
+ // don't change m_partial
+ return;
+ }
+
+ timeT segStart = copyFrom->getStartTime();
+ timeT segEnd = copyFrom->getEndMarkerTime();
+ timeT segDuration = segEnd - segStart;
+
+ int firstRepeat = 0;
+ int lastRepeat = 0;
+
+ if (!copyFrom->isRepeating() || segDuration <= 0) {
+ expandRepeats = false;
+ }
+
+ if (expandRepeats) {
+ firstRepeat = (from - segStart) / segDuration;
+ lastRepeat = (to - segStart) / segDuration;
+ to = std::min(to, copyFrom->getRepeatEndTime());
+ }
+
+ s->setRepeating(false);
+
+ if (s->getType() == Segment::Audio) {
+
+ Composition *c = copyFrom->getComposition();
+
+ for (int repeat = firstRepeat; repeat <= lastRepeat; ++repeat) {
+
+ timeT wrappedFrom = segStart;
+ timeT wrappedTo = segEnd;
+
+ if (!expandRepeats) {
+ wrappedFrom = from;
+ wrappedTo = to;
+ } else {
+ if (repeat == firstRepeat) {
+ wrappedFrom = segStart + (from - segStart) % segDuration;
+ }
+ if (repeat == lastRepeat) {
+ wrappedTo = segStart + (to - segStart) % segDuration;
+ }
+ }
+
+ if (wrappedFrom > segStart) {
+ if (c) {
+ s->setAudioStartTime
+ (s->getAudioStartTime() +
+ c->getRealTimeDifference(segStart + repeat * segDuration,
+ from));
+ }
+ s->setStartTime(from);
+ } else {
+ s->setStartTime(segStart + repeat * segDuration);
+ }
+
+ if (wrappedTo < segEnd) {
+ s->setEndMarkerTime(to);
+ if (c) {
+ s->setAudioEndTime
+ (s->getAudioStartTime() +
+ c->getRealTimeDifference(segStart + repeat * segDuration,
+ to));
+ }
+ } else {
+ s->setEndMarkerTime(segStart + (repeat + 1) * segDuration);
+ }
+
+ m_segments.insert(s);
+ if (repeat < lastRepeat) {
+ s = new Segment(*copyFrom);
+ s->setRepeating(false);
+ }
+ }
+
+ m_partial = true;
+ return;
+ }
+
+ s->erase(s->begin(), s->end());
+
+ for (int repeat = firstRepeat; repeat <= lastRepeat; ++repeat) {
+
+ Segment::const_iterator ifrom = copyFrom->begin();
+ Segment::const_iterator ito = copyFrom->end();
+
+ if (!expandRepeats) {
+ ifrom = copyFrom->findTime(from);
+ ito = copyFrom->findTime(to);
+ } else {
+ if (repeat == firstRepeat) {
+ ifrom = copyFrom->findTime
+ (segStart + (from - segStart) % segDuration);
+ }
+ if (repeat == lastRepeat) {
+ ito = copyFrom->findTime
+ (segStart + (to - segStart) % segDuration);
+ }
+ }
+
+ for (Segment::const_iterator i = ifrom;
+ i != ito && copyFrom->isBeforeEndMarker(i); ++i) {
+
+ timeT absTime = (*i)->getAbsoluteTime() + repeat * segDuration;
+ timeT duration = (*i)->getDuration();
+
+ Event *e = (*i)->copyMoving(repeat * segDuration);
+
+ if (absTime + duration <= to) {
+
+ s->insert(e);
+
+ } else {
+
+ s->insert(new Event(*e,
+ e->getAbsoluteTime(),
+ duration,
+ e->getSubOrdering(),
+ e->getNotationAbsoluteTime(),
+ e->getNotationDuration()));
+ delete e;
+ }
+ }
+ }
+
+ // need to call getEndMarkerTime() on copyFrom, not on s, because
+ // its return value may depend on the composition it's in
+ if (copyFrom->getEndMarkerTime() > to) {
+ s->setEndMarkerTime(to);
+ }
+
+ m_segments.insert(s);
+ m_partial = true;
+ return;
+}
+
+Segment *
+Clipboard::newSegment(const EventSelection *copyFrom)
+{
+ // create with copy ctor so as to inherit track, instrument etc
+ Segment *s = new Segment(copyFrom->getSegment());
+ s->erase(s->begin(), s->end());
+
+ const EventSelection::eventcontainer &events(copyFrom->getSegmentEvents());
+ for (EventSelection::eventcontainer::const_iterator i = events.begin();
+ i != events.end(); ++i) {
+ s->insert(new Event(**i));
+ }
+
+ m_segments.insert(s);
+ m_partial = true;
+ return s;
+}
+
+void
+Clipboard::setTimeSignatureSelection(const TimeSignatureSelection &ts)
+{
+ m_timeSigSelection = ts;
+ m_haveTimeSigSelection = true;
+}
+
+void
+Clipboard::clearTimeSignatureSelection()
+{
+ m_timeSigSelection = TimeSignatureSelection();
+ m_haveTimeSigSelection = false;
+}
+
+const TimeSignatureSelection &
+Clipboard::getTimeSignatureSelection() const
+{
+ return m_timeSigSelection;
+}
+
+void
+Clipboard::setTempoSelection(const TempoSelection &ts)
+{
+ m_tempoSelection = ts;
+ m_haveTempoSelection = true;
+}
+
+void
+Clipboard::clearTempoSelection()
+{
+ m_tempoSelection = TempoSelection();
+ m_haveTempoSelection = false;
+}
+
+const TempoSelection &
+Clipboard::getTempoSelection() const
+{
+ return m_tempoSelection;
+}
+
+void
+Clipboard::copyFrom(const Clipboard *c)
+{
+ if (c == this) return;
+ clear();
+
+ for (Clipboard::const_iterator i = c->begin(); i != c->end(); ++i) {
+ newSegment(*i);
+ }
+
+ m_partial = c->m_partial;
+
+ m_timeSigSelection = c->m_timeSigSelection;
+ m_haveTimeSigSelection = c->m_haveTimeSigSelection;
+
+ m_tempoSelection = c->m_tempoSelection;
+ m_haveTempoSelection = c->m_haveTempoSelection;
+
+ m_nominalStart = c->m_nominalStart;
+ m_nominalEnd = c->m_nominalEnd;
+}
+
+timeT
+Clipboard::getBaseTime() const
+{
+ if (hasNominalRange()) {
+ return m_nominalStart;
+ }
+
+ timeT t = 0;
+
+ for (iterator i = begin(); i != end(); ++i) {
+ if (i == begin() || (*i)->getStartTime() < t) {
+ t = (*i)->getStartTime();
+ }
+ }
+
+ if (m_haveTimeSigSelection && !m_timeSigSelection.empty()) {
+ if (m_timeSigSelection.begin()->first < t) {
+ t = m_timeSigSelection.begin()->first;
+ }
+ }
+
+ if (m_haveTempoSelection && !m_tempoSelection.empty()) {
+ if (m_tempoSelection.begin()->first < t) {
+ t = m_tempoSelection.begin()->first;
+ }
+ }
+
+ return t;
+}
+
+void
+Clipboard::setNominalRange(timeT start, timeT end)
+{
+ m_nominalStart = start;
+ m_nominalEnd = end;
+}
+
+void
+Clipboard::getNominalRange(timeT &start, timeT &end)
+{
+ start = m_nominalStart;
+ end = m_nominalEnd;
+}
+
+}
diff --git a/src/base/Clipboard.h b/src/base/Clipboard.h
new file mode 100644
index 0000000..e205e33
--- /dev/null
+++ b/src/base/Clipboard.h
@@ -0,0 +1,203 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _CLIPBOARD_H_
+#define _CLIPBOARD_H_
+
+#include <set>
+#include "Segment.h"
+#include "Selection.h"
+
+namespace Rosegarden
+{
+class EventSelection;
+
+/**
+ * Simple container for segments, that can serve as a clipboard for
+ * editing operations. Conceptually it has two "modes",
+ * single-segment and multiple-segment, although there's no particular
+ * distinction behind the scenes. The Clipboard owns all the segments
+ * it contains -- they should always be deep copies, not aliases.
+ */
+
+class Clipboard
+{
+public:
+ typedef std::multiset<Segment *, Segment::SegmentCmp> segmentcontainer;
+ typedef segmentcontainer::iterator iterator;
+ typedef segmentcontainer::const_iterator const_iterator;
+
+ Clipboard();
+ Clipboard(const Clipboard &);
+ Clipboard &operator=(const Clipboard &);
+ virtual ~Clipboard();
+
+ /**
+ * Empty the clipboard.
+ */
+ void clear();
+
+ /**
+ * Return true if the clipboard is empty.
+ */
+ bool isEmpty() const;
+
+ iterator begin() { return m_segments.begin(); }
+ const_iterator begin() const { return m_segments.begin(); }
+ iterator end() { return m_segments.end(); }
+ const_iterator end() const { return m_segments.end(); }
+
+ /**
+ * Return true if the clipboard only contains a single segment.
+ * Single-segment and multi-segment are conceptually rather
+ * separate -- for example, you can only paste into a segment
+ * from a single-segment clipboard.
+ */
+ bool isSingleSegment() const;
+
+ /**
+ * Return true if the clipboard contains at least one segment
+ * that originated as only part of another segment. If a
+ * paste is made from a clipboard with isPartial true, the
+ * paste command will generally want to be sure to normalize
+ * rests etc on the pasted region afterwards.
+ */
+ bool isPartial() const;
+
+ /**
+ * Return the single segment contained by the clipboard.
+ * If the clipboard is empty or contains more than one segment,
+ * returns null. (Use the iterator accessors begin()/end() to
+ * read from a clipboard for which isSingleSegment is false.)
+ */
+ Segment *getSingleSegment() const;
+
+ /**
+ * Add a new empty segment to the clipboard, and return a
+ * pointer to it. (The clipboard retains ownership.)
+ */
+ Segment *newSegment();
+
+ /**
+ * Add a new segment to the clipboard, containing copies of
+ * the events in copyFrom. (The clipboard retains ownership
+ * of the new segment.)
+ */
+ Segment *newSegment(const Segment *copyFrom);
+
+ /**
+ * Add one or more new segments to the clipboard, containing
+ * copies of the events in copyFrom found between from and to. If
+ * expandRepeats is true, include any events found in the
+ * repeating trail of the segment within this time. (The
+ * clipboard retains ownership of the new segment(s).)
+ *
+ * This may insert more than one new segment, if it is required to
+ * insert a repeating section of an audio segment. For this
+ * reason it does not return the inserted segment (even though in
+ * most situations it will only insert one).
+ */
+ void newSegment(const Segment *copyFrom, timeT from, timeT to,
+ bool expandRepeats);
+
+ /**
+ * Add a new segment to the clipboard, containing copied of
+ * the events in the given selection.
+ */
+ Segment *newSegment(const EventSelection *copyFrom);
+
+ /**
+ * Add a time signature selection to this clipboard, replacing any
+ * that already exists.
+ */
+ void setTimeSignatureSelection(const TimeSignatureSelection &);
+
+ bool hasTimeSignatureSelection() const { return m_haveTimeSigSelection; }
+
+ /**
+ * Remove any time signature selection from the clipboard.
+ */
+ void clearTimeSignatureSelection();
+
+ /**
+ * Retrieve any time signature selection found in the clipboard.
+ */
+ const TimeSignatureSelection &getTimeSignatureSelection() const;
+
+ /**
+ * Add a tempo selection to this clipboard, replacing any
+ * that already exists.
+ */
+ void setTempoSelection(const TempoSelection &);
+
+ bool hasTempoSelection() const { return m_haveTempoSelection; }
+
+ /**
+ * Remove any tempo selection from the clipboard.
+ */
+ void clearTempoSelection();
+
+ /**
+ * Retrieve any tempo selection found in the clipboard.
+ */
+ const TempoSelection &getTempoSelection() const;
+
+ /**
+ * Clear the current clipboard and re-fill it by copying from c.
+ */
+ void copyFrom(const Clipboard *c);
+
+ /**
+ * Get the earliest start time for anything in this clipboard,
+ * or the start of the nominal range if there is one.
+ */
+ timeT getBaseTime() const;
+
+ /**
+ * Set nominal start and end times for the range in the clipboard,
+ * if it is intended to cover a particular time range regardless
+ * of whether the data in it covers the full range or not.
+ */
+ void setNominalRange(timeT start, timeT end);
+
+ void clearNominalRange() { setNominalRange(0, 0); }
+
+ bool hasNominalRange() const { return m_nominalStart != m_nominalEnd; }
+
+ void getNominalRange(timeT &start, timeT &end);
+
+private:
+ segmentcontainer m_segments;
+ bool m_partial;
+
+ TimeSignatureSelection m_timeSigSelection;
+ bool m_haveTimeSigSelection;
+
+ TempoSelection m_tempoSelection;
+ bool m_haveTempoSelection;
+
+ timeT m_nominalStart;
+ timeT m_nominalEnd;
+};
+
+}
+
+#endif
diff --git a/src/base/Colour.cpp b/src/base/Colour.cpp
new file mode 100644
index 0000000..ea1f5a2
--- /dev/null
+++ b/src/base/Colour.cpp
@@ -0,0 +1,175 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2003
+ Mark Hymers <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Colour.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+namespace Rosegarden
+{
+
+// The Colour Class
+
+Colour::Colour()
+{
+ m_r = 0;
+ m_g = 0;
+ m_b = 0;
+}
+
+Colour::Colour(const unsigned int red, const unsigned int green, const unsigned int blue)
+{
+ this->setColour(red, green, blue);
+}
+
+Colour::Colour(const Colour& input)
+{
+ this->setColour(input.getRed(), input.getGreen(), input.getBlue());
+}
+
+Colour::~Colour()
+{
+
+}
+
+Colour&
+Colour::operator= (const Colour& input)
+{
+ // We don't have to check for assignment to self because it'll have
+ // no nasty effects (in fact, it'll do what it should - nothing)
+ this->setColour(input.getRed(), input.getGreen(), input.getBlue());
+ return *this;
+}
+
+void
+Colour::setColour(const unsigned int red, const unsigned int green, const unsigned int blue)
+{
+ (red<=255) ? m_r=red : m_r=0;
+ (green<=255) ? m_g=green : m_g=0;
+ (blue<=255) ? m_b=blue : m_b=0;
+}
+
+void
+Colour::getColour(unsigned int &red, unsigned int &green, unsigned int &blue) const
+{
+ red = m_r;
+ green = m_g;
+ blue = m_b;
+}
+
+unsigned int
+Colour::getRed() const
+{
+ return m_r;
+}
+
+unsigned int
+Colour::getBlue() const
+{
+ return m_b;
+}
+
+unsigned int
+Colour::getGreen() const
+{
+ return m_g;
+}
+
+void
+Colour::setRed(const unsigned int red)
+{
+ (red<=255) ? m_r=red : m_r=0;
+}
+
+void
+Colour::setBlue(const unsigned int blue)
+{
+ (blue<=255) ? m_b=blue: m_b=0;
+}
+
+void
+Colour::setGreen(const unsigned int green)
+{
+ (green<=255) ? m_g=green : m_g=0;
+}
+
+Colour
+Colour::getContrastingColour() const
+{
+ Colour ret(255-m_r, 255-m_g, 255-m_b);
+ return ret;
+}
+
+std::string
+Colour::toXmlString() const
+{
+ std::stringstream output;
+
+ output << "<colour red=\"" << m_r
+ << "\" green=\"" << m_g
+ << "\" blue=\"" << m_b
+#if (__GNUC__ < 3)
+ << "\"/>" << std::endl << std::ends;
+#else
+ << "\"/>" << std::endl;
+#endif
+
+ return output.str();
+}
+
+std::string
+Colour::dataToXmlString() const
+{
+ std::stringstream output;
+ output << "red=\"" << m_r
+ << "\" green=\"" << m_g
+ << "\" blue=\"" << m_b
+#if (__GNUC__ < 3)
+ << "\"" << std::ends;
+#else
+ << "\"";
+#endif
+
+ return output.str();
+}
+
+// Generic Colour routines:
+
+Colour
+getCombinationColour(const Colour &input1, const Colour &input2)
+{
+ Colour ret((input1.getRed()+input2.getRed())/2,
+ (input1.getGreen()+input2.getGreen())/2,
+ (input1.getBlue()+input2.getBlue())/2);
+ return ret;
+
+}
+
+}
diff --git a/src/base/Colour.h b/src/base/Colour.h
new file mode 100644
index 0000000..ba8cd6f
--- /dev/null
+++ b/src/base/Colour.h
@@ -0,0 +1,125 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2003
+ Mark Hymers <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _BASE_COLOUR_H_
+#define _BASE_COLOUR_H_
+
+#include <string>
+
+namespace Rosegarden
+{
+
+/**
+ * Colour is our internal colour storage mechanism; it's extremely basic
+ * but does what it needs to
+ */
+
+class Colour
+{
+public:
+ /**
+ * Create a Colour with values initialised to r=0, g=0, b=0
+ * i.e. Black.
+ */
+ Colour();
+
+ /**
+ * Create a Colour with the specified red, green, blue values.
+ * If out of specification (i.e. < 0 || > 255 the value will be set to 0.
+ */
+ Colour(unsigned int red, unsigned int green, unsigned int blue);
+ Colour(const Colour& input);
+
+ ~Colour();
+ Colour& operator= (const Colour& input);
+
+ /**
+ * Set the colour as appropriate; as with the constructor invalid values
+ * will be set to 0.
+ */
+ void setColour(unsigned int red, unsigned int green, unsigned int blue);
+
+ /**
+ * Sets the three pointers to the values stored in the colour.
+ */
+ void getColour(unsigned int &red, unsigned int &green, unsigned int &blue) const;
+
+ /**
+ * Returns the current Red value of the colour as an integer.
+ */
+ unsigned int getRed() const;
+
+ /**
+ * Returns the current Blue value of the colour as an integer.
+ */
+ unsigned int getBlue() const;
+
+ /**
+ * Returns the current Green value of the colour as an integer.
+ */
+ unsigned int getGreen() const;
+
+ /**
+ * Sets the Red value of the current colour. If the value isn't
+ * between 0 and 255 inclusive, it will set to 0
+ */
+ void setRed(unsigned int input);
+
+ /**
+ * Sets the Blue value of the current colour. If the value isn't
+ * between 0 and 255 inclusive, it will set to 0
+ */
+ void setBlue(unsigned int input);
+
+ /**
+ * Sets the Green value of the current colour. If the value isn't
+ * between 0 and 255 inclusive, it will set to 0
+ */
+ void setGreen(unsigned int input);
+
+ /**
+ * This uses a simple calculation to give us a contrasting colour.
+ * Useful for working out a visible text colour given
+ * any background colour
+ */
+ Colour getContrastingColour() const;
+
+ std::string toXmlString() const;
+
+ std::string dataToXmlString() const;
+
+private:
+ unsigned int m_r, m_g, m_b;
+};
+
+ /**
+ * This works out a colour which is directly in between the two inputs.
+ * Useful for working out what overlapping Segments should be coloured as
+ */
+ Colour getCombinationColour(const Colour &input1, const Colour &input2);
+
+}
+
+#endif
diff --git a/src/base/ColourMap.cpp b/src/base/ColourMap.cpp
new file mode 100644
index 0000000..322a4a7
--- /dev/null
+++ b/src/base/ColourMap.cpp
@@ -0,0 +1,266 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2003
+ Mark Hymers <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <string>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include "ColourMap.h"
+#include "XmlExportable.h"
+
+namespace Rosegarden
+{
+
+ColourMap::ColourMap()
+{
+ // Set up the default colour. The #defines can be found in ColourMap.h
+ Colour tempcolour(COLOUR_DEF_R, COLOUR_DEF_G, COLOUR_DEF_B);
+ m_map[0] = make_pair(tempcolour, std::string(""));
+}
+
+ColourMap::ColourMap(const Colour& input)
+{
+ // Set up the default colour based on the input
+ m_map[0] = make_pair(input, std::string(""));
+}
+
+ColourMap::~ColourMap()
+{
+ // Everything should destroy itself automatically
+}
+
+bool
+ColourMap::deleteItemByIndex(const unsigned int item_num)
+{
+ // We explicitly refuse to delete the default colour
+ if (item_num == 0)
+ return false;
+
+ unsigned int n_e = m_map.erase(item_num);
+ if (n_e != 0)
+ {
+ return true;
+ }
+
+ // Otherwise we didn't find the right item
+ return false;
+}
+
+Colour
+ColourMap::getColourByIndex(const unsigned int item_num) const
+{
+ // Iterate over the m_map and if we find a match, return the
+ // Colour. If we don't match, return the default colour. m_map
+ // was initialised with at least one item in the ctor, so this is
+ // safe.
+ Colour ret = (*m_map.begin()).second.first;
+
+ for (RCMap::const_iterator position = m_map.begin();
+ position != m_map.end(); ++position)
+ if (position->first == item_num)
+ ret = position->second.first;
+
+ return ret;
+}
+
+std::string
+ColourMap::getNameByIndex(const unsigned int item_num) const
+{
+ // Iterate over the m_map and if we find a match, return the name.
+ // If we don't match, return the default colour's name. m_map was
+ // initialised with at least one item in the ctor, so this is
+ // safe.
+ std::string ret = (*m_map.begin()).second.second;
+
+ for (RCMap::const_iterator position = m_map.begin();
+ position != m_map.end(); ++position)
+ if (position->first == item_num)
+ ret = position->second.second;
+
+ return ret;
+}
+
+bool
+ColourMap::addItem(const Colour colour, const std::string name)
+{
+ // If we want to limit the number of colours, here's the place to do it
+ unsigned int highest=0;
+
+ for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position)
+ {
+ if (position->first != highest)
+ break;
+
+ ++highest;
+ }
+
+ m_map[highest] = make_pair(colour, name);
+
+ return true;
+}
+
+// WARNING: This version of addItem is only for use by rosexmlhandler.cpp
+bool
+ColourMap::addItem(const Colour colour, const std::string name, const unsigned int id)
+{
+ m_map[id] = make_pair(colour, name);
+
+ return true;
+}
+
+bool
+ColourMap::modifyNameByIndex(const unsigned int item_num, const std::string name)
+{
+ // We don't allow a name to be given to the default colour
+ if (item_num == 0)
+ return false;
+
+ for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position)
+ if (position->first == item_num)
+ {
+ position->second.second = name;
+ return true;
+ }
+
+ // We didn't find the element
+ return false;
+}
+
+bool
+ColourMap::modifyColourByIndex(const unsigned int item_num, const Colour colour)
+{
+ for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position)
+ if (position->first == item_num)
+ {
+ position->second.first = colour;
+ return true;
+ }
+
+ // We didn't find the element
+ return false;
+}
+
+bool
+ColourMap::swapItems(const unsigned int item_1, const unsigned int item_2)
+{
+ // It would make no difference but we return false because
+ // we haven't altered the iterator (see docs in ColourMap.h)
+ if (item_1 == item_2)
+ return false;
+
+ // We refuse to swap the default colour for something else
+ // Basically because what would we do with the strings?
+ if ((item_1 == 0) || (item_2 == 0))
+ return false;
+
+ unsigned int one = 0, two = 0;
+
+ // Check that both elements exist
+ // It's not worth bothering about optimising this
+ for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position)
+ {
+ if (position->first == item_1) one = position->first;
+ if (position->first == item_2) two = position->first;
+ }
+
+ // If they both exist, do it
+ // There's probably a nicer way to do this
+ if ((one != 0) && (two != 0))
+ {
+ Colour tempC = m_map[one].first;
+ std::string tempS = m_map[one].second;
+ m_map[one].first = m_map[two].first;
+ m_map[one].second = m_map[two].second;
+ m_map[two].first = tempC;
+ m_map[two].second = tempS;
+
+ return true;
+ }
+
+ // Else they didn't
+ return false;
+}
+
+RCMap::const_iterator
+ColourMap::begin()
+{
+ RCMap::const_iterator ret = m_map.begin();
+ return ret;
+}
+
+RCMap::const_iterator
+ColourMap::end()
+{
+ RCMap::const_iterator ret = m_map.end();
+ return ret;
+}
+
+ColourMap&
+ColourMap::operator=(const ColourMap& input)
+{
+ if (this != &input)
+ m_map = input.m_map;
+
+ return *this;
+}
+
+int
+ColourMap::size() const
+{
+ return m_map.size();
+}
+
+std::string
+ColourMap::toXmlString(std::string name) const
+{
+ std::stringstream output;
+
+ output << " <colourmap name=\"" << XmlExportable::encode(name)
+ << "\">" << std::endl;
+
+ for (RCMap::const_iterator pos = m_map.begin(); pos != m_map.end(); ++pos)
+ {
+ output << " " << " <colourpair id=\"" << pos->first
+ << "\" name=\"" << XmlExportable::encode(pos->second.second)
+ << "\" " << pos->second.first.dataToXmlString() << "/>" << std::endl;
+ }
+
+#if (__GNUC__ < 3)
+ output << " </colourmap>" << std::endl << std::ends;
+#else
+ output << " </colourmap>" << std::endl;
+#endif
+
+
+ return output.str();
+
+}
+
+}
diff --git a/src/base/ColourMap.h b/src/base/ColourMap.h
new file mode 100644
index 0000000..973c1e0
--- /dev/null
+++ b/src/base/ColourMap.h
@@ -0,0 +1,138 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2003
+ Mark Hymers <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <utility>
+#include <map>
+#include <string>
+#include "Colour.h"
+
+#ifndef _BASE_COLOURMAP_H_
+#define _BASE_COLOURMAP_H_
+
+// These are the default, default colour
+#define COLOUR_DEF_R 255
+#define COLOUR_DEF_G 234
+#define COLOUR_DEF_B 182
+
+namespace Rosegarden
+{
+ typedef std::map<unsigned int, std::pair<Colour, std::string>, std::less<unsigned int> > RCMap;
+
+/**
+ * ColourMap is our table which maps the unsigned integer keys stored in
+ * segments to both a Colour and a String containing the 'name'
+ */
+
+class ColourMap
+{
+public:
+ // Functions:
+
+ /**
+ * Initialises an ColourMap with a default element set to
+ * whatever COLOUR_DEF_X defines the colour to be (see the source file)
+ */
+ ColourMap();
+ /**
+ * Initialises an ColourMap with a default element set to
+ * the value of the Colour passed in.
+ */
+ ColourMap(const Colour& input);
+ ~ColourMap();
+
+ /**
+ * Returns the Colour associated with the item_num passed in. Note that
+ * if the item_num doesn't represent a valid item, the routine returns
+ * the value of the Default colour. This means that if somehow some of
+ * the Segments get out of sync with the ColourMap and have invalid
+ * colour values, they'll be set to the Composition default colour.
+ */
+ Colour getColourByIndex(unsigned int item_num) const;
+
+ /**
+ * Returns the string associated with the item_num passed in. If the
+ * item_num doesn't exist, it'll return "" (the same name as the default
+ * colour has - for internationalization reasons).
+ */
+ std::string getNameByIndex(unsigned int item_num) const;
+
+ /**
+ * If item_num exists, this routine deletes it from the map.
+ */
+ bool deleteItemByIndex(unsigned int item_num);
+
+ /**
+ * This routine adds a Colour using the lowest possible index.
+ */
+ bool addItem(Colour colour, std::string name);
+
+ /**
+ * This routine adds a Colour using the given id. ONLY FOR USE IN
+ * rosexmlhandler.cpp
+ */
+ bool addItem(Colour colour, std::string name, unsigned int id);
+
+ /**
+ * If the item with item_num exists and isn't the default, this
+ * routine modifies the string associated with it
+ */
+ bool modifyNameByIndex(unsigned int item_num, std::string name);
+
+ /**
+ * If the item with item_num exists, this routine modifies the
+ * Colour associated with it
+ */
+ bool modifyColourByIndex(unsigned int item_num, Colour colour);
+
+ /**
+ * If both items exist, swap them.
+ */
+ bool swapItems(unsigned int item_1, unsigned int item_2);
+
+// void replace(ColourMap &input);
+
+ /**
+ * This returns a const iterator pointing to m_map.begin()
+ */
+ RCMap::const_iterator begin();
+
+ /**
+ * This returns a const iterator pointing to m_map.end()
+ */
+ RCMap::const_iterator end();
+
+ std::string toXmlString(std::string name) const;
+
+ ColourMap& operator=(const ColourMap& input);
+
+ int size() const;
+
+private:
+ RCMap m_map;
+};
+
+}
+
+#endif
diff --git a/src/base/Composition.cpp b/src/base/Composition.cpp
new file mode 100644
index 0000000..cde3a8b
--- /dev/null
+++ b/src/base/Composition.cpp
@@ -0,0 +1,2225 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Composition.h"
+#include "misc/Debug.h"
+#include "Segment.h"
+#include "FastVector.h"
+#include "BaseProperties.h"
+#include "BasicQuantizer.h"
+#include "NotationQuantizer.h"
+
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <cmath>
+#include <typeinfo>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_BAR_STUFF 1
+//#define DEBUG_TEMPO_STUFF 1
+
+
+namespace Rosegarden
+{
+
+const PropertyName Composition::NoAbsoluteTimeProperty = "NoAbsoluteTime";
+const PropertyName Composition::BarNumberProperty = "BarNumber";
+
+const std::string Composition::TempoEventType = "tempo";
+const PropertyName Composition::TempoProperty = "Tempo";
+const PropertyName Composition::TargetTempoProperty = "TargetTempo";
+const PropertyName Composition::TempoTimestampProperty = "TimestampSec";
+
+
+bool
+Composition::ReferenceSegmentEventCmp::operator()(const Event &e1,
+ const Event &e2) const
+{
+ if (e1.has(NoAbsoluteTimeProperty) ||
+ e2.has(NoAbsoluteTimeProperty)) {
+ RealTime r1 = getTempoTimestamp(&e1);
+ RealTime r2 = getTempoTimestamp(&e2);
+ return r1 < r2;
+ } else {
+ return e1 < e2;
+ }
+}
+
+Composition::ReferenceSegment::ReferenceSegment(std::string eventType) :
+ m_eventType(eventType)
+{
+ // nothing
+}
+
+Composition::ReferenceSegment::~ReferenceSegment()
+{
+ clear();
+}
+
+void
+Composition::ReferenceSegment::clear()
+{
+ for (iterator it = begin(); it != end(); ++it) delete (*it);
+ Impl::erase(begin(), end());
+}
+
+timeT
+Composition::ReferenceSegment::getDuration() const
+{
+ const_iterator i = end();
+ if (i == begin()) return 0;
+ --i;
+
+ return (*i)->getAbsoluteTime() + (*i)->getDuration();
+}
+
+Composition::ReferenceSegment::iterator
+Composition::ReferenceSegment::find(Event *e)
+{
+ return std::lower_bound
+ (begin(), end(), e, ReferenceSegmentEventCmp());
+}
+
+Composition::ReferenceSegment::iterator
+Composition::ReferenceSegment::insert(Event *e)
+{
+ if (!e->isa(m_eventType)) {
+ throw Event::BadType(std::string("event in ReferenceSegment"),
+ m_eventType, e->getType(), __FILE__, __LINE__);
+ }
+
+ iterator i = find(e);
+
+ if (i != end() && (*i)->getAbsoluteTime() == e->getAbsoluteTime()) {
+
+ Event *old = (*i);
+ (*i) = e;
+ delete old;
+ return i;
+
+ } else {
+ return Impl::insert(i, e);
+ }
+}
+
+void
+Composition::ReferenceSegment::erase(Event *e)
+{
+ iterator i = find(e);
+ if (i != end()) Impl::erase(i);
+}
+
+Composition::ReferenceSegment::iterator
+Composition::ReferenceSegment::findTime(timeT t)
+{
+ Event dummy("dummy", t, 0, MIN_SUBORDERING);
+ return find(&dummy);
+}
+
+Composition::ReferenceSegment::iterator
+Composition::ReferenceSegment::findRealTime(RealTime t)
+{
+ Event dummy("dummy", 0, 0, MIN_SUBORDERING);
+ dummy.set<Bool>(NoAbsoluteTimeProperty, true);
+ setTempoTimestamp(&dummy, t);
+ return find(&dummy);
+}
+
+Composition::ReferenceSegment::iterator
+Composition::ReferenceSegment::findNearestTime(timeT t)
+{
+ iterator i = findTime(t);
+ if (i == end() || (*i)->getAbsoluteTime() > t) {
+ if (i == begin()) return end();
+ else --i;
+ }
+ return i;
+}
+
+Composition::ReferenceSegment::iterator
+Composition::ReferenceSegment::findNearestRealTime(RealTime t)
+{
+ iterator i = findRealTime(t);
+ if (i == end() || (getTempoTimestamp(*i) > t)) {
+ if (i == begin()) return end();
+ else --i;
+ }
+ return i;
+}
+
+
+
+int Composition::m_defaultNbBars = 100;
+
+Composition::Composition() :
+ m_solo(false), // default is not soloing
+ m_selectedTrack(0),
+ m_timeSigSegment(TimeSignature::EventType),
+ m_tempoSegment(TempoEventType),
+ m_barPositionsNeedCalculating(true),
+ m_tempoTimestampsNeedCalculating(true),
+ m_basicQuantizer(new BasicQuantizer()),
+ m_notationQuantizer(new NotationQuantizer()),
+ m_position(0),
+ m_defaultTempo(getTempoForQpm(120.0)),
+ m_minTempo(0),
+ m_maxTempo(0),
+ m_startMarker(0),
+ m_endMarker(getBarRange(m_defaultNbBars).first),
+ m_loopStart(0),
+ m_loopEnd(0),
+ m_playMetronome(false),
+ m_recordMetronome(true),
+ m_nextTriggerSegmentId(0)
+{
+ // nothing else
+}
+
+Composition::~Composition()
+{
+ if (!m_observers.empty()) {
+ cerr << "Warning: Composition::~Composition() with " << m_observers.size()
+ << " observers still extant" << endl;
+ cerr << "Observers are:";
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ cerr << " " << (void *)(*i);
+ cerr << " [" << typeid(**i).name() << "]";
+ }
+ cerr << endl;
+ }
+
+ notifySourceDeletion();
+ clear();
+ delete m_basicQuantizer;
+ delete m_notationQuantizer;
+}
+
+Composition::iterator
+Composition::addSegment(Segment *segment)
+{
+ iterator res = weakAddSegment(segment);
+
+ if (res != end()) {
+ updateRefreshStatuses();
+ notifySegmentAdded(segment);
+ }
+
+ return res;
+}
+
+Composition::iterator
+Composition::weakAddSegment(Segment *segment)
+{
+ if (!segment) return end();
+
+ iterator res = m_segments.insert(segment);
+ segment->setComposition(this);
+
+ return res;
+}
+
+void
+Composition::deleteSegment(Composition::iterator i)
+{
+ if (i == end()) return;
+
+ Segment *p = (*i);
+ p->setComposition(0);
+
+ m_segments.erase(i);
+ notifySegmentRemoved(p);
+ delete p;
+
+ updateRefreshStatuses();
+}
+
+bool
+Composition::deleteSegment(Segment *segment)
+{
+ iterator i = findSegment(segment);
+ if (i == end()) return false;
+
+ deleteSegment(i);
+ return true;
+}
+
+bool
+Composition::detachSegment(Segment *segment)
+{
+ bool res = weakDetachSegment(segment);
+
+ if (res) {
+ notifySegmentRemoved(segment);
+ updateRefreshStatuses();
+ }
+
+ return res;
+}
+
+bool
+Composition::weakDetachSegment(Segment *segment)
+{
+ iterator i = findSegment(segment);
+ if (i == end()) return false;
+
+ segment->setComposition(0);
+ m_segments.erase(i);
+
+ return true;
+}
+
+bool
+Composition::contains(const Segment *s)
+{
+ iterator i = findSegment(s);
+ return (i != end());
+}
+
+Composition::iterator
+Composition::findSegment(const Segment *s)
+{
+ iterator i = m_segments.lower_bound(const_cast<Segment*>(s));
+
+ while (i != end()) {
+ if (*i == s) break;
+ if ((*i)->getStartTime() > s->getStartTime()) return end();
+ ++i;
+ }
+
+ return i;
+}
+
+void Composition::setSegmentStartTime(Segment *segment, timeT startTime)
+{
+ // remove the segment from the multiset
+ iterator i = findSegment(segment);
+ if (i == end()) return;
+
+ m_segments.erase(i);
+
+ segment->setStartTimeDataMember(startTime);
+
+ // re-add it
+ m_segments.insert(segment);
+}
+
+int
+Composition::getMaxContemporaneousSegmentsOnTrack(TrackId track) const
+{
+ // Could be made faster, but only if it needs to be.
+
+ // This is similar to the polyphony calculation in
+ // DocumentMetaConfigurationPage ctor.
+
+ std::set<Segment *> simultaneous;
+ std::multimap<timeT, Segment *> ends;
+
+ int maximum = 0;
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+ if ((*i)->getTrack() != track) continue;
+ timeT t0 = (*i)->getStartTime();
+ timeT t1 = (*i)->getRepeatEndTime();
+// std::cerr << "getMaxContemporaneousSegmentsOnTrack(" << track << "): segment " << *i << " from " << t0 << " to " << t1 << std::endl;
+ while (!ends.empty() && t0 >= ends.begin()->first) {
+ simultaneous.erase(ends.begin()->second);
+ ends.erase(ends.begin());
+ }
+ simultaneous.insert(*i);
+ ends.insert(std::multimap<timeT, Segment *>::value_type(t1, *i));
+ int current = simultaneous.size();
+ if (current > maximum) maximum = current;
+ }
+
+ return maximum;
+}
+
+int
+Composition::getSegmentVoiceIndex(const Segment *segment) const
+{
+ TrackId track = segment->getTrack();
+
+ // See function above
+
+ std::map<Segment *, int> indices;
+ std::set<int> used;
+ std::multimap<timeT, Segment *> ends;
+
+ int maximum = 0;
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+ if ((*i)->getTrack() != track) continue;
+ timeT t0 = (*i)->getStartTime();
+ timeT t1 = (*i)->getRepeatEndTime();
+ int index;
+ while (!ends.empty() && t0 >= ends.begin()->first) {
+ index = indices[ends.begin()->second];
+ used.erase(index);
+ indices.erase(ends.begin()->second);
+ ends.erase(ends.begin());
+ }
+ for (index = 0; ; ++index) {
+ if (used.find(index) == used.end()) break;
+ }
+ if (*i == segment) return index;
+ indices[*i] = index;
+ used.insert(index);
+ ends.insert(std::multimap<timeT, Segment *>::value_type(t1, *i));
+ }
+
+ std::cerr << "WARNING: Composition::getSegmentVoiceIndex: segment "
+ << segment << " not found in composition" << std::endl;
+ return 0;
+}
+
+TriggerSegmentRec *
+Composition::addTriggerSegment(Segment *s, int pitch, int velocity)
+{
+ TriggerSegmentId id = m_nextTriggerSegmentId;
+ return addTriggerSegment(s, id, pitch, velocity);
+}
+
+TriggerSegmentRec *
+Composition::addTriggerSegment(Segment *s, TriggerSegmentId id, int pitch, int velocity)
+{
+ TriggerSegmentRec *rec = getTriggerSegmentRec(id);
+ if (rec) return 0;
+ rec = new TriggerSegmentRec(id, s, pitch, velocity);
+ m_triggerSegments.insert(rec);
+ s->setComposition(this);
+ if (m_nextTriggerSegmentId <= id) m_nextTriggerSegmentId = id + 1;
+ return rec;
+}
+
+void
+Composition::deleteTriggerSegment(TriggerSegmentId id)
+{
+ TriggerSegmentRec dummyRec(id, 0);
+ triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec);
+ if (i == m_triggerSegments.end()) return;
+ (*i)->getSegment()->setComposition(0);
+ delete (*i)->getSegment();
+ delete *i;
+ m_triggerSegments.erase(i);
+}
+
+void
+Composition::detachTriggerSegment(TriggerSegmentId id)
+{
+ TriggerSegmentRec dummyRec(id, 0);
+ triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec);
+ if (i == m_triggerSegments.end()) return;
+ (*i)->getSegment()->setComposition(0);
+ delete *i;
+ m_triggerSegments.erase(i);
+}
+
+void
+Composition::clearTriggerSegments()
+{
+ for (triggersegmentcontaineriterator i = m_triggerSegments.begin();
+ i != m_triggerSegments.end(); ++i) {
+ delete (*i)->getSegment();
+ delete *i;
+ }
+ m_triggerSegments.clear();
+}
+
+int
+Composition::getTriggerSegmentId(Segment *s)
+{
+ for (triggersegmentcontaineriterator i = m_triggerSegments.begin();
+ i != m_triggerSegments.end(); ++i) {
+ if ((*i)->getSegment() == s) return (*i)->getId();
+ }
+ return -1;
+}
+
+Segment *
+Composition::getTriggerSegment(TriggerSegmentId id)
+{
+ TriggerSegmentRec *rec = getTriggerSegmentRec(id);
+ if (!rec) return 0;
+ return rec->getSegment();
+}
+
+TriggerSegmentRec *
+Composition::getTriggerSegmentRec(TriggerSegmentId id)
+{
+ TriggerSegmentRec dummyRec(id, 0);
+ triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec);
+ if (i == m_triggerSegments.end()) return 0;
+ return *i;
+}
+
+TriggerSegmentId
+Composition::getNextTriggerSegmentId() const
+{
+ return m_nextTriggerSegmentId;
+}
+
+void
+Composition::setNextTriggerSegmentId(TriggerSegmentId id)
+{
+ m_nextTriggerSegmentId = id;
+}
+
+void
+Composition::updateTriggerSegmentReferences()
+{
+ std::map<TriggerSegmentId, TriggerSegmentRec::SegmentRuntimeIdSet> refs;
+
+ for (iterator i = begin(); i != end(); ++i) {
+ for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if ((*j)->has(BaseProperties::TRIGGER_SEGMENT_ID)) {
+ TriggerSegmentId id =
+ (*j)->get<Int>(BaseProperties::TRIGGER_SEGMENT_ID);
+ refs[id].insert((*i)->getRuntimeId());
+ }
+ }
+ }
+
+ for (std::map<TriggerSegmentId,
+ TriggerSegmentRec::SegmentRuntimeIdSet>::iterator i = refs.begin();
+ i != refs.end(); ++i) {
+ TriggerSegmentRec *rec = getTriggerSegmentRec(i->first);
+ if (rec) rec->setReferences(i->second);
+ }
+}
+
+
+timeT
+Composition::getDuration() const
+{
+ timeT maxDuration = 0;
+
+ for (segmentcontainer::const_iterator i = m_segments.begin();
+ i != m_segments.end(); ++i) {
+
+ timeT segmentTotal = (*i)->getEndTime();
+
+ if (segmentTotal > maxDuration) {
+ maxDuration = segmentTotal;
+ }
+ }
+
+ return maxDuration;
+}
+
+void
+Composition::setStartMarker(const timeT &sM)
+{
+ m_startMarker = sM;
+ updateRefreshStatuses();
+}
+
+void
+Composition::setEndMarker(const timeT &eM)
+{
+ bool shorten = (eM < m_endMarker);
+ m_endMarker = eM;
+ updateRefreshStatuses();
+ notifyEndMarkerChange(shorten);
+}
+
+void
+Composition::clear()
+{
+ while (m_segments.size() > 0) {
+ deleteSegment(begin());
+ }
+
+ clearTracks();
+ clearMarkers();
+ clearTriggerSegments();
+
+ m_timeSigSegment.clear();
+ m_tempoSegment.clear();
+ m_defaultTempo = getTempoForQpm(120.0);
+ m_minTempo = 0;
+ m_maxTempo = 0;
+ m_loopStart = 0;
+ m_loopEnd = 0;
+ m_position = 0;
+ m_startMarker = 0;
+ m_endMarker = getBarRange(m_defaultNbBars).first;
+ m_solo = false;
+ m_selectedTrack = 0;
+ updateRefreshStatuses();
+}
+
+void
+Composition::calculateBarPositions() const
+{
+ if (!m_barPositionsNeedCalculating) return;
+
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::calculateBarPositions" << endl;
+#endif
+
+ ReferenceSegment &t = m_timeSigSegment;
+ ReferenceSegment::iterator i;
+
+ timeT lastBarNo = 0;
+ timeT lastSigTime = 0;
+ timeT barDuration = TimeSignature().getBarDuration();
+
+ if (getStartMarker() < 0) {
+ if (!t.empty() && (*t.begin())->getAbsoluteTime() <= 0) {
+ barDuration = TimeSignature(**t.begin()).getBarDuration();
+ }
+ lastBarNo = getStartMarker() / barDuration;
+ lastSigTime = getStartMarker();
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::calculateBarPositions: start marker = " << getStartMarker() << ", so initial bar number = " << lastBarNo << endl;
+#endif
+ }
+
+ for (i = t.begin(); i != t.end(); ++i) {
+
+ timeT myTime = (*i)->getAbsoluteTime();
+ int n = (myTime - lastSigTime) / barDuration;
+
+ // should only happen for first time sig, when it's at time < 0:
+ if (myTime < lastSigTime) --n;
+
+ // would there be a new bar here anyway?
+ if (barDuration * n + lastSigTime == myTime) { // yes
+ n += lastBarNo;
+ } else { // no
+ n += lastBarNo + 1;
+ }
+
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::calculateBarPositions: bar " << n
+ << " at " << myTime << endl;
+#endif
+
+ (*i)->set<Int>(BarNumberProperty, n);
+
+ lastBarNo = n;
+ lastSigTime = myTime;
+ barDuration = TimeSignature(**i).getBarDuration();
+ }
+
+ m_barPositionsNeedCalculating = false;
+}
+
+int
+Composition::getNbBars() const
+{
+ calculateBarPositions();
+
+ // the "-1" is a small kludge to deal with the case where the
+ // composition has a duration that's an exact number of bars
+ int bars = getBarNumber(getDuration() - 1) + 1;
+
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::getNbBars: returning " << bars << endl;
+#endif
+ return bars;
+}
+
+int
+Composition::getBarNumber(timeT t) const
+{
+ calculateBarPositions();
+ ReferenceSegment::iterator i = m_timeSigSegment.findNearestTime(t);
+ int n;
+
+ if (i == m_timeSigSegment.end()) { // precedes any time signatures
+
+ timeT bd = TimeSignature().getBarDuration();
+ if (t < 0) { // see comment in getTimeSignatureAtAux
+ i = m_timeSigSegment.begin();
+ if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() <= 0) {
+ bd = TimeSignature(**i).getBarDuration();
+ }
+ }
+
+ n = t / bd;
+ if (t < 0) {
+ // negative bars should be rounded down, except where
+ // the time is on a barline in which case we already
+ // have the right value (i.e. time -1920 is bar -1,
+ // but time -3840 is also bar -1, in 4/4)
+ if (n * bd != t) --n;
+ }
+
+ } else {
+
+ n = (*i)->get<Int>(BarNumberProperty);
+ timeT offset = t - (*i)->getAbsoluteTime();
+ n += offset / TimeSignature(**i).getBarDuration();
+ }
+
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::getBarNumber(" << t << "): returning " << n << endl;
+#endif
+ return n;
+}
+
+
+std::pair<timeT, timeT>
+Composition::getBarRangeForTime(timeT t) const
+{
+ return getBarRange(getBarNumber(t));
+}
+
+
+std::pair<timeT, timeT>
+Composition::getBarRange(int n) const
+{
+ calculateBarPositions();
+
+ Event dummy("dummy", 0);
+ dummy.set<Int>(BarNumberProperty, n);
+
+ ReferenceSegment::iterator j = std::lower_bound
+ (m_timeSigSegment.begin(), m_timeSigSegment.end(),
+ &dummy, BarNumberComparator());
+ ReferenceSegment::iterator i = j;
+
+ if (i == m_timeSigSegment.end() || (*i)->get<Int>(BarNumberProperty) > n) {
+ if (i == m_timeSigSegment.begin()) i = m_timeSigSegment.end();
+ else --i;
+ } else ++j; // j needs to point to following barline
+
+ timeT start, finish;
+
+ if (i == m_timeSigSegment.end()) { // precedes any time sig changes
+
+ timeT barDuration = TimeSignature().getBarDuration();
+ if (n < 0) { // see comment in getTimeSignatureAtAux
+ i = m_timeSigSegment.begin();
+ if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() <= 0) {
+ barDuration = TimeSignature(**i).getBarDuration();
+ }
+ }
+
+ start = n * barDuration;
+ finish = start + barDuration;
+
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::getBarRange[1]: bar " << n << ": (" << start
+ << " -> " << finish << ")" << endl;
+#endif
+
+ } else {
+
+ timeT barDuration = TimeSignature(**i).getBarDuration();
+ start = (*i)->getAbsoluteTime() +
+ (n - (*i)->get<Int>(BarNumberProperty)) * barDuration;
+ finish = start + barDuration;
+
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::getBarRange[2]: bar " << n << ": (" << start
+ << " -> " << finish << ")" << endl;
+#endif
+ }
+
+ // partial bar
+ if (j != m_timeSigSegment.end() && finish > (*j)->getAbsoluteTime()) {
+ finish = (*j)->getAbsoluteTime();
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::getBarRange[3]: bar " << n << ": (" << start
+ << " -> " << finish << ")" << endl;
+#endif
+ }
+
+ return std::pair<timeT, timeT>(start, finish);
+}
+
+
+int
+Composition::addTimeSignature(timeT t, TimeSignature timeSig)
+{
+#ifdef DEBUG_BAR_STUFF
+ cerr << "Composition::addTimeSignature(" << t << ", " << timeSig.getNumerator() << "/" << timeSig.getDenominator() << ")" << endl;
+#endif
+
+ ReferenceSegment::iterator i =
+ m_timeSigSegment.insert(timeSig.getAsEvent(t));
+ m_barPositionsNeedCalculating = true;
+
+ updateRefreshStatuses();
+ notifyTimeSignatureChanged();
+
+ return std::distance(m_timeSigSegment.begin(), i);
+}
+
+TimeSignature
+Composition::getTimeSignatureAt(timeT t) const
+{
+ TimeSignature timeSig;
+ (void)getTimeSignatureAt(t, timeSig);
+ return timeSig;
+}
+
+timeT
+Composition::getTimeSignatureAt(timeT t, TimeSignature &timeSig) const
+{
+ ReferenceSegment::iterator i = getTimeSignatureAtAux(t);
+
+ if (i == m_timeSigSegment.end()) {
+ timeSig = TimeSignature();
+ return 0;
+ } else {
+ timeSig = TimeSignature(**i);
+ return (*i)->getAbsoluteTime();
+ }
+}
+
+TimeSignature
+Composition::getTimeSignatureInBar(int barNo, bool &isNew) const
+{
+ isNew = false;
+ timeT t = getBarRange(barNo).first;
+
+ ReferenceSegment::iterator i = getTimeSignatureAtAux(t);
+
+ if (i == m_timeSigSegment.end()) return TimeSignature();
+ if (t == (*i)->getAbsoluteTime()) isNew = true;
+
+ return TimeSignature(**i);
+}
+
+Composition::ReferenceSegment::iterator
+Composition::getTimeSignatureAtAux(timeT t) const
+{
+ ReferenceSegment::iterator i = m_timeSigSegment.findNearestTime(t);
+
+ // In negative time, if there's no time signature actually defined
+ // prior to the point of interest then we use the next time
+ // signature after it, so long as it's no later than time zero.
+ // This is the only rational way to deal with count-in bars where
+ // the correct time signature otherwise won't appear until we hit
+ // bar zero.
+
+ if (t < 0 && i == m_timeSigSegment.end()) {
+ i = m_timeSigSegment.begin();
+ if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() > 0) {
+ i = m_timeSigSegment.end();
+ }
+ }
+
+ return i;
+}
+
+int
+Composition::getTimeSignatureCount() const
+{
+ return m_timeSigSegment.size();
+}
+
+int
+Composition::getTimeSignatureNumberAt(timeT t) const
+{
+ ReferenceSegment::iterator i = getTimeSignatureAtAux(t);
+ if (i == m_timeSigSegment.end()) return -1;
+ else return std::distance(m_timeSigSegment.begin(), i);
+}
+
+std::pair<timeT, TimeSignature>
+Composition::getTimeSignatureChange(int n) const
+{
+ return std::pair<timeT, TimeSignature>
+ (m_timeSigSegment[n]->getAbsoluteTime(),
+ TimeSignature(*m_timeSigSegment[n]));
+}
+
+void
+Composition::removeTimeSignature(int n)
+{
+ m_timeSigSegment.erase(m_timeSigSegment[n]);
+ m_barPositionsNeedCalculating = true;
+ updateRefreshStatuses();
+ notifyTimeSignatureChanged();
+}
+
+
+tempoT
+Composition::getTempoAtTime(timeT t) const
+{
+ ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t);
+
+ // In negative time, if there's no tempo event actually defined
+ // prior to the point of interest then we use the next one after
+ // it, so long as it's no later than time zero. This is the only
+ // rational way to deal with count-in bars where the correct
+ // tempo otherwise won't appear until we hit bar zero. See also
+ // getTimeSignatureAt
+
+ if (i == m_tempoSegment.end()) {
+ if (t < 0) {
+#ifdef DEBUG_TEMPO_STUFF
+ cerr << "Composition: Negative time " << t << " for tempo, using 0" << endl;
+#endif
+ return getTempoAtTime(0);
+ }
+ else return m_defaultTempo;
+ }
+
+ tempoT tempo = (tempoT)((*i)->get<Int>(TempoProperty));
+
+ if ((*i)->has(TargetTempoProperty)) {
+
+ tempoT target = (tempoT)((*i)->get<Int>(TargetTempoProperty));
+ ReferenceSegment::iterator j = i;
+ ++j;
+
+ if (target > 0 || (target == 0 && j != m_tempoSegment.end())) {
+
+ timeT t0 = (*i)->getAbsoluteTime();
+ timeT t1 = (j != m_tempoSegment.end() ?
+ (*j)->getAbsoluteTime() : getEndMarker());
+
+ if (t1 < t0) return tempo;
+
+ if (target == 0) {
+ target = (tempoT)((*j)->get<Int>(TempoProperty));
+ }
+
+ // tempo ramps are linear in 1/tempo
+ double s0 = 1.0 / double(tempo);
+ double s1 = 1.0 / double(target);
+ double s = s0 + (t - t0) * ((s1 - s0) / (t1 - t0));
+
+ tempoT result = tempoT((1.0 / s) + 0.01);
+
+#ifdef DEBUG_TEMPO_STUFF
+ cerr << "Composition: Calculated tempo " << result << " at " << t << endl;
+#endif
+
+ return result;
+ }
+ }
+
+#ifdef DEBUG_TEMPO_STUFF
+ cerr << "Composition: Found tempo " << tempo << " at " << t << endl;
+#endif
+ return tempo;
+}
+
+int
+Composition::addTempoAtTime(timeT time, tempoT tempo, tempoT targetTempo)
+{
+ // If there's an existing tempo at this time, the ReferenceSegment
+ // object will remove the duplicate, but we have to ensure that
+ // the minimum and maximum tempos are updated if necessary.
+
+ bool fullTempoUpdate = false;
+
+ int n = getTempoChangeNumberAt(time);
+ if (n >= 0) {
+ std::pair<timeT, tempoT> tc = getTempoChange(n);
+ if (tc.first == time) {
+ if (tc.second == m_minTempo || tc.second == m_maxTempo) {
+ fullTempoUpdate = true;
+ } else {
+ std::pair<bool, tempoT> tr = getTempoRamping(n);
+ if (tr.first &&
+ (tr.second == m_minTempo || tr.second == m_maxTempo)) {
+ fullTempoUpdate = true;
+ }
+ }
+ }
+ }
+
+ Event *tempoEvent = new Event(TempoEventType, time);
+ tempoEvent->set<Int>(TempoProperty, tempo);
+
+ if (targetTempo >= 0) {
+ tempoEvent->set<Int>(TargetTempoProperty, targetTempo);
+ }
+
+ ReferenceSegment::iterator i = m_tempoSegment.insert(tempoEvent);
+
+ if (fullTempoUpdate) {
+
+ updateExtremeTempos();
+
+ } else {
+
+ if (tempo < m_minTempo || m_minTempo == 0) m_minTempo = tempo;
+ if (targetTempo > 0 && targetTempo < m_minTempo) m_minTempo = targetTempo;
+
+ if (tempo > m_maxTempo || m_maxTempo == 0) m_maxTempo = tempo;
+ if (targetTempo > 0 && targetTempo > m_maxTempo) m_maxTempo = targetTempo;
+ }
+
+ m_tempoTimestampsNeedCalculating = true;
+ updateRefreshStatuses();
+
+#ifdef DEBUG_TEMPO_STUFF
+ cerr << "Composition: Added tempo " << tempo << " at " << time << endl;
+#endif
+ notifyTempoChanged();
+
+ return std::distance(m_tempoSegment.begin(), i);
+}
+
+int
+Composition::getTempoChangeCount() const
+{
+ return m_tempoSegment.size();
+}
+
+int
+Composition::getTempoChangeNumberAt(timeT t) const
+{
+ ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t);
+ if (i == m_tempoSegment.end()) return -1;
+ else return std::distance(m_tempoSegment.begin(), i);
+}
+
+std::pair<timeT, tempoT>
+Composition::getTempoChange(int n) const
+{
+ return std::pair<timeT, tempoT>
+ (m_tempoSegment[n]->getAbsoluteTime(),
+ tempoT(m_tempoSegment[n]->get<Int>(TempoProperty)));
+}
+
+std::pair<bool, tempoT>
+Composition::getTempoRamping(int n, bool calculate) const
+{
+ tempoT target = -1;
+ if (m_tempoSegment[n]->has(TargetTempoProperty)) {
+ target = m_tempoSegment[n]->get<Int>(TargetTempoProperty);
+ }
+ bool ramped = (target >= 0);
+ if (target == 0) {
+ if (calculate) {
+ if (m_tempoSegment.size() > n+1) {
+ target = m_tempoSegment[n+1]->get<Int>(TempoProperty);
+ }
+ }
+ }
+ if (target < 0 || (calculate && (target == 0))) {
+ target = m_tempoSegment[n]->get<Int>(TempoProperty);
+ }
+ return std::pair<bool, tempoT>(ramped, target);
+}
+
+void
+Composition::removeTempoChange(int n)
+{
+ tempoT oldTempo = m_tempoSegment[n]->get<Int>(TempoProperty);
+ tempoT oldTarget = -1;
+
+ if (m_tempoSegment[n]->has(TargetTempoProperty)) {
+ oldTarget = m_tempoSegment[n]->get<Int>(TargetTempoProperty);
+ }
+
+ m_tempoSegment.erase(m_tempoSegment[n]);
+ m_tempoTimestampsNeedCalculating = true;
+
+ if (oldTempo == m_minTempo ||
+ oldTempo == m_maxTempo ||
+ (oldTarget > 0 && oldTarget == m_minTempo) ||
+ (oldTarget > 0 && oldTarget == m_maxTempo)) {
+ updateExtremeTempos();
+ }
+
+ updateRefreshStatuses();
+ notifyTempoChanged();
+}
+
+void
+Composition::updateExtremeTempos()
+{
+ m_minTempo = 0;
+ m_maxTempo = 0;
+ for (ReferenceSegment::iterator i = m_tempoSegment.begin();
+ i != m_tempoSegment.end(); ++i) {
+ tempoT tempo = (*i)->get<Int>(TempoProperty);
+ tempoT target = -1;
+ if ((*i)->has(TargetTempoProperty)) {
+ target = (*i)->get<Int>(TargetTempoProperty);
+ }
+ if (tempo < m_minTempo || m_minTempo == 0) m_minTempo = tempo;
+ if (target > 0 && target < m_minTempo) m_minTempo = target;
+ if (tempo > m_maxTempo || m_maxTempo == 0) m_maxTempo = tempo;
+ if (target > 0 && target > m_maxTempo) m_maxTempo = target;
+ }
+ if (m_minTempo == 0) {
+ m_minTempo = m_defaultTempo;
+ m_maxTempo = m_defaultTempo;
+ }
+}
+
+RealTime
+Composition::getElapsedRealTime(timeT t) const
+{
+ calculateTempoTimestamps();
+
+ ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t);
+ if (i == m_tempoSegment.end()) {
+ i = m_tempoSegment.begin();
+ if (t >= 0 ||
+ (i == m_tempoSegment.end() || (*i)->getAbsoluteTime() > 0)) {
+ return time2RealTime(t, m_defaultTempo);
+ }
+ }
+
+ RealTime elapsed;
+
+ tempoT target = -1;
+ timeT nextTempoTime = t;
+
+ if (!getTempoTarget(i, target, nextTempoTime)) target = -1;
+
+ if (target > 0) {
+ elapsed = getTempoTimestamp(*i) +
+ time2RealTime(t - (*i)->getAbsoluteTime(),
+ tempoT((*i)->get<Int>(TempoProperty)),
+ nextTempoTime - (*i)->getAbsoluteTime(),
+ target);
+ } else {
+ elapsed = getTempoTimestamp(*i) +
+ time2RealTime(t - (*i)->getAbsoluteTime(),
+ tempoT((*i)->get<Int>(TempoProperty)));
+ }
+
+#ifdef DEBUG_TEMPO_STUFF
+ cerr << "Composition::getElapsedRealTime: " << t << " -> "
+ << elapsed << " (last tempo change at " << (*i)->getAbsoluteTime() << ")" << endl;
+#endif
+
+ return elapsed;
+}
+
+timeT
+Composition::getElapsedTimeForRealTime(RealTime t) const
+{
+ calculateTempoTimestamps();
+
+ ReferenceSegment::iterator i = m_tempoSegment.findNearestRealTime(t);
+ if (i == m_tempoSegment.end()) {
+ i = m_tempoSegment.begin();
+ if (t >= RealTime::zeroTime ||
+ (i == m_tempoSegment.end() || (*i)->getAbsoluteTime() > 0)) {
+ return realTime2Time(t, m_defaultTempo);
+ }
+ }
+
+ timeT elapsed;
+
+ tempoT target = -1;
+ timeT nextTempoTime = 0;
+ if (!getTempoTarget(i, target, nextTempoTime)) target = -1;
+
+ if (target > 0) {
+ elapsed = (*i)->getAbsoluteTime() +
+ realTime2Time(t - getTempoTimestamp(*i),
+ (tempoT)((*i)->get<Int>(TempoProperty)),
+ nextTempoTime - (*i)->getAbsoluteTime(),
+ target);
+ } else {
+ elapsed = (*i)->getAbsoluteTime() +
+ realTime2Time(t - getTempoTimestamp(*i),
+ (tempoT)((*i)->get<Int>(TempoProperty)));
+ }
+
+#ifdef DEBUG_TEMPO_STUFF
+ static int doError = true;
+ if (doError) {
+ doError = false;
+ RealTime cfReal = getElapsedRealTime(elapsed);
+ timeT cfTimeT = getElapsedTimeForRealTime(cfReal);
+ doError = true;
+ cerr << "getElapsedTimeForRealTime: " << t << " -> "
+ << elapsed << " (error " << (cfReal - t)
+ << " or " << (cfTimeT - elapsed) << ", tempo "
+ << (*i)->getAbsoluteTime() << ":"
+ << (tempoT)((*i)->get<Int>(TempoProperty)) << ")" << endl;
+ }
+#endif
+ return elapsed;
+}
+
+void
+Composition::calculateTempoTimestamps() const
+{
+ if (!m_tempoTimestampsNeedCalculating) return;
+
+ timeT lastTimeT = 0;
+ RealTime lastRealTime;
+
+ tempoT tempo = m_defaultTempo;
+ tempoT target = -1;
+
+#ifdef DEBUG_TEMPO_STUFF
+ cerr << "Composition::calculateTempoTimestamps: Tempo events are:" << endl;
+#endif
+
+ for (ReferenceSegment::iterator i = m_tempoSegment.begin();
+ i != m_tempoSegment.end(); ++i) {
+
+ RealTime myTime;
+
+ if (target > 0) {
+ myTime = lastRealTime +
+ time2RealTime((*i)->getAbsoluteTime() - lastTimeT, tempo,
+ (*i)->getAbsoluteTime() - lastTimeT, target);
+ } else {
+ myTime = lastRealTime +
+ time2RealTime((*i)->getAbsoluteTime() - lastTimeT, tempo);
+ }
+
+ setTempoTimestamp(*i, myTime);
+
+#ifdef DEBUG_TEMPO_STUFF
+ (*i)->dump(cerr);
+#endif
+
+ lastRealTime = myTime;
+ lastTimeT = (*i)->getAbsoluteTime();
+ tempo = tempoT((*i)->get<Int>(TempoProperty));
+
+ target = -1;
+ timeT nextTempoTime = 0;
+ if (!getTempoTarget(i, target, nextTempoTime)) target = -1;
+ }
+
+ m_tempoTimestampsNeedCalculating = false;
+}
+
+#ifdef DEBUG_TEMPO_STUFF
+static int DEBUG_silence_recursive_tempo_printout = 0;
+#endif
+
+RealTime
+Composition::time2RealTime(timeT t, tempoT tempo) const
+{
+ static timeT cdur = Note(Note::Crotchet).getDuration();
+
+ double dt = (double(t) * 100000 * 60) / (double(tempo) * cdur);
+
+ int sec = int(dt);
+ int nsec = int((dt - sec) * 1000000000);
+
+ RealTime rt(sec, nsec);
+
+#ifdef DEBUG_TEMPO_STUFF
+ if (!DEBUG_silence_recursive_tempo_printout) {
+ cerr << "Composition::time2RealTime: t " << t << ", sec " << sec << ", nsec "
+ << nsec << ", tempo " << tempo
+ << ", cdur " << cdur << ", dt " << dt << ", rt " << rt << endl;
+ DEBUG_silence_recursive_tempo_printout = 1;
+ timeT ct = realTime2Time(rt, tempo);
+ timeT et = t - ct;
+ RealTime ert = time2RealTime(et, tempo);
+ cerr << "cf. realTime2Time(" << rt << ") -> " << ct << " [err " << et << " (" << ert << "?)]" << endl;
+ DEBUG_silence_recursive_tempo_printout=0;
+ }
+#endif
+
+ return rt;
+}
+
+RealTime
+Composition::time2RealTime(timeT time, tempoT tempo,
+ timeT targetTime, tempoT targetTempo) const
+{
+ static timeT cdur = Note(Note::Crotchet).getDuration();
+
+ // The real time elapsed at musical time t, in seconds, during a
+ // smooth tempo change from "tempo" at musical time zero to
+ // "targetTempo" at musical time "targetTime", is
+ //
+ // 2
+ // at + t (b - a)
+ // ---------
+ // 2n
+ // where
+ //
+ // a is the initial tempo in seconds per tick
+ // b is the target tempo in seconds per tick
+ // n is targetTime in ticks
+
+ if (targetTime == 0 || targetTempo == tempo) {
+ return time2RealTime(time, targetTempo);
+ }
+
+ double a = (100000 * 60) / (double(tempo) * cdur);
+ double b = (100000 * 60) / (double(targetTempo) * cdur);
+ double t = time;
+ double n = targetTime;
+ double result = (a * t) + (t * t * (b - a)) / (2 * n);
+
+ int sec = int(result);
+ int nsec = int((result - sec) * 1000000000);
+
+ RealTime rt(sec, nsec);
+
+#ifdef DEBUG_TEMPO_STUFF
+ if (!DEBUG_silence_recursive_tempo_printout) {
+ cerr << "Composition::time2RealTime[2]: time " << time << ", tempo "
+ << tempo << ", targetTime " << targetTime << ", targetTempo "
+ << targetTempo << ": rt " << rt << endl;
+ DEBUG_silence_recursive_tempo_printout = 1;
+// RealTime nextRt = time2RealTime(targetTime, tempo, targetTime, targetTempo);
+ timeT ct = realTime2Time(rt, tempo, targetTime, targetTempo);
+ cerr << "cf. realTime2Time: rt " << rt << " -> " << ct << endl;
+ DEBUG_silence_recursive_tempo_printout=0;
+ }
+#endif
+
+ return rt;
+}
+
+timeT
+Composition::realTime2Time(RealTime rt, tempoT tempo) const
+{
+ static timeT cdur = Note(Note::Crotchet).getDuration();
+
+ double tsec = (double(rt.sec) * cdur) * (tempo / (60.0 * 100000.0));
+ double tnsec = (double(rt.nsec) * cdur) * (tempo / 100000.0);
+
+ double dt = tsec + (tnsec / 60000000000.0);
+ timeT t = (timeT)(dt + (dt < 0 ? -1e-6 : 1e-6));
+
+#ifdef DEBUG_TEMPO_STUFF
+ if (!DEBUG_silence_recursive_tempo_printout) {
+ cerr << "Composition::realTime2Time: rt.sec " << rt.sec << ", rt.nsec "
+ << rt.nsec << ", tempo " << tempo
+ << ", cdur " << cdur << ", tsec " << tsec << ", tnsec " << tnsec << ", dt " << dt << ", t " << t << endl;
+ DEBUG_silence_recursive_tempo_printout = 1;
+ RealTime crt = time2RealTime(t, tempo);
+ RealTime ert = rt - crt;
+ timeT et = realTime2Time(ert, tempo);
+ cerr << "cf. time2RealTime(" << t << ") -> " << crt << " [err " << ert << " (" << et << "?)]" << endl;
+ DEBUG_silence_recursive_tempo_printout = 0;
+ }
+#endif
+
+ return t;
+}
+
+timeT
+Composition::realTime2Time(RealTime rt, tempoT tempo,
+ timeT targetTime, tempoT targetTempo) const
+{
+ static timeT cdur = Note(Note::Crotchet).getDuration();
+
+ // Inverse of the expression in time2RealTime above.
+ //
+ // The musical time elapsed at real time t, in ticks, during a
+ // smooth tempo change from "tempo" at real time zero to
+ // "targetTempo" at real time "targetTime", is
+ //
+ // 2na (+/-) sqrt((2nb)^2 + 8(b-a)tn)
+ // - ----------------------------------
+ // 2(b-a)
+ // where
+ //
+ // a is the initial tempo in seconds per tick
+ // b is the target tempo in seconds per tick
+ // n is target real time in ticks
+
+ if (targetTempo == tempo) return realTime2Time(rt, tempo);
+
+ double a = (100000 * 60) / (double(tempo) * cdur);
+ double b = (100000 * 60) / (double(targetTempo) * cdur);
+ double t = double(rt.sec) + double(rt.nsec) / 1e9;
+ double n = targetTime;
+
+ double term1 = 2.0 * n * a;
+ double term2 = (2.0 * n * a) * (2.0 * n * a) + 8 * (b - a) * t * n;
+
+ if (term2 < 0) {
+ // We're screwed, but at least let's not crash
+ std::cerr << "ERROR: Composition::realTime2Time: term2 < 0 (it's " << term2 << ")" << std::endl;
+#ifdef DEBUG_TEMPO_STUFF
+ std::cerr << "rt = " << rt << ", tempo = " << tempo << ", targetTime = " << targetTime << ", targetTempo = " << targetTempo << std::endl;
+ std::cerr << "n = " << n << ", b = " << b << ", a = " << a << ", t = " << t <<std::endl;
+ std::cerr << "that's sqrt( (" << ((2.0*n*a*2.0*n*a)) << ") + "
+ << (8*(b-a)*t*n) << " )" << endl;
+
+ std::cerr << "so our original expression was " << rt << " = "
+ << a << "t + (t^2 * (" << b << " - " << a << ")) / " << 2*n << std::endl;
+#endif
+
+ return realTime2Time(rt, tempo);
+ }
+
+ double term3 = sqrt(term2);
+
+ // We only want the positive root
+ if (term3 > 0) term3 = -term3;
+
+ double result = - (term1 + term3) / (2 * (b - a));
+
+#ifdef DEBUG_TEMPO_STUFF
+ std::cerr << "Composition::realTime2Time:" <<endl;
+ std::cerr << "n = " << n << ", b = " << b << ", a = " << a << ", t = " << t <<std::endl;
+ std::cerr << "+/-sqrt(term2) = " << term3 << std::endl;
+ std::cerr << "result = " << result << endl;
+#endif
+
+ return long(result + 0.1);
+}
+
+bool
+Composition::getTempoTarget(ReferenceSegment::const_iterator i,
+ tempoT &target,
+ timeT &targetTime) const
+{
+ target = -1;
+ targetTime = 0;
+ bool have = false;
+
+ if ((*i)->has(TargetTempoProperty)) {
+ target = (*i)->get<Int>(TargetTempoProperty);
+ if (target >= 0) {
+ ReferenceSegment::const_iterator j(i);
+ if (++j != m_tempoSegment.end()) {
+ if (target == 0) target = (*j)->get<Int>(TempoProperty);
+ targetTime = (*j)->getAbsoluteTime();
+ } else {
+ targetTime = getEndMarker();
+ if (targetTime < (*i)->getAbsoluteTime()) {
+ target = -1;
+ }
+ }
+ if (target > 0) have = true;
+ }
+ }
+
+ return have;
+}
+
+RealTime
+Composition::getTempoTimestamp(const Event *e)
+{
+ RealTime res;
+ e->get<RealTimeT>(TempoTimestampProperty, res);
+ return res;
+}
+
+void
+Composition::setTempoTimestamp(Event *e, RealTime t)
+{
+ e->setMaybe<RealTimeT>(TempoTimestampProperty, t);
+}
+
+void
+Composition::getMusicalTimeForAbsoluteTime(timeT absTime,
+ int &bar, int &beat,
+ int &fraction, int &remainder)
+{
+ bar = getBarNumber(absTime);
+
+ TimeSignature timeSig = getTimeSignatureAt(absTime);
+ timeT barStart = getBarStart(bar);
+ timeT beatDuration = timeSig.getBeatDuration();
+ beat = (absTime - barStart) / beatDuration + 1;
+
+ remainder = (absTime - barStart) % beatDuration;
+ timeT fractionDuration = Note(Note::Shortest).getDuration();
+ fraction = remainder / fractionDuration;
+ remainder = remainder % fractionDuration;
+}
+
+void
+Composition::getMusicalTimeForDuration(timeT absTime, timeT duration,
+ int &bars, int &beats,
+ int &fractions, int &remainder)
+{
+ TimeSignature timeSig = getTimeSignatureAt(absTime);
+ timeT barDuration = timeSig.getBarDuration();
+ timeT beatDuration = timeSig.getBeatDuration();
+
+ bars = duration / barDuration;
+ beats = (duration % barDuration) / beatDuration;
+ remainder = (duration % barDuration) % beatDuration;
+ timeT fractionDuration = Note(Note::Shortest).getDuration();
+ fractions = remainder / fractionDuration;
+ remainder = remainder % fractionDuration;
+}
+
+timeT
+Composition::getAbsoluteTimeForMusicalTime(int bar, int beat,
+ int fraction, int remainder)
+{
+ timeT t = getBarStart(bar - 1);
+ TimeSignature timesig = getTimeSignatureAt(t);
+ t += (beat-1) * timesig.getBeatDuration();
+ t += Note(Note::Shortest).getDuration() * fraction;
+ t += remainder;
+ return t;
+}
+
+timeT
+Composition::getDurationForMusicalTime(timeT absTime,
+ int bars, int beats,
+ int fractions, int remainder)
+{
+ TimeSignature timeSig = getTimeSignatureAt(absTime);
+ timeT barDuration = timeSig.getBarDuration();
+ timeT beatDuration = timeSig.getBeatDuration();
+ timeT t = bars * barDuration + beats * beatDuration + fractions *
+ Note(Note::Shortest).getDuration() + remainder;
+ return t;
+}
+
+void
+Composition::setPosition(timeT position)
+{
+ m_position = position;
+}
+
+void Composition::setPlayMetronome(bool value)
+{
+ m_playMetronome = value;
+ notifyMetronomeChanged();
+}
+
+void Composition::setRecordMetronome(bool value)
+{
+ m_recordMetronome = value;
+ notifyMetronomeChanged();
+}
+
+
+
+#ifdef TRACK_DEBUG
+// track debug convenience function
+//
+static void dumpTracks(Composition::trackcontainer& tracks)
+{
+ Composition::trackiterator it = tracks.begin();
+ for (; it != tracks.end(); ++it) {
+ std::cerr << "tracks[" << (*it).first << "] = "
+ << (*it).second << std::endl;
+ }
+}
+#endif
+
+Track* Composition::getTrackById(TrackId track) const
+{
+ trackconstiterator i = m_tracks.find(track);
+
+ if (i != m_tracks.end())
+ return (*i).second;
+
+ std::cerr << "Composition::getTrackById("
+ << track << ") - WARNING - track id not found, this is probably a BUG "
+ << __FILE__ << ":" << __LINE__ << std::endl;
+ std::cerr << "Available track ids are: " << std::endl;
+ for (trackconstiterator i = m_tracks.begin(); i != m_tracks.end(); ++i) {
+ std::cerr << (*i).second->getId() << std::endl;
+ }
+
+ return 0;
+}
+
+// Move a track object to a new id and position in the container -
+// used when deleting and undoing deletion of tracks.
+//
+//
+void Composition::resetTrackIdAndPosition(TrackId oldId, TrackId newId,
+ int position)
+{
+ trackiterator titerator = m_tracks.find(oldId);
+
+ if (titerator != m_tracks.end())
+ {
+ // detach old track
+ Track *track = (*titerator).second;
+ m_tracks.erase(titerator);
+
+ // set new position and
+ track->setId(newId);
+ track->setPosition(position);
+ m_tracks[newId] = track;
+
+ // modify segment mappings
+ //
+ for (segmentcontainer::const_iterator i = m_segments.begin();
+ i != m_segments.end(); ++i)
+ {
+ if ((*i)->getTrack() == oldId) (*i)->setTrack(newId);
+ }
+
+ checkSelectedAndRecordTracks();
+ updateRefreshStatuses();
+ notifyTrackChanged(getTrackById(newId));
+ }
+ else
+ std::cerr << "Composition::resetTrackIdAndPosition - "
+ << "can't move track " << oldId << " to " << newId
+ << std::endl;
+}
+
+void Composition::setSelectedTrack(TrackId track)
+{
+ m_selectedTrack = track;
+ notifySoloChanged();
+}
+
+void Composition::setSolo(bool value)
+{
+ m_solo = value;
+ notifySoloChanged();
+}
+
+// Insert a Track representation into the Composition
+//
+void Composition::addTrack(Track *track)
+{
+ // make sure a track with the same id isn't already there
+ //
+ if (m_tracks.find(track->getId()) == m_tracks.end()) {
+
+ m_tracks[track->getId()] = track;
+ track->setOwningComposition(this);
+ updateRefreshStatuses();
+ notifyTrackChanged(track);
+
+ } else {
+ std::cerr << "Composition::addTrack("
+ << track << "), id = " << track->getId()
+ << " - WARNING - track id already present "
+ << __FILE__ << ":" << __LINE__ << std::endl;
+ // throw Exception("track id already present");
+ }
+}
+
+
+void Composition::deleteTrack(Rosegarden::TrackId track)
+{
+ trackiterator titerator = m_tracks.find(track);
+
+ if (titerator == m_tracks.end()) {
+
+ std::cerr << "Composition::deleteTrack : no track of id " << track << std::endl;
+ throw Exception("track id not found");
+
+ } else {
+
+ delete ((*titerator).second);
+ m_tracks.erase(titerator);
+ checkSelectedAndRecordTracks();
+ updateRefreshStatuses();
+ notifyTrackDeleted(track);
+ }
+
+}
+
+bool Composition::detachTrack(Rosegarden::Track *track)
+{
+ trackiterator it = m_tracks.begin();
+
+ for (; it != m_tracks.end(); ++it)
+ {
+ if ((*it).second == track)
+ break;
+ }
+
+ if (it == m_tracks.end()) {
+ std::cerr << "Composition::detachTrack() : no such track " << track << std::endl;
+ throw Exception("track id not found");
+ return false;
+ }
+
+ ((*it).second)->setOwningComposition(0);
+
+ m_tracks.erase(it);
+ updateRefreshStatuses();
+ checkSelectedAndRecordTracks();
+
+ return true;
+}
+
+void Composition::checkSelectedAndRecordTracks()
+{
+ // reset m_selectedTrack and m_recordTrack to the next valid track id
+ // if the track they point to has been deleted
+
+ if (m_tracks.find(m_selectedTrack) == m_tracks.end()) {
+
+ m_selectedTrack = getClosestValidTrackId(m_selectedTrack);
+ notifySoloChanged();
+
+ }
+
+ for (recordtrackcontainer::iterator i = m_recordTracks.begin();
+ i != m_recordTracks.end(); ++i) {
+ if (m_tracks.find(*i) == m_tracks.end()) {
+ m_recordTracks.erase(i);
+ }
+ }
+
+}
+
+TrackId
+Composition::getClosestValidTrackId(TrackId id) const
+{
+ long diff = LONG_MAX;
+ TrackId closestValidTrackId = 0;
+
+ for (trackcontainer::const_iterator i = getTracks().begin();
+ i != getTracks().end(); ++i) {
+
+ long cdiff = labs(i->second->getId() - id);
+
+ if (cdiff < diff) {
+ diff = cdiff;
+ closestValidTrackId = i->second->getId();
+
+ } else break; // std::map is sorted, so if the diff increases, we're passed closest valid id
+
+ }
+
+ return closestValidTrackId;
+}
+
+TrackId
+Composition::getMinTrackId() const
+{
+ if (getTracks().size() == 0) return 0;
+
+ trackcontainer::const_iterator i = getTracks().begin();
+ return i->first;
+}
+
+TrackId
+Composition::getMaxTrackId() const
+{
+ if (getTracks().size() == 0) return 0;
+
+ trackcontainer::const_iterator i = getTracks().end();
+ --i;
+
+ return i->first;
+}
+
+void
+Composition::setTrackRecording(TrackId track, bool recording)
+{
+ if (recording) {
+ m_recordTracks.insert(track);
+ } else {
+ m_recordTracks.erase(track);
+ }
+ Track *t = getTrackById(track);
+ if (t) {
+ t->setArmed(recording);
+ }
+}
+
+bool
+Composition::isTrackRecording(TrackId track) const
+{
+ return m_recordTracks.find(track) != m_recordTracks.end();
+}
+
+
+// Export the Composition as XML, also iterates through
+// Tracks and any further sub-objects
+//
+//
+std::string Composition::toXmlString()
+{
+ std::stringstream composition;
+
+ composition << "<composition recordtracks=\"";
+ for (recordtrackiterator i = m_recordTracks.begin();
+ i != m_recordTracks.end(); ) {
+ composition << *i;
+ if (++i != m_recordTracks.end()) {
+ composition << ",";
+ }
+ }
+ composition << "\" pointer=\"" << m_position;
+ composition << "\" defaultTempo=\"";
+ composition << std::setiosflags(std::ios::fixed)
+ << std::setprecision(4)
+ << getTempoQpm(m_defaultTempo);
+ composition << "\" compositionDefaultTempo=\"";
+ composition << m_defaultTempo;
+
+ if (m_loopStart != m_loopEnd)
+ {
+ composition << "\" loopstart=\"" << m_loopStart;
+ composition << "\" loopend=\"" << m_loopEnd;
+ }
+
+ composition << "\" startMarker=\"" << m_startMarker;
+ composition << "\" endMarker=\"" << m_endMarker;
+
+ // Add the Solo if set
+ //
+ if (m_solo)
+ composition << "\" solo=\"" << m_solo;
+
+ composition << "\" selected=\"" << m_selectedTrack;
+ composition << "\" playmetronome=\"" << m_playMetronome;
+ composition << "\" recordmetronome=\"" << m_recordMetronome;
+ composition << "\" nexttriggerid=\"" << m_nextTriggerSegmentId;
+ composition << "\">" << endl << endl;
+
+ composition << endl;
+
+ for (trackiterator tit = getTracks().begin();
+ tit != getTracks().end();
+ ++tit)
+ {
+ if ((*tit).second)
+ composition << " " << (*tit).second->toXmlString() << endl;
+ }
+
+ composition << endl;
+
+ for (ReferenceSegment::iterator i = m_timeSigSegment.begin();
+ i != m_timeSigSegment.end(); ++i) {
+
+ // Might be nice just to stream the events, but that's
+ // normally done by XmlStorableEvent in gui/ at the
+ // moment. Still, this isn't too much of a hardship
+
+ composition << " <timesignature time=\"" << (*i)->getAbsoluteTime()
+ << "\" numerator=\""
+ << (*i)->get<Int>(TimeSignature::NumeratorPropertyName)
+ << "\" denominator=\""
+ << (*i)->get<Int>(TimeSignature::DenominatorPropertyName)
+ << "\"";
+
+ bool common = false;
+ (*i)->get<Bool>(TimeSignature::ShowAsCommonTimePropertyName, common);
+ if (common) composition << " common=\"true\"";
+
+ bool hidden = false;
+ (*i)->get<Bool>(TimeSignature::IsHiddenPropertyName, hidden);
+ if (hidden) composition << " hidden=\"true\"";
+
+ bool hiddenBars = false;
+ (*i)->get<Bool>(TimeSignature::HasHiddenBarsPropertyName, hiddenBars);
+ if (hiddenBars) composition << " hiddenbars=\"true\"";
+
+ composition << "/>" << endl;
+ }
+
+ composition << endl;
+
+ for (ReferenceSegment::iterator i = m_tempoSegment.begin();
+ i != m_tempoSegment.end(); ++i) {
+
+ tempoT tempo = tempoT((*i)->get<Int>(TempoProperty));
+ tempoT target = -1;
+ if ((*i)->has(TargetTempoProperty)) {
+ target = tempoT((*i)->get<Int>(TargetTempoProperty));
+ }
+ composition << " <tempo time=\"" << (*i)->getAbsoluteTime()
+ << "\" bph=\"" << ((tempo * 6) / 10000)
+ << "\" tempo=\"" << tempo;
+ if (target >= 0) {
+ composition << "\" target=\"" << target;
+ }
+ composition << "\"/>" << endl;
+ }
+
+ composition << endl;
+
+ composition << "<metadata>" << endl
+ << m_metadata.toXmlString() << endl
+ << "</metadata>" << endl << endl;
+
+ composition << "<markers>" << endl;
+ for (markerconstiterator mIt = m_markers.begin();
+ mIt != m_markers.end(); ++mIt)
+ {
+ composition << (*mIt)->toXmlString();
+ }
+ composition << "</markers>" << endl;
+
+
+#if (__GNUC__ < 3)
+ composition << "</composition>" << std::ends;
+#else
+ composition << "</composition>";
+#endif
+
+ return composition.str();
+}
+
+void
+Composition::clearTracks()
+{
+ trackiterator it = m_tracks.begin();
+
+ for (; it != m_tracks.end(); it++)
+ delete ((*it).second);
+
+ m_tracks.erase(m_tracks.begin(), m_tracks.end());
+}
+
+Track*
+Composition::getTrackByPosition(int position) const
+{
+ trackconstiterator it = m_tracks.begin();
+
+ for (; it != m_tracks.end(); it++)
+ {
+ if ((*it).second->getPosition() == position)
+ return (*it).second;
+ }
+
+ return 0;
+
+}
+
+int
+Composition::getTrackPositionById(TrackId id) const
+{
+ Track *track = getTrackById(id);
+ if (!track) return -1;
+ return track->getPosition();
+}
+
+
+Rosegarden::TrackId
+Composition::getNewTrackId() const
+{
+ // Re BR #1070325: another track deletion problem
+ // Formerly this was returning the count of tracks currently in
+ // existence -- returning a duplicate ID if some had been deleted
+ // from the middle. Let's find one that's really available instead.
+
+ TrackId highWater = 0;
+
+ trackconstiterator it = m_tracks.begin();
+
+ for (; it != m_tracks.end(); it++)
+ {
+ if ((*it).second->getId() >= highWater)
+ highWater = (*it).second->getId() + 1;
+ }
+
+ return highWater;
+}
+
+
+void
+Composition::notifySegmentAdded(Segment *s) const
+{
+ // If there is an earlier repeating segment on the same track, we
+ // need to notify the change of its repeat end time
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+
+ if (((*i)->getTrack() == s->getTrack())
+ && ((*i)->isRepeating())
+ && ((*i)->getStartTime() < s->getStartTime())) {
+
+ notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime());
+ }
+ }
+
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentAdded(this, s);
+ }
+}
+
+
+void
+Composition::notifySegmentRemoved(Segment *s) const
+{
+ // If there is an earlier repeating segment on the same track, we
+ // need to notify the change of its repeat end time
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+
+ if (((*i)->getTrack() == s->getTrack())
+ && ((*i)->isRepeating())
+ && ((*i)->getStartTime() < s->getStartTime())) {
+
+ notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime());
+ }
+ }
+
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentRemoved(this, s);
+ }
+}
+
+void
+Composition::notifySegmentRepeatChanged(Segment *s, bool repeat) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentRepeatChanged(this, s, repeat);
+ }
+}
+
+void
+Composition::notifySegmentRepeatEndChanged(Segment *s, timeT t) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentRepeatEndChanged(this, s, t);
+ }
+}
+
+void
+Composition::notifySegmentEventsTimingChanged(Segment *s, timeT delay, RealTime rtDelay) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentEventsTimingChanged(this, s, delay, rtDelay);
+ }
+}
+
+void
+Composition::notifySegmentTransposeChanged(Segment *s, int transpose) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentTransposeChanged(this, s, transpose);
+ }
+}
+
+void
+Composition::notifySegmentTrackChanged(Segment *s, TrackId oldId, TrackId newId) const
+{
+ // If there is an earlier repeating segment on either the
+ // origin or destination track, we need to notify the change
+ // of its repeat end time
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+
+ if (((*i)->getTrack() == oldId || (*i)->getTrack() == newId)
+ && ((*i)->isRepeating())
+ && ((*i)->getStartTime() < s->getStartTime())) {
+
+ notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime());
+ }
+ }
+
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentTrackChanged(this, s, newId);
+ }
+}
+
+void
+Composition::notifySegmentStartChanged(Segment *s, timeT t)
+{
+ updateRefreshStatuses(); // not ideal, but best way to ensure track heights are recomputed
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentStartChanged(this, s, t);
+ }
+}
+
+void
+Composition::notifySegmentEndMarkerChange(Segment *s, bool shorten)
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentEndMarkerChanged(this, s, shorten);
+ }
+}
+
+void
+Composition::notifyEndMarkerChange(bool shorten) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->endMarkerTimeChanged(this, shorten);
+ }
+}
+
+void
+Composition::notifyTrackChanged(Track *t) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->trackChanged(this, t);
+ }
+}
+
+void
+Composition::notifyTrackDeleted(TrackId t) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->trackDeleted(this, t);
+ }
+}
+
+void
+Composition::notifyMetronomeChanged() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->metronomeChanged(this);
+ }
+}
+
+void
+Composition::notifyTimeSignatureChanged() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->timeSignatureChanged(this);
+ }
+}
+
+void
+Composition::notifySoloChanged() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->soloChanged(this, isSolo(), getSelectedTrack());
+ }
+}
+
+void
+Composition::notifyTempoChanged() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->tempoChanged(this);
+ }
+}
+
+
+void
+Composition::notifySourceDeletion() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->compositionDeleted(this);
+ }
+}
+
+
+void breakpoint()
+{
+ //std::cerr << "breakpoint()\n";
+}
+
+// Just empty out the markers
+void
+Composition::clearMarkers()
+{
+ markerconstiterator it = m_markers.begin();
+
+ for (; it != m_markers.end(); ++it)
+ {
+ delete *it;
+ }
+
+ m_markers.clear();
+}
+
+void
+Composition::addMarker(Rosegarden::Marker *marker)
+{
+ m_markers.push_back(marker);
+ updateRefreshStatuses();
+}
+
+bool
+Composition::detachMarker(Rosegarden::Marker *marker)
+{
+ markeriterator it = m_markers.begin();
+
+ for (; it != m_markers.end(); ++it)
+ {
+ if (*it == marker)
+ {
+ m_markers.erase(it);
+ updateRefreshStatuses();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+Composition::isMarkerAtPosition(Rosegarden::timeT time) const
+{
+ markerconstiterator it = m_markers.begin();
+
+ for (; it != m_markers.end(); ++it)
+ if ((*it)->getTime() == time) return true;
+
+ return false;
+}
+
+void
+Composition::setSegmentColourMap(Rosegarden::ColourMap &newmap)
+{
+ m_segmentColourMap = newmap;
+
+ updateRefreshStatuses();
+}
+
+void
+Composition::setGeneralColourMap(Rosegarden::ColourMap &newmap)
+{
+ m_generalColourMap = newmap;
+
+ updateRefreshStatuses();
+}
+
+void
+Composition::dump(std::ostream& out, bool) const
+{
+ out << "Composition segments : " << endl;
+
+ for(iterator i = begin(); i != end(); ++i) {
+ Segment* s = *i;
+
+ out << "Segment start : " << s->getStartTime() << " - end : " << s->getEndMarkerTime()
+ << " - repeating : " << s->isRepeating()
+ << " - track id : " << s->getTrack()
+ << " - label : " << s->getLabel()
+ << endl;
+
+ }
+
+}
+
+
+
+}
+
+
diff --git a/src/base/Composition.h b/src/base/Composition.h
new file mode 100644
index 0000000..24865dd
--- /dev/null
+++ b/src/base/Composition.h
@@ -0,0 +1,1134 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _COMPOSITION_H_
+#define _COMPOSITION_H_
+
+#include <set>
+#include <map>
+
+#include "FastVector.h"
+
+#include "RealTime.h"
+#include "Segment.h"
+#include "Track.h"
+#include "Configuration.h"
+#include "XmlExportable.h"
+#include "ColourMap.h"
+#include "TriggerSegment.h"
+
+#include "Marker.h"
+
+namespace Rosegarden
+{
+// We store tempo in quarter-notes per minute * 10^5 (hundred
+// thousandths of a quarter-note per minute). This means the maximum
+// tempo in a 32-bit integer is about 21400 qpm. We use a signed int
+// for compatibility with the Event integer type -- but note that we
+// use 0 (rather than -1) to indicate "tempo not set", by convention
+// (though see usage of target tempo in e.g. addTempoAtTime).
+typedef int tempoT;
+
+class Quantizer;
+class BasicQuantizer;
+class NotationQuantizer;
+
+/**
+ * Composition contains a complete representation of a piece of music.
+ * It is a container for multiple Segments, as well as any associated
+ * non-Event data.
+ *
+ * The Composition owns the Segments it holds, and deletes them on
+ * destruction. When Segments are removed, it will also delete them.
+ */
+
+class CompositionObserver;
+
+class Composition : public XmlExportable
+{
+ friend class Track; // to call notifyTrackChanged()
+ friend class Segment; // to call notifySegmentRepeatChanged()
+
+public:
+ typedef std::multiset<Segment*, Segment::SegmentCmp> segmentcontainer;
+ typedef segmentcontainer::iterator iterator;
+ typedef segmentcontainer::const_iterator const_iterator;
+
+ typedef std::map<TrackId, Track*> trackcontainer;
+ typedef trackcontainer::iterator trackiterator;
+ typedef trackcontainer::const_iterator trackconstiterator;
+
+ typedef std::vector<Marker*> markercontainer;
+ typedef markercontainer::iterator markeriterator;
+ typedef markercontainer::const_iterator markerconstiterator;
+
+ typedef std::set<TriggerSegmentRec *, TriggerSegmentCmp> triggersegmentcontainer;
+ typedef triggersegmentcontainer::iterator triggersegmentcontaineriterator;
+ typedef triggersegmentcontainer::const_iterator triggersegmentcontainerconstiterator;
+
+ typedef std::set<TrackId> recordtrackcontainer;
+ typedef recordtrackcontainer::iterator recordtrackiterator;
+ typedef recordtrackcontainer::const_iterator recordtrackconstiterator;
+
+ Composition();
+ virtual ~Composition();
+
+private:
+ Composition(const Composition &);
+ Composition &operator=(const Composition &);
+public:
+
+ /**
+ * Remove all Segments from the Composition and destroy them
+ */
+ void clear();
+
+ /**
+ * Return the absolute end time of the segment that ends last
+ */
+ timeT getDuration() const;
+
+
+ //////
+ //
+ // START AND END MARKERS
+
+ timeT getStartMarker() const { return m_startMarker; }
+ timeT getEndMarker() const { return m_endMarker; }
+
+ void setStartMarker(const timeT &sM);
+ void setEndMarker(const timeT &eM);
+
+
+ //////
+ //
+ // INSTRUMENT & TRACK
+
+ Track* getTrackById(TrackId track) const;
+
+ Track* getTrackByPosition(int position) const;
+
+ int getTrackPositionById(TrackId track) const; // -1 if not found
+
+ trackcontainer& getTracks() { return m_tracks; }
+
+ const trackcontainer& getTracks() const { return m_tracks; }
+
+ // Reset id and position
+ void resetTrackIdAndPosition(TrackId oldId, TrackId newId, int position);
+
+ TrackId getMinTrackId() const;
+ TrackId getMaxTrackId() const;
+
+ const recordtrackcontainer &getRecordTracks() const { return m_recordTracks; }
+ void setTrackRecording(TrackId track, bool recording);
+ bool isTrackRecording(TrackId track) const;
+
+ // Get and set Solo Track
+ //
+ TrackId getSelectedTrack() const { return m_selectedTrack; }
+
+ void setSelectedTrack(TrackId track);
+
+ // Are we soloing a Track?
+ //
+ bool isSolo() const { return m_solo; }
+ void setSolo(bool value);
+
+ unsigned int getNbTracks() const { return m_tracks.size(); }
+
+ /**
+ * Clear out the Track container
+ */
+ void clearTracks();
+
+ /**
+ * Insert a new Track. The Composition takes over ownership of
+ * the track object.
+ */
+ void addTrack(Track *track);
+
+ /**
+ * Delete a Track by index
+ */
+ void deleteTrack(TrackId track);
+
+ /**
+ * Detach a Track (revert ownership of the Track object to the
+ * caller).
+ */
+ bool detachTrack(Track *track);
+
+ /**
+ * Get the highest running track id (generated and kept
+ * through addTrack)
+ */
+ TrackId getNewTrackId() const;
+
+
+ //////
+ //
+ // MARKERS
+
+ markercontainer& getMarkers() { return m_markers; }
+ const markercontainer& getMarkers() const { return m_markers; }
+
+ /**
+ * Add a new Marker. The Composition takes ownership of the
+ * marker object.
+ */
+ void addMarker(Marker *marker);
+
+ /**
+ * Detach a Marker (revert ownership of the Marker object to the
+ * caller).
+ */
+ bool detachMarker(Marker *marker);
+
+ bool isMarkerAtPosition(timeT time) const;
+
+ void clearMarkers();
+
+
+ //////
+ //
+ // SEGMENT
+
+ segmentcontainer& getSegments() { return m_segments; }
+ const segmentcontainer& getSegments() const { return m_segments; }
+
+ unsigned int getNbSegments() const { return m_segments.size(); }
+
+ /**
+ * Add a new Segment and return an iterator pointing to it
+ * The inserted Segment is owned by the Composition object
+ */
+ iterator addSegment(Segment*);
+
+ /**
+ * Delete the Segment pointed to by the specified iterator
+ *
+ * NOTE: The Segment is deleted from the Composition and
+ * destroyed
+ */
+ void deleteSegment(iterator);
+
+ /**
+ * Delete the Segment if it is part of the Composition
+ * \return true if the Segment was found and deleted
+ *
+ * NOTE: The Segment is deleted from the composition and
+ * destroyed
+ */
+ bool deleteSegment(Segment*);
+
+ /**
+ * DO NOT USE THIS METHOD
+ *
+ * Set a Segment's start time while keeping the integrity of the
+ * Composition multiset.
+ *
+ * The segment is removed and re-inserted from the composition
+ * so the ordering is preserved.
+ */
+ void setSegmentStartTime(Segment*, timeT);
+
+ /**
+ * Test whether a Segment exists in this Composition.
+ */
+ bool contains(const Segment *);
+
+ /**
+ * Return an iterator pointing at the given Segment, or end()
+ * if it does not exist in this Composition.
+ */
+ iterator findSegment(const Segment *);
+
+ /**
+ * Remove the Segment if it is part of the Composition,
+ * but do not destroy it (passing it to addSegment again
+ * would restore it correctly).
+ * \return true if the Segment was found and removed
+ *
+ * NOTE: Many of the Segment methods will fail if the
+ * Segment is not in a Composition. You should not
+ * expect to do anything meaningful with a Segment that
+ * has been detached from the Composition in this way.
+ */
+ bool detachSegment(Segment*);
+
+ /**
+ * Add a new Segment which has been "weakly detached"
+ *
+ * Like addSegment(), but doesn't send the segmentAdded signal
+ * nor updating refresh statuses
+ */
+ iterator weakAddSegment(Segment*);
+
+ /**
+ * Detach a segment which you're going to re-add (with weakAddSegment)
+ * later.
+ * Like detachSegment(), but without sending the segmentDeleted signal
+ * nor updating refresh statuses.
+ */
+ bool weakDetachSegment(Segment*);
+
+ /**
+ * Get the largest number of segments that "overlap" at any one
+ * time on the given track. I have given this function a nice
+ * long name to make it feel important.
+ */
+ int getMaxContemporaneousSegmentsOnTrack(TrackId track) const;
+
+ /**
+ * Retrieve a "vertical" index for this segment within its track.
+ * Currently this is based on studying the way that segments on
+ * the track overlap and returning the lowest integer such that no
+ * prior starting segment that overlaps with this one would use
+ * the same integer. In future this could use proper voice
+ * ordering.
+ */
+ int getSegmentVoiceIndex(const Segment *) const;
+
+
+ //////
+ //
+ // TRIGGER SEGMENTS
+
+ triggersegmentcontainer &getTriggerSegments() { return m_triggerSegments; }
+ const triggersegmentcontainer &getTriggerSegments() const { return m_triggerSegments; }
+
+ /**
+ * Add a new trigger Segment with a given base pitch and base
+ * velocity, and return its record. If pitch or velocity is -1,
+ * it will be taken from the first note event in the segment
+ */
+ TriggerSegmentRec *addTriggerSegment(Segment *, int pitch = -1, int velocity = -1);
+
+ /**
+ * Delete a trigger Segment.
+ */
+ void deleteTriggerSegment(TriggerSegmentId);
+
+ /**
+ * Detach a trigger Segment from the Composition.
+ */
+ void detachTriggerSegment(TriggerSegmentId);
+
+ /**
+ * Delete all trigger Segments.
+ */
+ void clearTriggerSegments();
+
+ /**
+ * Return the TriggerSegmentId for the given Segment, or -1 if it is
+ * not a trigger Segment.
+ */
+ int getTriggerSegmentId(Segment *);
+
+ /**
+ * Return the Segment for a given TriggerSegmentId
+ */
+ Segment *getTriggerSegment(TriggerSegmentId);
+
+ /**
+ * Return the TriggerSegmentRec (with Segment, base pitch, base velocity,
+ * references etc) for a given TriggerSegmentId
+ */
+ TriggerSegmentRec *getTriggerSegmentRec(TriggerSegmentId);
+
+ /**
+ * Add a new trigger Segment with a given ID and base pitch and
+ * velocity. Fails and returns 0 if the ID is already in use.
+ * This is intended for use from file load or from undo/redo.
+ */
+ TriggerSegmentRec *addTriggerSegment(Segment *, TriggerSegmentId,
+ int basePitch = -1, int baseVelocity = -1);
+
+ /**
+ * Get the ID of the next trigger segment that will be inserted.
+ */
+ TriggerSegmentId getNextTriggerSegmentId() const;
+
+ /**
+ * Specify the next trigger ID. This is intended for use from file
+ * load only. Do not use this function unless you know what you're
+ * doing.
+ */
+ void setNextTriggerSegmentId(TriggerSegmentId);
+
+ /**
+ * Update the trigger segment references for all trigger segments.
+ * To be called after file load.
+ */
+ void updateTriggerSegmentReferences();
+
+
+ //////
+ //
+ // BAR
+
+ /**
+ * Return the total number of bars in the composition
+ */
+ int getNbBars() const;
+
+ /**
+ * Return the number of the bar that starts at or contains time t.
+ *
+ * Will happily return computed bar numbers for times before
+ * the start or beyond the real end of the composition.
+ */
+ int getBarNumber(timeT t) const;
+
+ /**
+ * Return the starting time of bar n
+ */
+ timeT getBarStart(int n) const {
+ return getBarRange(n).first;
+ }
+
+ /**
+ * Return the ending time of bar n
+ */
+ timeT getBarEnd(int n) const {
+ return getBarRange(n).second;
+ }
+
+ /**
+ * Return the time range of bar n.
+ *
+ * Will happily return theoretical timings for bars before the
+ * start or beyond the end of composition (i.e. there is no
+ * requirement that 0 <= n < getNbBars()).
+ */
+ std::pair<timeT, timeT> getBarRange(int n) const;
+
+ /**
+ * Return the starting time of the bar that contains time t
+ */
+ timeT getBarStartForTime(timeT t) const {
+ return getBarRangeForTime(t).first;
+ }
+
+ /**
+ * Return the ending time of the bar that contains time t
+ */
+ timeT getBarEndForTime(timeT t) const {
+ return getBarRangeForTime(t).second;
+ }
+
+ /**
+ * Return the starting and ending times of the bar that contains
+ * time t.
+ *
+ * Will happily return theoretical timings for bars before the
+ * start or beyond the end of composition.
+ */
+ std::pair<timeT, timeT> getBarRangeForTime(timeT t) const;
+
+ /**
+ * Get the default number of bars in a new empty composition
+ */
+ static int getDefaultNbBars() { return m_defaultNbBars; }
+
+ /**
+ * Set the default number of bars in a new empty composition
+ */
+ static void setDefaultNbBars(int b) { m_defaultNbBars = b; }
+
+
+ //////
+ //
+ // TIME SIGNATURE
+
+ /**
+ * Add the given time signature at the given time. Returns the
+ * resulting index of the time signature (suitable for passing
+ * to removeTimeSignature, for example)
+ */
+ int addTimeSignature(timeT t, TimeSignature timeSig);
+
+ /**
+ * Return the time signature in effect at time t
+ */
+ TimeSignature getTimeSignatureAt(timeT t) const;
+
+ /**
+ * Return the time signature in effect at time t, and the time at
+ * which it came into effect
+ */
+ timeT getTimeSignatureAt(timeT, TimeSignature &) const;
+
+ /**
+ * Return the time signature in effect in bar n. Also sets
+ * isNew to true if the time signature is a new one that did
+ * not appear in the previous bar.
+ */
+ TimeSignature getTimeSignatureInBar(int n, bool &isNew) const;
+
+ /**
+ * Return the total number of time signature changes in the
+ * composition.
+ */
+ int getTimeSignatureCount() const;
+
+ /**
+ * Return the index of the last time signature change before
+ * or at the given time, in a range suitable for passing to
+ * getTimeSignatureChange. Return -1 if there has been no
+ * time signature by this time.
+ */
+ int getTimeSignatureNumberAt(timeT time) const;
+
+ /**
+ * Return the absolute time of and time signature introduced
+ * by time-signature change n.
+ */
+ std::pair<timeT, TimeSignature> getTimeSignatureChange(int n) const;
+
+ /**
+ * Remove time signature change event n from the composition.
+ */
+ void removeTimeSignature(int n);
+
+
+
+ //////
+ //
+ // TEMPO
+
+ /**
+ * Return the (approximate) number of quarters per minute for a
+ * given tempo.
+ */
+ static double getTempoQpm(tempoT tempo) { return double(tempo) / 100000.0; }
+ static tempoT getTempoForQpm(double qpm) { return tempoT(qpm * 100000 + 0.01); }
+
+ /**
+ * Return the tempo in effect at time t. If a ramped tempo change
+ * is in effect at the time, it will be properly interpolated and
+ * a computed value returned.
+ */
+ tempoT getTempoAtTime(timeT t) const;
+
+ /**
+ * Return the tempo in effect at the current playback position.
+ */
+ tempoT getCurrentTempo() const { return getTempoAtTime(getPosition()); }
+
+ /**
+ * Set a default tempo for the composition. This will be
+ * overridden by any tempo events encountered during playback.
+ */
+ void setCompositionDefaultTempo(tempoT tempo) { m_defaultTempo = tempo; }
+ tempoT getCompositionDefaultTempo() const { return m_defaultTempo; }
+
+ /**
+ * Add a tempo-change event at the given time, to the given tempo.
+ * Removes any existing tempo event at that time. Returns the
+ * index of the new tempo event in a form suitable for passing to
+ * removeTempoChange.
+ *
+ * If targetTempo == -1, adds a single constant tempo change.
+ * If targetTempo == 0, adds a smooth tempo ramp from this tempo
+ * change to the next.
+ * If targetTempo > 0, adds a smooth tempo ramp from this tempo
+ * ending at targetTempo at the time of the next tempo change.
+ */
+ int addTempoAtTime(timeT time, tempoT tempo, tempoT targetTempo = -1);
+
+ /**
+ * Return the number of tempo changes in the composition.
+ */
+ int getTempoChangeCount() const;
+
+ /**
+ * Return the index of the last tempo change before the given
+ * time, in a range suitable for passing to getTempoChange.
+ * Return -1 if the default tempo is in effect at this time.
+ */
+ int getTempoChangeNumberAt(timeT time) const;
+
+ /**
+ * Return the absolute time of and tempo introduced by tempo
+ * change number n. If the tempo is ramped, this returns only
+ * the starting tempo.
+ */
+ std::pair<timeT, tempoT> getTempoChange(int n) const;
+
+ /**
+ * Return whether the tempo change number n is a ramped tempo or
+ * not, and if it is, return the target tempo for the ramp.
+ *
+ * If calculate is false, return a target tempo of 0 if the tempo
+ * change is defined to ramp to the following tempo. If calculate
+ * is true, return a target tempo equal to the following tempo in
+ * this case.
+ */
+ std::pair<bool, tempoT> getTempoRamping(int n, bool calculate = true) const;
+
+ /**
+ * Remove tempo change event n from the composition.
+ */
+ void removeTempoChange(int n);
+
+ /**
+ * Get the slowest assigned tempo in the composition.
+ */
+ tempoT getMinTempo() const {
+ return ((m_minTempo != 0) ? m_minTempo : m_defaultTempo);
+ }
+
+ /**
+ * Get the fastest assigned tempo in the composition.
+ */
+ tempoT getMaxTempo() const {
+ return ((m_maxTempo != 0) ? m_maxTempo : m_defaultTempo);
+ }
+
+
+ //////
+ //
+ // REAL TIME
+
+ /**
+ * Return the number of microseconds elapsed between
+ * the beginning of the composition and the given timeT time.
+ * (timeT units are independent of tempo; this takes into
+ * account any tempo changes in the first t units of time.)
+ *
+ * This is a fairly efficient operation, not dependent on the
+ * magnitude of t or the number of tempo changes in the piece.
+ */
+ RealTime getElapsedRealTime(timeT t) const;
+
+ /**
+ * Return the nearest time in timeT units to the point at the
+ * given number of microseconds after the beginning of the
+ * composition. (timeT units are independent of tempo; this takes
+ * into account any tempo changes in the first t microseconds.)
+ * The result will be approximate, as timeT units are obviously
+ * less precise than microseconds.
+ *
+ * This is a fairly efficient operation, not dependent on the
+ * magnitude of t or the number of tempo changes in the piece.
+ */
+ timeT getElapsedTimeForRealTime(RealTime t) const;
+
+ /**
+ * Return the number of microseconds elapsed between
+ * the two given timeT indices into the composition, taking
+ * into account any tempo changes between the two times.
+ */
+ RealTime getRealTimeDifference(timeT t0, timeT t1) const {
+ if (t1 > t0) return getElapsedRealTime(t1) - getElapsedRealTime(t0);
+ else return getElapsedRealTime(t0) - getElapsedRealTime(t1);
+ }
+
+
+ //////
+ //
+ // OTHER TIME CONVERSIONS
+
+ /**
+ * Return (by reference) the bar number and beat/division values
+ * corresponding to a given absolute time.
+ */
+ void getMusicalTimeForAbsoluteTime(timeT absoluteTime,
+ int &bar, int &beat,
+ int &fraction, int &remainder);
+
+ /**
+ * Return (by reference) the number of bars and beats/divisions
+ * corresponding to a given duration. The absolute time at which
+ * the duration starts is also required, so as to know the correct
+ * time signature.
+ */
+ void getMusicalTimeForDuration(timeT absoluteTime, timeT duration,
+ int &bars, int &beats,
+ int &fractions, int &remainder);
+
+ /**
+ * Return the absolute time corresponding to a given bar number
+ * and beat/division values.
+ */
+ timeT getAbsoluteTimeForMusicalTime(int bar, int beat,
+ int fraction, int remainder);
+
+ /**
+ * Return the duration corresponding to a given number of bars and
+ * beats/divisions. The absolute time at which the duration
+ * starts is also required, so as to know the correct time
+ * signature.
+ */
+ timeT getDurationForMusicalTime(timeT absoluteTime,
+ int bars, int beats,
+ int fractions, int remainder);
+
+
+ /**
+ * Get the current playback position.
+ */
+ timeT getPosition() const { return m_position; }
+
+ /**
+ * Set the current playback position.
+ */
+ void setPosition(timeT position);
+
+
+
+ //////
+ //
+ // LOOP
+
+ timeT getLoopStart() const { return m_loopStart; }
+ timeT getLoopEnd() const { return m_loopEnd;}
+
+ void setLoopStart(const timeT &lS) { m_loopStart = lS; }
+ void setLoopEnd(const timeT &lE) { m_loopEnd = lE; }
+
+ // Determine if we're currently looping
+ //
+ bool isLooping() const { return (m_loopStart != m_loopEnd); }
+
+
+
+ //////
+ //
+ // OTHER STUFF
+
+
+ // Some set<> API delegation
+ iterator begin() { return m_segments.begin(); }
+ const_iterator begin() const { return m_segments.begin(); }
+ iterator end() { return m_segments.end(); }
+ const_iterator end() const { return m_segments.end(); }
+
+
+ // XML exportable method
+ //
+ virtual std::string toXmlString();
+
+ // Who's making this racket?
+ //
+ Configuration &getMetadata() {
+ return m_metadata;
+ }
+ const Configuration &getMetadata() const {
+ return m_metadata;
+ }
+
+ std::string getCopyrightNote() const {
+ return m_metadata.get<String>(CompositionMetadataKeys::Copyright,
+ "");
+ }
+ void setCopyrightNote(const std::string &cr) {
+ m_metadata.set<String>(CompositionMetadataKeys::Copyright, cr);
+ }
+
+
+ // We can have the metronome on or off while playing or
+ // recording - get and set values from here
+ //
+ bool usePlayMetronome() const { return m_playMetronome; }
+ bool useRecordMetronome() const { return m_recordMetronome; }
+
+ void setPlayMetronome(bool value);
+ void setRecordMetronome(bool value);
+
+
+ // Colour stuff
+ ColourMap& getSegmentColourMap() { return m_segmentColourMap; }
+ const ColourMap& getSegmentColourMap() const { return m_segmentColourMap; }
+ void setSegmentColourMap(ColourMap &newmap);
+
+ // General colourmap for non-segments
+ //
+ ColourMap& getGeneralColourMap() { return m_generalColourMap; }
+ void setGeneralColourMap(ColourMap &newmap);
+
+
+ //////
+ //
+ // QUANTIZERS
+
+ /**
+ * Return a quantizer that quantizes to the our most basic
+ * units (i.e. a unit quantizer whose unit is our shortest
+ * note duration).
+ */
+ const BasicQuantizer *getBasicQuantizer() const {
+ return m_basicQuantizer;
+ }
+
+ /**
+ * Return a quantizer that does quantization for notation
+ * only.
+ */
+ const NotationQuantizer *getNotationQuantizer() const {
+ return m_notationQuantizer;
+ }
+
+
+ //////
+ //
+ // REFRESH STATUS
+
+ // delegate RefreshStatusArray API
+ unsigned int getNewRefreshStatusId() {
+ return m_refreshStatusArray.getNewRefreshStatusId();
+ }
+
+ RefreshStatus& getRefreshStatus(unsigned int id) {
+ return m_refreshStatusArray.getRefreshStatus(id);
+ }
+
+ /// Set all refresh statuses to true
+ void updateRefreshStatuses() {
+ m_refreshStatusArray.updateRefreshStatuses();
+ }
+
+
+ void addObserver(CompositionObserver *obs) { m_observers.push_back(obs); }
+ void removeObserver(CompositionObserver *obs) { m_observers.remove(obs); }
+
+ //////
+ // DEBUG FACILITIES
+ void dump(std::ostream&, bool full=false) const;
+
+protected:
+
+ static const std::string TempoEventType;
+ static const PropertyName TempoProperty;
+ static const PropertyName TargetTempoProperty;
+
+ static const PropertyName NoAbsoluteTimeProperty;
+ static const PropertyName BarNumberProperty;
+ static const PropertyName TempoTimestampProperty;
+
+
+ struct ReferenceSegmentEventCmp
+ {
+ bool operator()(const Event &e1, const Event &e2) const;
+ bool operator()(const Event *e1, const Event *e2) const {
+ return operator()(*e1, *e2);
+ }
+ };
+
+ struct BarNumberComparator
+ {
+ bool operator()(const Event &e1, const Event &e2) const {
+ return (e1.get<Int>(BarNumberProperty) <
+ e2.get<Int>(BarNumberProperty));
+ }
+ bool operator()(const Event *e1, const Event *e2) const {
+ return operator()(*e1, *e2);
+ }
+ };
+
+ /**
+ * Ensure the selected and record trackids still point to something valid
+ * Must be called after deletion of detach of a track
+ */
+ void checkSelectedAndRecordTracks();
+ TrackId getClosestValidTrackId(TrackId id) const;
+
+
+ //--------------- Data members ---------------------------------
+ //
+ trackcontainer m_tracks;
+ segmentcontainer m_segments;
+
+ // The tracks we are armed for record on
+ //
+ recordtrackcontainer m_recordTracks;
+
+ // Are we soloing and if so which Track?
+ //
+ bool m_solo;
+ TrackId m_selectedTrack;
+
+ /**
+ * This is a bit like a segment, but can only contain one sort of
+ * event, and can only have one event at each absolute time
+ */
+ class ReferenceSegment :
+ public FastVector<Event *> // not a set: want random access for bars
+ {
+ typedef FastVector<Event *> Impl;
+
+ public:
+ ReferenceSegment(std::string eventType);
+ virtual ~ReferenceSegment();
+ private:
+ ReferenceSegment(const ReferenceSegment &);
+ ReferenceSegment& operator=(const ReferenceSegment &);
+ public:
+ typedef Impl::iterator iterator;
+ typedef Impl::size_type size_type;
+ typedef Impl::difference_type difference_type;
+
+ void clear();
+
+ timeT getDuration() const;
+
+ /// Inserts a single event, removing any existing one at that time
+ iterator insert(Event *e); // may throw Event::BadType
+
+ void erase(Event *e);
+
+ iterator findTime(timeT time);
+ iterator findNearestTime(timeT time);
+
+ iterator findRealTime(RealTime time);
+ iterator findNearestRealTime(RealTime time);
+
+ std::string getEventType() const { return m_eventType; }
+
+ private:
+ iterator find(Event *e);
+ std::string m_eventType;
+ };
+
+ /// Contains time signature events
+ mutable ReferenceSegment m_timeSigSegment;
+
+ /// Contains tempo events
+ mutable ReferenceSegment m_tempoSegment;
+
+ /// affects m_timeSigSegment
+ void calculateBarPositions() const;
+ mutable bool m_barPositionsNeedCalculating;
+ ReferenceSegment::iterator getTimeSignatureAtAux(timeT t) const;
+
+ /// affects m_tempoSegment
+ void calculateTempoTimestamps() const;
+ mutable bool m_tempoTimestampsNeedCalculating;
+ RealTime time2RealTime(timeT time, tempoT tempo) const;
+ RealTime time2RealTime(timeT time, tempoT tempo,
+ timeT targetTempoTime, tempoT targetTempo) const;
+ timeT realTime2Time(RealTime rtime, tempoT tempo) const;
+ timeT realTime2Time(RealTime rtime, tempoT tempo,
+ timeT targetTempoTime, tempoT targetTempo) const;
+
+ bool getTempoTarget(ReferenceSegment::const_iterator i,
+ tempoT &target,
+ timeT &targetTime) const;
+
+ static RealTime getTempoTimestamp(const Event *e);
+ static void setTempoTimestamp(Event *e, RealTime r);
+
+ typedef std::list<CompositionObserver *> ObserverSet;
+ ObserverSet m_observers;
+
+ void notifySegmentAdded(Segment *) const;
+ void notifySegmentRemoved(Segment *) const;
+ void notifySegmentRepeatChanged(Segment *, bool) const;
+ void notifySegmentRepeatEndChanged(Segment *, timeT) const;
+ void notifySegmentEventsTimingChanged(Segment *s, timeT delay, RealTime rtDelay) const;
+ void notifySegmentTransposeChanged(Segment *s, int transpose) const;
+ void notifySegmentTrackChanged(Segment *s, TrackId oldId, TrackId newId) const;
+ void notifySegmentStartChanged(Segment *, timeT);
+ void notifySegmentEndMarkerChange(Segment *s, bool shorten);
+ void notifyEndMarkerChange(bool shorten) const;
+ void notifyTrackChanged(Track*) const;
+ void notifyTrackDeleted(TrackId) const;
+ void notifyMetronomeChanged() const;
+ void notifyTimeSignatureChanged() const;
+ void notifySoloChanged() const;
+ void notifyTempoChanged() const;
+ void notifySourceDeletion() const;
+
+ void updateExtremeTempos();
+
+ BasicQuantizer *m_basicQuantizer;
+ NotationQuantizer *m_notationQuantizer;
+
+ timeT m_position;
+ tempoT m_defaultTempo;
+ tempoT m_minTempo; // cached from tempo segment
+ tempoT m_maxTempo; // cached from tempo segment
+
+ // Notional Composition markers - these define buffers for the
+ // start and end of the piece, Segments can still exist outside
+ // of these markers - these are for visual and playback cueing.
+ //
+ timeT m_startMarker;
+ timeT m_endMarker;
+
+ static int m_defaultNbBars;
+
+ // Loop start and end positions. If they're both the same
+ // value (usually 0) then there's no loop set.
+ //
+ timeT m_loopStart;
+ timeT m_loopEnd;
+
+ Configuration m_metadata;
+
+ bool m_playMetronome;
+ bool m_recordMetronome;
+
+ RefreshStatusArray<RefreshStatus> m_refreshStatusArray;
+
+ // User defined markers in the composition
+ //
+ markercontainer m_markers;
+
+ // Trigger segments (unsorted segments fired by events elsewhere)
+ //
+ triggersegmentcontainer m_triggerSegments;
+ TriggerSegmentId m_nextTriggerSegmentId;
+
+ ColourMap m_segmentColourMap;
+ ColourMap m_generalColourMap;
+};
+
+
+/**
+ * If you subclass from CompositionObserver, you can then attach to a
+ * Composition to receive notification when something changes.
+ *
+ * Normally all the methods in this class would be pure virtual. But
+ * because there are so many, that imposes far too much work on the
+ * subclass implementation in a case where it only really wants to
+ * know about one thing, such as segments being deleted. So we have
+ * empty default implementations, and you'll just have to take a bit
+ * more care to make sure you really are making the correct
+ * declarations in the subclass.
+ */
+
+class CompositionObserver
+{
+public:
+ CompositionObserver() : m_compositionDeleted(false) {}
+
+ virtual ~CompositionObserver() {}
+
+ /**
+ * Called after the segment has been added to the composition
+ */
+ virtual void segmentAdded(const Composition *, Segment *) { }
+
+ /**
+ * Called after the segment has been removed from the segment,
+ * and just before it is deleted
+ */
+ virtual void segmentRemoved(const Composition *, Segment *) { }
+
+ /**
+ * Called when the segment's repeat status has changed
+ */
+ virtual void segmentRepeatChanged(const Composition *, Segment *, bool) { }
+
+ /**
+ * Called when the segment's repeat end time has changed
+ */
+ virtual void segmentRepeatEndChanged(const Composition *, Segment *, timeT) { }
+
+ /**
+ * Called when the segment's delay timing has changed
+ */
+ virtual void segmentEventsTimingChanged(const Composition *, Segment *,
+ timeT /* delay */,
+ RealTime /* rtDelay */) { }
+
+ /**
+ * Called when the segment's transpose value has changed
+ */
+ virtual void segmentTransposeChanged(const Composition *, Segment *,
+ int /* transpose */) { }
+
+ /**
+ * Called when the segment's start time has changed
+ */
+ virtual void segmentStartChanged(const Composition *, Segment *,
+ timeT /* newStartTime */) { }
+
+ /**
+ * Called when the segment's end marker time has changed
+ */
+ virtual void segmentEndMarkerChanged(const Composition *, Segment *,
+ bool /* shorten */) { }
+
+ /**
+ * Called when the segment's track has changed
+ */
+ virtual void segmentTrackChanged(const Composition *, Segment *,
+ TrackId /* id */) { }
+
+ /**
+ * Called after the composition's end marker time has been
+ * changed
+ */
+ virtual void endMarkerTimeChanged(const Composition *, bool /* shorten */) { }
+
+ /**
+ * Called when a track is changed (instrument id, muted status...)
+ */
+ virtual void trackChanged(const Composition *, Track*) { }
+
+ /**
+ * Called when a track has been deleted
+ */
+ virtual void trackDeleted(const Composition *, TrackId) { }
+
+ /**
+ * Called when some time signature has changed
+ */
+ virtual void timeSignatureChanged(const Composition *) { }
+
+ /**
+ * Called when metronome status has changed (on/off)
+ */
+ virtual void metronomeChanged(const Composition *) { }
+
+ /**
+ * Called when solo status changes (solo on/off, and selected track)
+ */
+ virtual void soloChanged(const Composition *, bool /* solo */,
+ TrackId /* selectedTrack */) { }
+
+ /**
+ * Called when solo status changes (solo on/off, and selected track)
+ */
+ virtual void tempoChanged(const Composition *) { }
+
+ /**
+ * Called from the composition dtor
+ */
+ virtual void compositionDeleted(const Composition *) {
+ m_compositionDeleted = true;
+ }
+
+ bool isCompositionDeleted() { return m_compositionDeleted; }
+
+protected:
+ bool m_compositionDeleted;
+};
+
+}
+
+
+#endif
+
diff --git a/src/base/CompositionTimeSliceAdapter.cpp b/src/base/CompositionTimeSliceAdapter.cpp
new file mode 100644
index 0000000..b91b804
--- /dev/null
+++ b/src/base/CompositionTimeSliceAdapter.cpp
@@ -0,0 +1,283 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2002
+ Randall Farmer <[email protected]>
+ with additional work by Chris Cannam.
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+// !!!TODO: handle timeslices
+
+#include <list>
+#include <utility>
+
+#include "CompositionTimeSliceAdapter.h"
+#include "Segment.h"
+#include "Composition.h"
+#include "Selection.h"
+
+namespace Rosegarden {
+
+using std::list;
+using std::pair;
+
+CompositionTimeSliceAdapter::CompositionTimeSliceAdapter(Composition *c,
+ timeT begin,
+ timeT end) :
+ m_composition(c),
+ m_begin(begin),
+ m_end(end)
+{
+ if (begin == end) {
+ m_begin = 0;
+ m_end = c->getDuration();
+ }
+
+ for (Composition::iterator ci = m_composition->begin();
+ ci != m_composition->end(); ++ci) {
+ m_segmentList.push_back(*ci);
+ }
+}
+
+CompositionTimeSliceAdapter::CompositionTimeSliceAdapter(Composition *c,
+ SegmentSelection* s,
+ timeT begin,
+ timeT end) :
+ m_composition(c),
+ m_begin(begin),
+ m_end(end)
+{
+ if (begin == end) {
+ m_begin = 0;
+ m_end = c->getDuration();
+ }
+
+ for (Composition::iterator ci = m_composition->begin();
+ ci != m_composition->end(); ++ci) {
+ if (!s || s->find(*ci) != s->end()) {
+ m_segmentList.push_back(*ci);
+ }
+ }
+}
+
+CompositionTimeSliceAdapter::CompositionTimeSliceAdapter(Composition *c,
+ const TrackSet &trackIDs,
+ timeT begin,
+ timeT end) :
+ m_composition(c),
+ m_begin(begin),
+ m_end(end)
+{
+ if (begin == end) {
+ m_begin = 0;
+ m_end = c->getDuration();
+ }
+
+ for (Composition::iterator ci = m_composition->begin();
+ ci != m_composition->end(); ++ci) {
+ if (trackIDs.find((*ci)->getTrack()) != trackIDs.end()) {
+ m_segmentList.push_back(*ci);
+ }
+ }
+}
+
+CompositionTimeSliceAdapter::iterator
+CompositionTimeSliceAdapter::begin() const
+{
+ if (m_beginItr.m_a == 0) {
+ m_beginItr = iterator(this);
+ fill(m_beginItr, false);
+ }
+ return m_beginItr;
+}
+
+CompositionTimeSliceAdapter::iterator
+CompositionTimeSliceAdapter::end() const
+{
+ return iterator(this);
+}
+
+void
+CompositionTimeSliceAdapter::fill(iterator &i, bool atEnd) const
+{
+ // The segment iterators should all point to events starting at or
+ // after m_begin (if atEnd false) or at or before m_end (if atEnd true).
+
+ for (unsigned int k = 0; k < m_segmentList.size(); ++k) {
+ Segment::iterator j = m_segmentList[k]->findTime(atEnd ? m_end : m_begin);
+ i.m_segmentItrList.push_back(j);
+ }
+
+ // fill m_curEvent & m_curTrack
+ if (!atEnd) ++i;
+}
+
+CompositionTimeSliceAdapter::iterator&
+CompositionTimeSliceAdapter::iterator::operator=(const iterator &i)
+{
+ if (&i == this) return *this;
+ m_segmentItrList.clear();
+
+ for (segmentitrlist::const_iterator j = i.m_segmentItrList.begin();
+ j != i.m_segmentItrList.end(); ++j) {
+ m_segmentItrList.push_back(Segment::iterator(*j));
+ }
+
+ m_a = i.m_a;
+ m_curTrack = i.m_curTrack;
+ m_curEvent = i.m_curEvent;
+ m_needFill = i.m_needFill;
+ return *this;
+}
+
+CompositionTimeSliceAdapter::iterator::iterator(const iterator &i) :
+ m_a(i.m_a),
+ m_curEvent(i.m_curEvent),
+ m_curTrack(i.m_curTrack),
+ m_needFill(i.m_needFill)
+{
+ for (segmentitrlist::const_iterator j = i.m_segmentItrList.begin();
+ j != i.m_segmentItrList.end(); ++j) {
+ m_segmentItrList.push_back(Segment::iterator(*j));
+ }
+}
+
+CompositionTimeSliceAdapter::iterator&
+CompositionTimeSliceAdapter::iterator::operator++()
+{
+ assert(m_a != 0);
+
+ // needFill is only set true for iterators created at end()
+ if (m_needFill) {
+ m_a->fill(*this, true);
+ m_needFill = false;
+ }
+
+ Event *e = 0;
+ unsigned int pos = 0;
+
+ for (unsigned int i = 0; i < m_a->m_segmentList.size(); ++i) {
+
+ if (!m_a->m_segmentList[i]->isBeforeEndMarker(m_segmentItrList[i])) continue;
+
+ if (!e || strictLessThan(*m_segmentItrList[i], e)) {
+ e = *m_segmentItrList[i];
+ m_curTrack = m_a->m_segmentList[i]->getTrack();
+ pos = i;
+ }
+ }
+
+ // Check whether we're past the end time, if there is one
+ if (!e || e->getAbsoluteTime() >= m_a->m_end) {
+ m_curEvent = 0;
+ m_curTrack = -1;
+ return *this;
+ }
+
+ // e is now an Event* less than or equal to any that the iterator
+ // hasn't already passed over
+ m_curEvent = e;
+
+ // m_segmentItrList[pos] is a segment::iterator that points to e
+ ++m_segmentItrList[pos];
+
+ return *this;
+}
+
+CompositionTimeSliceAdapter::iterator&
+CompositionTimeSliceAdapter::iterator::operator--()
+{
+ assert(m_a != 0);
+
+ // needFill is only set true for iterators created at end()
+ if (m_needFill) {
+ m_a->fill(*this, true);
+ m_needFill = false;
+ }
+
+ Event *e = 0;
+ int pos = -1;
+
+ // Decrement is more subtle than increment. We have to scan the
+ // iterators available, and decrement the one that points to
+ // m_curEvent. Then to fill m_curEvent we need to find the next
+ // greatest event back that is not itself m_curEvent.
+
+ for (unsigned int i = 0; i < m_a->m_segmentList.size(); ++i) {
+
+ if (m_segmentItrList[i] == m_a->m_segmentList[i]->begin()) continue;
+
+ Segment::iterator si(m_segmentItrList[i]);
+ --si;
+
+ if (*si == m_curEvent) {
+ pos = i;
+ } else if (!e || !strictLessThan(*si, e)) {
+ e = *si;
+ m_curTrack = m_a->m_segmentList[i]->getTrack();
+ }
+ }
+
+ if (e) m_curEvent = e;
+ if (pos >= 0) {
+ --m_segmentItrList[pos];
+ }
+
+ return *this;
+}
+
+bool
+CompositionTimeSliceAdapter::iterator::operator==(const iterator& other) const {
+ return m_a == other.m_a && m_curEvent == other.m_curEvent;
+}
+
+bool
+CompositionTimeSliceAdapter::iterator::operator!=(const iterator& other) const {
+ return !operator==(other);
+}
+
+Event *
+CompositionTimeSliceAdapter::iterator::operator*() const {
+ return m_curEvent;
+}
+
+Event &
+CompositionTimeSliceAdapter::iterator::operator->() const {
+ return *m_curEvent;
+}
+
+int
+CompositionTimeSliceAdapter::iterator::getTrack() const {
+ return m_curTrack;
+}
+
+bool
+CompositionTimeSliceAdapter::iterator::strictLessThan(Event *e1, Event *e2) {
+ // We need a complete ordering of events -- we can't cope with two events
+ // comparing equal. i.e. one of e1 < e2 and e2 < e1 must be true. The
+ // ordering can be arbitrary -- we just compare addresses for events the
+ // event comparator doesn't distinguish between. We know we're always
+ // dealing with event pointers, not copies of events.
+ if (*e1 < *e2) return true;
+ else if (*e2 < *e1) return false;
+ else return e1 < e2;
+}
+
+}
diff --git a/src/base/CompositionTimeSliceAdapter.h b/src/base/CompositionTimeSliceAdapter.h
new file mode 100644
index 0000000..01307e3
--- /dev/null
+++ b/src/base/CompositionTimeSliceAdapter.h
@@ -0,0 +1,149 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2002
+ Randall Farmer <[email protected]>
+ with additional work by Chris Cannam.
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _COMPOSITION_TIMESLICE_ADAPTER_H_
+#define _COMPOSITION_TIMESLICE_ADAPTER_H_
+
+#include <list>
+#include <utility>
+
+#include "Segment.h"
+
+namespace Rosegarden {
+
+class Event;
+class Composition;
+class SegmentSelection;
+
+/**
+ * CompositionTimeSliceAdapter provides the ability to iterate through
+ * all the events in a Composition in time order, across many segments
+ * at once.
+ *
+ * The CompositionTimeSliceAdapter is suitable for use as the backing
+ * container for the Set classes, notably GenericChord (see Sets.h).
+ * This combination enables you to iterate through a Composition as a
+ * sequence of chords composed of all Events on a set of Segments that
+ * lie within a particular quantize range of one another.
+ */
+
+class CompositionTimeSliceAdapter
+{
+public:
+ class iterator;
+ typedef std::set<TrackId> TrackSet;
+
+ /**
+ * Construct a CompositionTimeSliceAdapter that operates on the
+ * given section in time of the given composition. If begin and
+ * end are equal, the whole composition will be used.
+ */
+ CompositionTimeSliceAdapter(Composition* c,
+ timeT begin = 0,
+ timeT end = 0);
+
+ /**
+ * Construct a CompositionTimeSliceAdapter that operates on the
+ * given section in time of the given set of segments within the
+ * given composition. If begin and end are equal, the whole
+ * duration of the composition will be used.
+ */
+ CompositionTimeSliceAdapter(Composition* c,
+ SegmentSelection* s,
+ timeT begin = 0,
+ timeT end = 0);
+
+ /**
+ * Construct a CompositionTimeSliceAdapter that operates on the
+ * given section in time of all the segments in the given set of
+ * tracks within the given composition. If begin and end are
+ * equal, the whole duration of the composition will be used.
+ */
+ CompositionTimeSliceAdapter(Composition *c,
+ const TrackSet &trackIDs,
+ timeT begin = 0,
+ timeT end = 0);
+
+ ~CompositionTimeSliceAdapter() { };
+
+ // bit sloppy -- we don't have a const_iterator
+ iterator begin() const;
+ iterator end() const;
+
+ typedef std::vector<Segment *> segmentlist;
+ typedef std::vector<Segment::iterator> segmentitrlist;
+
+ Composition *getComposition() { return m_composition; }
+
+ class iterator {
+ friend class CompositionTimeSliceAdapter;
+
+ public:
+ iterator() :
+ m_a(0), m_curEvent(0), m_curTrack(-1), m_needFill(true) { }
+ iterator(const CompositionTimeSliceAdapter *a) :
+ m_a(a), m_curEvent(0), m_curTrack(-1), m_needFill(true) { }
+ iterator(const iterator &);
+ iterator &operator=(const iterator &);
+ ~iterator() { };
+
+ iterator &operator++();
+ iterator &operator--();
+
+ bool operator==(const iterator& other) const;
+ bool operator!=(const iterator& other) const;
+
+ Event *operator*() const;
+ Event &operator->() const;
+
+ int getTrack() const;
+
+ private:
+ segmentitrlist m_segmentItrList;
+ const CompositionTimeSliceAdapter *m_a;
+ Event* m_curEvent;
+ int m_curTrack;
+ bool m_needFill;
+
+ static bool strictLessThan(Event *, Event *);
+ };
+
+
+private:
+ friend class iterator;
+
+ Composition* m_composition;
+ mutable iterator m_beginItr;
+ timeT m_begin;
+ timeT m_end;
+
+ segmentlist m_segmentList;
+
+ void fill(iterator &, bool atEnd) const;
+};
+
+}
+
+#endif
diff --git a/src/base/Configuration.cpp b/src/base/Configuration.cpp
new file mode 100644
index 0000000..a3d836f
--- /dev/null
+++ b/src/base/Configuration.cpp
@@ -0,0 +1,232 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+
+// Class to hold extraneous bits of configuration which
+// don't sit inside the Composition itself - sequencer
+// and other general stuff that we want to keep separate.
+//
+//
+
+#include <string>
+#include <algorithm>
+
+#include "Configuration.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+
+namespace Rosegarden
+{
+
+Configuration::Configuration(const Configuration &conf) :
+ PropertyMap(),
+ XmlExportable()
+{
+ clear();
+
+ // Copy everything
+ //
+ for (const_iterator i = conf.begin(); i != conf.end(); ++i)
+ insert(PropertyPair(i->first, i->second->clone()));
+}
+
+Configuration::~Configuration()
+{
+ clear();
+}
+
+
+std::vector<std::string>
+Configuration::getPropertyNames()
+{
+ std::vector<std::string> v;
+ for (const_iterator i = begin(); i != end(); ++i) {
+ v.push_back(i->first.getName());
+ }
+ std::sort(v.begin(), v.end());
+ return v;
+}
+
+
+bool
+Configuration::has(const PropertyName &name) const
+{
+ const_iterator i = find(name);
+ return (i != end());
+}
+
+
+std::string
+Configuration::toXmlString()
+{
+ using std::endl;
+ std::stringstream config;
+
+ // This simple implementation just assumes everything's a string.
+ // Override it if you want something fancier (or reimplement it to
+ // support the whole gamut -- the reader in rosexmlhandler.cpp
+ // already can)
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+ config << "<property name=\""
+ << encode(i->first.getName()) << "\" value=\""
+ << encode(get<String>(i->first)) << "\"/>" << endl;
+ }
+
+#if (__GNUC__ < 3)
+ config << endl << std::ends;
+#else
+ config << endl;
+#endif
+
+ return config.str();
+}
+
+Configuration&
+Configuration::operator=(const Configuration &conf)
+{
+ clear();
+
+ // Copy everything
+ //
+ for (const_iterator i = conf.begin(); i != conf.end(); ++i)
+ insert(PropertyPair(i->first, i->second->clone()));
+
+ return (*this);
+}
+
+
+
+namespace CompositionMetadataKeys
+{
+ const PropertyName Copyright = "copyright";
+ const PropertyName Composer = "composer";
+ const PropertyName Title = "title";
+ const PropertyName Subtitle = "subtitle";
+ const PropertyName Arranger = "arranger";
+ // The following are recognized only by LilyPond output
+ const PropertyName Dedication = "dedication";
+ const PropertyName Subsubtitle = "subsubtitle";
+ const PropertyName Poet = "poet";
+ const PropertyName Meter = "meter";
+ const PropertyName Opus = "opus";
+ const PropertyName Instrument = "instrument";
+ const PropertyName Piece = "piece";
+ const PropertyName Tagline = "tagline";
+
+ // The tab order of the edit fields in HeadersConfigurationPage
+ // is defined by the creation order of the edit fields.
+ // The edit fields are created in the order of the keys in getFixedKeys().
+ std::vector<PropertyName> getFixedKeys() {
+ std::vector<PropertyName> keys;
+ keys.push_back(Dedication);
+ keys.push_back(Title);
+ keys.push_back(Subtitle);
+ keys.push_back(Subsubtitle);
+ keys.push_back(Poet);
+ keys.push_back(Instrument);
+ keys.push_back(Composer);
+ keys.push_back(Meter);
+ keys.push_back(Arranger);
+ keys.push_back(Piece);
+ keys.push_back(Opus);
+ keys.push_back(Copyright);
+ keys.push_back(Tagline);
+
+ return keys;
+ }
+}
+
+
+// Keep these in lower case
+const PropertyName DocumentConfiguration::SequencerOptions = "sequenceroptions";
+const PropertyName DocumentConfiguration::ZoomLevel = "zoomlevel";
+const PropertyName DocumentConfiguration::TransportMode = "transportmode";
+
+
+DocumentConfiguration::DocumentConfiguration()
+{
+ set<Int>(ZoomLevel, 0);
+ set<String>(TransportMode, ""); // apparently generates an exception if not initialized
+}
+
+DocumentConfiguration::DocumentConfiguration(const DocumentConfiguration &conf):
+ Configuration()
+{
+ for (const_iterator i = conf.begin(); i != conf.end(); ++i)
+ insert(PropertyPair(i->first, i->second->clone()));
+}
+
+DocumentConfiguration::~DocumentConfiguration()
+{
+ clear();
+}
+
+
+DocumentConfiguration&
+DocumentConfiguration::operator=(const DocumentConfiguration &conf)
+{
+ clear();
+
+ for (const_iterator i = conf.begin(); i != conf.end(); ++i)
+ insert(PropertyPair(i->first, i->second->clone()));
+
+ return *this;
+}
+
+
+// Convert to XML string for export
+//
+std::string
+DocumentConfiguration::toXmlString()
+{
+ using std::endl;
+
+ std::stringstream config;
+
+ config << endl << "<configuration>" << endl;
+
+ config << " <" << ZoomLevel << " type=\"Int\">" << get<Int>(ZoomLevel)
+ << "</" << ZoomLevel << ">\n";
+
+ config << " <" << TransportMode << " type=\"String\">" << get<String>(TransportMode)
+ << "</" << TransportMode << ">\n";
+
+ config << "</configuration>" << endl;
+
+#if (__GNUC__ < 3)
+ config << endl << std::ends;
+#else
+ config << endl;
+#endif
+
+ return config.str();
+}
+
+}
+
+
diff --git a/src/base/Configuration.h b/src/base/Configuration.h
new file mode 100644
index 0000000..23b3776
--- /dev/null
+++ b/src/base/Configuration.h
@@ -0,0 +1,211 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+
+// Class to hold extraenous bits of configuration which
+// don't sit inside the Composition itself - sequencer
+// and other general stuff that we want to keep separate.
+//
+//
+
+#include <string>
+#include <vector>
+
+#include "Instrument.h"
+#include "RealTime.h"
+#include "PropertyMap.h"
+#include "Exception.h"
+#include "XmlExportable.h"
+
+#ifndef _CONFIGURATION_H_
+#define _CONFIGURATION_H_
+
+namespace Rosegarden
+{
+
+class Configuration : public PropertyMap, public XmlExportable
+{
+public:
+ class NoData : public Exception {
+ public:
+ NoData(std::string property, std::string file, int line) :
+ Exception("No data found for property " + property, file, line) { }
+ };
+
+ class BadType : public Exception {
+ public:
+ BadType(std::string property, std::string expected, std::string actual,
+ std::string file, int line) :
+ Exception("Bad type for " + property + " (expected " +
+ expected + ", found " + actual + ")", file, line) { }
+ };
+
+ Configuration() {;}
+ Configuration(const Configuration &);
+ ~Configuration();
+
+ bool has(const PropertyName &name) const;
+
+ template <PropertyType P>
+ void
+ set(const PropertyName &name,
+ typename PropertyDefn<P>::basic_type value);
+
+ /**
+ * get() with a default value
+ */
+ template <PropertyType P>
+ typename PropertyDefn<P>::basic_type
+ get(const PropertyName &name,
+ typename PropertyDefn<P>::basic_type defaultVal) const;
+
+ /**
+ * regular get()
+ */
+ template <PropertyType P>
+ typename PropertyDefn<P>::basic_type get(const PropertyName &name) const;
+
+ // For exporting -- doesn't write the <configuration> part of
+ // the element in case you want to write it into another element
+ //
+ virtual std::string toXmlString();
+
+ /// Return all the contained property names in alphabetical order
+ std::vector<std::string> getPropertyNames();
+
+ // Assignment
+ //
+ Configuration& operator=(const Configuration &);
+
+private:
+
+};
+
+namespace CompositionMetadataKeys
+{
+ extern const PropertyName Composer;
+ extern const PropertyName Arranger;
+ extern const PropertyName Copyright;
+ extern const PropertyName Title;
+ extern const PropertyName Subtitle;
+ // The following are recognized only by LilyPond output
+ extern const PropertyName Subsubtitle;
+ extern const PropertyName Dedication;
+ extern const PropertyName Poet;
+ extern const PropertyName Meter;
+ extern const PropertyName Opus;
+ extern const PropertyName Instrument;
+ extern const PropertyName Piece;
+ extern const PropertyName Tagline;
+
+
+ std::vector<PropertyName> getFixedKeys();
+}
+
+class DocumentConfiguration : public Configuration
+{
+public:
+ DocumentConfiguration();
+ DocumentConfiguration(const DocumentConfiguration &);
+ ~DocumentConfiguration();
+
+ DocumentConfiguration& operator=(const DocumentConfiguration &);
+
+ // for exporting -- doesn't write the <configuration> part of
+ // the element in case you want to write it into another element
+ //
+ virtual std::string toXmlString();
+
+ // Property names
+ static const PropertyName SequencerOptions;
+
+ static const PropertyName ZoomLevel;
+
+ static const PropertyName TransportMode;
+};
+
+
+template <PropertyType P>
+void
+Configuration::set(const PropertyName &name,
+ typename PropertyDefn<P>::basic_type value)
+{
+ iterator i = find(name);
+
+ if (i != end()) {
+
+ // A property with the same name has
+ // already been set - recycle it, just change the data
+ PropertyStoreBase *sb = i->second;
+ (static_cast<PropertyStore<P> *>(sb))->setData(value);
+
+ } else {
+
+ PropertyStoreBase *p = new PropertyStore<P>(value);
+ insert(PropertyPair(name, p));
+
+ }
+
+}
+
+template <PropertyType P>
+typename PropertyDefn<P>::basic_type
+Configuration::get(const PropertyName &name,
+ typename PropertyDefn<P>::basic_type defaultVal) const
+
+{
+ const_iterator i = find(name);
+
+ if (i == end()) return defaultVal;
+
+ PropertyStoreBase *sb = i->second;
+ if (sb->getType() == P) {
+ return (static_cast<PropertyStore<P> *>(sb))->getData();
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+}
+
+template <PropertyType P>
+typename PropertyDefn<P>::basic_type
+Configuration::get(const PropertyName &name) const
+
+{
+ const_iterator i = find(name);
+
+ if (i == end()) throw NoData(name.getName(), __FILE__, __LINE__);
+
+ PropertyStoreBase *sb = i->second;
+ if (sb->getType() == P) {
+ return (static_cast<PropertyStore<P> *>(sb))->getData();
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+}
+
+
+}
+
+#endif // _AUDIODEVICE_H_
diff --git a/src/base/ControlParameter.cpp b/src/base/ControlParameter.cpp
new file mode 100644
index 0000000..8606cf3
--- /dev/null
+++ b/src/base/ControlParameter.cpp
@@ -0,0 +1,144 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include "ControlParameter.h"
+#include "MidiTypes.h"
+
+namespace Rosegarden
+{
+
+ControlParameter::ControlParameter():
+ m_name("<unnamed>"),
+ m_type(Rosegarden::Controller::EventType),
+ m_description("<none>"),
+ m_min(0),
+ m_max(127),
+ m_default(0),
+ m_controllerValue(0),
+ m_colourIndex(0),
+ m_ipbPosition(-1) // doesn't appear on IPB by default
+{
+}
+
+
+ControlParameter::ControlParameter(const std::string &name,
+ const std::string &type,
+ const std::string &description,
+ int min,
+ int max,
+ int def,
+ MidiByte controllerValue,
+ unsigned int colour,
+ int ipbPosition):
+ m_name(name),
+ m_type(type),
+ m_description(description),
+ m_min(min),
+ m_max(max),
+ m_default(def),
+ m_controllerValue(controllerValue),
+ m_colourIndex(colour),
+ m_ipbPosition(ipbPosition)
+{
+}
+
+
+ControlParameter::ControlParameter(const ControlParameter &control):
+ XmlExportable(),
+ m_name(control.getName()),
+ m_type(control.getType()),
+ m_description(control.getDescription()),
+ m_min(control.getMin()),
+ m_max(control.getMax()),
+ m_default(control.getDefault()),
+ m_controllerValue(control.getControllerValue()),
+ m_colourIndex(control.getColourIndex()),
+ m_ipbPosition(control.getIPBPosition())
+{
+}
+
+ControlParameter&
+ControlParameter::operator=(const ControlParameter &control)
+{
+ m_name = control.getName();
+ m_type = control.getType();
+ m_description = control.getDescription();
+ m_min = control.getMin();
+ m_max = control.getMax();
+ m_default = control.getDefault();
+ m_controllerValue = control.getControllerValue();
+ m_colourIndex = control.getColourIndex();
+ m_ipbPosition = control.getIPBPosition();
+
+ return *this;
+}
+
+bool ControlParameter::operator==(const ControlParameter &control)
+{
+ return m_type == control.getType() &&
+ m_controllerValue == control.getControllerValue() &&
+ m_min == control.getMin() &&
+ m_max == control.getMax();
+}
+
+bool operator<(const ControlParameter &a, const ControlParameter &b)
+{
+ if (a.m_type != b.m_type)
+ return a.m_type < b.m_type;
+ else if (a.m_controllerValue != b.m_controllerValue)
+ return a.m_controllerValue < b.m_controllerValue;
+ else
+ return false;
+}
+
+
+std::string
+ControlParameter::toXmlString()
+{
+ std::stringstream control;
+
+ control << " <control name=\"" << encode(m_name)
+ << "\" type=\"" << encode(m_type)
+ << "\" description=\"" << encode(m_description)
+ << "\" min=\"" << m_min
+ << "\" max=\"" << m_max
+ << "\" default=\"" << m_default
+ << "\" controllervalue=\"" << int(m_controllerValue)
+ << "\" colourindex=\"" << m_colourIndex
+ << "\" ipbposition=\"" << m_ipbPosition;
+
+#if (__GNUC__ < 3)
+ control << "\"/>" << endl << std::ends;
+#else
+ control << "\"/>" << std::endl;
+#endif
+
+ return control.str();
+}
+
+}
diff --git a/src/base/ControlParameter.h b/src/base/ControlParameter.h
new file mode 100644
index 0000000..d9e487f
--- /dev/null
+++ b/src/base/ControlParameter.h
@@ -0,0 +1,124 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _CONTROLPARAMETER_H_
+#define _CONTROLPARAMETER_H_
+
+#include <string>
+
+#include "XmlExportable.h"
+#include "MidiProgram.h"
+
+namespace Rosegarden
+{
+
+class ControlParameter : public XmlExportable
+{
+public:
+ ControlParameter();
+ ControlParameter(const std::string &name,
+ const std::string &type,
+ const std::string &description,
+ int min = 0,
+ int max = 127,
+ int def = 0,
+ MidiByte controllerValue = 0,
+ unsigned int colour = 0,
+ int ipbPositon = -1);
+ ControlParameter(const ControlParameter &control);
+ ControlParameter& operator=(const ControlParameter &control);
+ bool operator==(const ControlParameter &control);
+
+ friend bool operator<(const ControlParameter &a, const ControlParameter &b);
+
+ // ControlParameter comparison on IPB position
+ //
+ struct ControlPositionCmp
+ {
+ bool operator()(ControlParameter *c1,
+ ControlParameter *c2)
+ {
+ return (c1->getIPBPosition() < c2->getIPBPosition());
+ }
+
+ bool operator()(const ControlParameter &c1,
+ const ControlParameter &c2)
+ {
+ return (c1.getIPBPosition() < c2.getIPBPosition());
+ }
+ };
+
+ std::string getName() const { return m_name; }
+ std::string getType() const { return m_type; }
+ std::string getDescription() const { return m_description; }
+
+ int getMin() const { return m_min; }
+ int getMax() const { return m_max; }
+ int getDefault() const { return m_default; }
+
+ MidiByte getControllerValue() const { return m_controllerValue; }
+
+ unsigned int getColourIndex() const { return m_colourIndex; }
+
+ int getIPBPosition() const { return m_ipbPosition; }
+
+ void setName(const std::string &name) { m_name = name; }
+ void setType(const std::string &type) { m_type = type; }
+ void setDescription(const std::string &des) { m_description = des; }
+
+ void setMin(int min) { m_min = min; }
+ void setMax(int max) { m_max = max; }
+ void setDefault(int def) { m_default = def; }
+
+ void setControllerValue(MidiByte con) { m_controllerValue = con; }
+
+ void setColourIndex(unsigned int colour) { m_colourIndex = colour; }
+
+ void setIPBPosition(int position) { m_ipbPosition = position; }
+
+ virtual std::string toXmlString();
+
+protected:
+
+ // ControlParameter name as it's displayed ("Velocity", "Controller")
+ std::string m_name;
+
+ // use event types in here ("controller", "pitchbend");
+ std::string m_type;
+
+ std::string m_description;
+
+ int m_min;
+ int m_max;
+ int m_default;
+
+ MidiByte m_controllerValue;
+
+ unsigned int m_colourIndex;
+
+ int m_ipbPosition; // position on Instrument Parameter Box
+
+
+};
+
+}
+
+#endif // _CONTROLPARAMETER_H_
diff --git a/src/base/Controllable.h b/src/base/Controllable.h
new file mode 100644
index 0000000..9062d13
--- /dev/null
+++ b/src/base/Controllable.h
@@ -0,0 +1,48 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _CONTROLLABLE_DEVICE_H_
+#define _CONTROLLABLE_DEVICE_H_
+
+#include "ControlParameter.h"
+
+namespace Rosegarden
+{
+
+typedef std::vector<ControlParameter> ControlList;
+
+class Controllable
+{
+public:
+ virtual ~Controllable() {}
+
+ virtual const ControlList &getControlParameters() const = 0;
+ virtual const ControlParameter *getControlParameter(int index) const = 0;
+ virtual const ControlParameter *getControlParameter(const std::string &type,
+ MidiByte controllerNumber) const = 0;
+
+protected:
+ Controllable() { }
+};
+
+}
+
+#endif
diff --git a/src/base/Device.cpp b/src/base/Device.cpp
new file mode 100644
index 0000000..796846a
--- /dev/null
+++ b/src/base/Device.cpp
@@ -0,0 +1,31 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Device.h"
+
+namespace Rosegarden
+{
+
+const DeviceId Device::NO_DEVICE = 10000;
+const DeviceId Device::ALL_DEVICES = 10001;
+const DeviceId Device::CONTROL_DEVICE = 10002;
+
+}
diff --git a/src/base/Device.h b/src/base/Device.h
new file mode 100644
index 0000000..47a8ec0
--- /dev/null
+++ b/src/base/Device.h
@@ -0,0 +1,102 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _DEVICE_H_
+#define _DEVICE_H_
+
+#include "XmlExportable.h"
+#include "Instrument.h"
+#include <string>
+#include <vector>
+
+// A Device can query underlying hardware/sound APIs to
+// generate a list of Instruments.
+//
+
+namespace Rosegarden
+{
+
+typedef unsigned int DeviceId;
+
+class Instrument;
+typedef std::vector<Instrument *> InstrumentList;
+
+class Device : public XmlExportable
+{
+public:
+ typedef enum
+ {
+ Midi,
+ Audio,
+ SoftSynth
+ } DeviceType;
+
+ // special device ids
+ static const DeviceId NO_DEVICE;
+ static const DeviceId ALL_DEVICES;
+ static const DeviceId CONTROL_DEVICE;
+
+ Device(DeviceId id, const std::string &name, DeviceType type):
+ m_name(name), m_type(type), m_id(id) { }
+
+ virtual ~Device()
+ {
+ InstrumentList::iterator it = m_instruments.begin();
+ for (; it != m_instruments.end(); it++)
+ delete (*it);
+ m_instruments.erase(m_instruments.begin(), m_instruments.end());
+ }
+
+ void setType(DeviceType type) { m_type = type; }
+ DeviceType getType() const { return m_type; }
+
+ void setName(const std::string &name) { m_name = name; }
+ std::string getName() const { return m_name; }
+
+ void setId(DeviceId id) { m_id = id; }
+ DeviceId getId() const { return m_id; }
+
+ // Accessing instrument lists - Devices should only
+ // show the world what they want it to see
+ //
+ virtual void addInstrument(Instrument*) = 0;
+
+ // Two functions - one to return all Instruments on a
+ // Device - one to return all Instruments that a user
+ // is allowed to select (Presentation Instruments).
+ //
+ virtual InstrumentList getAllInstruments() const = 0;
+ virtual InstrumentList getPresentationInstruments() const = 0;
+
+ std::string getConnection() const { return m_connection; }
+ void setConnection(std::string connection) { m_connection = connection; }
+
+protected:
+ InstrumentList m_instruments;
+ std::string m_name;
+ DeviceType m_type;
+ DeviceId m_id;
+ std::string m_connection;
+};
+
+}
+
+#endif // _DEVICE_H_
diff --git a/src/base/Equation.cpp b/src/base/Equation.cpp
new file mode 100644
index 0000000..a97fca4
--- /dev/null
+++ b/src/base/Equation.cpp
@@ -0,0 +1,69 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Equation.h"
+
+namespace Rosegarden {
+
+void Equation::solve(Unknown u, double &y, double &m, double &x, double &c)
+{
+ switch(u) {
+ case Y: y = m*x + c; break;
+ case M: m = (y - c) / x; break;
+ case X: x = (y - c) / m; break;
+ case C: c = y - m*x; break;
+ }
+}
+
+void Equation::solve(Unknown u, int &y, double &m, int &x, int &c)
+{
+ switch(u) {
+ case Y: y = static_cast<int>(m*x) + c; break;
+ case M: m = static_cast<double>(y - c) / static_cast<double>(x); break;
+ case X: x = static_cast<int>(static_cast<float>(y - c) / m); break;
+ case C: c = y - static_cast<int>(m*x); break;
+ }
+}
+
+void Equation::solveForYByEndPoints(Point a, Point b, double x, double &y)
+{
+ double m, c, y1, x1;
+
+ m = static_cast<double>(b.y - a.y) / static_cast<double>(b.x - a.x);
+
+ x1 = a.x; y1 = a.y;
+ solve(C, y1, m, x1, c);
+ solve(Y, y, m, x, c);
+}
+
+void Equation::solveForYByEndPoints(Point a, Point b, int x, int &y)
+{
+ double m;
+ int c;
+
+ m = static_cast<double>(b.y - a.y) / static_cast<double>(b.x - a.x);
+
+ solve(C, a.y, m, a.x, c);
+ solve(Y, y, m, x, c);
+}
+
+}
diff --git a/src/base/Equation.h b/src/base/Equation.h
new file mode 100644
index 0000000..61377a5
--- /dev/null
+++ b/src/base/Equation.h
@@ -0,0 +1,51 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _EQUATION_H_
+#define _EQUATION_H_
+
+namespace Rosegarden {
+
+/**
+ * Equation solving helper class
+ */
+class Equation
+{
+public:
+ enum Unknown { Y, M, X, C };
+
+ struct Point {
+ Point(int xx, int yy) : x(xx), y(yy) { }
+ int x;
+ int y;
+ };
+
+ static void solve(Unknown u, double &y, double &m, double &x, double &c);
+ static void solve(Unknown u, int &y, double &m, int &x, int &c);
+
+ static void solveForYByEndPoints(Point a, Point b, double x, double &y);
+ static void solveForYByEndPoints(Point a, Point b, int x, int &y);
+};
+
+}
+
+#endif
diff --git a/src/base/Event.cpp b/src/base/Event.cpp
new file mode 100644
index 0000000..e63e51b
--- /dev/null
+++ b/src/base/Event.cpp
@@ -0,0 +1,445 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <cstdio>
+#include <cctype>
+#include <iostream>
+#include "Event.h"
+#include "XmlExportable.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+namespace Rosegarden
+{
+using std::string;
+using std::ostream;
+
+PropertyName Event::EventData::NotationTime = "!notationtime";
+PropertyName Event::EventData::NotationDuration = "!notationduration";
+
+
+Event::EventData::EventData(const std::string &type, timeT absoluteTime,
+ timeT duration, short subOrdering) :
+ m_refCount(1),
+ m_type(type),
+ m_absoluteTime(absoluteTime),
+ m_duration(duration),
+ m_subOrdering(subOrdering),
+ m_properties(0)
+{
+ // empty
+}
+
+Event::EventData::EventData(const std::string &type, timeT absoluteTime,
+ timeT duration, short subOrdering,
+ const PropertyMap *properties) :
+ m_refCount(1),
+ m_type(type),
+ m_absoluteTime(absoluteTime),
+ m_duration(duration),
+ m_subOrdering(subOrdering),
+ m_properties(properties ? new PropertyMap(*properties) : 0)
+{
+ // empty
+}
+
+Event::EventData *Event::EventData::unshare()
+{
+ --m_refCount;
+
+ EventData *newData = new EventData
+ (m_type, m_absoluteTime, m_duration, m_subOrdering, m_properties);
+
+ return newData;
+}
+
+Event::EventData::~EventData()
+{
+ if (m_properties) delete m_properties;
+}
+
+timeT
+Event::EventData::getNotationTime() const
+{
+ if (!m_properties) return m_absoluteTime;
+ PropertyMap::const_iterator i = m_properties->find(NotationTime);
+ if (i == m_properties->end()) return m_absoluteTime;
+ else return static_cast<PropertyStore<Int> *>(i->second)->getData();
+}
+
+timeT
+Event::EventData::getNotationDuration() const
+{
+ if (!m_properties) return m_duration;
+ PropertyMap::const_iterator i = m_properties->find(NotationDuration);
+ if (i == m_properties->end()) return m_duration;
+ else return static_cast<PropertyStore<Int> *>(i->second)->getData();
+}
+
+void
+Event::EventData::setTime(const PropertyName &name, timeT t, timeT deft)
+{
+ if (!m_properties) m_properties = new PropertyMap();
+ PropertyMap::iterator i = m_properties->find(name);
+
+ if (t != deft) {
+ if (i == m_properties->end()) {
+ m_properties->insert(PropertyPair(name, new PropertyStore<Int>(t)));
+ } else {
+ static_cast<PropertyStore<Int> *>(i->second)->setData(t);
+ }
+ } else if (i != m_properties->end()) {
+ delete i->second;
+ m_properties->erase(i);
+ }
+}
+
+PropertyMap *
+Event::find(const PropertyName &name, PropertyMap::iterator &i)
+{
+ PropertyMap *map = m_data->m_properties;
+
+ if (!map || ((i = map->find(name)) == map->end())) {
+
+ map = m_nonPersistentProperties;
+ if (!map) return 0;
+
+ i = map->find(name);
+ if (i == map->end()) return 0;
+ }
+
+ return map;
+}
+
+bool
+Event::has(const PropertyName &name) const
+{
+#ifndef NDEBUG
+ ++m_hasCount;
+#endif
+
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+ if (map) return true;
+ else return false;
+}
+
+void
+Event::unset(const PropertyName &name)
+{
+#ifndef NDEBUG
+ ++m_unsetCount;
+#endif
+
+ unshare();
+ PropertyMap::iterator i;
+ PropertyMap *map = find(name, i);
+ if (map) {
+ delete i->second;
+ map->erase(i);
+ }
+}
+
+
+PropertyType
+Event::getPropertyType(const PropertyName &name) const
+ // throw (NoData)
+{
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+ if (map) {
+ return i->second->getType();
+ } else {
+ throw NoData(name.getName(), __FILE__, __LINE__);
+ }
+}
+
+
+string
+Event::getPropertyTypeAsString(const PropertyName &name) const
+ // throw (NoData)
+{
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+ if (map) {
+ return i->second->getTypeName();
+ } else {
+ throw NoData(name.getName(), __FILE__, __LINE__);
+ }
+}
+
+
+string
+Event::getAsString(const PropertyName &name) const
+ // throw (NoData)
+{
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+ if (map) {
+ return i->second->unparse();
+ } else {
+ throw NoData(name.getName(), __FILE__, __LINE__);
+ }
+}
+
+// We could derive from XmlExportable and make this a virtual method
+// overriding XmlExportable's pure virtual. We don't, because this
+// class has no other virtual methods and for such a core class we
+// could do without the overhead (given that it wouldn't really gain
+// us anything anyway).
+
+string
+Event::toXmlString()
+{
+ return toXmlString(0);
+}
+
+string
+Event::toXmlString(timeT expectedTime)
+{
+ std::stringstream out;
+
+ out << "<event";
+
+ if (getType().length() != 0) {
+ out << " type=\"" << getType() << "\"";
+ }
+
+ if (getDuration() != 0) {
+ out << " duration=\"" << getDuration() << "\"";
+ }
+
+ if (getSubOrdering() != 0) {
+ out << " subordering=\"" << getSubOrdering() << "\"";
+ }
+
+ if (expectedTime == 0) {
+ out << " absoluteTime=\"" << getAbsoluteTime() << "\"";
+ } else if (getAbsoluteTime() != expectedTime) {
+ out << " timeOffset=\"" << (getAbsoluteTime() - expectedTime) << "\"";
+ }
+
+ out << ">";
+
+ // Save all persistent properties as <property> elements
+
+ PropertyNames propertyNames(getPersistentPropertyNames());
+ for (PropertyNames::const_iterator i = propertyNames.begin();
+ i != propertyNames.end(); ++i) {
+
+ out << "<property name=\""
+ << XmlExportable::encode(i->getName()) << "\" ";
+ string type = getPropertyTypeAsString(*i);
+ for (unsigned int j = 0; j < type.size(); ++j) {
+ type[j] = (isupper(type[j]) ? tolower(type[j]) : type[j]);
+ }
+
+ out << type << "=\""
+ << XmlExportable::encode(getAsString(*i))
+ << "\"/>";
+ }
+
+ // Save non-persistent properties (the persistence applies to
+ // copying events, not load/save) as <nproperty> elements
+ // unless they're view-local. View-local properties are
+ // assumed to have "::" in their name somewhere.
+
+ propertyNames = getNonPersistentPropertyNames();
+ for (PropertyNames::const_iterator i = propertyNames.begin();
+ i != propertyNames.end(); ++i) {
+
+ std::string s(i->getName());
+ if (s.find("::") != std::string::npos) continue;
+
+ out << "<nproperty name=\""
+ << XmlExportable::encode(s) << "\" ";
+ string type = getPropertyTypeAsString(*i);
+ for (unsigned int j = 0; j < type.size(); ++j) {
+ type[j] = (isupper(type[j]) ? tolower(type[j]) : type[j]);
+ }
+ out << type << "=\""
+ << XmlExportable::encode(getAsString(*i))
+ << "\"/>";
+ }
+
+ out << "</event>";
+
+#if (__GNUC__ < 3)
+ out << std::ends;
+#endif
+
+ return out.str();
+}
+
+
+#ifndef NDEBUG
+void
+Event::dump(ostream& out) const
+{
+ out << "Event type : " << m_data->m_type.c_str() << '\n';
+
+ out << "\tAbsolute Time : " << m_data->m_absoluteTime
+ << "\n\tDuration : " << m_data->m_duration
+ << "\n\tSub-ordering : " << m_data->m_subOrdering
+ << "\n\tPersistent properties : \n";
+
+ if (m_data->m_properties) {
+ for (PropertyMap::const_iterator i = m_data->m_properties->begin();
+ i != m_data->m_properties->end(); ++i) {
+ out << "\t\t" << i->first.getName() << " [" << i->first.getValue() << "] \t" << *(i->second) << "\n";
+ }
+ }
+
+ if (m_nonPersistentProperties) {
+ out << "\n\tNon-persistent properties : \n";
+
+ for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin();
+ i != m_nonPersistentProperties->end(); ++i) {
+ out << "\t\t" << i->first.getName() << " [" << i->first.getValue() << "] \t" << *(i->second) << '\n';
+ }
+ }
+
+ out << "Event storage size : " << getStorageSize() << '\n';
+}
+
+
+int Event::m_getCount = 0;
+int Event::m_setCount = 0;
+int Event::m_setMaybeCount = 0;
+int Event::m_hasCount = 0;
+int Event::m_unsetCount = 0;
+clock_t Event::m_lastStats = clock();
+
+void
+Event::dumpStats(ostream& out)
+{
+ clock_t now = clock();
+ int ms = (now - m_lastStats) * 1000 / CLOCKS_PER_SEC;
+ out << "\nEvent stats, since start of run or last report ("
+ << ms << "ms ago):" << std::endl;
+
+ out << "Calls to get<>: " << m_getCount << std::endl;
+ out << "Calls to set<>: " << m_setCount << std::endl;
+ out << "Calls to setMaybe<>: " << m_setMaybeCount << std::endl;
+ out << "Calls to has: " << m_hasCount << std::endl;
+ out << "Calls to unset: " << m_unsetCount << std::endl;
+
+ m_getCount = m_setCount = m_setMaybeCount = m_hasCount = m_unsetCount = 0;
+ m_lastStats = clock();
+}
+
+#else
+
+void
+Event::dumpStats(ostream&)
+{
+ // nothing
+}
+
+#endif
+
+Event::PropertyNames
+Event::getPropertyNames() const
+{
+ PropertyNames v;
+ if (m_data->m_properties) {
+ for (PropertyMap::const_iterator i = m_data->m_properties->begin();
+ i != m_data->m_properties->end(); ++i) {
+ v.push_back(i->first);
+ }
+ }
+ if (m_nonPersistentProperties) {
+ for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin();
+ i != m_nonPersistentProperties->end(); ++i) {
+ v.push_back(i->first);
+ }
+ }
+ return v;
+}
+
+Event::PropertyNames
+Event::getPersistentPropertyNames() const
+{
+ PropertyNames v;
+ if (m_data->m_properties) {
+ for (PropertyMap::const_iterator i = m_data->m_properties->begin();
+ i != m_data->m_properties->end(); ++i) {
+ v.push_back(i->first);
+ }
+ }
+ return v;
+}
+
+Event::PropertyNames
+Event::getNonPersistentPropertyNames() const
+{
+ PropertyNames v;
+ if (m_nonPersistentProperties) {
+ for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin();
+ i != m_nonPersistentProperties->end(); ++i) {
+ v.push_back(i->first);
+ }
+ }
+ return v;
+}
+
+void
+Event::clearNonPersistentProperties()
+{
+ if (m_nonPersistentProperties) m_nonPersistentProperties->clear();
+}
+
+size_t
+Event::getStorageSize() const
+{
+ size_t s = sizeof(Event) + sizeof(EventData) + m_data->m_type.size();
+ if (m_data->m_properties) {
+ for (PropertyMap::const_iterator i = m_data->m_properties->begin();
+ i != m_data->m_properties->end(); ++i) {
+ s += sizeof(i->first);
+ s += i->second->getStorageSize();
+ }
+ }
+ if (m_nonPersistentProperties) {
+ for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin();
+ i != m_nonPersistentProperties->end(); ++i) {
+ s += sizeof(i->first);
+ s += i->second->getStorageSize();
+ }
+ }
+ return s;
+}
+
+bool
+operator<(const Event &a, const Event &b)
+{
+ timeT at = a.getAbsoluteTime();
+ timeT bt = b.getAbsoluteTime();
+ if (at != bt) return at < bt;
+ else return a.getSubOrdering() < b.getSubOrdering();
+}
+
+}
diff --git a/src/base/Event.h b/src/base/Event.h
new file mode 100644
index 0000000..f236681
--- /dev/null
+++ b/src/base/Event.h
@@ -0,0 +1,584 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _EVENT_H_
+#define _EVENT_H_
+
+#include "PropertyMap.h"
+#include "Exception.h"
+
+#include <string>
+#include <vector>
+#ifndef NDEBUG
+#include <iostream>
+#include <ctime>
+#endif
+
+
+namespace Rosegarden
+{
+
+typedef long timeT;
+
+/**
+ * The Event class represents an event with some basic attributes and
+ * an arbitrary number of properties of dynamically-determined name
+ * and type.
+ *
+ * An Event has a type; a duration, often zero for events other than
+ * notes; an absolute time, the time at which the event begins, which
+ * is used to order events within a Segment; and a "sub-ordering", used
+ * to determine an order for events that have the same absolute time
+ * (for example to ensure that the clef always appears before the key
+ * signature at the start of a piece). Besides these, an event can
+ * have any number of properties, which are typed values stored and
+ * retrieved by name. Properties may be persistent or non-persistent,
+ * depending on whether they are saved to file with the rest of the
+ * event data or are considered to be only cached values that can be
+ * recomputed at will if necessary.
+ */
+
+class Event
+{
+public:
+ class NoData : public Exception {
+ public:
+ NoData(std::string property) :
+ Exception("No data found for property " + property) { }
+ NoData(std::string property, std::string file, int line) :
+ Exception("No data found for property " + property, file, line) { }
+ };
+
+ class BadType : public Exception {
+ public:
+ BadType(std::string property, std::string expected, std::string actl) :
+ Exception("Bad type for " + property + " (expected " +
+ expected + ", found " + actl + ")") { }
+ BadType(std::string property, std::string expected, std::string actual,
+ std::string file, int line) :
+ Exception("Bad type for " + property + " (expected " +
+ expected + ", found " + actual + ")", file, line) { }
+ };
+
+ Event(const std::string &type,
+ timeT absoluteTime, timeT duration = 0, short subOrdering = 0) :
+ m_data(new EventData(type, absoluteTime, duration, subOrdering)),
+ m_nonPersistentProperties(0) { }
+
+ Event(const std::string &type,
+ timeT absoluteTime, timeT duration, short subOrdering,
+ timeT notationAbsoluteTime, timeT notationDuration) :
+ m_data(new EventData(type, absoluteTime, duration, subOrdering)),
+ m_nonPersistentProperties(0) {
+ setNotationAbsoluteTime(notationAbsoluteTime);
+ setNotationDuration(notationDuration);
+ }
+
+ Event(const Event &e) :
+ m_nonPersistentProperties(0) { share(e); }
+
+ // these ctors can't use default args: default has to be obtained from e
+
+ Event(const Event &e, timeT absoluteTime) :
+ m_nonPersistentProperties(0) {
+ share(e);
+ unshare();
+ m_data->m_absoluteTime = absoluteTime;
+ setNotationAbsoluteTime(absoluteTime);
+ setNotationDuration(m_data->m_duration);
+ }
+
+ Event(const Event &e, timeT absoluteTime, timeT duration) :
+ m_nonPersistentProperties(0) {
+ share(e);
+ unshare();
+ m_data->m_absoluteTime = absoluteTime;
+ m_data->m_duration = duration;
+ setNotationAbsoluteTime(absoluteTime);
+ setNotationDuration(duration);
+ }
+
+ Event(const Event &e, timeT absoluteTime, timeT duration, short subOrdering):
+ m_nonPersistentProperties(0) {
+ share(e);
+ unshare();
+ m_data->m_absoluteTime = absoluteTime;
+ m_data->m_duration = duration;
+ m_data->m_subOrdering = subOrdering;
+ setNotationAbsoluteTime(absoluteTime);
+ setNotationDuration(duration);
+ }
+
+ Event(const Event &e, timeT absoluteTime, timeT duration, short subOrdering,
+ timeT notationAbsoluteTime) :
+ m_nonPersistentProperties(0) {
+ share(e);
+ unshare();
+ m_data->m_absoluteTime = absoluteTime;
+ m_data->m_duration = duration;
+ m_data->m_subOrdering = subOrdering;
+ setNotationAbsoluteTime(notationAbsoluteTime);
+ setNotationDuration(duration);
+ }
+
+ Event(const Event &e, timeT absoluteTime, timeT duration, short subOrdering,
+ timeT notationAbsoluteTime, timeT notationDuration) :
+ m_nonPersistentProperties(0) {
+ share(e);
+ unshare();
+ m_data->m_absoluteTime = absoluteTime;
+ m_data->m_duration = duration;
+ m_data->m_subOrdering = subOrdering;
+ setNotationAbsoluteTime(notationAbsoluteTime);
+ setNotationDuration(notationDuration);
+ }
+
+ ~Event() { lose(); }
+
+ Event *copyMoving(timeT offset) const {
+ return new Event(*this,
+ m_data->m_absoluteTime + offset,
+ m_data->m_duration,
+ m_data->m_subOrdering,
+ getNotationAbsoluteTime() + offset,
+ getNotationDuration());
+ }
+
+ Event &operator=(const Event &e) {
+ if (&e != this) { lose(); share(e); }
+ return *this;
+ }
+
+ friend bool operator<(const Event&, const Event&);
+
+ // Accessors
+ const std::string &getType() const { return m_data->m_type; }
+ bool isa(const std::string &t) const { return (m_data->m_type == t); }
+ timeT getAbsoluteTime() const { return m_data->m_absoluteTime; }
+ timeT getDuration() const { return m_data->m_duration; }
+ short getSubOrdering() const { return m_data->m_subOrdering; }
+
+ bool has(const PropertyName &name) const;
+
+ template <PropertyType P>
+ typename PropertyDefn<P>::basic_type get(const PropertyName &name) const;
+ // throw (NoData, BadType);
+
+ // no throw, returns bool
+ template <PropertyType P>
+ bool get(const PropertyName &name, typename PropertyDefn<P>::basic_type &val) const;
+
+ template <PropertyType P>
+ bool isPersistent(const PropertyName &name) const;
+ // throw (NoData);
+
+ template <PropertyType P>
+ void setPersistence(const PropertyName &name, bool persistent);
+ // throw (NoData);
+
+ PropertyType getPropertyType(const PropertyName &name) const;
+ // throw (NoData);
+
+ std::string getPropertyTypeAsString(const PropertyName &name) const;
+ // throw (NoData);
+
+ std::string getAsString(const PropertyName &name) const;
+ // throw (NoData);
+
+ template <PropertyType P>
+ void set(const PropertyName &name, typename PropertyDefn<P>::basic_type value,
+ bool persistent = true);
+ // throw (BadType);
+
+ // set non-persistent, but only if there's no persistent value already
+ template <PropertyType P>
+ void setMaybe(const PropertyName &name, typename PropertyDefn<P>::basic_type value);
+ // throw (BadType);
+
+ template <PropertyType P>
+ void setFromString(const PropertyName &name, std::string value,
+ bool persistent = true);
+ // throw (BadType);
+
+ void unset(const PropertyName &name);
+
+ timeT getNotationAbsoluteTime() const { return m_data->getNotationTime(); }
+ timeT getNotationDuration() const { return m_data->getNotationDuration(); }
+
+ typedef std::vector<PropertyName> PropertyNames;
+ PropertyNames getPropertyNames() const;
+ PropertyNames getPersistentPropertyNames() const;
+ PropertyNames getNonPersistentPropertyNames() const;
+
+ void clearNonPersistentProperties();
+
+ struct EventCmp
+ {
+ bool operator()(const Event &e1, const Event &e2) const {
+ return e1 < e2;
+ }
+ bool operator()(const Event *e1, const Event *e2) const {
+ return *e1 < *e2;
+ }
+ };
+
+ struct EventEndCmp
+ {
+ bool operator()(const Event &e1, const Event &e2) const {
+ return e1.getAbsoluteTime() + e1.getDuration() <=
+ e2.getAbsoluteTime() + e2.getDuration();
+ }
+ bool operator()(const Event *e1, const Event *e2) const {
+ return e1->getAbsoluteTime() + e1->getDuration() <=
+ e2->getAbsoluteTime() + e2->getDuration();
+ }
+ };
+
+ static bool compareEvent2Time(const Event *e, timeT t) {
+ return e->getAbsoluteTime() < t;
+ }
+
+ static bool compareTime2Event(timeT t, const Event *e) {
+ return t < e->getAbsoluteTime();
+ }
+
+ // approximate, for debugging and inspection purposes
+ size_t getStorageSize() const;
+
+ /**
+ * Get the XML string representing the object.
+ */
+ std::string toXmlString();
+
+ /**
+ * Get the XML string representing the object. If the absolute
+ * time of the event differs from the given absolute time, include
+ * the difference between the two as a timeOffset attribute.
+ * If expectedTime == 0, include an absoluteTime attribute instead.
+ */
+ std::string toXmlString(timeT expectedTime);
+
+#ifndef NDEBUG
+ void dump(std::ostream&) const;
+#else
+ void dump(std::ostream&) const {}
+#endif
+ static void dumpStats(std::ostream&);
+
+protected:
+ // these are for subclasses such as XmlStorableEvent
+
+ Event() :
+ m_data(new EventData("", 0, 0, 0)),
+ m_nonPersistentProperties(0) { }
+
+ void setType(const std::string &t) { unshare(); m_data->m_type = t; }
+ void setAbsoluteTime(timeT t) { unshare(); m_data->m_absoluteTime = t; }
+ void setDuration(timeT d) { unshare(); m_data->m_duration = d; }
+ void setSubOrdering(short o) { unshare(); m_data->m_subOrdering = o; }
+ void setNotationAbsoluteTime(timeT t) { unshare(); m_data->setNotationTime(t); }
+ void setNotationDuration(timeT d) { unshare(); m_data->setNotationDuration(d); }
+
+private:
+ bool operator==(const Event &); // not implemented
+
+ struct EventData // Data that are shared between shallow-copied instances
+ {
+ EventData(const std::string &type,
+ timeT absoluteTime, timeT duration, short subOrdering);
+ EventData(const std::string &type,
+ timeT absoluteTime, timeT duration, short subOrdering,
+ const PropertyMap *properties);
+ EventData *unshare();
+ ~EventData();
+ unsigned int m_refCount;
+
+ std::string m_type;
+ timeT m_absoluteTime;
+ timeT m_duration;
+ short m_subOrdering;
+
+ PropertyMap *m_properties;
+
+ // These are properties because we don't care so much about
+ // raw speed in get/set, but we do care about storage size for
+ // events that don't have them or that have zero values:
+ timeT getNotationTime() const;
+ timeT getNotationDuration() const;
+ void setNotationTime(timeT t) {
+ setTime(NotationTime, t, m_absoluteTime);
+ }
+ void setNotationDuration(timeT d) {
+ setTime(NotationDuration, d, m_duration);
+ }
+
+ private:
+ EventData(const EventData &);
+ EventData &operator=(const EventData &);
+ static PropertyName NotationTime;
+ static PropertyName NotationDuration;
+ void setTime(const PropertyName &name, timeT value, timeT deft);
+ };
+
+ EventData *m_data;
+ PropertyMap *m_nonPersistentProperties; // Unique to an instance
+
+ void share(const Event &e) {
+ m_data = e.m_data;
+ m_data->m_refCount++;
+ }
+
+ bool unshare() { // returns true if unshare was necessary
+ if (m_data->m_refCount > 1) {
+ m_data = m_data->unshare();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void lose() {
+ if (--m_data->m_refCount == 0) delete m_data;
+ delete m_nonPersistentProperties;
+ m_nonPersistentProperties = 0;
+ }
+
+ // returned iterator (in i) only valid if return map value is non-zero
+ PropertyMap *find(const PropertyName &name, PropertyMap::iterator &i);
+
+ const PropertyMap *find(const PropertyName &name,
+ PropertyMap::const_iterator &i) const {
+ PropertyMap::iterator j;
+ PropertyMap *map = const_cast<Event *>(this)->find(name, j);
+ i = j;
+ return map;
+ }
+
+ PropertyMap::iterator insert(const PropertyPair &pair, bool persistent) {
+ PropertyMap **map =
+ (persistent ? &m_data->m_properties : &m_nonPersistentProperties);
+ if (!*map) *map = new PropertyMap();
+ return (*map)->insert(pair).first;
+ }
+
+#ifndef NDEBUG
+ static int m_getCount;
+ static int m_setCount;
+ static int m_setMaybeCount;
+ static int m_hasCount;
+ static int m_unsetCount;
+ static clock_t m_lastStats;
+#endif
+};
+
+
+template <PropertyType P>
+bool
+Event::get(const PropertyName &name, typename PropertyDefn<P>::basic_type &val) const
+{
+#ifndef NDEBUG
+ ++m_getCount;
+#endif
+
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+
+ if (map) {
+
+ PropertyStoreBase *sb = i->second;
+ if (sb->getType() == P) {
+ val = (static_cast<PropertyStore<P> *>(sb))->getData();
+ return true;
+ }
+ else {
+#ifndef NDEBUG
+ std::cerr << "Event::get() Error: Attempt to get property \"" << name
+ << "\" as " << PropertyDefn<P>::typeName() <<", actual type is "
+ << sb->getTypeName() << std::endl;
+#endif
+ return false;
+ }
+
+ } else {
+ return false;
+ }
+}
+
+
+template <PropertyType P>
+typename PropertyDefn<P>::basic_type
+Event::get(const PropertyName &name) const
+ // throw (NoData, BadType)
+{
+#ifndef NDEBUG
+ ++m_getCount;
+#endif
+
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+
+ if (map) {
+
+ PropertyStoreBase *sb = i->second;
+ if (sb->getType() == P)
+ return (static_cast<PropertyStore<P> *>(sb))->getData();
+ else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+
+ } else {
+
+#ifndef NDEBUG
+ std::cerr << "Event::get(): Error dump follows:" << std::endl;
+ dump(std::cerr);
+#endif
+ throw NoData(name.getName(), __FILE__, __LINE__);
+ }
+}
+
+
+template <PropertyType P>
+bool
+Event::isPersistent(const PropertyName &name) const
+ // throw (NoData)
+{
+ PropertyMap::const_iterator i;
+ const PropertyMap *map = find(name, i);
+
+ if (map) {
+ return (map == m_data->m_properties);
+ } else {
+ throw NoData(name.getName(), __FILE__, __LINE__);
+ }
+}
+
+
+template <PropertyType P>
+void
+Event::setPersistence(const PropertyName &name, bool persistent)
+ // throw (NoData)
+{
+ unshare();
+ PropertyMap::iterator i;
+ PropertyMap *map = find(name, i);
+
+ if (map) {
+ insert(*i, persistent);
+ map->erase(i);
+ } else {
+ throw NoData(name.getName(), __FILE__, __LINE__);
+ }
+}
+
+
+template <PropertyType P>
+void
+Event::set(const PropertyName &name, typename PropertyDefn<P>::basic_type value,
+ bool persistent)
+ // throw (BadType)
+{
+#ifndef NDEBUG
+ ++m_setCount;
+#endif
+
+ // this is a little slow, could bear improvement
+
+ unshare();
+ PropertyMap::iterator i;
+ PropertyMap *map = find(name, i);
+
+ if (map) {
+ bool persistentBefore = (map == m_data->m_properties);
+ if (persistentBefore != persistent) {
+ i = insert(*i, persistent);
+ map->erase(name);
+ }
+
+ PropertyStoreBase *sb = i->second;
+ if (sb->getType() == P) {
+ (static_cast<PropertyStore<P> *>(sb))->setData(value);
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+
+ } else {
+ PropertyStoreBase *p = new PropertyStore<P>(value);
+ insert(PropertyPair(name, p), persistent);
+ }
+}
+
+
+
+// setMaybe<> is actually called rather more frequently than set<>, so
+// it makes sense for best performance to implement it separately
+// rather than through calls to has, isPersistent and set<>
+
+template <PropertyType P>
+void
+Event::setMaybe(const PropertyName &name, typename PropertyDefn<P>::basic_type value)
+ // throw (BadType)
+{
+#ifndef NDEBUG
+ ++m_setMaybeCount;
+#endif
+
+ unshare();
+ PropertyMap::iterator i;
+ PropertyMap *map = find(name, i);
+
+ if (map) {
+ if (map == m_data->m_properties) return; // persistent, so ignore it
+
+ PropertyStoreBase *sb = i->second;
+
+ if (sb->getType() == P) {
+ (static_cast<PropertyStore<P> *>(sb))->setData(value);
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+ } else {
+ PropertyStoreBase *p = new PropertyStore<P>(value);
+ insert(PropertyPair(name, p), false);
+ }
+}
+
+
+template <PropertyType P>
+void
+Event::setFromString(const PropertyName &name, std::string value, bool persistent)
+ // throw (BadType)
+{
+ set<P>(name, PropertyDefn<P>::parse(value), persistent);
+}
+
+
+//////////////////////////////////////////////////////////////////////
+
+}
+
+#endif
diff --git a/src/base/Exception.cpp b/src/base/Exception.cpp
new file mode 100644
index 0000000..04dad69
--- /dev/null
+++ b/src/base/Exception.cpp
@@ -0,0 +1,46 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Exception.h"
+
+#include <iostream>
+
+namespace Rosegarden {
+
+Exception::Exception(std::string message) :
+ m_message(message)
+{
+#ifndef NDEBUG
+ std::cerr << "WARNING: Rosegarden::Exception: \""
+ << message << "\"" << std::endl;
+#endif
+}
+
+Exception::Exception(std::string message, std::string file, int line) :
+ m_message(message)
+{
+#ifndef NDEBUG
+ std::cerr << "WARNING: Rosegarden::Exception: \""
+ << message << "\" at " << file << ":" << line << std::endl;
+#endif
+}
+
+}
diff --git a/src/base/Exception.h b/src/base/Exception.h
new file mode 100644
index 0000000..ba39a0f
--- /dev/null
+++ b/src/base/Exception.h
@@ -0,0 +1,47 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _EXCEPTION_H_
+#define _EXCEPTION_H_
+
+#include <string>
+#include <exception>
+
+namespace Rosegarden {
+
+class Exception : public virtual std::exception
+{
+public:
+ Exception(std::string message);
+ Exception(std::string message, std::string file, int line);
+
+ virtual ~Exception() throw () {}
+
+ std::string getMessage() const { return m_message; }
+
+private:
+ std::string m_message;
+};
+
+
+}
+
+#endif
diff --git a/src/base/FastVector.h b/src/base/FastVector.h
new file mode 100644
index 0000000..0ba8e82
--- /dev/null
+++ b/src/base/FastVector.h
@@ -0,0 +1,596 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _FAST_VECTOR_H_
+#define _FAST_VECTOR_H_
+
+#include <iterator>
+#include <cstdlib> /* for malloc, realloc, free */
+#include <cstring> /* for memmove */
+
+#include <cassert>
+
+
+/**
+ FastVector is a sequence class with an interface similar to that
+ of the STL vector, with several nice properties and one nasty one:
+
+ * It allows fast random access, like the STL vector -- although
+ access is not quite as fast, as a little arithmetic is required.
+
+ * Appending (push_back) and prepending (push_front) are both fast.
+
+ * The worst-case behaviour is repeated random inserts and deletes
+ of single items, and performance in this case is still as good
+ as vector where builtin types are stored, and much better where
+ deep-copied objects are stored.
+
+ * Performance is not as good as vector for very short sequences
+ (where vector's simple implementation excels), but it's not bad.
+
+ * BUT: To achieve all this, it cheats. Objects are moved around
+ from place to place in the vector using memmove(), rather than
+ deep copy. If you store objects with internal pointers, they
+ will break badly. Storing simple structures will be no problem,
+ and if you just store pointers to objects you'll be fine, but
+ it's unwise (for example) to store other containers.
+
+ * One other difference from the STL vector: It uses placement new
+ with the copy constructor to construct objects, rather than
+ the default constructor and assignment. Thus the copy
+ constructor must work on the stored objects, though assignment
+ doesn't have to.
+
+ Do not use this class if:
+
+ * You do not require random access (operator[]). Use the STL
+ linked list instead, it'll almost certainly be faster.
+
+ * Your sequence is constructed once at a non-time-critical
+ moment, and subsequently is only read. Use STL vector, as
+ it's more standard and lookup is slightly quicker.
+
+ * Your sequence is unlikely to contain more than a dozen objects
+ which are only appended (push_back) and you do not require
+ prepend (push_front). Use STL vector, as it's more standard,
+ simpler and often quicker in this case.
+
+ * You want to pass sequences to other libraries or return them
+ from library functions. Use a standard container instead.
+
+ * You want to store objects that contain internal pointers or
+ that do not have a working copy constructor.
+
+ Chris Cannam, 1996-2001
+*/
+
+template <class T>
+class FastVector
+{
+public:
+ typedef T value_type;
+ typedef long size_type;
+ typedef long difference_type;
+
+private:
+ class iterator_base : public
+
+#if defined(_STL_1997_) || (__GNUC__ > 2)
+ std::iterator<std::random_access_iterator_tag, T, difference_type>
+#else
+#if defined(__STL_USE_NAMESPACES)
+ std::
+#endif
+ random_access_iterator<T, difference_type>
+#endif
+ {
+ public:
+ iterator_base() :
+ m_v(0), m_i(-1) {
+ }
+ iterator_base(const iterator_base &i) :
+ m_v(i.m_v), m_i(i.m_i) {
+ }
+ iterator_base &operator=(const iterator_base &i) {
+ if (&i != this) { m_v = i.m_v; m_i = i.m_i; }
+ return *this;
+ }
+
+ iterator_base &operator--() { --m_i; return *this; }
+ iterator_base operator--(int) {
+ iterator_base i(*this);
+ --m_i;
+ return i;
+ }
+ iterator_base &operator++() { ++m_i; return *this; }
+ iterator_base operator++(int) {
+ iterator_base i(*this);
+ ++m_i;
+ return i;
+ }
+
+ bool operator==(const iterator_base &i) const {
+ return (m_v == i.m_v && m_i == i.m_i);
+ }
+
+ bool operator!=(const iterator_base &i) const {
+ return (m_v != i.m_v || m_i != i.m_i);
+ }
+
+ iterator_base &operator+=(FastVector<T>::difference_type i) {
+ m_i += i; return *this;
+ }
+ iterator_base &operator-=(FastVector<T>::difference_type i) {
+ m_i -= i; return *this;
+ }
+
+ iterator_base operator+(FastVector<T>::difference_type i) const {
+ iterator_base n(*this); n += i; return n;
+ }
+ iterator_base operator-(FastVector<T>::difference_type i) const {
+ iterator_base n(*this); n -= i; return n;
+ }
+
+ typename FastVector<T>::difference_type operator-(const iterator_base &i) const{
+ assert(m_v == i.m_v);
+ return m_i - i.m_i;
+ }
+
+ protected:
+ iterator_base(FastVector<T> *v, size_type i) : m_v(v), m_i(i) { }
+ FastVector<T> *m_v;
+ size_type m_i;
+ };
+
+public:
+ // I'm sure these can be simplified
+
+ class iterator : public
+ iterator_base
+ {
+ public:
+ iterator() : iterator_base() { }
+ iterator(const iterator_base &i) : iterator_base(i) { }
+ iterator &operator=(const iterator &i) {
+ iterator_base::operator=(i);
+ return *this;
+ }
+
+ T &operator*() { return iterator_base::m_v->at(iterator_base::m_i); }
+ T *operator->() { return &(operator*()); }
+
+ const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_i); }
+ const T *operator->() const { return &(operator*()); }
+
+ protected:
+ friend class FastVector<T>;
+ iterator(FastVector<T> *v, size_type i) : iterator_base(v,i) { }
+ };
+
+ class reverse_iterator : public
+ iterator_base
+ {
+ public:
+ reverse_iterator() : iterator_base() { }
+ reverse_iterator(const iterator_base &i) : iterator_base(i) { }
+ reverse_iterator &operator=(const reverse_iterator &i) {
+ iterator_base::operator=(i);
+ return *this;
+ }
+
+ T &operator*() { return iterator_base::m_v->at(iterator_base::m_v->size() - iterator_base::m_i - 1); }
+ T *operator->() { return &(operator*()); }
+
+ const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_v->size() - iterator_base::m_i - 1); }
+ const T *operator->() const { return &(operator*()); }
+
+ protected:
+ friend class FastVector<T>;
+ reverse_iterator(FastVector<T> *v, size_type i) : iterator_base(v,i) { }
+ };
+
+ class const_iterator : public
+ iterator_base
+ {
+ public:
+ const_iterator() : iterator_base() { }
+ const_iterator(const iterator_base &i) : iterator_base(i) { }
+ const_iterator &operator=(const const_iterator &i) {
+ iterator_base::operator=(i);
+ return *this;
+ }
+
+ const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_i); }
+ const T *operator->() const { return &(operator*()); }
+
+ protected:
+ friend class FastVector<T>;
+ const_iterator(const FastVector<T> *v, size_type i) :
+ iterator_base(const_cast<FastVector<T> *>(v),i) { }
+ };
+
+ class const_reverse_iterator : public
+ iterator_base
+ {
+ public:
+ const_reverse_iterator() : iterator_base() { }
+ const_reverse_iterator(const iterator_base &i) : iterator_base(i) { }
+ const_reverse_iterator &operator=(const const_reverse_iterator &i) {
+ iterator_base::operator=(i);
+ return *this;
+ }
+
+ const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_v->size() - iterator_base::m_i - 1); }
+ const T *operator->() const { return &(operator*()); }
+
+ protected:
+ friend class FastVector<T>;
+ const_reverse_iterator(const FastVector<T> *v, size_type i) :
+ iterator_base(const_cast<FastVector<T> *>(v),i) { }
+ };
+
+public:
+ FastVector() :
+ m_items(0), m_count(0), m_gapStart(-1),
+ m_gapLength(0), m_size(0) { }
+ FastVector(const FastVector<T> &);
+ virtual ~FastVector();
+
+ template <class InputIterator>
+ FastVector(InputIterator first, InputIterator last) :
+ m_items(0), m_count(0), m_gapStart(-1),
+ m_gapLength(0), m_size(0) {
+ insert(begin(), first, last);
+ }
+
+ FastVector<T> &operator=(const FastVector<T> &);
+
+ virtual iterator begin() { return iterator(this, 0); }
+ virtual iterator end() { return iterator(this, m_count); }
+
+ virtual const_iterator begin() const { return const_iterator(this, 0); }
+ virtual const_iterator end() const { return const_iterator(this, m_count); }
+
+ virtual reverse_iterator rbegin() { return reverse_iterator(this, 0); }
+ virtual reverse_iterator rend() { return reverse_iterator(this, m_count); }
+
+ virtual const_reverse_iterator rbegin() const { return const_reverse_iterator(this, 0); }
+ virtual const_reverse_iterator rend() const { return const_reverse_iterator(this, m_count); }
+
+ size_type size() const { return m_count; }
+ bool empty() const { return m_count == 0; }
+
+ /// not all of these are defined yet
+ void swap(FastVector<T> &v);
+ bool operator==(const FastVector<T> &) const;
+ bool operator!=(const FastVector<T> &v) const { return !operator==(v); }
+ bool operator<(const FastVector<T> &) const;
+ bool operator>(const FastVector<T> &) const;
+ bool operator<=(const FastVector<T> &) const;
+ bool operator>=(const FastVector<T> &) const;
+
+ T& at(size_type index) {
+ assert(index >= 0 && index < m_count);
+ return m_items[externalToInternal(index)];
+ }
+ const T& at(size_type index) const {
+ return (const_cast<FastVector<T> *>(this))->at(index);
+ }
+
+ T &operator[](size_type index) {
+ return at(index);
+ }
+ const T &operator[](size_type index) const {
+ return at(index);
+ }
+
+ virtual T* array(size_type index, size_type count);
+
+ /** We *guarantee* that push methods etc modify the FastVector
+ only through a call to insert(size_type, T), and that erase
+ etc modify it only through a call to remove(size_type). This
+ is important because subclasses only need to override those
+ functions to catch all mutations */
+ virtual void push_front(const T& item) { insert(0, item); }
+ virtual void push_back(const T& item) { insert(m_count, item); }
+
+ virtual iterator insert(const iterator &p, const T &t) {
+ insert(p.m_i, t);
+ return p;
+ }
+
+ template <class InputIterator>
+ iterator insert(const iterator &p, InputIterator &i, InputIterator &j);
+
+ virtual iterator erase(const iterator &i) {
+ assert(i.m_v == this);
+ remove(i.m_i);
+ return iterator(this, i.m_i);
+ }
+
+ virtual iterator erase(const iterator &i, const iterator &j);
+ virtual void clear();
+
+protected:
+ /// basic insert -- all others call this
+ virtual void insert(size_type index, const T&);
+
+ /// basic remove -- erase(), clear() call this
+ virtual void remove(size_type index);
+
+private:
+ void resize(size_type needed); // needed is internal (i.e. including gap)
+
+ void moveGapTo(size_type index); // index is external
+ void closeGap() {
+ if (m_gapStart >= 0) moveGapTo(m_count);
+ m_gapStart = -1;
+ }
+
+ size_type bestNewCount(size_type n, size_t) const {
+ if (m_size == 0) {
+ if (n < 8) return 8;
+ else return n;
+ } else {
+ // double up each time -- it's faster than just incrementing
+ size_type s(m_size);
+ if (s > n*2) return s/2;
+ while (s <= n) s *= 2;
+ return s;
+ }
+ }
+
+ size_type externalToInternal(size_type index) const {
+ return ((index < m_gapStart || m_gapStart < 0) ?
+ index : index + m_gapLength);
+ }
+
+ size_type minSize() const { return 8; }
+ size_t minBlock() const {
+ return minSize() * sizeof(T) > 64 ? minSize() * sizeof(T) : 64;
+ }
+
+ T* m_items;
+ size_type m_count; // not counting gap
+ size_type m_gapStart; // -1 for no gap
+ size_type m_gapLength; // undefined if no gap
+ size_type m_size;
+};
+
+
+template <class T>
+void *operator new(size_t, FastVector<T> *, void *space)
+{
+ return space;
+}
+
+template <class T>
+FastVector<T>::FastVector(const FastVector<T> &l) :
+ m_items(0), m_count(0), m_gapStart(-1),
+ m_gapLength(0), m_size(0)
+{
+ resize(l.size());
+ for (size_type i = 0; i < l.size(); ++i) push_back(l.at(i));
+}
+
+template <class T>
+FastVector<T>::~FastVector()
+{
+ clear();
+ free(static_cast<void *>(m_items));
+}
+
+template <class T>
+FastVector<T>& FastVector<T>::operator=(const FastVector<T>& l)
+{
+ if (&l == this) return *this;
+
+ clear();
+
+ if (l.size() >= m_size) resize(l.size());
+ for (size_type i = 0; i < l.size(); ++i) push_back(l.at(i));
+
+ return *this;
+}
+
+template <class T>
+void FastVector<T>::moveGapTo(size_type index)
+{
+ // shift some elements left or right so as to line the gap up with
+ // the prospective insertion or deletion point.
+
+ assert(m_gapStart >= 0);
+
+ if (m_gapStart < index) {
+ // need to move some stuff left to fill the gap
+ memmove(&m_items[m_gapStart],
+ &m_items[m_gapStart + m_gapLength],
+ (index - m_gapStart) * sizeof(T));
+
+ } else if (m_gapStart > index) {
+ // need to move some stuff right to fill the gap
+ memmove(&m_items[index + m_gapLength], &m_items[index],
+ (m_gapStart - index) * sizeof(T));
+ }
+
+ m_gapStart = index;
+}
+
+template <class T>
+void FastVector<T>::resize(size_type needed)
+{
+ size_type newSize = bestNewCount(needed, sizeof(T));
+
+ if (m_items) {
+ m_items = static_cast<T *>(realloc(m_items, newSize * sizeof(T)));
+ } else {
+ m_items = static_cast<T *>(malloc(newSize * sizeof(T)));
+ }
+
+ m_size = newSize;
+}
+
+template <class T>
+void FastVector<T>::remove(size_type index)
+{
+ assert(index >= 0 && index < m_count);
+
+ if (index == m_count - 1) {
+ // shorten the list without disturbing an existing gap, unless
+ // the item we're taking was the only one after the gap
+ m_items[externalToInternal(index)].T::~T();
+ if (m_gapStart == index) m_gapStart = -1;
+ } else {
+ if (m_gapStart >= 0) {
+ // moveGapTo shifts the gap around ready for insertion.
+ // It actually moves the indexed object out of the way, so
+ // that it's now at the end of the gap. We have to cope.
+ moveGapTo(index);
+ m_items[m_gapStart + m_gapLength].T::~T();
+ ++m_gapLength;
+ } else { // no gap, make one
+ m_gapStart = index;
+ m_items[m_gapStart].T::~T();
+ m_gapLength = 1;
+ }
+ }
+
+ if (--m_count == 0) m_gapStart = -1;
+ if (m_count < m_size/3 && m_size > minSize()) {
+ closeGap();
+ resize(m_count); // recover some memory
+ }
+}
+
+template <class T>
+void FastVector<T>::insert(size_type index, const T&t)
+{
+ assert(index >= 0 && index <= m_count);
+
+ if (index == m_count) {
+
+ // Appending. No need to disturb the gap, if there is one --
+ // we'd rather waste a bit of memory than bother closing it up
+
+ if (externalToInternal(m_count) >= m_size || !m_items) {
+ resize(m_size + 1);
+ }
+
+ new (this, &m_items[externalToInternal(index)]) T(t);
+
+ } else if (m_gapStart < 0) {
+
+ // Inserting somewhere, when there's no gap we can use.
+
+ if (m_count >= m_size) resize(m_size + 1);
+
+ // I think it's going to be more common to insert elements
+ // at the same point repeatedly than at random points.
+ // So, if we can make a gap here ready for more insertions
+ // *without* exceeding the m_size limit (i.e. if we've got
+ // slack left over from a previous gap), then let's. But
+ // not too much -- ideally we'd like some space left for
+ // appending. Say half.
+
+ if (m_count < m_size-2) {
+ m_gapStart = index+1;
+ m_gapLength = (m_size - m_count) / 2;
+ memmove(&m_items[m_gapStart + m_gapLength], &m_items[index],
+ (m_count - index) * sizeof(T));
+ } else {
+ memmove(&m_items[index + 1], &m_items[index],
+ (m_count - index) * sizeof(T));
+ }
+
+ new (this, &m_items[index]) T(t);
+
+ } else {
+
+ // There's already a gap, all we have to do is move it (with
+ // no need to resize)
+
+ if (index != m_gapStart) moveGapTo(index);
+ new (this, &m_items[m_gapStart]) T(t);
+ if (--m_gapLength == 0) m_gapStart = -1;
+ else ++m_gapStart;
+ }
+
+ ++m_count;
+}
+
+template <class T>
+template <class InputIterator>
+typename FastVector<T>::iterator FastVector<T>::insert
+(const FastVector<T>::iterator &p, InputIterator &i, InputIterator &j)
+{
+ size_type n = p.m_i;
+ while (i != j) {
+ --j;
+ insert(n, *j);
+ }
+ return begin() + n;
+}
+
+template <class T>
+typename FastVector<T>::iterator FastVector<T>::erase
+(const FastVector<T>::iterator &i, const FastVector<T>::iterator &j)
+{
+ assert(i.m_v == this && j.m_v == this && j.m_i >= i.m_i);
+ for (size_type k = i.m_i; k < j.m_i; ++k) remove(i.m_i);
+ return iterator(this, i.m_i);
+}
+
+template <class T>
+void FastVector<T>::clear()
+{
+ // Use erase(), which uses remove() -- a subclass that overrides
+ // remove() will not want to have to provide this method as well
+ erase(begin(), end());
+}
+
+template <class T>
+T* FastVector<T>::array(size_type index, size_type count)
+{
+ assert(index >= 0 && count > 0 && index + count <= m_count);
+
+ if (m_gapStart < 0 || index + count <= m_gapStart) {
+ return m_items + index;
+ } else if (index >= m_gapStart) {
+ return m_items + index + m_gapLength;
+ } else {
+ closeGap();
+ return m_items + index;
+ }
+}
+
+template <class T>
+bool FastVector<T>::operator==(const FastVector<T> &v) const
+{
+ if (size() != v.size()) return false;
+ for (size_type i = 0; i < m_count; ++i) {
+ if (at(i) != v.at(i)) return false;
+ }
+ return true;
+}
+
+#endif
+
+
diff --git a/src/base/Instrument.cpp b/src/base/Instrument.cpp
new file mode 100644
index 0000000..add1767
--- /dev/null
+++ b/src/base/Instrument.cpp
@@ -0,0 +1,645 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <stdio.h>
+
+#include "Instrument.h"
+#include "MidiDevice.h"
+#include "AudioPluginInstance.h"
+#include "AudioLevel.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+
+namespace Rosegarden
+{
+
+const unsigned int PluginContainer::PLUGIN_COUNT = 5;
+
+PluginContainer::PluginContainer(bool havePlugins)
+{
+ if (havePlugins) {
+ // Add a number of plugin place holders (unassigned)
+ for (unsigned int i = 0; i < PLUGIN_COUNT; i++)
+ addPlugin(new AudioPluginInstance(i));
+ }
+}
+
+PluginContainer::~PluginContainer()
+{
+ clearPlugins();
+}
+
+void
+PluginContainer::addPlugin(AudioPluginInstance *instance)
+{
+ m_audioPlugins.push_back(instance);
+}
+
+bool
+PluginContainer::removePlugin(unsigned int position)
+{
+ PluginInstanceIterator it = m_audioPlugins.begin();
+
+ for (; it != m_audioPlugins.end(); it++)
+ {
+ if ((*it)->getPosition() == position)
+ {
+ delete (*it);
+ m_audioPlugins.erase(it);
+ return true;
+ }
+
+ }
+
+ return false;
+}
+
+void
+PluginContainer::clearPlugins()
+{
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++)
+ delete (*it);
+
+ m_audioPlugins.erase(m_audioPlugins.begin(), m_audioPlugins.end());
+}
+
+void
+PluginContainer::emptyPlugins()
+{
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++)
+ {
+ (*it)->setAssigned(false);
+ (*it)->setBypass(false);
+ (*it)->clearPorts();
+ }
+}
+
+
+// Get an instance for an index
+//
+AudioPluginInstance*
+PluginContainer::getPlugin(unsigned int position)
+{
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++)
+ {
+ if ((*it)->getPosition() == position)
+ return *it;
+ }
+
+ return 0;
+}
+
+
+const unsigned int Instrument::SYNTH_PLUGIN_POSITION = 999;
+
+
+Instrument::Instrument(InstrumentId id,
+ InstrumentType it,
+ const std::string &name,
+ Device *device):
+ PluginContainer(it == Audio || it == SoftSynth),
+ m_id(id),
+ m_name(name),
+ m_type(it),
+ m_channel(0),
+ //m_input_channel(-1),
+ m_transpose(MidiMidValue),
+ m_pan(MidiMidValue),
+ m_volume(100),
+ m_level(0.0),
+ m_recordLevel(0.0),
+ m_device(device),
+ m_sendBankSelect(false),
+ m_sendProgramChange(false),
+ m_sendPan(false),
+ m_sendVolume(false),
+ m_mappedId(0),
+ m_audioInput(1000),
+ m_audioInputChannel(0),
+ m_audioOutput(0)
+{
+ if (it == Audio || it == SoftSynth)
+ {
+ // In an audio instrument we use the m_channel attribute to
+ // hold the number of audio channels this Instrument uses -
+ // not the MIDI channel number. Default is 2 (stereo).
+ //
+ m_channel = 2;
+
+ m_pan = 100; // audio pan ranges from -100 to 100 but
+ // we store within an unsigned char as
+ // 0 to 200.
+ }
+
+ if (it == SoftSynth) {
+ addPlugin(new AudioPluginInstance(SYNTH_PLUGIN_POSITION));
+ }
+}
+
+Instrument::Instrument(InstrumentId id,
+ InstrumentType it,
+ const std::string &name,
+ MidiByte channel,
+ Device *device):
+ PluginContainer(it == Audio || it == SoftSynth),
+ m_id(id),
+ m_name(name),
+ m_type(it),
+ m_channel(channel),
+ //m_input_channel(-1),
+ m_transpose(MidiMidValue),
+ m_pan(MidiMidValue),
+ m_volume(100),
+ m_level(0.0),
+ m_recordLevel(0.0),
+ m_device(device),
+ m_sendBankSelect(false),
+ m_sendProgramChange(false),
+ m_sendPan(false),
+ m_sendVolume(false),
+ m_mappedId(0),
+ m_audioInput(1000),
+ m_audioInputChannel(0),
+ m_audioOutput(0)
+{
+ // Add a number of plugin place holders (unassigned)
+ //
+ if (it == Audio || it == SoftSynth)
+ {
+ // In an audio instrument we use the m_channel attribute to
+ // hold the number of audio channels this Instrument uses -
+ // not the MIDI channel number. Default is 2 (stereo).
+ //
+ m_channel = 2;
+
+ m_pan = 100; // audio pan ranges from -100 to 100 but
+ // we store within an unsigned char as
+
+ } else {
+/*
+ *
+ * Let's try getting rid of this default behavior, and replacing it with a
+ * change to the factory autoload instead, because this just doesn't work out
+ * very well, and it's fiddly trying to sort the overall behavior into something
+ * less quirky (dmm)
+ *
+ // Also defined in Midi.h but we don't use that - not here
+ // in the clean inner sanctum.
+ //
+ const MidiByte MIDI_PERCUSSION_CHANNEL = 9;
+ const MidiByte MIDI_EXTENDED_PERCUSSION_CHANNEL = 10;
+
+ if (m_channel == MIDI_PERCUSSION_CHANNEL ||
+ m_channel == MIDI_EXTENDED_PERCUSSION_CHANNEL) {
+ setPercussion(true);
+ }
+*/
+ }
+
+ if (it == SoftSynth) {
+ addPlugin(new AudioPluginInstance(SYNTH_PLUGIN_POSITION));
+ }
+}
+
+Instrument::Instrument(const Instrument &ins):
+ XmlExportable(),
+ PluginContainer(ins.getType() == Audio || ins.getType() == SoftSynth),
+ m_id(ins.getId()),
+ m_name(ins.getName()),
+ m_type(ins.getType()),
+ m_channel(ins.getMidiChannel()),
+ //m_input_channel(ins.getMidiInputChannel()),
+ m_program(ins.getProgram()),
+ m_transpose(ins.getMidiTranspose()),
+ m_pan(ins.getPan()),
+ m_volume(ins.getVolume()),
+ m_level(ins.getLevel()),
+ m_recordLevel(ins.getRecordLevel()),
+ m_device(ins.getDevice()),
+ m_sendBankSelect(ins.sendsBankSelect()),
+ m_sendProgramChange(ins.sendsProgramChange()),
+ m_sendPan(ins.sendsPan()),
+ m_sendVolume(ins.sendsVolume()),
+ m_mappedId(ins.getMappedId()),
+ m_audioInput(ins.m_audioInput),
+ m_audioInputChannel(ins.m_audioInputChannel),
+ m_audioOutput(ins.m_audioOutput)
+{
+ if (ins.getType() == Audio || ins.getType() == SoftSynth)
+ {
+ // In an audio instrument we use the m_channel attribute to
+ // hold the number of audio channels this Instrument uses -
+ // not the MIDI channel number. Default is 2 (stereo).
+ //
+ m_channel = 2;
+ }
+
+ if (ins.getType() == SoftSynth) {
+ addPlugin(new AudioPluginInstance(SYNTH_PLUGIN_POSITION));
+ }
+}
+
+Instrument &
+Instrument::operator=(const Instrument &ins)
+{
+ if (&ins == this) return *this;
+
+ m_id = ins.getId();
+ m_name = ins.getName();
+ m_type = ins.getType();
+ m_channel = ins.getMidiChannel();
+ //m_input_channel = ins.getMidiInputChannel();
+ m_program = ins.getProgram();
+ m_transpose = ins.getMidiTranspose();
+ m_pan = ins.getPan();
+ m_volume = ins.getVolume();
+ m_level = ins.getLevel();
+ m_recordLevel = ins.getRecordLevel();
+ m_device = ins.getDevice();
+ m_sendBankSelect = ins.sendsBankSelect();
+ m_sendProgramChange = ins.sendsProgramChange();
+ m_sendPan = ins.sendsPan();
+ m_sendVolume = ins.sendsVolume();
+ m_mappedId = ins.getMappedId();
+ m_audioInput = ins.m_audioInput;
+ m_audioInputChannel = ins.m_audioInputChannel;
+ m_audioOutput = ins.m_audioOutput;
+
+ return *this;
+}
+
+
+Instrument::~Instrument()
+{
+}
+
+std::string
+Instrument::getPresentationName() const
+{
+ if (m_type == Audio || m_type == SoftSynth || !m_device) {
+ return m_name;
+ } else {
+ return m_device->getName() + " " + m_name;
+ }
+}
+
+void
+Instrument::setProgramChange(MidiByte program)
+{
+ m_program = MidiProgram(m_program.getBank(), program);
+}
+
+MidiByte
+Instrument::getProgramChange() const
+{
+ return m_program.getProgram();
+}
+
+void
+Instrument::setMSB(MidiByte msb)
+{
+ m_program = MidiProgram(MidiBank(m_program.getBank().isPercussion(),
+ msb,
+ m_program.getBank().getLSB()),
+ m_program.getProgram());
+}
+
+MidiByte
+Instrument::getMSB() const
+{
+ return m_program.getBank().getMSB();
+}
+
+void
+Instrument::setLSB(MidiByte lsb)
+{
+ m_program = MidiProgram(MidiBank(m_program.getBank().isPercussion(),
+ m_program.getBank().getMSB(),
+ lsb),
+ m_program.getProgram());
+}
+
+MidiByte
+Instrument::getLSB() const
+{
+ return m_program.getBank().getLSB();
+}
+
+void
+Instrument::setPercussion(bool percussion)
+{
+ m_program = MidiProgram(MidiBank(percussion,
+ m_program.getBank().getMSB(),
+ m_program.getBank().getLSB()),
+ m_program.getProgram());
+}
+
+bool
+Instrument::isPercussion() const
+{
+ return m_program.getBank().isPercussion();
+}
+
+void
+Instrument::setAudioInputToBuss(BussId buss, int channel)
+{
+ m_audioInput = buss;
+ m_audioInputChannel = channel;
+}
+
+void
+Instrument::setAudioInputToRecord(int recordIn, int channel)
+{
+ m_audioInput = recordIn + 1000;
+ m_audioInputChannel = channel;
+}
+
+int
+Instrument::getAudioInput(bool &isBuss, int &channel) const
+{
+ channel = m_audioInputChannel;
+
+ if (m_audioInput >= 1000) {
+ isBuss = false;
+ return m_audioInput - 1000;
+ } else {
+ isBuss = true;
+ return m_audioInput;
+ }
+}
+
+
+// Implementation of the virtual method to output this class
+// as XML. We don't send out the name as it's redundant in
+// the file - that is driven from the sequencer.
+//
+//
+std::string
+Instrument::toXmlString()
+{
+
+ std::stringstream instrument;
+
+ // We don't send system Instruments out this way -
+ // only user Instruments.
+ //
+ if (m_id < AudioInstrumentBase)
+ {
+#if (__GNUC__ < 3)
+ instrument << std::ends;
+#endif
+ return instrument.str();
+ }
+
+ instrument << " <instrument id=\"" << m_id;
+ instrument << "\" channel=\"" << (int)m_channel;
+ instrument << "\" type=\"";
+
+ if (m_type == Midi)
+ {
+ instrument << "midi\">" << std::endl;
+
+ if (m_sendBankSelect)
+ {
+ instrument << " <bank percussion=\""
+ << (isPercussion() ? "true" : "false") << "\" msb=\""
+ << (int)getMSB();
+ instrument << "\" lsb=\"" << (int)getLSB() << "\"/>" << std::endl;
+ }
+
+ if (m_sendProgramChange)
+ {
+ instrument << " <program id=\""
+ << (int)getProgramChange() << "\"/>"
+ << std::endl;
+ }
+
+ instrument << " <pan value=\""
+ << (int)m_pan << "\"/>" << std::endl;
+
+ instrument << " <volume value=\""
+ << (int)m_volume << "\"/>" << std::endl;
+
+ for (StaticControllerConstIterator it = m_staticControllers.begin();
+ it != m_staticControllers.end(); ++it)
+ {
+ instrument << " <controlchange type=\"" << int(it->first)
+ << "\" value=\"" << int(it->second) << "\"/>" << std::endl;
+ }
+
+ }
+ else // Audio or SoftSynth
+ {
+
+ if (m_type == Audio) {
+ instrument << "audio\">" << std::endl;
+ } else {
+ instrument << "softsynth\">" << std::endl;
+ }
+
+ instrument << " <pan value=\""
+ << (int)m_pan << "\"/>" << std::endl;
+
+ instrument << " <level value=\""
+ << m_level << "\"/>" << std::endl;
+
+ instrument << " <recordLevel value=\""
+ << m_recordLevel << "\"/>" << std::endl;
+
+ bool aibuss;
+ int channel;
+ int ai = getAudioInput(aibuss, channel);
+
+ instrument << " <audioInput value=\""
+ << ai << "\" type=\""
+ << (aibuss ? "buss" : "record")
+ << "\" channel=\"" << channel
+ << "\"/>" << std::endl;
+
+ instrument << " <audioOutput value=\""
+ << m_audioOutput << "\"/>" << std::endl;
+
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++)
+ {
+ instrument << (*it)->toXmlString();
+ }
+ }
+
+ instrument << " </instrument>" << std::endl
+#if (__GNUC__ < 3)
+ << std::endl << std::ends;
+#else
+ << std::endl;
+#endif
+
+ return instrument.str();
+
+}
+
+
+// Return a program name given a bank select (and whether
+// we send it or not)
+//
+std::string
+Instrument::getProgramName() const
+{
+ if (m_sendProgramChange == false)
+ return std::string("");
+
+ MidiProgram program(m_program);
+
+ if (!m_sendBankSelect)
+ program = MidiProgram(MidiBank(isPercussion(), 0, 0), program.getProgram());
+
+ return ((dynamic_cast<MidiDevice*>(m_device))->getProgramName(program));
+}
+
+void
+Instrument::setControllerValue(MidiByte controller, MidiByte value)
+{
+ for (StaticControllerIterator it = m_staticControllers.begin();
+ it != m_staticControllers.end(); ++it)
+ {
+ if (it->first == controller)
+ {
+ it->second = value;
+ return;
+ }
+ }
+
+ m_staticControllers.push_back(std::pair<MidiByte, MidiByte>(controller, value));
+
+}
+
+MidiByte
+Instrument::getControllerValue(MidiByte controller) const
+{
+ for (StaticControllerConstIterator it = m_staticControllers.begin();
+ it != m_staticControllers.end(); ++it)
+ {
+
+ if (it->first == controller)
+ return it->second;
+ }
+
+ throw std::string("<no controller of that value>");
+}
+
+const MidiKeyMapping *
+Instrument::getKeyMapping() const
+{
+ MidiDevice *md = dynamic_cast<MidiDevice*>(m_device);
+ if (!md) return 0;
+
+ const MidiKeyMapping *mkm = md->getKeyMappingForProgram(m_program);
+ if (mkm) return mkm;
+
+ if (isPercussion()) { // if any key mapping is available, use it
+ const KeyMappingList &kml = md->getKeyMappings();
+ if (kml.begin() != kml.end()) {
+ return &(*kml.begin());
+ }
+ }
+
+ return 0;
+}
+
+
+Buss::Buss(BussId id) :
+ PluginContainer(true),
+ m_id(id),
+ m_level(0.0),
+ m_pan(100),
+ m_mappedId(0)
+{
+}
+
+Buss::~Buss()
+{
+}
+
+std::string
+Buss::toXmlString()
+{
+ std::stringstream buss;
+
+ buss << " <buss id=\"" << m_id << "\">" << std::endl;
+ buss << " <pan value=\"" << (int)m_pan << "\"/>" << std::endl;
+ buss << " <level value=\"" << m_level << "\"/>" << std::endl;
+
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++) {
+ buss << (*it)->toXmlString();
+ }
+
+ buss << " </buss>" << std::endl;
+
+#if (__GNUC__ < 3)
+ buss << std::ends;
+#endif
+
+ return buss.str();
+}
+
+std::string
+Buss::getName() const
+{
+ char buffer[20];
+ sprintf(buffer, "Submaster %d", m_id);
+ return buffer;
+}
+
+std::string
+Buss::getPresentationName() const
+{
+ return getName();
+}
+
+RecordIn::RecordIn() :
+ m_mappedId(0)
+{
+}
+
+RecordIn::~RecordIn()
+{
+}
+
+std::string
+RecordIn::toXmlString()
+{
+ // We don't actually save these, as they have nothing persistent
+ // in them. The studio just remembers how many there should be.
+ return "";
+}
+
+
+}
+
diff --git a/src/base/Instrument.h b/src/base/Instrument.h
new file mode 100644
index 0000000..8c348f0
--- /dev/null
+++ b/src/base/Instrument.h
@@ -0,0 +1,349 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _INSTRUMENT_H_
+#define _INSTRUMENT_H_
+
+#include <string>
+#include <vector>
+
+#include "XmlExportable.h"
+#include "MidiProgram.h"
+
+// An Instrument connects a Track (which itself contains
+// a list of Segments) to a device that can play that
+// Track.
+//
+// An Instrument is either MIDI or Audio (or whatever else
+// we decide to implement).
+//
+//
+
+namespace Rosegarden
+{
+
+// plugins
+class AudioPluginInstance;
+typedef std::vector<AudioPluginInstance*>::iterator PluginInstanceIterator;
+
+typedef std::vector<std::pair<MidiByte, MidiByte> > StaticControllers;
+typedef std::vector<std::pair<MidiByte, MidiByte> >::iterator StaticControllerIterator;
+typedef std::vector<std::pair<MidiByte, MidiByte> >::const_iterator StaticControllerConstIterator;
+
+
+// Instrument number groups
+//
+const InstrumentId SystemInstrumentBase = 0;
+const InstrumentId AudioInstrumentBase = 1000;
+const InstrumentId MidiInstrumentBase = 2000;
+const InstrumentId SoftSynthInstrumentBase = 10000;
+
+const unsigned int AudioInstrumentCount = 16;
+const unsigned int SoftSynthInstrumentCount = 24;
+
+const MidiByte MidiMaxValue = 127;
+const MidiByte MidiMidValue = 64;
+const MidiByte MidiMinValue = 0;
+
+typedef unsigned int BussId;
+
+// Predeclare Device
+//
+class Device;
+
+class PluginContainer
+{
+public:
+ static const unsigned int PLUGIN_COUNT; // for non-synth plugins
+
+ PluginInstanceIterator beginPlugins() { return m_audioPlugins.begin(); }
+ PluginInstanceIterator endPlugins() { return m_audioPlugins.end(); }
+
+ // Plugin management
+ //
+ void addPlugin(AudioPluginInstance *instance);
+ bool removePlugin(unsigned int position);
+ void clearPlugins();
+ void emptyPlugins(); // empty the plugins but don't clear them down
+
+ // Get a plugin for this container
+ //
+ AudioPluginInstance* getPlugin(unsigned int position);
+
+ virtual unsigned int getId() const = 0;
+ virtual std::string getName() const = 0;
+ virtual std::string getPresentationName() const = 0;
+
+protected:
+ PluginContainer(bool havePlugins);
+ virtual ~PluginContainer();
+
+ std::vector<AudioPluginInstance*> m_audioPlugins;
+};
+
+class Instrument : public XmlExportable, public PluginContainer
+{
+public:
+ static const unsigned int SYNTH_PLUGIN_POSITION;
+
+ enum InstrumentType { Midi, Audio, SoftSynth };
+
+ Instrument(InstrumentId id,
+ InstrumentType it,
+ const std::string &name,
+ Device *device);
+
+ Instrument(InstrumentId id,
+ InstrumentType it,
+ const std::string &name,
+ MidiByte channel,
+ Device *device);
+
+
+ // Copy constructor and assignment
+ //
+ Instrument(const Instrument &);
+ Instrument &operator=(const Instrument &);
+
+ ~Instrument();
+
+ virtual std::string getName() const { return m_name; }
+ virtual std::string getPresentationName() const;
+
+ void setId(InstrumentId id) { m_id = id; }
+ InstrumentId getId() const { return m_id; }
+
+ void setName(const std::string &name) { m_name = name; }
+ InstrumentType getType() const { return m_type; }
+
+ void setType(InstrumentType type) { m_type = type; }
+ InstrumentType getInstrumentType() { return m_type; }
+
+
+ // ---------------- MIDI Controllers -----------------
+ //
+ void setMidiChannel(MidiByte mC) { m_channel = mC; }
+ MidiByte getMidiChannel() const { return m_channel; }
+
+ //void setMidiInputChannel(char ic) { m_input_channel = ic; }
+ //char getMidiInputChannel() const { return m_input_channel; }
+
+ void setMidiTranspose(MidiByte mT) { m_transpose = mT; }
+ MidiByte getMidiTranspose() const { return m_transpose; }
+
+ // Pan is 0-127 for MIDI instruments, and (for some
+ // unfathomable reason) 0-200 for audio instruments.
+ //
+ void setPan(MidiByte pan) { m_pan = pan; }
+ MidiByte getPan() const { return m_pan; }
+
+ // Volume is 0-127 for MIDI instruments. It's not used for
+ // audio instruments -- see "level" instead.
+ //
+ void setVolume(MidiByte volume) { m_volume = volume; }
+ MidiByte getVolume() const { return m_volume; }
+
+ void setProgram(const MidiProgram &program) { m_program = program; }
+ const MidiProgram &getProgram() const { return m_program; }
+
+ void setSendBankSelect(bool value) { m_sendBankSelect = value; }
+ bool sendsBankSelect() const { return m_sendBankSelect; }
+
+ void setSendProgramChange(bool value) { m_sendProgramChange = value; }
+ bool sendsProgramChange() const { return m_sendProgramChange; }
+
+ void setSendPan(bool value) { m_sendPan = value; }
+ bool sendsPan() const { return m_sendPan; }
+
+ void setSendVolume(bool value) { m_sendVolume = value; }
+ bool sendsVolume() const { return m_sendVolume; }
+
+ void setControllerValue(MidiByte controller, MidiByte value);
+ MidiByte getControllerValue(MidiByte controller) const;
+
+ // This is retrieved from the reference MidiProgram in the Device
+ const MidiKeyMapping *getKeyMapping() const;
+
+ // Convenience functions (strictly redundant with get/setProgram):
+ //
+ void setProgramChange(MidiByte program);
+ MidiByte getProgramChange() const;
+
+ void setMSB(MidiByte msb);
+ MidiByte getMSB() const;
+
+ void setLSB(MidiByte msb);
+ MidiByte getLSB() const;
+
+ void setPercussion(bool percussion);
+ bool isPercussion() const;
+
+ // --------------- Audio Controllers -----------------
+ //
+ void setLevel(float dB) { m_level = dB; }
+ float getLevel() const { return m_level; }
+
+ void setRecordLevel(float dB) { m_recordLevel = dB; }
+ float getRecordLevel() const { return m_recordLevel; }
+
+ void setAudioChannels(unsigned int ch) { m_channel = MidiByte(ch); }
+ unsigned int getAudioChannels() const { return (unsigned int)(m_channel); }
+
+ // An audio input can be a buss or a record input. The channel number
+ // is required for mono instruments, ignored for stereo ones.
+ void setAudioInputToBuss(BussId buss, int channel = 0);
+ void setAudioInputToRecord(int recordIn, int channel = 0);
+ int getAudioInput(bool &isBuss, int &channel) const;
+
+ void setAudioOutput(BussId buss) { m_audioOutput = buss; }
+ BussId getAudioOutput() const { return m_audioOutput; }
+
+ // Implementation of virtual function
+ //
+ virtual std::string toXmlString();
+
+ // Get and set the parent device
+ //
+ Device* getDevice() const { return m_device; }
+ void setDevice(Device* dev) { m_device = dev; }
+
+ // Return a string describing the current program for
+ // this Instrument
+ //
+ std::string getProgramName() const;
+
+ // MappedId management - should typedef this type once
+ // we have the energy to shake this all out.
+ //
+ int getMappedId() const { return m_mappedId; }
+ void setMappedId(int id) { m_mappedId = id; }
+
+ StaticControllers& getStaticControllers() { return m_staticControllers; }
+
+private:
+ InstrumentId m_id;
+ std::string m_name;
+ InstrumentType m_type;
+
+ // Standard MIDI controllers and parameters
+ //
+ MidiByte m_channel;
+ //char m_input_channel;
+ MidiProgram m_program;
+ MidiByte m_transpose;
+ MidiByte m_pan; // required by audio
+ MidiByte m_volume;
+
+ // Used for Audio volume (dB value)
+ //
+ float m_level;
+
+ // Record level for Audio recording (dB value)
+ //
+ float m_recordLevel;
+
+ Device *m_device;
+
+ // Do we send at this intrument or do we leave these
+ // things up to the parent device and God? These are
+ // directly relatable to GUI elements
+ //
+ bool m_sendBankSelect;
+ bool m_sendProgramChange;
+ bool m_sendPan;
+ bool m_sendVolume;
+
+ // Instruments are directly related to faders for volume
+ // control. Here we can store the remote fader id.
+ //
+ int m_mappedId;
+
+ // Which input terminal we're connected to. This is a BussId if
+ // less than 1000 or a record input number (plus 1000) if >= 1000.
+ // The channel number is only used for mono instruments.
+ //
+ int m_audioInput;
+ int m_audioInputChannel;
+
+ // Which buss we output to. Zero is always the master.
+ //
+ BussId m_audioOutput;
+
+ // A static controller map that can be saved/loaded and queried along with this instrument.
+ // These values are modified from the IPB - if they appear on the IPB then they are sent
+ // at playback start time to the sequencer.
+ //
+ //
+ StaticControllers m_staticControllers;
+};
+
+
+class Buss : public XmlExportable, public PluginContainer
+{
+public:
+ Buss(BussId id);
+ ~Buss();
+
+ void setId(BussId id) { m_id = id; }
+ BussId getId() const { return m_id; }
+
+ void setLevel(float dB) { m_level = dB; }
+ float getLevel() const { return m_level; }
+
+ void setPan(MidiByte pan) { m_pan = pan; }
+ MidiByte getPan() const { return m_pan; }
+
+ int getMappedId() const { return m_mappedId; }
+ void setMappedId(int id) { m_mappedId = id; }
+
+ virtual std::string toXmlString();
+ virtual std::string getName() const;
+ virtual std::string getPresentationName() const;
+
+private:
+ BussId m_id;
+ float m_level;
+ MidiByte m_pan;
+ int m_mappedId;
+};
+
+
+// audio record input of a sort that can be connected to
+
+class RecordIn : public XmlExportable
+{
+public:
+ RecordIn();
+ ~RecordIn();
+
+ int getMappedId() const { return m_mappedId; }
+ void setMappedId(int id) { m_mappedId = id; }
+
+ virtual std::string toXmlString();
+
+private:
+ int m_mappedId;
+};
+
+
+}
+
+#endif // _INSTRUMENT_H_
diff --git a/src/base/LayoutEngine.cpp b/src/base/LayoutEngine.cpp
new file mode 100644
index 0000000..b6b3cf5
--- /dev/null
+++ b/src/base/LayoutEngine.cpp
@@ -0,0 +1,63 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "LayoutEngine.h"
+
+namespace Rosegarden
+{
+
+LayoutEngine::LayoutEngine() :
+ m_status(0)
+{
+ // empty
+}
+
+LayoutEngine::~LayoutEngine()
+{
+ // empty
+}
+
+
+HorizontalLayoutEngine::HorizontalLayoutEngine(Composition *c) :
+ LayoutEngine(),
+ RulerScale(c)
+{
+ // empty
+}
+
+HorizontalLayoutEngine::~HorizontalLayoutEngine()
+{
+ // empty
+}
+
+
+VerticalLayoutEngine::VerticalLayoutEngine() :
+ LayoutEngine()
+{
+ // empty
+}
+
+VerticalLayoutEngine::~VerticalLayoutEngine()
+{
+ // empty
+}
+
+}
diff --git a/src/base/LayoutEngine.h b/src/base/LayoutEngine.h
new file mode 100644
index 0000000..179d119
--- /dev/null
+++ b/src/base/LayoutEngine.h
@@ -0,0 +1,161 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _LAYOUT_ENGINE_H_
+#define _LAYOUT_ENGINE_H_
+
+#include "RulerScale.h"
+
+namespace Rosegarden {
+
+class Staff;
+class TimeSignature;
+
+
+/**
+ * Base classes for layout engines. The intention is that
+ * different sorts of renderers (piano-roll, score etc) can be
+ * implemented by simply plugging different implementations
+ * of Staff and LayoutEngine into a single view class.
+ */
+class LayoutEngine
+{
+public:
+ LayoutEngine();
+ virtual ~LayoutEngine();
+
+ /**
+ * Resets internal data stores for all staffs
+ */
+ virtual void reset() = 0;
+
+ /**
+ * Resets internal data stores for a specific staff.
+ *
+ * If startTime == endTime, act on the whole staff; otherwise only
+ * the given section.
+ */
+ virtual void resetStaff(Staff &staff,
+ timeT startTime = 0,
+ timeT endTime = 0) = 0;
+
+ /**
+ * Precomputes layout data for a single staff, updating any
+ * internal data stores associated with that staff and updating
+ * any layout-related properties in the events on the staff's
+ * segment.
+ *
+ * If startTime == endTime, act on the whole staff; otherwise only
+ * the given section.
+ */
+ virtual void scanStaff(Staff &staff,
+ timeT startTime = 0,
+ timeT endTime = 0) = 0;
+
+ /**
+ * Computes any layout data that may depend on the results of
+ * scanning more than one staff. This may mean doing most of
+ * the layout (likely for horizontal layout) or nothing at all
+ * (likely for vertical layout).
+ *
+ * If startTime == endTime, act on the whole staff; otherwise only
+ * the given section.
+ */
+ virtual void finishLayout(timeT startTime = 0,
+ timeT endTime = 0) = 0;
+
+ unsigned int getStatus() const { return m_status; }
+
+protected:
+ unsigned int m_status;
+};
+
+
+class HorizontalLayoutEngine : public LayoutEngine,
+ public RulerScale
+{
+public:
+ HorizontalLayoutEngine(Composition *c);
+ virtual ~HorizontalLayoutEngine();
+
+ /**
+ * Sets a page width for the layout.
+ *
+ * A layout implementation does not have to use this. Some might
+ * use it (for example) to ensure that bar lines fall precisely at
+ * the right-hand margin of each page. The computed x-coordinates
+ * will still require to be wrapped into lines by the staff or
+ * view implementation, however.
+ *
+ * A width of zero indicates no requirement for division into
+ * pages.
+ */
+ virtual void setPageWidth(double) { /* default: ignore it */ }
+
+ /**
+ * Returns the number of the first visible bar line on the given
+ * staff
+ */
+ virtual int getFirstVisibleBarOnStaff(Staff &) {
+ return getFirstVisibleBar();
+ }
+
+ /**
+ * Returns the number of the last visible bar line on the given
+ * staff
+ */
+ virtual int getLastVisibleBarOnStaff(Staff &) {
+ return getLastVisibleBar();
+ }
+
+ /**
+ * Returns true if the specified bar has the correct length
+ */
+ virtual bool isBarCorrectOnStaff(Staff &, int/* barNo */) {
+ return true;
+ }
+
+ /**
+ * Returns true if there is a new time signature in the given bar,
+ * setting timeSignature appropriately and setting timeSigX to its
+ * x-coord
+ */
+ virtual bool getTimeSignaturePosition
+ (Staff &, int /* barNo */, TimeSignature &, double &/* timeSigX */) {
+ return 0;
+ }
+};
+
+
+
+class VerticalLayoutEngine : public LayoutEngine
+{
+public:
+ VerticalLayoutEngine();
+ virtual ~VerticalLayoutEngine();
+
+ // I don't think we need to add anything here for now
+};
+
+}
+
+
+#endif
diff --git a/src/base/LegatoQuantizer.cpp b/src/base/LegatoQuantizer.cpp
new file mode 100644
index 0000000..dcc2458
--- /dev/null
+++ b/src/base/LegatoQuantizer.cpp
@@ -0,0 +1,141 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "LegatoQuantizer.h"
+#include "BaseProperties.h"
+#include "NotationTypes.h"
+#include "Selection.h"
+#include "Composition.h"
+#include "Profiler.h"
+
+#include <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+
+LegatoQuantizer::LegatoQuantizer(timeT unit) :
+ Quantizer(RawEventData),
+ m_unit(unit)
+{
+ if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration();
+}
+
+LegatoQuantizer::LegatoQuantizer(std::string source, std::string target, timeT unit) :
+ Quantizer(source, target),
+ m_unit(unit)
+{
+ if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration();
+}
+
+LegatoQuantizer::LegatoQuantizer(const LegatoQuantizer &q) :
+ Quantizer(q.m_target),
+ m_unit(q.m_unit)
+{
+ // nothing else
+}
+
+LegatoQuantizer::~LegatoQuantizer()
+{
+ // nothing
+}
+
+void
+LegatoQuantizer::quantizeRange(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ Segment::iterator tmp;
+ while (from != to) {
+ quantizeSingle(s, from, tmp);
+ from = tmp;
+ if (!s->isBeforeEndMarker(from) ||
+ (s->isBeforeEndMarker(to) &&
+ ((*from)->getAbsoluteTime() >= (*to)->getAbsoluteTime()))) break;
+ }
+}
+
+void
+LegatoQuantizer::quantizeSingle(Segment *s, Segment::iterator i,
+ Segment::iterator &nexti) const
+{
+ // Stretch each note out to reach the quantized start time of the
+ // next note whose quantized start time is greater than or equal
+ // to the end time of this note after quantization
+
+ timeT t = getFromSource(*i, AbsoluteTimeValue);
+ timeT d = getFromSource(*i, DurationValue);
+
+ timeT d0(d), t0(t);
+
+ timeT barStart = s->getBarStartForTime(t);
+
+ t -= barStart;
+ t = quantizeTime(t);
+ t += barStart;
+
+ nexti = i;
+ ++nexti;
+
+ for (Segment::iterator j = i; s->isBeforeEndMarker(j); ++j) {
+ if (!(*j)->isa(Note::EventType)) continue;
+
+ timeT qt = (*j)->getAbsoluteTime();
+ qt -= barStart;
+ qt = quantizeTime(qt);
+ qt += barStart;
+
+ if (qt >= t + d) {
+ d = qt - t;
+ }
+ if (qt > t) {
+ break;
+ }
+ }
+
+ if (t0 != t || d0 != d) {
+ setToTarget(s, i, t, d);
+ nexti = s->findTime(t + d);
+ }
+}
+
+timeT
+LegatoQuantizer::quantizeTime(timeT t) const
+{
+ if (m_unit != 0) {
+ timeT low = (t / m_unit) * m_unit;
+ timeT high = low + m_unit;
+ t = ((high - t > t - low) ? low : high);
+ }
+ return t;
+}
+
+}
diff --git a/src/base/LegatoQuantizer.h b/src/base/LegatoQuantizer.h
new file mode 100644
index 0000000..645da05
--- /dev/null
+++ b/src/base/LegatoQuantizer.h
@@ -0,0 +1,64 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef LEGATO_QUANTIZER_H
+#define LEGATO_QUANTIZER_H
+
+#include "Quantizer.h"
+
+namespace Rosegarden {
+
+class BasicQuantizer;
+
+class LegatoQuantizer : public Quantizer
+{
+public:
+ // The default unit is the shortest note type. A unit of
+ // zero means do no quantization -- unlike for BasicQuantizer
+ // this does have a purpose, as it still does the legato step
+ LegatoQuantizer(timeT unit = -1);
+ LegatoQuantizer(std::string source, std::string target, timeT unit = -1);
+ LegatoQuantizer(const LegatoQuantizer &);
+ virtual ~LegatoQuantizer();
+
+ void setUnit(timeT unit) { m_unit = unit; }
+ timeT getUnit() const { return m_unit; }
+
+ virtual void quantizeRange(Segment *,
+ Segment::iterator,
+ Segment::iterator) const;
+
+protected:
+ virtual void quantizeSingle(Segment *, Segment::iterator,
+ Segment::iterator &) const;
+
+ timeT quantizeTime(timeT) const;
+
+private:
+ LegatoQuantizer &operator=(const BasicQuantizer &); // not provided
+
+ timeT m_unit;
+};
+
+}
+
+#endif
+
diff --git a/src/base/Marker.cpp b/src/base/Marker.cpp
new file mode 100644
index 0000000..beab5f6
--- /dev/null
+++ b/src/base/Marker.cpp
@@ -0,0 +1,55 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Marker.h"
+#include "misc/Debug.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+namespace Rosegarden
+{
+
+int Marker::m_sequence = 0;
+
+std::string
+Marker::toXmlString()
+{
+ std::stringstream marker;
+
+ marker << " <marker time=\"" << m_time
+ << "\" name=\"" << encode(m_name)
+ << "\" description=\"" << encode(m_description)
+ << "\"/>" << std::endl;
+#if (__GNUC__ < 3)
+ marker << std::ends;
+#endif
+
+ return marker.str();
+}
+
+}
+
diff --git a/src/base/Marker.h b/src/base/Marker.h
new file mode 100644
index 0000000..624081d
--- /dev/null
+++ b/src/base/Marker.h
@@ -0,0 +1,78 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _MARKER_H_
+#define _MARKER_H_
+
+#include <string>
+
+#include "Event.h"
+#include "XmlExportable.h"
+
+// A Marker is a user defined point in a Composition that can be
+// used to define looping points - jump to, make notes at etc.
+//
+// Not to be confused with Composition or Segment start and
+// end markers. Which they probably could be quite easily.
+// I probably should've thought the name through a bit better
+// really..
+//
+
+namespace Rosegarden
+{
+
+class Marker : public XmlExportable
+{
+public:
+ Marker():m_time(0), m_name(std::string("<unnamed>")),
+ m_description(std::string("<none>")) { m_id = nextSeqVal(); }
+
+ Marker(timeT time, const std::string &name,
+ const std::string &description):
+ m_time(time), m_name(name), m_description(description) { m_id = nextSeqVal(); }
+
+ int getID() const { return m_id; }
+ timeT getTime() const { return m_time; }
+ std::string getName() const { return m_name; }
+ std::string getDescription() const { return m_description; }
+
+ void setTime(timeT time) { m_time = time; }
+ void setName(const std::string &name) { m_name = name; }
+ void setDescription(const std::string &des) { m_description = des; }
+
+ // export as XML
+ virtual std::string toXmlString();
+
+protected:
+
+ int m_id;
+ timeT m_time;
+ std::string m_name;
+ std::string m_description;
+
+private:
+ static int nextSeqVal() { return ++m_sequence; } // assume there won't be concurrency problem
+ static int m_sequence;
+};
+
+}
+
+#endif // _CONTROLPARAMETER_H_
diff --git a/src/base/MidiDevice.cpp b/src/base/MidiDevice.cpp
new file mode 100644
index 0000000..cceeb0e
--- /dev/null
+++ b/src/base/MidiDevice.cpp
@@ -0,0 +1,839 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "MidiDevice.h"
+#include "MidiTypes.h"
+#include "Instrument.h"
+#include "ControlParameter.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <set>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+
+namespace Rosegarden
+{
+
+MidiDevice::MidiDevice():
+ Device(0, "Default Midi Device", Device::Midi),
+ m_metronome(0),
+ m_direction(Play),
+ m_variationType(NoVariations),
+ m_librarian(std::pair<std::string, std::string>("<none>", "<none>"))
+{
+ generatePresentationList();
+ generateDefaultControllers();
+
+ // create a default Metronome
+ m_metronome = new MidiMetronome(MidiInstrumentBase + 9);
+}
+
+MidiDevice::MidiDevice(DeviceId id,
+ const std::string &name,
+ DeviceDirection dir):
+ Device(id, name, Device::Midi),
+ m_metronome(0),
+ m_direction(dir),
+ m_variationType(NoVariations),
+ m_librarian(std::pair<std::string, std::string>("<none>", "<none>"))
+{
+ generatePresentationList();
+ generateDefaultControllers();
+
+ // create a default Metronome
+ m_metronome = new MidiMetronome(MidiInstrumentBase + 9);
+}
+
+MidiDevice::MidiDevice(DeviceId id,
+ const MidiDevice &dev) :
+ Device(id, dev.getName(), Device::Midi),
+ m_programList(dev.m_programList),
+ m_bankList(dev.m_bankList),
+ m_controlList(dev.m_controlList),
+ m_metronome(0),
+ m_direction(dev.getDirection()),
+ m_variationType(dev.getVariationType()),
+ m_librarian(dev.getLibrarian())
+{
+ // Create and assign a metronome if required
+ //
+ if (dev.getMetronome()) {
+ m_metronome = new MidiMetronome(*dev.getMetronome());
+ }
+
+ generatePresentationList();
+ generateDefaultControllers();
+}
+
+MidiDevice::MidiDevice(const MidiDevice &dev) :
+ Device(dev.getId(), dev.getName(), dev.getType()),
+ Controllable(),
+ m_programList(dev.m_programList),
+ m_bankList(dev.m_bankList),
+ m_controlList(dev.m_controlList),
+ m_metronome(0),
+ m_direction(dev.getDirection()),
+ m_variationType(dev.getVariationType()),
+ m_librarian(dev.getLibrarian())
+{
+ // Create and assign a metronome if required
+ //
+ if (dev.getMetronome())
+ {
+ m_metronome = new MidiMetronome(*dev.getMetronome());
+ }
+
+ // Copy the instruments
+ //
+ InstrumentList insList = dev.getAllInstruments();
+ InstrumentList::iterator iIt = insList.begin();
+ for (; iIt != insList.end(); iIt++)
+ {
+ Instrument *newInst = new Instrument(**iIt);
+ newInst->setDevice(this);
+ m_instruments.push_back(newInst);
+ }
+
+ // generate presentation instruments
+ generatePresentationList();
+}
+
+
+MidiDevice &
+MidiDevice::operator=(const MidiDevice &dev)
+{
+ if (&dev == this) return *this;
+
+ m_id = dev.getId();
+ m_name = dev.getName();
+ m_type = dev.getType();
+ m_librarian = dev.getLibrarian();
+
+ m_programList = dev.getPrograms();
+ m_bankList = dev.getBanks();
+ m_controlList = dev.getControlParameters();
+ m_direction = dev.getDirection();
+ m_variationType = dev.getVariationType();
+
+ // clear down instruments list
+ m_instruments.clear();
+ m_presentationInstrumentList.clear();
+
+ // Create and assign a metronome if required
+ //
+ if (dev.getMetronome())
+ {
+ if (m_metronome) delete m_metronome;
+ m_metronome = new MidiMetronome(*dev.getMetronome());
+ }
+ else
+ {
+ delete m_metronome;
+ m_metronome = 0;
+ }
+
+ // Copy the instruments
+ //
+ InstrumentList insList = dev.getAllInstruments();
+ InstrumentList::iterator iIt = insList.begin();
+ for (; iIt != insList.end(); iIt++)
+ {
+ Instrument *newInst = new Instrument(**iIt);
+ newInst->setDevice(this);
+ m_instruments.push_back(newInst);
+ }
+
+ // generate presentation instruments
+ generatePresentationList();
+
+ return (*this);
+}
+
+MidiDevice::~MidiDevice()
+{
+ delete m_metronome;
+ //!!! delete key mappings
+}
+
+void
+MidiDevice::generatePresentationList()
+{
+ // Fill the presentation list for the instruments
+ //
+ m_presentationInstrumentList.clear();
+
+ InstrumentList::iterator it;
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++)
+ {
+ if ((*it)->getId() >= MidiInstrumentBase) {
+ m_presentationInstrumentList.push_back(*it);
+ }
+ }
+}
+
+void
+MidiDevice::generateDefaultControllers()
+{
+ m_controlList.clear();
+
+ static std::string controls[][9] = {
+ { "Pan", Rosegarden::Controller::EventType, "<none>", "0", "127", "64", "10", "2", "0" },
+ { "Chorus", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "93", "3", "1" },
+ { "Volume", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "7", "1", "2" },
+ { "Reverb", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "91", "3", "3" },
+ { "Sustain", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "64", "4", "-1" },
+ { "Expression", Rosegarden::Controller::EventType, "<none>", "0", "127", "100", "11", "2", "-1" },
+ { "Modulation", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "1", "4", "-1" },
+ { "PitchBend", Rosegarden::PitchBend::EventType, "<none>", "0", "16383", "8192", "1", "4", "-1" }
+ };
+
+ for (unsigned int i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) {
+
+ Rosegarden::ControlParameter con(controls[i][0],
+ controls[i][1],
+ controls[i][2],
+ atoi(controls[i][3].c_str()),
+ atoi(controls[i][4].c_str()),
+ atoi(controls[i][5].c_str()),
+ Rosegarden::MidiByte(atoi(controls[i][6].c_str())),
+ atoi(controls[i][7].c_str()),
+ atoi(controls[i][8].c_str()));
+ addControlParameter(con);
+ }
+
+
+}
+
+void
+MidiDevice::clearBankList()
+{
+ m_bankList.clear();
+}
+
+void
+MidiDevice::clearProgramList()
+{
+ m_programList.clear();
+}
+
+void
+MidiDevice::clearControlList()
+{
+ m_controlList.clear();
+}
+
+void
+MidiDevice::addProgram(const MidiProgram &prog)
+{
+ // Refuse duplicates
+ for (ProgramList::const_iterator it = m_programList.begin();
+ it != m_programList.end(); ++it) {
+ if (*it == prog) return;
+ }
+
+ m_programList.push_back(prog);
+}
+
+void
+MidiDevice::addBank(const MidiBank &bank)
+{
+ m_bankList.push_back(bank);
+}
+
+void
+MidiDevice::removeMetronome()
+{
+ delete m_metronome;
+ m_metronome = 0;
+}
+
+void
+MidiDevice::setMetronome(const MidiMetronome &metronome)
+{
+ delete m_metronome;
+ m_metronome = new MidiMetronome(metronome);
+}
+
+BankList
+MidiDevice::getBanks(bool percussion) const
+{
+ BankList banks;
+
+ for (BankList::const_iterator it = m_bankList.begin();
+ it != m_bankList.end(); ++it) {
+ if (it->isPercussion() == percussion) banks.push_back(*it);
+ }
+
+ return banks;
+}
+
+BankList
+MidiDevice::getBanksByMSB(bool percussion, MidiByte msb) const
+{
+ BankList banks;
+
+ for (BankList::const_iterator it = m_bankList.begin();
+ it != m_bankList.end(); ++it) {
+ if (it->isPercussion() == percussion && it->getMSB() == msb)
+ banks.push_back(*it);
+ }
+
+ return banks;
+}
+
+BankList
+MidiDevice::getBanksByLSB(bool percussion, MidiByte lsb) const
+{
+ BankList banks;
+
+ for (BankList::const_iterator it = m_bankList.begin();
+ it != m_bankList.end(); ++it) {
+ if (it->isPercussion() == percussion && it->getLSB() == lsb)
+ banks.push_back(*it);
+ }
+
+ return banks;
+}
+
+MidiByteList
+MidiDevice::getDistinctMSBs(bool percussion, int lsb) const
+{
+ std::set<MidiByte> msbs;
+
+ for (BankList::const_iterator it = m_bankList.begin();
+ it != m_bankList.end(); ++it) {
+ if (it->isPercussion() == percussion &&
+ (lsb == -1 || it->getLSB() == lsb)) msbs.insert(it->getMSB());
+ }
+
+ MidiByteList v;
+ for (std::set<MidiByte>::iterator i = msbs.begin(); i != msbs.end(); ++i) {
+ v.push_back(*i);
+ }
+
+ return v;
+}
+
+MidiByteList
+MidiDevice::getDistinctLSBs(bool percussion, int msb) const
+{
+ std::set<MidiByte> lsbs;
+
+ for (BankList::const_iterator it = m_bankList.begin();
+ it != m_bankList.end(); ++it) {
+ if (it->isPercussion() == percussion &&
+ (msb == -1 || it->getMSB() == msb)) lsbs.insert(it->getLSB());
+ }
+
+ MidiByteList v;
+ for (std::set<MidiByte>::iterator i = lsbs.begin(); i != lsbs.end(); ++i) {
+ v.push_back(*i);
+ }
+
+ return v;
+}
+
+ProgramList
+MidiDevice::getPrograms(const MidiBank &bank) const
+{
+ ProgramList programs;
+
+ for (ProgramList::const_iterator it = m_programList.begin();
+ it != m_programList.end(); ++it) {
+ if (it->getBank() == bank) programs.push_back(*it);
+ }
+
+ return programs;
+}
+
+std::string
+MidiDevice::getBankName(const MidiBank &bank) const
+{
+ for (BankList::const_iterator it = m_bankList.begin();
+ it != m_bankList.end(); ++it) {
+ if (*it == bank) return it->getName();
+ }
+ return "";
+}
+
+void
+MidiDevice::addKeyMapping(const MidiKeyMapping &mapping)
+{
+ //!!! handle dup names
+ m_keyMappingList.push_back(mapping);
+}
+
+const MidiKeyMapping *
+MidiDevice::getKeyMappingByName(const std::string &name) const
+{
+ for (KeyMappingList::const_iterator i = m_keyMappingList.begin();
+ i != m_keyMappingList.end(); ++i) {
+ if (i->getName() == name) return &(*i);
+ }
+ return 0;
+}
+
+const MidiKeyMapping *
+MidiDevice::getKeyMappingForProgram(const MidiProgram &program) const
+{
+ ProgramList::const_iterator it;
+
+ for (it = m_programList.begin(); it != m_programList.end(); it++) {
+ if (*it == program) {
+ std::string kmn = it->getKeyMapping();
+ if (kmn == "") return 0;
+ return getKeyMappingByName(kmn);
+ }
+ }
+
+ return 0;
+}
+
+void
+MidiDevice::setKeyMappingForProgram(const MidiProgram &program,
+ std::string mapping)
+{
+ ProgramList::iterator it;
+
+ for (it = m_programList.begin(); it != m_programList.end(); it++) {
+ if (*it == program) {
+ it->setKeyMapping(mapping);
+ }
+ }
+}
+
+
+std::string
+MidiDevice::toXmlString()
+{
+ std::stringstream midiDevice;
+
+ midiDevice << " <device id=\"" << m_id
+ << "\" name=\"" << m_name
+ << "\" direction=\"" << (m_direction == Play ?
+ "play" : "record")
+ << "\" variation=\"" << (m_variationType == VariationFromLSB ?
+ "LSB" :
+ m_variationType == VariationFromMSB ?
+ "MSB" : "")
+ << "\" connection=\"" << encode(m_connection)
+ << "\" type=\"midi\">" << std::endl << std::endl;
+
+ midiDevice << " <librarian name=\"" << encode(m_librarian.first)
+ << "\" email=\"" << encode(m_librarian.second)
+ << "\"/>" << std::endl;
+
+ if (m_metronome)
+ {
+ // Write out the metronome - watch the MidiBytes
+ // when using the stringstream
+ //
+ midiDevice << " <metronome "
+ << "instrument=\"" << m_metronome->getInstrument() << "\" "
+ << "barpitch=\"" << (int)m_metronome->getBarPitch() << "\" "
+ << "beatpitch=\"" << (int)m_metronome->getBeatPitch() << "\" "
+ << "subbeatpitch=\"" << (int)m_metronome->getSubBeatPitch() << "\" "
+ << "depth=\"" << (int)m_metronome->getDepth() << "\" "
+ << "barvelocity=\"" << (int)m_metronome->getBarVelocity() << "\" "
+ << "beatvelocity=\"" << (int)m_metronome->getBeatVelocity() << "\" "
+ << "subbeatvelocity=\"" << (int)m_metronome->getSubBeatVelocity()
+ << "\"/>"
+ << std::endl << std::endl;
+ }
+
+ // and now bank information
+ //
+ BankList::iterator it;
+ InstrumentList::iterator iit;
+ ProgramList::iterator pt;
+
+ for (it = m_bankList.begin(); it != m_bankList.end(); it++)
+ {
+ midiDevice << " <bank "
+ << "name=\"" << encode(it->getName()) << "\" "
+ << "percussion=\"" << (it->isPercussion() ? "true" : "false") << "\" "
+ << "msb=\"" << (int)it->getMSB() << "\" "
+ << "lsb=\"" << (int)it->getLSB() << "\">"
+ << std::endl;
+
+ // Not terribly efficient
+ //
+ for (pt = m_programList.begin(); pt != m_programList.end(); pt++)
+ {
+ if (pt->getBank() == *it)
+ {
+ midiDevice << " <program "
+ << "id=\"" << (int)pt->getProgram() << "\" "
+ << "name=\"" << encode(pt->getName()) << "\" ";
+ if (!pt->getKeyMapping().empty()) {
+ midiDevice << "keymapping=\""
+ << encode(pt->getKeyMapping()) << "\" ";
+ }
+ midiDevice << "/>" << std::endl;
+ }
+ }
+
+ midiDevice << " </bank>" << std::endl << std::endl;
+ }
+
+ // Now controllers (before Instruments, which can depend on
+ // Controller colours)
+ //
+ midiDevice << " <controls>" << std::endl;
+ ControlList::iterator cIt;
+ for (cIt = m_controlList.begin(); cIt != m_controlList.end() ; ++cIt)
+ midiDevice << cIt->toXmlString();
+ midiDevice << " </controls>" << std::endl << std::endl;
+
+ // Add instruments
+ //
+ for (iit = m_instruments.begin(); iit != m_instruments.end(); iit++)
+ midiDevice << (*iit)->toXmlString();
+
+ KeyMappingList::iterator kit;
+
+ for (kit = m_keyMappingList.begin(); kit != m_keyMappingList.end(); kit++)
+ {
+ midiDevice << " <keymapping "
+ << "name=\"" << encode(kit->getName()) << "\">\n";
+
+ for (MidiKeyMapping::KeyNameMap::const_iterator nmi =
+ kit->getMap().begin(); nmi != kit->getMap().end(); ++nmi) {
+ midiDevice << " <key number=\"" << (int)nmi->first
+ << "\" name=\"" << encode(nmi->second) << "\"/>\n";
+ }
+
+ midiDevice << " </keymapping>\n";
+ }
+
+#if (__GNUC__ < 3)
+ midiDevice << " </device>" << std::endl << std::ends;
+#else
+ midiDevice << " </device>" << std::endl;
+#endif
+
+ return midiDevice.str();
+}
+
+// Only copy across non System instruments
+//
+InstrumentList
+MidiDevice::getAllInstruments() const
+{
+ return m_instruments;
+}
+
+// Omitting special system Instruments
+//
+InstrumentList
+MidiDevice::getPresentationInstruments() const
+{
+ return m_presentationInstrumentList;
+}
+
+void
+MidiDevice::addInstrument(Instrument *instrument)
+{
+ m_instruments.push_back(instrument);
+ generatePresentationList();
+}
+
+std::string
+MidiDevice::getProgramName(const MidiProgram &program) const
+{
+ ProgramList::const_iterator it;
+
+ for (it = m_programList.begin(); it != m_programList.end(); it++)
+ {
+ if (*it == program) return it->getName();
+ }
+
+ return std::string("");
+}
+
+void
+MidiDevice::replaceBankList(const BankList &bankList)
+{
+ m_bankList = bankList;
+}
+
+void
+MidiDevice::replaceProgramList(const ProgramList &programList)
+{
+ m_programList = programList;
+}
+
+void
+MidiDevice::replaceKeyMappingList(const KeyMappingList &keyMappingList)
+{
+ m_keyMappingList = keyMappingList;
+}
+
+
+// Merge the new bank list in without duplication
+//
+void
+MidiDevice::mergeBankList(const BankList &bankList)
+{
+ BankList::const_iterator it;
+ BankList::iterator oIt;
+ bool clash = false;
+
+ for (it = bankList.begin(); it != bankList.end(); it++)
+ {
+ for (oIt = m_bankList.begin(); oIt != m_bankList.end(); oIt++)
+ {
+ if (*it == *oIt)
+ {
+ clash = true;
+ break;
+ }
+ }
+
+ if (clash == false)
+ addBank(*it);
+ else
+ clash = false;
+ }
+
+}
+
+void
+MidiDevice::mergeProgramList(const ProgramList &programList)
+{
+ ProgramList::const_iterator it;
+ ProgramList::iterator oIt;
+ bool clash = false;
+
+ for (it = programList.begin(); it != programList.end(); it++)
+ {
+ for (oIt = m_programList.begin(); oIt != m_programList.end(); oIt++)
+ {
+ if (*it == *oIt)
+ {
+ clash = true;
+ break;
+ }
+ }
+
+ if (clash == false)
+ addProgram(*it);
+ else
+ clash = false;
+ }
+}
+
+void
+MidiDevice::mergeKeyMappingList(const KeyMappingList &keyMappingList)
+{
+ KeyMappingList::const_iterator it;
+ KeyMappingList::iterator oIt;
+ bool clash = false;
+
+ for (it = keyMappingList.begin(); it != keyMappingList.end(); it++)
+ {
+ for (oIt = m_keyMappingList.begin(); oIt != m_keyMappingList.end(); oIt++)
+ {
+ if (it->getName() == oIt->getName())
+ {
+ clash = true;
+ break;
+ }
+ }
+
+ if (clash == false)
+ addKeyMapping(*it);
+ else
+ clash = false;
+ }
+}
+
+void
+MidiDevice::addControlParameter(const ControlParameter &con)
+{
+ m_controlList.push_back(con);
+}
+
+void
+MidiDevice::addControlParameter(const ControlParameter &con, int index)
+{
+ ControlList controls;
+
+ // if we're out of range just add the control
+ if (index >= (int)m_controlList.size())
+ {
+ m_controlList.push_back(con);
+ return;
+ }
+
+ // add new controller in at a position
+ for (int i = 0; i < (int)m_controlList.size(); ++i)
+ {
+ if (index == i) controls.push_back(con);
+ controls.push_back(m_controlList[i]);
+ }
+
+ m_controlList = controls;
+}
+
+
+bool
+MidiDevice::removeControlParameter(int index)
+{
+ ControlList::iterator it = m_controlList.begin();
+ int i = 0;
+
+ for (; it != m_controlList.end(); ++it)
+ {
+ if (index == i)
+ {
+ m_controlList.erase(it);
+ return true;
+ }
+ i++;
+ }
+
+ return false;
+}
+
+bool
+MidiDevice::modifyControlParameter(const ControlParameter &con, int index)
+{
+ if (index < 0 || index > (int)m_controlList.size()) return false;
+ m_controlList[index] = con;
+ return true;
+}
+
+void
+MidiDevice::replaceControlParameters(const ControlList &con)
+{
+ m_controlList = con;
+}
+
+
+// Check to see if passed ControlParameter is unique. Either the
+// type must be unique or in the case of Controller::EventType the
+// ControllerValue must be unique.
+//
+// Controllers (Control type)
+//
+//
+bool
+MidiDevice::isUniqueControlParameter(const ControlParameter &con) const
+{
+ ControlList::const_iterator it = m_controlList.begin();
+
+ for (; it != m_controlList.end(); ++it)
+ {
+ if (it->getType() == con.getType())
+ {
+ if (it->getType() == Rosegarden::Controller::EventType &&
+ it->getControllerValue() != con.getControllerValue())
+ continue;
+
+ return false;
+ }
+
+ }
+
+ return true;
+}
+
+// Cheat a bit here and remove the VOLUME controller here - just
+// so that the MIDIMixer is made a bit easier.
+//
+ControlList
+MidiDevice::getIPBControlParameters() const
+{
+ ControlList retList;
+
+ Rosegarden::MidiByte MIDI_CONTROLLER_VOLUME = 0x07;
+
+ for (ControlList::const_iterator it = m_controlList.begin();
+ it != m_controlList.end(); ++it)
+ {
+ if (it->getIPBPosition() != -1 &&
+ it->getControllerValue() != MIDI_CONTROLLER_VOLUME)
+ retList.push_back(*it);
+ }
+
+ return retList;
+}
+
+
+
+
+ControlParameter *
+MidiDevice::getControlParameter(int index)
+{
+ if (index >= 0 && ((unsigned int)index) < m_controlList.size())
+ return &m_controlList[index];
+
+ return 0;
+}
+
+const ControlParameter *
+MidiDevice::getControlParameter(int index) const
+{
+ return ((MidiDevice *)this)->getControlParameter(index);
+}
+
+ControlParameter *
+MidiDevice::getControlParameter(const std::string &type, Rosegarden::MidiByte controllerValue)
+{
+ ControlList::iterator it = m_controlList.begin();
+
+ for (; it != m_controlList.end(); ++it)
+ {
+ if (it->getType() == type)
+ {
+ // Return matched on type for most events
+ //
+ if (type != Rosegarden::Controller::EventType)
+ return &*it;
+
+ // Also match controller value for Controller events
+ //
+ if (it->getControllerValue() == controllerValue)
+ return &*it;
+ }
+ }
+
+ return 0;
+}
+
+const ControlParameter *
+MidiDevice::getControlParameter(const std::string &type, Rosegarden::MidiByte controllerValue) const
+{
+ return ((MidiDevice *)this)->getControlParameter(type, controllerValue);
+}
+
+}
+
+
diff --git a/src/base/MidiDevice.h b/src/base/MidiDevice.h
new file mode 100644
index 0000000..0a3c17f
--- /dev/null
+++ b/src/base/MidiDevice.h
@@ -0,0 +1,213 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _MIDIDEVICE_H_
+#define _MIDIDEVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "Device.h"
+#include "Instrument.h"
+#include "MidiProgram.h"
+#include "ControlParameter.h"
+#include "Controllable.h"
+
+namespace Rosegarden
+{
+
+typedef std::vector<std::string> StringList;
+typedef std::vector<MidiByte> MidiByteList;
+
+class MidiDevice : public Device, public Controllable
+{
+public:
+ typedef enum
+ {
+ Play = 0,
+ Record = 1
+ } DeviceDirection;
+
+ typedef enum
+ {
+ NoVariations,
+ VariationFromLSB,
+ VariationFromMSB
+ } VariationType;
+
+ MidiDevice();
+ MidiDevice(const MidiDevice &);
+ MidiDevice(DeviceId id,
+ const MidiDevice &);
+ MidiDevice(DeviceId id,
+ const std::string &name,
+ DeviceDirection dir);
+ MidiDevice(DeviceId id,
+ const std::string &name,
+ const std::string &label,
+ DeviceDirection dir);
+ virtual ~MidiDevice();
+
+ // Assignment
+ MidiDevice &operator=(const MidiDevice &);
+
+ // Instrument must be on heap; I take ownership of it
+ virtual void addInstrument(Instrument*);
+
+ void removeMetronome();
+ void setMetronome(const MidiMetronome &);
+ const MidiMetronome* getMetronome() const { return m_metronome; }
+
+ void addProgram(const MidiProgram &program);
+ void addBank(const MidiBank &bank);
+ void addKeyMapping(const MidiKeyMapping &mapping); // I own the result!
+
+ void clearBankList();
+ void clearProgramList();
+ void clearControlList();
+
+ const BankList &getBanks() const { return m_bankList; }
+ BankList getBanks(bool percussion) const;
+ BankList getBanksByMSB(bool percussion, MidiByte msb) const;
+ BankList getBanksByLSB(bool percussion, MidiByte lsb) const;
+
+ MidiByteList getDistinctMSBs(bool percussion, int lsb = -1) const;
+ MidiByteList getDistinctLSBs(bool percussion, int msb = -1) const;
+
+ const ProgramList &getPrograms() const { return m_programList; }
+ ProgramList getPrograms(const MidiBank &bank) const;
+
+ const KeyMappingList &getKeyMappings() const { return m_keyMappingList; }
+ const MidiKeyMapping *getKeyMappingByName(const std::string &) const;
+ const MidiKeyMapping *getKeyMappingForProgram(const MidiProgram &program) const;
+ void setKeyMappingForProgram(const MidiProgram &program, std::string mapping);
+
+ std::string getBankName(const MidiBank &bank) const;
+ std::string getProgramName(const MidiProgram &program) const;
+
+ void replaceBankList(const BankList &bank);
+ void replaceProgramList(const ProgramList &program);
+ void replaceKeyMappingList(const KeyMappingList &mappings);
+
+ void mergeBankList(const BankList &bank);
+ void mergeProgramList(const ProgramList &program);
+ void mergeKeyMappingList(const KeyMappingList &mappings);
+
+ virtual InstrumentList getAllInstruments() const;
+ virtual InstrumentList getPresentationInstruments() const;
+
+ // Retrieve Librarian details
+ //
+ const std::string getLibrarianName() const { return m_librarian.first; }
+ const std::string getLibrarianEmail() const { return m_librarian.second; }
+ std::pair<std::string, std::string> getLibrarian() const
+ { return m_librarian; }
+
+ // Set Librarian details
+ //
+ void setLibrarian(const std::string &name, const std::string &email)
+ { m_librarian = std::pair<std::string, std::string>(name, email); }
+
+ DeviceDirection getDirection() const { return m_direction; }
+ void setDirection(DeviceDirection dir) { m_direction = dir; }
+
+ VariationType getVariationType() const { return m_variationType; }
+ void setVariationType(VariationType v) { m_variationType = v; }
+
+ // Controllers - for mapping Controller names to values for use in
+ // the InstrumentParameterBoxes (IPBs) and Control rulers.
+ //
+ ControlList::const_iterator beginControllers() const
+ { return m_controlList.begin(); }
+ ControlList::const_iterator endControllers() const
+ { return m_controlList.end(); }
+
+ // implemented from Controllable interface
+ //
+ virtual const ControlList &getControlParameters() const { return m_controlList; }
+
+ // Only those on the IPB list
+ //
+ ControlList getIPBControlParameters() const;
+
+ // Access ControlParameters (read/write)
+ //
+ virtual ControlParameter *getControlParameter(int index);
+ virtual const ControlParameter *getControlParameter(int index) const;
+ virtual ControlParameter *getControlParameter(const std::string &type, MidiByte controllerNumber);
+ virtual const ControlParameter *getControlParameter(const std::string &type, MidiByte controllerNumber) const;
+
+ // Modify ControlParameters
+ //
+ void addControlParameter(const ControlParameter &con);
+ void addControlParameter(const ControlParameter &con, int index);
+ bool removeControlParameter(int index);
+ bool modifyControlParameter(const ControlParameter &con, int index);
+
+ void replaceControlParameters(const ControlList &);
+
+ // Check to see if the passed ControlParameter is unique in
+ // our ControlParameter list.
+ //
+ bool isUniqueControlParameter(const ControlParameter &con) const;
+
+ // Generate some default controllers for the MidiDevice
+ //
+ void generateDefaultControllers();
+
+ virtual std::string toXmlString();
+
+ // Accessors for recording property
+ bool isRecording() {return m_recording; }
+ void setRecording(bool recording) {m_recording = recording;}
+
+protected:
+ void generatePresentationList();
+
+ ProgramList m_programList;
+ BankList m_bankList;
+ ControlList m_controlList;
+ KeyMappingList m_keyMappingList;
+ MidiMetronome *m_metronome;
+
+ // used when we're presenting the instruments
+ InstrumentList m_presentationInstrumentList;
+
+ // Is this device Play or Record?
+ //
+ DeviceDirection m_direction;
+
+ // Is this device recording?
+ //
+ bool m_recording;
+
+ // Should we present LSB or MSB of bank info as a Variation number?
+ //
+ VariationType m_variationType;
+
+ // Librarian contact details
+ //
+ std::pair<std::string, std::string> m_librarian; // name. email
+};
+
+}
+
+#endif // _MIDIDEVICE_H_
diff --git a/src/base/MidiProgram.cpp b/src/base/MidiProgram.cpp
new file mode 100644
index 0000000..c026a0a
--- /dev/null
+++ b/src/base/MidiProgram.cpp
@@ -0,0 +1,224 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "MidiProgram.h"
+
+namespace Rosegarden {
+
+MidiBank::MidiBank() :
+ m_percussion(false), m_msb(0), m_lsb(0), m_name()
+{
+ // nothing else
+}
+
+MidiBank::MidiBank(bool percussion, MidiByte msb, MidiByte lsb, std::string name) :
+ m_percussion(percussion), m_msb(msb), m_lsb(lsb), m_name(name)
+{
+ // nothing else
+}
+
+bool
+MidiBank::operator==(const MidiBank &b) const
+{
+ return m_percussion == b.m_percussion && m_msb == b.m_msb && m_lsb == b.m_lsb;
+}
+
+bool
+MidiBank::isPercussion() const
+{
+ return m_percussion;
+}
+
+MidiByte
+MidiBank::getMSB() const
+{
+ return m_msb;
+}
+
+MidiByte
+MidiBank::getLSB() const
+{
+ return m_lsb;
+}
+
+std::string
+MidiBank::getName() const
+{
+ return m_name;
+}
+
+void
+MidiBank::setName(std::string name)
+{
+ m_name = name;
+}
+
+
+MidiProgram::MidiProgram() :
+ m_bank(), m_program(0), m_name()
+{
+ // nothing else
+}
+
+MidiProgram::MidiProgram(const MidiBank &bank, MidiByte program, std::string name, std::string keyMapping) :
+ m_bank(bank), m_program(program), m_name(name), m_keyMapping(keyMapping)
+{
+ // nothing else
+}
+
+bool
+MidiProgram::operator==(const MidiProgram &p) const
+{
+ return m_bank == p.m_bank && m_program == p.m_program;
+}
+
+const MidiBank &
+MidiProgram::getBank() const
+{
+ return m_bank;
+}
+
+MidiByte
+MidiProgram::getProgram() const
+{
+ return m_program;
+}
+
+const std::string &
+MidiProgram::getName() const
+{
+ return m_name;
+}
+
+void
+MidiProgram::setName(const std::string &name)
+{
+ m_name = name;
+}
+
+const std::string &
+MidiProgram::getKeyMapping() const
+{
+ return m_keyMapping;
+}
+
+void
+MidiProgram::setKeyMapping(const std::string &keyMapping)
+{
+ m_keyMapping = keyMapping;
+}
+
+MidiKeyMapping::MidiKeyMapping() :
+ m_name("")
+{
+}
+
+MidiKeyMapping::MidiKeyMapping(const std::string &name) :
+ m_name(name)
+{
+ // nothing else
+}
+
+MidiKeyMapping::MidiKeyMapping(const std::string &name, const KeyNameMap &map) :
+ m_name(name),
+ m_map(map)
+{
+ // nothing else
+}
+
+bool
+MidiKeyMapping::operator==(const MidiKeyMapping &m) const
+{
+ return (m_map == m.m_map);
+}
+
+std::string
+MidiKeyMapping::getMapForKeyName(MidiByte pitch) const
+{
+ KeyNameMap::const_iterator i = m_map.find(pitch);
+ if (i != m_map.end()) {
+ return i->second;
+ } else {
+ return "";
+ }
+}
+
+int
+MidiKeyMapping::getOffset(MidiByte pitch) const
+{
+ int c;
+ for (KeyNameMap::const_iterator i = m_map.begin(); i != m_map.end(); ++i) {
+ if (i->first == pitch) return c;
+ ++c;
+ }
+ return -1;
+}
+
+int
+MidiKeyMapping::getPitchForOffset(int offset) const
+{
+ KeyNameMap::const_iterator i = m_map.begin();
+ while (i != m_map.end() && offset > 0) {
+ ++i; --offset;
+ }
+ if (i == m_map.end()) return -1;
+ else return i->first;
+}
+
+int
+MidiKeyMapping::getPitchExtent() const
+{
+ int minPitch = 0, maxPitch = 0;
+ KeyNameMap::const_iterator mi = m_map.begin();
+ if (mi != m_map.end()) {
+ minPitch = mi->first;
+ mi = m_map.end();
+ --mi;
+ maxPitch = mi->first;
+ return maxPitch - minPitch + 1;
+ }
+ return maxPitch - minPitch;
+}
+
+
+
+MidiMetronome::MidiMetronome(InstrumentId instrument,
+ MidiByte barPitch,
+ MidiByte beatPitch,
+ MidiByte subBeatPitch,
+ int depth,
+ MidiByte barVely,
+ MidiByte beatVely,
+ MidiByte subBeatVely):
+ m_instrument(instrument),
+ m_barPitch(barPitch),
+ m_beatPitch(beatPitch),
+ m_subBeatPitch(subBeatPitch),
+ m_depth(depth),
+ m_barVelocity(barVely),
+ m_beatVelocity(beatVely),
+ m_subBeatVelocity(subBeatVely)
+{
+ // nothing else
+}
+
+}
+
diff --git a/src/base/MidiProgram.h b/src/base/MidiProgram.h
new file mode 100644
index 0000000..e44f631
--- /dev/null
+++ b/src/base/MidiProgram.h
@@ -0,0 +1,180 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _MIDIBANK_H_
+#define _MIDIBANK_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+namespace Rosegarden
+{
+typedef unsigned char MidiByte;
+typedef unsigned int InstrumentId;
+
+class MidiBank
+{
+public:
+ MidiBank();
+ MidiBank(bool percussion, MidiByte msb, MidiByte lsb, std::string name = "");
+
+ // comparator disregards name
+ bool operator==(const MidiBank &b) const;
+
+ bool isPercussion() const;
+ MidiByte getMSB() const;
+ MidiByte getLSB() const;
+ std::string getName() const;
+
+ void setName(std::string name);
+
+private:
+ bool m_percussion;
+ MidiByte m_msb;
+ MidiByte m_lsb;
+ std::string m_name;
+};
+
+typedef std::vector<MidiBank> BankList;
+
+class MidiProgram
+{
+public:
+ MidiProgram();
+ MidiProgram(const MidiBank &bank, MidiByte program, std::string name = "",
+ std::string keyMapping = "");
+
+ // comparator disregards name
+ bool operator==(const MidiProgram &p) const;
+
+ const MidiBank& getBank() const;
+ MidiByte getProgram() const;
+ const std::string &getName() const;
+ const std::string &getKeyMapping() const;
+
+ void setName(const std::string &name);
+ void setKeyMapping(const std::string &name);
+
+private:
+ MidiBank m_bank;
+ MidiByte m_program;
+ std::string m_name;
+ std::string m_keyMapping;
+};
+
+typedef std::vector<MidiProgram> ProgramList;
+
+class MidiKeyMapping
+{
+public:
+ typedef std::map<MidiByte, std::string> KeyNameMap;
+
+ MidiKeyMapping();
+ MidiKeyMapping(const std::string &name);
+ MidiKeyMapping(const std::string &name, const KeyNameMap &map);
+
+ bool operator==(const MidiKeyMapping &m) const;
+
+ const std::string &getName() const { return m_name; }
+ void setName(const std::string &name) { m_name = name; }
+
+ const KeyNameMap &getMap() const { return m_map; }
+ KeyNameMap &getMap() { return m_map; }
+ std::string getMapForKeyName(MidiByte pitch) const;
+ void setMap(const KeyNameMap &map) { m_map = map; }
+
+ // Return 0 if the supplied argument is the lowest pitch in the
+ // mapping, 1 if it is the second-lowest, etc. Return -1 if it
+ // is not in the mapping at all. Not instant.
+ int getOffset(MidiByte pitch) const;
+
+ // Return the offset'th pitch in the mapping. Return -1 if there
+ // are fewer than offset pitches in the mapping (or offset < 0).
+ // Not instant.
+ int getPitchForOffset(int offset) const;
+
+ // Return the difference between the top and bottom pitches
+ // contained in the map.
+ //
+ int getPitchExtent() const;
+
+private:
+ std::string m_name;
+ KeyNameMap m_map;
+};
+
+typedef std::vector<MidiKeyMapping> KeyMappingList;
+
+// A mapped MIDI instrument - a drum track click for example
+//
+class MidiMetronome
+{
+public:
+ MidiMetronome(InstrumentId instrument,
+ MidiByte barPitch = 37,
+ MidiByte beatPitch = 37,
+ MidiByte subBeatPitch = 37,
+ int depth = 2,
+ MidiByte barVely = 120,
+ MidiByte beatVely = 100,
+ MidiByte subBeatVely = 80);
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ MidiByte getBarPitch() const { return m_barPitch; }
+ MidiByte getBeatPitch() const { return m_beatPitch; }
+ MidiByte getSubBeatPitch() const { return m_subBeatPitch; }
+ int getDepth() const { return m_depth; }
+ MidiByte getBarVelocity() const { return m_barVelocity; }
+ MidiByte getBeatVelocity() const { return m_beatVelocity; }
+ MidiByte getSubBeatVelocity() const { return m_subBeatVelocity; }
+
+ void setInstrument(InstrumentId id) { m_instrument = id; }
+ void setBarPitch(MidiByte pitch) { m_barPitch = pitch; }
+ void setBeatPitch(MidiByte pitch) { m_beatPitch = pitch; }
+ void setSubBeatPitch(MidiByte pitch) { m_subBeatPitch = pitch; }
+ void setDepth(int depth) { m_depth = depth; }
+ void setBarVelocity(MidiByte barVely) { m_barVelocity = barVely; }
+ void setBeatVelocity(MidiByte beatVely) { m_beatVelocity = beatVely; }
+ void setSubBeatVelocity(MidiByte subBeatVely) { m_subBeatVelocity = subBeatVely; }
+
+private:
+ InstrumentId m_instrument;
+ MidiByte m_barPitch;
+ MidiByte m_beatPitch;
+ MidiByte m_subBeatPitch;
+ int m_depth;
+ MidiByte m_barVelocity;
+ MidiByte m_beatVelocity;
+ MidiByte m_subBeatVelocity;
+};
+
+
+// MidiFilter is a bitmask of MappedEvent::MappedEventType.
+// Look in sound/MappedEvent.h
+//
+typedef unsigned int MidiFilter;
+
+
+}
+
+#endif
+
diff --git a/src/base/MidiTypes.cpp b/src/base/MidiTypes.cpp
new file mode 100644
index 0000000..4118502
--- /dev/null
+++ b/src/base/MidiTypes.cpp
@@ -0,0 +1,320 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "MidiTypes.h"
+
+namespace Rosegarden
+{
+
+static MidiByte getByte(const Event &e, const PropertyName &name) {
+ long value = -1;
+ try {
+ value = e.get<Int>(name);
+ } catch (...) { }
+ if (value < 0 || value > 255) throw MIDIValueOutOfRange(name.getName());
+ return MidiByte(value);
+}
+
+//////////////////////////////////////////////////////////////////////
+// PitchBend
+//////////////////////////////////////////////////////////////////////
+
+const std::string PitchBend::EventType = "pitchbend";
+const int PitchBend::EventSubOrdering = -5;
+
+const PropertyName PitchBend::MSB = "msb";
+const PropertyName PitchBend::LSB = "lsb";
+
+PitchBend::PitchBend(Rosegarden::MidiByte msb,
+ Rosegarden::MidiByte lsb) :
+ m_msb(msb),
+ m_lsb(lsb)
+{
+}
+
+PitchBend::PitchBend(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("PitchBend model event", EventType, e.getType());
+ }
+ m_msb = getByte(e, MSB);
+ m_lsb = getByte(e, LSB);
+}
+
+PitchBend::~PitchBend()
+{
+}
+
+Event*
+PitchBend::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<Int>(MSB, (long)m_msb);
+ e->set<Int>(LSB, (long)m_lsb);
+ return e;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// Controller
+//////////////////////////////////////////////////////////////////////
+
+const std::string Controller::EventType = "controller";
+const int Controller::EventSubOrdering = -5;
+
+const PropertyName Controller::NUMBER = "number";
+const PropertyName Controller::VALUE = "value";
+
+Controller::Controller(Rosegarden::MidiByte number,
+ Rosegarden::MidiByte value):
+ m_number(number),
+ m_value(value)
+{
+}
+
+Controller::Controller(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("Controller model event", EventType, e.getType());
+ }
+ m_number = getByte(e, NUMBER);
+ m_value = getByte(e, VALUE);
+}
+
+Controller::~Controller()
+{
+}
+
+Event*
+Controller::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<Int>(NUMBER, (long)m_number);
+ e->set<Int>(VALUE, (long)m_value);
+ return e;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// Key Pressure
+//////////////////////////////////////////////////////////////////////
+
+const std::string KeyPressure::EventType = "keypressure";
+const int KeyPressure::EventSubOrdering = -5;
+
+const PropertyName KeyPressure::PITCH = "pitch";
+const PropertyName KeyPressure::PRESSURE = "pressure";
+
+KeyPressure::KeyPressure(Rosegarden::MidiByte pitch,
+ Rosegarden::MidiByte pressure):
+ m_pitch(pitch),
+ m_pressure(pressure)
+{
+}
+
+KeyPressure::KeyPressure(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("KeyPressure model event", EventType, e.getType());
+ }
+ m_pitch = getByte(e, PITCH);
+ m_pressure = getByte(e, PRESSURE);
+}
+
+KeyPressure::~KeyPressure()
+{
+}
+
+Event*
+KeyPressure::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<Int>(PITCH, (long)m_pitch);
+ e->set<Int>(PRESSURE, (long)m_pressure);
+ return e;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// Channel Pressure
+//////////////////////////////////////////////////////////////////////
+
+const std::string ChannelPressure::EventType = "channelpressure";
+const int ChannelPressure::EventSubOrdering = -5;
+
+const PropertyName ChannelPressure::PRESSURE = "pressure";
+
+ChannelPressure::ChannelPressure(Rosegarden::MidiByte pressure):
+ m_pressure(pressure)
+{
+}
+
+ChannelPressure::ChannelPressure(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("ChannelPressure model event", EventType, e.getType());
+ }
+ m_pressure = getByte(e, PRESSURE);
+}
+
+ChannelPressure::~ChannelPressure()
+{
+}
+
+Event*
+ChannelPressure::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<Int>(PRESSURE, (long)m_pressure);
+ return e;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// ProgramChange
+//////////////////////////////////////////////////////////////////////
+
+const std::string ProgramChange::EventType = "programchange";
+const int ProgramChange::EventSubOrdering = -5;
+
+const PropertyName ProgramChange::PROGRAM = "program";
+
+ProgramChange::ProgramChange(Rosegarden::MidiByte program):
+ m_program(program)
+{
+}
+
+ProgramChange::ProgramChange(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("ProgramChange model event", EventType, e.getType());
+ }
+ m_program = getByte(e, PROGRAM);
+}
+
+ProgramChange::~ProgramChange()
+{
+}
+
+Event*
+ProgramChange::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<Int>(PROGRAM, (long)m_program);
+ return e;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// SystemExclusive
+//////////////////////////////////////////////////////////////////////
+
+const std::string SystemExclusive::EventType = "systemexclusive";
+const int SystemExclusive::EventSubOrdering = -5;
+
+const PropertyName SystemExclusive::DATABLOCK = "datablock";
+
+SystemExclusive::SystemExclusive(std::string rawData) :
+ m_rawData(rawData)
+{
+}
+
+SystemExclusive::SystemExclusive(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("SystemExclusive model event", EventType, e.getType());
+ }
+ std::string datablock;
+ e.get<String>(DATABLOCK, datablock);
+ m_rawData = toRaw(datablock);
+}
+
+SystemExclusive::~SystemExclusive()
+{
+}
+
+Event*
+SystemExclusive::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ std::string hex(toHex(m_rawData));
+ e->set<String>(DATABLOCK, hex);
+ return e;
+}
+
+std::string
+SystemExclusive::toHex(std::string r)
+{
+ static char hexchars[] = "0123456789ABCDEF";
+ std::string h;
+ for (unsigned int i = 0; i < r.size(); ++i) {
+ if (i > 0) h += ' ';
+ unsigned char b = (unsigned char)r[i];
+ h += hexchars[(b / 16) % 16];
+ h += hexchars[b % 16];
+ }
+ return h;
+}
+
+std::string
+SystemExclusive::toRaw(std::string rh)
+{
+ std::string r;
+ std::string h;
+
+ // remove whitespace
+ for (unsigned int i = 0; i < rh.size(); ++i) {
+ if (!isspace(rh[i])) h += rh[i];
+ }
+
+ for (unsigned int i = 0; i < h.size()/2; ++i) {
+ unsigned char b = toRawNibble(h[2*i]) * 16 + toRawNibble(h[2*i+1]);
+ r += b;
+ }
+
+ return r;
+}
+
+unsigned char
+SystemExclusive::toRawNibble(char c)
+{
+ if (islower(c)) c = toupper(c);
+ if (isdigit(c)) return c - '0';
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ throw BadEncoding();
+}
+
+bool
+SystemExclusive::isHex(std::string rh)
+{
+ // arf
+ try {
+ std::string r = toRaw(rh);
+ } catch (BadEncoding) {
+ return false;
+ }
+ return true;
+}
+
+
+}
+
diff --git a/src/base/MidiTypes.h b/src/base/MidiTypes.h
new file mode 100644
index 0000000..10416a9
--- /dev/null
+++ b/src/base/MidiTypes.h
@@ -0,0 +1,224 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _MIDI_TYPES_H_
+#define _MIDI_TYPES_H_
+
+#include <list>
+
+#include "Event.h"
+#include "Instrument.h"
+
+// Internal representation of some very MIDI specific event types
+// that fall clearly outside of NotationTypes and still require
+// representation.
+//
+
+
+namespace Rosegarden
+{
+
+class MIDIValueOutOfRange : public Exception {
+public:
+ MIDIValueOutOfRange(std::string name) :
+ Exception("Value of " + name + " out of byte range") { }
+ MIDIValueOutOfRange(std::string name, std::string file, int line) :
+ Exception("Value of " + name + " out of byte range", file, line) { }
+};
+
+
+// Rosegarden's internal represetation of MIDI PitchBend
+//
+class PitchBend
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+
+ static const PropertyName MSB;
+ static const PropertyName LSB;
+
+ PitchBend(MidiByte msb, MidiByte lsb);
+ PitchBend(const Event &);
+ ~PitchBend();
+
+ MidiByte getMSB() const { return m_msb; }
+ MidiByte getLSB() const { return m_lsb; }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ MidiByte m_msb;
+ MidiByte m_lsb;
+};
+
+
+// Controller
+//
+
+class Controller
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+
+ static const PropertyName NUMBER; // controller number
+ static const PropertyName VALUE; // and value
+
+ Controller(MidiByte number,
+ MidiByte value);
+
+ Controller(const Event &);
+ ~Controller();
+
+ MidiByte getNumber() const { return m_number; }
+ MidiByte getValue() const { return m_value; }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ MidiByte m_number;
+ MidiByte m_value;
+
+};
+
+
+// Key pressure
+//
+
+class KeyPressure
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+
+ static const PropertyName PITCH;
+ static const PropertyName PRESSURE;
+
+ KeyPressure(MidiByte pitch, MidiByte pressure);
+ KeyPressure(const Event &event);
+ ~KeyPressure();
+
+ MidiByte getPitch() const { return m_pitch; }
+ MidiByte getPressure() const { return m_pressure; }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ MidiByte m_pitch;
+ MidiByte m_pressure;
+};
+
+
+// Channel pressure
+//
+
+class ChannelPressure
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+
+ static const PropertyName PRESSURE;
+
+ ChannelPressure(MidiByte pressure);
+ ChannelPressure(const Event &event);
+ ~ChannelPressure();
+
+ MidiByte getPressure() const { return m_pressure; }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ MidiByte m_pressure;
+};
+
+
+// Program Change
+//
+
+class ProgramChange
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+
+ static const PropertyName PROGRAM;
+
+ ProgramChange(MidiByte program);
+ ProgramChange(const Event &event);
+ ~ProgramChange();
+
+ MidiByte getProgram() const { return m_program; }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ MidiByte m_program;
+};
+
+
+// System exclusive
+//
+
+class SystemExclusive
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+
+ struct BadEncoding : public Exception {
+ BadEncoding() : Exception("Bad SysEx encoding") { }
+ };
+
+ static const PropertyName DATABLOCK;
+
+ SystemExclusive(std::string rawData);
+ SystemExclusive(const Event &event);
+ ~SystemExclusive();
+
+ std::string getRawData() const { return m_rawData; }
+ std::string getHexData() const { return toHex(m_rawData); }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+ static std::string toHex(std::string rawData);
+ static std::string toRaw(std::string hexData);
+ static bool isHex(std::string data);
+
+private:
+ std::string m_rawData;
+ static unsigned char toRawNibble(char);
+};
+
+
+
+}
+
+
+#endif
diff --git a/src/base/NotationQuantizer.cpp b/src/base/NotationQuantizer.cpp
new file mode 100644
index 0000000..9e76a94
--- /dev/null
+++ b/src/base/NotationQuantizer.cpp
@@ -0,0 +1,1205 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "NotationQuantizer.h"
+#include "BaseProperties.h"
+#include "NotationTypes.h"
+#include "Selection.h"
+#include "Composition.h"
+#include "Sets.h"
+#include "Profiler.h"
+
+#include <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_NOTATION_QUANTIZER 1
+
+namespace Rosegarden {
+
+using namespace BaseProperties;
+
+class NotationQuantizer::Impl
+{
+public:
+ Impl(NotationQuantizer *const q) :
+ m_unit(Note(Note::Demisemiquaver).getDuration()),
+ m_simplicityFactor(13),
+ m_maxTuplet(3),
+ m_articulate(true),
+ m_q(q),
+ m_provisionalBase("notationquantizer-provisionalBase"),
+ m_provisionalAbsTime("notationquantizer-provisionalAbsTime"),
+ m_provisionalDuration("notationquantizer-provisionalDuration"),
+ m_provisionalNoteType("notationquantizer-provisionalNoteType"),
+ m_provisionalScore("notationquantizer-provisionalScore")
+ { }
+
+ Impl(const Impl &i) :
+ m_unit(i.m_unit),
+ m_simplicityFactor(i.m_simplicityFactor),
+ m_maxTuplet(i.m_maxTuplet),
+ m_articulate(i.m_articulate),
+ m_q(i.m_q),
+ m_provisionalBase(i.m_provisionalBase),
+ m_provisionalAbsTime(i.m_provisionalAbsTime),
+ m_provisionalDuration(i.m_provisionalDuration),
+ m_provisionalNoteType(i.m_provisionalNoteType),
+ m_provisionalScore(i.m_provisionalScore)
+ { }
+
+ class ProvisionalQuantizer : public Quantizer {
+ // This class exists only to pick out the provisional abstime
+ // and duration values from half-quantized events, so that we
+ // can treat them using the normal Chord class
+ public:
+ ProvisionalQuantizer(Impl *i) : Quantizer("blah", "blahblah"), m_impl(i) { }
+ virtual timeT getQuantizedDuration(const Event *e) const {
+ return m_impl->getProvisional((Event *)e, DurationValue);
+ }
+ virtual timeT getQuantizedAbsoluteTime(const Event *e) const {
+ timeT t = m_impl->getProvisional((Event *)e, AbsoluteTimeValue);
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "ProvisionalQuantizer::getQuantizedAbsoluteTime: returning " << t << endl;
+#endif
+ return t;
+ }
+
+ private:
+ Impl *m_impl;
+ };
+
+ void quantizeRange(Segment *,
+ Segment::iterator,
+ Segment::iterator) const;
+
+ void quantizeAbsoluteTime(Segment *, Segment::iterator) const;
+ long scoreAbsoluteTimeForBase(Segment *, const Segment::iterator &,
+ int depth, timeT base, timeT sigTime,
+ timeT t, timeT d, int noteType,
+ const Segment::iterator &,
+ const Segment::iterator &,
+ bool &right) const;
+ void quantizeDurationProvisional(Segment *, Segment::iterator) const;
+ void quantizeDuration(Segment *, Chord &) const;
+
+ void scanTupletsInBar(Segment *,
+ timeT barStart, timeT barDuration,
+ timeT wholeStart, timeT wholeDuration,
+ const std::vector<int> &divisions) const;
+ void scanTupletsAt(Segment *, Segment::iterator, int depth,
+ timeT base, timeT barStart,
+ timeT tupletStart, timeT tupletBase) const;
+ bool isValidTupletAt(Segment *, const Segment::iterator &,
+ int depth, timeT base, timeT sigTime,
+ timeT tupletBase) const;
+
+ void setProvisional(Event *, ValueType value, timeT t) const;
+ timeT getProvisional(Event *, ValueType value) const;
+ void unsetProvisionalProperties(Event *) const;
+
+ timeT m_unit;
+ int m_simplicityFactor;
+ int m_maxTuplet;
+ bool m_articulate;
+ bool m_contrapuntal;
+
+private:
+ NotationQuantizer *const m_q;
+
+ PropertyName m_provisionalBase;
+ PropertyName m_provisionalAbsTime;
+ PropertyName m_provisionalDuration;
+ PropertyName m_provisionalNoteType;
+ PropertyName m_provisionalScore;
+};
+
+NotationQuantizer::NotationQuantizer() :
+ Quantizer(NotationPrefix),
+ m_impl(new Impl(this))
+{
+ // nothing else
+}
+
+NotationQuantizer::NotationQuantizer(std::string source, std::string target) :
+ Quantizer(source, target),
+ m_impl(new Impl(this))
+{
+ // nothing else
+}
+
+NotationQuantizer::NotationQuantizer(const NotationQuantizer &q) :
+ Quantizer(q.m_target),
+ m_impl(new Impl(*q.m_impl))
+{
+ // nothing else
+}
+
+NotationQuantizer::~NotationQuantizer()
+{
+ delete m_impl;
+}
+
+void
+NotationQuantizer::setUnit(timeT unit)
+{
+ m_impl->m_unit = unit;
+}
+
+timeT
+NotationQuantizer::getUnit() const
+{
+ return m_impl->m_unit;
+}
+
+void
+NotationQuantizer::setMaxTuplet(int m)
+{
+ m_impl->m_maxTuplet = m;
+}
+
+int
+NotationQuantizer::getMaxTuplet() const
+{
+ return m_impl->m_maxTuplet;
+}
+
+void
+NotationQuantizer::setSimplicityFactor(int s)
+{
+ m_impl->m_simplicityFactor = s;
+}
+
+int
+NotationQuantizer::getSimplicityFactor() const
+{
+ return m_impl->m_simplicityFactor;
+}
+
+void
+NotationQuantizer::setContrapuntal(bool c)
+{
+ m_impl->m_contrapuntal = c;
+}
+
+bool
+NotationQuantizer::getContrapuntal() const
+{
+ return m_impl->m_contrapuntal;
+}
+
+void
+NotationQuantizer::setArticulate(bool a)
+{
+ m_impl->m_articulate = a;
+}
+
+bool
+NotationQuantizer::getArticulate() const
+{
+ return m_impl->m_articulate;
+}
+
+void
+NotationQuantizer::Impl::setProvisional(Event *e, ValueType v, timeT t) const
+{
+ if (v == AbsoluteTimeValue) {
+ e->setMaybe<Int>(m_provisionalAbsTime, t);
+ } else {
+ e->setMaybe<Int>(m_provisionalDuration, t);
+ }
+}
+
+timeT
+NotationQuantizer::Impl::getProvisional(Event *e, ValueType v) const
+{
+ timeT t;
+ if (v == AbsoluteTimeValue) {
+ t = e->getAbsoluteTime();
+ e->get<Int>(m_provisionalAbsTime, t);
+ } else {
+ t = e->getDuration();
+ e->get<Int>(m_provisionalDuration, t);
+ }
+ return t;
+}
+
+void
+NotationQuantizer::Impl::unsetProvisionalProperties(Event *e) const
+{
+ e->unset(m_provisionalBase);
+ e->unset(m_provisionalAbsTime);
+ e->unset(m_provisionalDuration);
+ e->unset(m_provisionalNoteType);
+ e->unset(m_provisionalScore);
+}
+
+void
+NotationQuantizer::Impl::quantizeAbsoluteTime(Segment *s, Segment::iterator i) const
+{
+ Profiler profiler("NotationQuantizer::Impl::quantizeAbsoluteTime");
+
+ Composition *comp = s->getComposition();
+
+ TimeSignature timeSig;
+ timeT t = m_q->getFromSource(*i, AbsoluteTimeValue);
+ timeT sigTime = comp->getTimeSignatureAt(t, timeSig);
+
+ timeT d = getProvisional(*i, DurationValue);
+ int noteType = Note::getNearestNote(d).getNoteType();
+ (*i)->setMaybe<Int>(m_provisionalNoteType, noteType);
+
+ int maxDepth = 8 - noteType;
+ if (maxDepth < 4) maxDepth = 4;
+ std::vector<int> divisions;
+ timeSig.getDivisions(maxDepth, divisions);
+ if (timeSig == TimeSignature()) // special case for 4/4
+ divisions[0] = 2;
+
+ // At each depth of beat subdivision, we find the closest match
+ // and assign it a score according to distance and depth. The
+ // calculation for the score should accord "better" scores to
+ // shorter distance and lower depth, but it should avoid giving
+ // a "perfect" score to any combination of distance and depth
+ // except where both are 0. Also, the effective depth is
+ // 2 more than the value of our depth counter, which counts
+ // from 0 at a point where the effective depth is already 1.
+
+ timeT base = timeSig.getBarDuration();
+
+ timeT bestBase = -2;
+ long bestScore = 0;
+ bool bestRight = false;
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "quantizeAbsoluteTime: t is " << t << ", d is " << d << endl;
+#endif
+
+ // scoreAbsoluteTimeForBase wants to know the previous starting
+ // note (N) and the previous starting note that ends (roughly)
+ // before this one starts (N'). Much more efficient to calculate
+ // them once now before the loop.
+
+ static timeT shortTime = Note(Note::Shortest).getDuration();
+
+ Segment::iterator j(i);
+ Segment::iterator n(s->end()), nprime(s->end());
+ for (;;) {
+ if (j == s->begin()) break;
+ --j;
+ if ((*j)->isa(Note::EventType)) {
+ if (n == s->end()) n = j;
+ if ((*j)->getAbsoluteTime() + (*j)->getDuration() + shortTime/2
+ <= (*i)->getAbsoluteTime()) {
+ nprime = j;
+ break;
+ }
+ }
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ if (n != s->end() && n != nprime) {
+ cout << "found n (distinct from nprime) at " << (*n)->getAbsoluteTime() << endl;
+ }
+ if (nprime != s->end()) {
+ cout << "found nprime at " << (*nprime)->getAbsoluteTime()
+ << ", duration " << (*nprime)->getDuration() << endl;
+ }
+#endif
+
+ for (int depth = 0; depth < maxDepth; ++depth) {
+
+ base /= divisions[depth];
+ if (base < m_unit) break;
+ bool right = false;
+ long score = scoreAbsoluteTimeForBase(s, i, depth, base, sigTime,
+ t, d, noteType, n, nprime, right);
+
+ if (depth == 0 || score < bestScore) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << " [*]";
+#endif
+ bestBase = base;
+ bestScore = score;
+ bestRight = right;
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << endl;
+#endif
+ }
+
+ if (bestBase == -2) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "Quantizer::quantizeAbsoluteTime: weirdness: no snap found" << endl;
+#endif
+ } else {
+ // we need to snap relative to the time sig, not relative
+ // to the start of the whole composition
+ t -= sigTime;
+
+ t = (t / bestBase) * bestBase;
+ if (bestRight) t += bestBase;
+
+/*
+ timeT low = (t / bestBase) * bestBase;
+ timeT high = low + bestBase;
+ t = ((high - t > t - low) ? low : high);
+*/
+
+ t += sigTime;
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "snap base is " << bestBase << ", snapped to " << t << endl;
+#endif
+ }
+
+ setProvisional(*i, AbsoluteTimeValue, t);
+ (*i)->setMaybe<Int>(m_provisionalBase, bestBase);
+ (*i)->setMaybe<Int>(m_provisionalScore, bestScore);
+}
+
+long
+NotationQuantizer::Impl::scoreAbsoluteTimeForBase(Segment *s,
+ const Segment::iterator & /* i */,
+ int depth,
+ timeT base,
+ timeT sigTime,
+ timeT t,
+ timeT d,
+ int noteType,
+ const Segment::iterator &n,
+ const Segment::iterator &nprime,
+ bool &wantRight)
+ const
+{
+ Profiler profiler("NotationQuantizer::Impl::scoreAbsoluteTimeForBase");
+
+ // Lower score is better.
+
+ static timeT shortTime = Note(Note::Shortest).getDuration();
+
+ double simplicityFactor(m_simplicityFactor);
+ simplicityFactor -= Note::Crotchet - noteType;
+ if (simplicityFactor < 10) simplicityFactor = 10;
+
+ double effectiveDepth = pow(depth + 2, simplicityFactor / 10);
+
+ //!!! use velocity to adjust the effective depth as well? -- louder
+ // notes are more likely to be on big boundaries. Actually, perhaps
+ // introduce a generally-useful "salience" score a la Dixon et al
+
+ long leftScore = 0;
+
+ for (int ri = 0; ri < 2; ++ri) {
+
+ bool right = (ri == 1);
+
+ long distance = (t - sigTime) % base;
+ if (right) distance = base - distance;
+ long score = long((distance + shortTime / 2) * effectiveDepth);
+
+ double penalty1 = 1.0;
+
+ // seriously penalise moving a note beyond its own end time
+ if (d > 0 && right && distance >= d * 0.9) {
+ penalty1 = double(distance) / d + 0.5;
+ }
+
+ double penalty2 = 1.0;
+
+ // Examine the previous starting note (N), and the previous
+ // starting note that ends before this one starts (N').
+
+ // We should penalise moving this note to before the performed end
+ // of N' and seriously penalise moving it to the same quantized
+ // start time as N' -- but we should encourage moving it to the
+ // same time as the provisional end of N', or to the same start
+ // time as N if N != N'.
+
+ if (!right) {
+ if (n != s->end()) {
+ if (n != nprime) {
+ timeT nt = getProvisional(*n, AbsoluteTimeValue);
+ if (t - distance == nt) penalty2 = penalty2 * 2 / 3;
+ }
+ if (nprime != s->end()) {
+ timeT npt = getProvisional(*nprime, AbsoluteTimeValue);
+ timeT npd = getProvisional(*nprime, DurationValue);
+ if (t - distance <= npt) penalty2 *= 4;
+ else if (t - distance < npt + npd) penalty2 *= 2;
+ else if (t - distance == npt + npd) penalty2 = penalty2 * 2 / 3;
+ }
+ }
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << " depth/eff/dist/t/score/pen1/pen2/res: " << depth << "/" << effectiveDepth << "/" << distance << "/" << (right ? t + distance : t - distance) << "/" << score << "/" << penalty1 << "/" << penalty2 << "/" << (score * penalty1 * penalty2);
+ if (right) cout << " -> ";
+ else cout << " <- ";
+ if (ri == 0) cout << endl;
+#endif
+
+ score = long(score * penalty1);
+ score = long(score * penalty2);
+
+ if (ri == 0) {
+ leftScore = score;
+ } else {
+ if (score < leftScore) {
+ wantRight = true;
+ return score;
+ } else {
+ wantRight = false;
+ return leftScore;
+ }
+ }
+ }
+
+ return leftScore;
+}
+
+void
+NotationQuantizer::Impl::quantizeDurationProvisional(Segment *, Segment::iterator i)
+ const
+{
+ Profiler profiler("NotationQuantizer::Impl::quantizeDurationProvisional");
+
+ // Calculate a first guess at the likely notation duration based
+ // only on its performed duration, without considering start time.
+
+ timeT d = m_q->getFromSource(*i, DurationValue);
+ if (d == 0) {
+ setProvisional(*i, DurationValue, d);
+ return;
+ }
+
+ Note shortNote = Note::getNearestNote(d, 2);
+
+ timeT shortTime = shortNote.getDuration();
+ timeT time = shortTime;
+
+ if (shortTime != d) {
+
+ Note longNote(shortNote);
+
+ if ((shortNote.getDots() > 0 ||
+ shortNote.getNoteType() == Note::Shortest)) { // can't dot that
+
+ if (shortNote.getNoteType() < Note::Longest) {
+ longNote = Note(shortNote.getNoteType() + 1, 0);
+ }
+
+ } else {
+ longNote = Note(shortNote.getNoteType(), 1);
+ }
+
+ timeT longTime = longNote.getDuration();
+
+ // we should prefer to round up to a note with fewer dots rather
+ // than down to one with more
+
+ //!!! except in dotted time, etc -- we really want this to work on a
+ // similar attraction-to-grid basis to the abstime quantization
+
+ if ((longNote.getDots() + 1) * (longTime - d) <
+ (shortNote.getDots() + 1) * (d - shortTime)) {
+ time = longTime;
+ }
+ }
+
+ setProvisional(*i, DurationValue, time);
+
+ if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ // We're going to recalculate these, and use our own results
+ (*i)->unset(BEAMED_GROUP_ID);
+ (*i)->unset(BEAMED_GROUP_TYPE);
+ (*i)->unset(BEAMED_GROUP_TUPLET_BASE);
+ (*i)->unset(BEAMED_GROUP_TUPLED_COUNT);
+ (*i)->unset(BEAMED_GROUP_UNTUPLED_COUNT);
+//!!! (*i)->unset(TUPLET_NOMINAL_DURATION);
+ }
+}
+
+void
+NotationQuantizer::Impl::quantizeDuration(Segment *s, Chord &c) const
+{
+ static int totalFracCount = 0;
+ static float totalFrac = 0;
+
+ Profiler profiler("NotationQuantizer::Impl::quantizeDuration");
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "quantizeDuration: chord has " << c.size() << " notes" << endl;
+#endif
+
+ Composition *comp = s->getComposition();
+
+ TimeSignature timeSig;
+// timeT t = m_q->getFromSource(*c.getInitialElement(), AbsoluteTimeValue);
+// timeT sigTime = comp->getTimeSignatureAt(t, timeSig);
+
+ timeT d = getProvisional(*c.getInitialElement(), DurationValue);
+ int noteType = Note::getNearestNote(d).getNoteType();
+ int maxDepth = 8 - noteType;
+ if (maxDepth < 4) maxDepth = 4;
+ std::vector<int> divisions;
+ timeSig.getDivisions(maxDepth, divisions);
+
+ Segment::iterator nextNote = c.getNextNote();
+ timeT nextNoteTime =
+ (s->isBeforeEndMarker(nextNote) ?
+ getProvisional(*nextNote, AbsoluteTimeValue) :
+ s->getEndMarkerTime());
+
+ timeT nonContrapuntalDuration = 0;
+
+ for (Chord::iterator ci = c.begin(); ci != c.end(); ++ci) {
+
+ if (!(**ci)->isa(Note::EventType)) continue;
+ if ((**ci)->has(m_provisionalDuration) &&
+ (**ci)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ // dealt with already in tuplet code, we'd only mess it up here
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "not recalculating duration for tuplet" << endl;
+#endif
+ continue;
+ }
+
+ timeT ud = 0;
+
+ if (!m_contrapuntal) {
+ // if not contrapuntal, give all notes in chord equal duration
+ if (nonContrapuntalDuration > 0) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "setting duration trivially to " << nonContrapuntalDuration << endl;
+#endif
+ setProvisional(**ci, DurationValue, nonContrapuntalDuration);
+ continue;
+ } else {
+ // establish whose duration to use, then set it at the
+ // bottom after it's been quantized
+ Segment::iterator li = c.getLongestElement();
+ if (li != s->end()) ud = m_q->getFromSource(*li, DurationValue);
+ else ud = m_q->getFromSource(**ci, DurationValue);
+ }
+ } else {
+ ud = m_q->getFromSource(**ci, DurationValue);
+ }
+
+ timeT qt = getProvisional(**ci, AbsoluteTimeValue);
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "note at time " << (**ci)->getAbsoluteTime() << " (provisional time " << qt << ")" << endl;
+#endif
+
+ timeT base = timeSig.getBarDuration();
+ std::pair<timeT, timeT> bases;
+ for (int depth = 0; depth < maxDepth; ++depth) {
+ if (base >= ud) {
+ bases = std::pair<timeT, timeT>(base / divisions[depth], base);
+ }
+ base /= divisions[depth];
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "duration is " << ud << ", probably between "
+ << bases.first << " and " << bases.second << endl;
+#endif
+
+ timeT qd = getProvisional(**ci, DurationValue);
+
+ timeT spaceAvailable = nextNoteTime - qt;
+
+ if (spaceAvailable > 0) {
+ float frac = float(ud) / float(spaceAvailable);
+ totalFrac += frac;
+ totalFracCount += 1;
+ }
+
+ if (!m_contrapuntal && qd > spaceAvailable) {
+
+ qd = Note::getNearestNote(spaceAvailable).getDuration();
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "non-contrapuntal segment, rounded duration down to "
+ << qd << " (as only " << spaceAvailable << " available)"
+ << endl;
+#endif
+
+ } else {
+
+ //!!! Note longer than the longest note we have. Deal with
+ //this -- how? Quantize the end time? Split the note?
+ //(Prefer to do that in a separate phase later if requested.)
+ //Leave it as it is? (Yes, for now.)
+ if (bases.first == 0) return;
+
+ timeT absTimeBase = bases.first;
+ (**ci)->get<Int>(m_provisionalBase, absTimeBase);
+
+ spaceAvailable = std::min(spaceAvailable,
+ comp->getBarEndForTime(qt) - qt);
+
+ // We have a really good possibility of staccato if we have a
+ // note on a boundary whose base is double the note duration
+ // and there's nothing else until the next boundary and we're
+ // shorter than about a quaver (i.e. the base is a quaver or
+ // less)
+
+ if (qd*2 <= absTimeBase && (qd*8/3) >= absTimeBase &&
+ bases.second == absTimeBase) {
+
+ if (nextNoteTime >= qt + bases.second) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "We rounded to " << qd
+ << " but we're on " << absTimeBase << " absTimeBase"
+ << " and the next base is " << bases.second
+ << " and we have room for it, so"
+ << " rounding up again" << endl;
+#endif
+ qd = bases.second;
+ }
+
+ } else {
+
+ // Alternatively, if we rounded down but there's space to
+ // round up, consider doing so
+
+ //!!! mark staccato if necessary, and take existing marks into account
+
+ Note note(Note::getNearestNote(qd));
+
+ if (qd < ud || (qd == ud && note.getDots() == 2)) {
+
+ if (note.getNoteType() < Note::Longest) {
+
+ if (bases.second <= spaceAvailable) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "We rounded down to " << qd
+ << " but have room for " << bases.second
+ << ", rounding up again" << endl;
+#endif
+ qd = bases.second;
+ } else {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "We rounded down to " << qd
+ << "; can't fit " << bases.second << endl;
+#endif
+ }
+ }
+ }
+ }
+ }
+
+ setProvisional(**ci, DurationValue, qd);
+ if (!m_contrapuntal) nonContrapuntalDuration = qd;
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "totalFrac " << totalFrac << ", totalFracCount " << totalFracCount << ", avg " << (totalFracCount > 0 ? (totalFrac / totalFracCount) : 0) << endl;
+#endif
+}
+
+
+void
+NotationQuantizer::Impl::scanTupletsInBar(Segment *s,
+ timeT barStart,
+ timeT barDuration,
+ timeT wholeStart,
+ timeT wholeEnd,
+ const std::vector<int> &divisions) const
+{
+ Profiler profiler("NotationQuantizer::Impl::scanTupletsInBar");
+
+ //!!! need to further constrain the area scanned so as to cope with
+ // partial bars
+
+ timeT base = barDuration;
+
+ for (int depth = -1; depth < int(divisions.size()) - 2; ++depth) {
+
+ if (depth >= 0) base /= divisions[depth];
+ if (base <= Note(Note::Semiquaver).getDuration()) break;
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "\nscanTupletsInBar: trying at depth " << depth << " (base " << base << ")" << endl;
+#endif
+
+ // check for triplets if our next divisor is 2 and the following
+ // one is not 3
+
+ if (divisions[depth+1] != 2 || divisions[depth+2] == 3) continue;
+
+ timeT tupletBase = base / 3;
+ timeT tupletStart = barStart;
+
+ while (tupletStart < barStart + barDuration) {
+
+ timeT tupletEnd = tupletStart + base;
+ if (tupletStart < wholeStart || tupletEnd > wholeEnd) {
+ tupletStart = tupletEnd;
+ continue;
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "scanTupletsInBar: testing " << tupletStart << "," << base << " at tuplet base " << tupletBase << endl;
+#endif
+
+ // find first note within a certain distance whose start time
+ // quantized to tupletStart or greater
+ Segment::iterator j = s->findTime(tupletStart - tupletBase / 3);
+ timeT jTime = tupletEnd;
+
+ while (s->isBeforeEndMarker(j) &&
+ (!(*j)->isa(Note::EventType) ||
+ !(*j)->get<Int>(m_provisionalAbsTime, jTime) ||
+ jTime < tupletStart)) {
+ if ((*j)->getAbsoluteTime() > tupletEnd + tupletBase / 3) {
+ break;
+ }
+ ++j;
+ }
+
+ if (jTime >= tupletEnd) { // nothing to make tuplets of
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "scanTupletsInBar: nothing here" << endl;
+#endif
+ tupletStart = tupletEnd;
+ continue;
+ }
+
+ scanTupletsAt(s, j, depth+1, base, barStart,
+ tupletStart, tupletBase);
+
+ tupletStart = tupletEnd;
+ }
+ }
+}
+
+
+void
+NotationQuantizer::Impl::scanTupletsAt(Segment *s,
+ Segment::iterator i,
+ int depth,
+ timeT base,
+ timeT sigTime,
+ timeT tupletStart,
+ timeT tupletBase) const
+{
+ Profiler profiler("NotationQuantizer::Impl::scanTupletsAt");
+
+ Segment::iterator j = i;
+ timeT tupletEnd = tupletStart + base;
+ timeT jTime = tupletEnd;
+
+ std::vector<Event *> candidates;
+ int count = 0;
+
+ while (s->isBeforeEndMarker(j) &&
+ ((*j)->isa(Note::EventRestType) ||
+ ((*j)->get<Int>(m_provisionalAbsTime, jTime) &&
+ jTime < tupletEnd))) {
+
+ if (!(*j)->isa(Note::EventType)) { ++j; continue; }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "scanTupletsAt time " << jTime << " (unquantized "
+ << (*j)->getAbsoluteTime() << "), found note" << endl;
+#endif
+
+ // reject any group containing anything already a tuplet
+ if ((*j)->has(BEAMED_GROUP_TUPLET_BASE)) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "already made tuplet here" << endl;
+#endif
+ return;
+ }
+
+ timeT originalBase;
+
+ if (!(*j)->get<Int>(m_provisionalBase, originalBase)) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "some notes not provisionally quantized, no good" << endl;
+#endif
+ return;
+ }
+
+ if (originalBase == base) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "accepting note at original base" << endl;
+#endif
+ candidates.push_back(*j);
+ } else if (((jTime - sigTime) % base) == 0) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "accepting note that happens to lie on original base" << endl;
+#endif
+ candidates.push_back(*j);
+ } else {
+
+ // This is a note that did not quantize to the original base
+ // (the first note in the tuplet would have, but we can't tell
+ // anything from that). Reject the entire group if it fails
+ // any of the likelihood tests for tuplets.
+
+ if (!isValidTupletAt(s, j, depth, base, sigTime, tupletBase)) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "no good" << endl;
+#endif
+ return;
+ }
+
+ candidates.push_back(*j);
+ ++count;
+ }
+
+ ++j;
+ }
+
+ // must have at least one note that is not already quantized to the
+ // original base
+ if (count < 1) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "scanTupletsAt: found no note not already quantized to " << base << endl;
+#endif
+ return;
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "scanTupletsAt: Tuplet group of duration " << base << " starting at " << tupletStart << endl;
+#endif
+
+ // Woo-hoo! It looks good.
+
+ int groupId = s->getNextId();
+ std::map<int, bool> multiples;
+
+ for (std::vector<Event *>::iterator ei = candidates.begin();
+ ei != candidates.end(); ++ei) {
+
+ //!!! Interesting -- we can't modify rests here, but Segment's
+ // normalizeRests won't insert the correct sort of rest for us...
+ // what to do?
+ //!!! insert a tupleted rest, and prevent Segment::normalizeRests
+ // from messing about with it
+ if (!(*ei)->isa(Note::EventType)) continue;
+ (*ei)->set<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
+
+ //!!! This is too easy, because we rejected any notes of
+ //durations not conforming to a single multiple of the
+ //tupletBase in isValidTupletAt
+
+ (*ei)->set<Int>(BEAMED_GROUP_ID, groupId);
+ (*ei)->set<Int>(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3
+ (*ei)->set<Int>(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above
+ (*ei)->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT, base/tupletBase);
+
+ timeT t = (*ei)->getAbsoluteTime();
+ t -= tupletStart;
+ timeT low = (t / tupletBase) * tupletBase;
+ timeT high = low + tupletBase;
+ t = ((high - t > t - low) ? low : high);
+
+ multiples[t / tupletBase] = true;
+
+ t += tupletStart;
+
+ setProvisional(*ei, AbsoluteTimeValue, t);
+ setProvisional(*ei, DurationValue, tupletBase);
+ }
+
+ // fill in with tupleted rests
+
+ for (int m = 0; m < base / tupletBase; ++m) {
+
+ if (multiples[m]) continue;
+
+ timeT absTime = tupletStart + m * tupletBase;
+ timeT duration = tupletBase;
+//!!! while (multiples[++m]) duration += tupletBase;
+
+ Event *rest = new Event(Note::EventRestType, absTime, duration);
+
+ rest->set<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
+ rest->set<Int>(BEAMED_GROUP_ID, groupId);
+ rest->set<Int>(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3
+ rest->set<Int>(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above
+ rest->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT, base/tupletBase);
+
+ m_q->m_toInsert.push_back(rest);
+ }
+}
+
+bool
+NotationQuantizer::Impl::isValidTupletAt(Segment *s,
+ const Segment::iterator &i,
+ int depth,
+ timeT /* base */,
+ timeT sigTime,
+ timeT tupletBase) const
+{
+ Profiler profiler("NotationQuantizer::Impl::isValidTupletAt");
+
+ //!!! This is basically wrong; we need to be able to deal with groups
+ // that contain e.g. a crotchet and a quaver, tripleted.
+
+ timeT ud = m_q->getFromSource(*i, DurationValue);
+
+ if (ud > (tupletBase * 5 / 4)) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "\nNotationQuantizer::isValidTupletAt: note too long at "
+ << (*i)->getDuration() << " (tupletBase is " << tupletBase << ")"
+ << endl;
+#endif
+ return false; // too long
+ }
+
+ //!!! This bit is a cop-out. It means we reject anything that looks
+ // like it's going to have rests in it. Bah.
+ if (ud <= (tupletBase * 3 / 8)) {
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "\nNotationQuantizer::isValidTupletAt: note too short at "
+ << (*i)->getDuration() << " (tupletBase is " << tupletBase << ")"
+ << endl;
+#endif
+ return false;
+ }
+
+ long score = 0;
+ if (!(*i)->get<Int>(m_provisionalScore, score)) return false;
+
+ timeT t = m_q->getFromSource(*i, AbsoluteTimeValue);
+ timeT d = getProvisional(*i, DurationValue);
+ int noteType = (*i)->get<Int>(m_provisionalNoteType);
+
+ //!!! not as complete as the calculation we do in the original scoring
+ bool dummy;
+ long tupletScore = scoreAbsoluteTimeForBase
+ (s, i, depth, tupletBase, sigTime, t, d, noteType, s->end(), s->end(), dummy);
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "\nNotationQuantizer::isValidTupletAt: score " << score
+ << " vs tupletScore " << tupletScore << endl;
+#endif
+ return (tupletScore < score);
+}
+
+
+void
+NotationQuantizer::quantizeRange(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ m_impl->quantizeRange(s, from, to);
+}
+
+void
+NotationQuantizer::Impl::quantizeRange(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ Profiler *profiler = new Profiler("NotationQuantizer::Impl::quantizeRange");
+
+ clock_t start = clock();
+ int events = 0, notes = 0, passes = 0;
+ int setGood = 0, setBad = 0;
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "NotationQuantizer::Impl::quantizeRange: from time "
+ << (from == s->end() ? -1 : (*from)->getAbsoluteTime())
+ << " to "
+ << (to == s->end() ? -1 : (*to)->getAbsoluteTime())
+ << endl;
+#endif
+
+ timeT segmentEndTime = s->getEndMarkerTime();
+
+ // This process does several passes over the data. It's assumed
+ // that this is not going to be invoked in any really time-critical
+ // place.
+
+ // Calculate absolute times on the first pass, so that we know
+ // which things are chords. We need to assign absolute times to
+ // all events, but we only need do durations for notes.
+
+ PropertyName provisionalBase("notationquantizer-provisionalBase");
+
+ // We don't use setToTarget until we have our final values ready,
+ // as it erases and replaces the events. Just set the properties.
+
+ // Set a provisional duration to each note first
+
+ for (Segment::iterator i = from; i != to; ++i) {
+
+ ++events;
+ if ((*i)->isa(Note::EventRestType)) continue;
+ if ((*i)->isa(Note::EventType)) ++notes;
+ quantizeDurationProvisional(s, i);
+ }
+ ++passes;
+
+ // now do the absolute-time calculation
+
+ timeT wholeStart = 0, wholeEnd = 0;
+
+ Segment::iterator i = from;
+
+ for (Segment::iterator nexti = i; i != to; i = nexti) {
+
+ ++nexti;
+
+ if ((*i)->isa(Note::EventRestType)) {
+ if (i == from) ++from;
+ s->erase(i);
+ continue;
+ }
+
+ quantizeAbsoluteTime(s, i);
+
+ timeT t0 = (*i)->get<Int>(m_provisionalAbsTime);
+ timeT t1 = (*i)->get<Int>(m_provisionalDuration) + t0;
+ if (wholeStart == wholeEnd) {
+ wholeStart = t0;
+ wholeEnd = t1;
+ } else if (t1 > wholeEnd) {
+ wholeEnd = t1;
+ }
+ }
+ ++passes;
+
+ // now we've grouped into chords, look for tuplets next
+
+ Composition *comp = s->getComposition();
+
+ if (m_maxTuplet >= 2) {
+
+ std::vector<int> divisions;
+ comp->getTimeSignatureAt(wholeStart).getDivisions(7, divisions);
+
+ for (int barNo = comp->getBarNumber(wholeStart);
+ barNo <= comp->getBarNumber(wholeEnd); ++barNo) {
+
+ bool isNew = false;
+ TimeSignature timeSig = comp->getTimeSignatureInBar(barNo, isNew);
+ if (isNew) timeSig.getDivisions(7, divisions);
+ scanTupletsInBar(s, comp->getBarStart(barNo),
+ timeSig.getBarDuration(),
+ wholeStart, wholeEnd, divisions);
+ }
+ ++passes;
+ }
+
+ ProvisionalQuantizer provisionalQuantizer((Impl *)this);
+
+ for (i = from; i != to; ++i) {
+
+ if (!(*i)->isa(Note::EventType)) continue;
+
+ // could potentially supply clef and key here, but at the
+ // moment Chord doesn't do anything with them (unlike
+ // NotationChord) and we don't have any really clever
+ // ideas for how to use them here anyway
+// Chord c(*s, i, m_q);
+ Chord c(*s, i, &provisionalQuantizer);
+
+ quantizeDuration(s, c);
+
+ bool ended = false;
+ for (Segment::iterator ci = c.getInitialElement();
+ s->isBeforeEndMarker(ci); ++ci) {
+ if (ci == to) ended = true;
+ if (ci == c.getFinalElement()) break;
+ }
+ if (ended) break;
+
+ i = c.getFinalElement();
+ }
+ ++passes;
+
+ // staccato (we now do slurs separately, in SegmentNotationHelper::autoSlur)
+
+ if (m_articulate) {
+
+ for (i = from; i != to; ++i) {
+
+ if (!(*i)->isa(Note::EventType)) continue;
+
+ timeT qd = getProvisional(*i, DurationValue);
+ timeT ud = m_q->getFromSource(*i, DurationValue);
+
+ if (ud < (qd * 3 / 4) &&
+ qd <= Note(Note::Crotchet).getDuration()) {
+ Marks::addMark(**i, Marks::Staccato, true);
+ } else if (ud > qd) {
+ Marks::addMark(**i, Marks::Tenuto, true);
+ }
+ }
+ ++passes;
+ }
+
+ i = from;
+
+ for (Segment::iterator nexti = i; i != to; i = nexti) {
+
+ ++nexti;
+
+ if ((*i)->isa(Note::EventRestType)) continue;
+
+ timeT t = getProvisional(*i, AbsoluteTimeValue);
+ timeT d = getProvisional(*i, DurationValue);
+
+ unsetProvisionalProperties(*i);
+
+ if ((*i)->getAbsoluteTime() == t &&
+ (*i)->getDuration() == d) ++setBad;
+ else ++setGood;
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "Setting to target at " << t << "," << d << endl;
+#endif
+
+ m_q->setToTarget(s, i, t, d);
+ }
+ ++passes;
+/*
+ cerr << "NotationQuantizer: " << events << " events ("
+ << notes << " notes), " << passes << " passes, "
+ << setGood << " good sets, " << setBad << " bad sets, "
+ << ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms elapsed"
+ << endl;
+*/
+ if (s->getEndTime() < segmentEndTime) {
+ s->setEndMarkerTime(segmentEndTime);
+ }
+
+ delete profiler; // on heap so it updates before the next line:
+ Profiles::getInstance()->dump();
+
+}
+
+
+}
+
diff --git a/src/base/NotationQuantizer.h b/src/base/NotationQuantizer.h
new file mode 100644
index 0000000..87b0d72
--- /dev/null
+++ b/src/base/NotationQuantizer.h
@@ -0,0 +1,93 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef NOTATION_QUANTIZER_H_
+#define NOTATION_QUANTIZER_H_
+
+#include "Quantizer.h"
+
+namespace Rosegarden {
+
+class NotationQuantizer : public Quantizer
+{
+public:
+ NotationQuantizer();
+ NotationQuantizer(std::string source, std::string target);
+ NotationQuantizer(const NotationQuantizer &);
+ ~NotationQuantizer();
+
+ /**
+ * Set the absolute time minimum unit. Default is demisemiquaver.
+ */
+ void setUnit(timeT);
+ timeT getUnit() const;
+
+ /**
+ * Set the simplicity factor. This controls the relative "pull"
+ * towards larger units and more obvious beats in placing notes.
+ * The value 10 means no pull to larger units, lower values mean
+ * an active pull away from them. Default is 13.
+ */
+ void setSimplicityFactor(int);
+ int getSimplicityFactor() const;
+
+ /**
+ * Set the maximum size of tuplet group. 2 = two-in-the-time-of-three
+ * groupings, 3 = triplets, etc. Default is 3. Set <2 to switch off
+ * tuplets altogether.
+ */
+ void setMaxTuplet(int);
+ int getMaxTuplet() const;
+
+ /**
+ * Set whether we assume the music may be contrapuntal -- that is,
+ * may have notes that overlap rather than simply a sequence of
+ * individual notes and chords.
+ */
+ void setContrapuntal(bool);
+ bool getContrapuntal() const;
+
+ /**
+ * Set whether to add articulations (staccato, tenuto, slurs).
+ * Default is true. Doesn't affect quantization, only the marks
+ * that are added to quantized notes.
+ */
+ void setArticulate(bool);
+ bool getArticulate() const;
+
+protected:
+ virtual void quantizeRange(Segment *,
+ Segment::iterator,
+ Segment::iterator) const;
+
+protected:
+ // avoid having to rebuild absolutely everything each time we
+ // tweak the implementation
+ class Impl;
+ Impl *m_impl;
+
+private:
+ NotationQuantizer &operator=(const NotationQuantizer &); // not provided
+};
+
+}
+
+#endif
diff --git a/src/base/NotationRules.h b/src/base/NotationRules.h
new file mode 100644
index 0000000..a745afa
--- /dev/null
+++ b/src/base/NotationRules.h
@@ -0,0 +1,133 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _NOTATION_RULES_H_
+#define _NOTATION_RULES_H_
+
+
+/**
+ * Common major and minor scales.
+ *
+ * For example, sixth note in 12-basis on Cmajor scale:
+ * scale_Cmajor[5] = 9
+ */
+static int scale_Cmajor[] = { 0, 2, 4, 5, 7, 9, 11 };
+static int scale_Cminor[] = { 0, 2, 3, 5, 7, 8, 10 };
+static int scale_Cminor_harmonic[] = { 0, 2, 3, 5, 7, 8, 11 };
+/**
+ * Steps of common major and minor scales.
+ *
+ * For example, get accidental in 12-basis on Cmajor scale:
+ * 10 - scale_Cmajor[steps_Cmajor[10]] = 10 - 9 = +1
+ */
+static int steps_Cmajor[] = { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 };
+static int steps_Cminor[] = { 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 };
+static int steps_Cminor_harmonic[] = { 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 5, 6 };
+/**
+ * Same as previosly, but the use of accidentals is explicitly written.
+ *
+ * For example, get accidental in 12-basis on Cmajor scale:
+ * 10 - scale_Cmajor[steps_Cmajor_with_sharps[10]] = 10 - 9 = +1
+ * 10 - scale_Cmajor[steps_Cmajor_with_flats[10]] = 10 - 11 = -1
+ */
+static int steps_Cmajor_with_sharps[] = { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 };
+static int steps_Cmajor_with_flats[] = { 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 };
+
+namespace Rosegarden
+{
+
+/*
+ * NotationRules.h
+ *
+ * This file contains the model for rules which are used in notation decisions.
+ *
+ */
+
+class NotationRules
+{
+public:
+ NotationRules() { };
+ ~NotationRules() { };
+
+ /**
+ * If a single note is above the middle line, the preferred direction is up.
+ *
+ * If a single note is on the middle line, the preferred direction is down.
+ *
+ * If a single note is below the middle line, the preferred direction is down.
+ */
+ bool isStemUp(int heightOnStaff) { return heightOnStaff < 4; }
+
+ /**
+ * If the highest note in a chord is more distant from the middle
+ * line than the lowest note in a chord, the preferred direction is down.
+ *
+ * If the extreme notes in a chord are an equal distance from the
+ * middle line, the preferred direction is down.
+ *
+ * If the lowest note in a chord is more distant from the middle
+ * line than the highest note in a chord, the preferred direction is up.
+ */
+ bool isStemUp(int highestHeightOnStaff, int lowestHeightOnStaff) {
+ return (highestHeightOnStaff + lowestHeightOnStaff) < 2*4;
+ }
+
+ /**
+ * If majority of notes are below the middle line,
+ * the preferred direction is up.
+ *
+ * If notes are equally distributed around the middle line,
+ * the preferred direction is down.
+ *
+ * If majority of notes are above the middle line,
+ * the preferred direction is down.
+ */
+ bool isBeamAboveWeighted(int weightAbove, int weightBelow) {
+ return weightBelow > weightAbove;
+ }
+
+ /**
+ * If the highest note in a group is more distant from the middle
+ * line than the lowest note in a group, the preferred direction is down.
+ *
+ * If the extreme notes in a group are an equal distance from the
+ * middle line, the preferred direction is down.
+ *
+ * If the lowest note in a group is more distant from the middle
+ * line than the highest note in a group, the preferred direction is up.
+ */
+ bool isBeamAbove(int highestHeightOnStaff, int lowestHeightOnStaff) {
+ return (highestHeightOnStaff + lowestHeightOnStaff) < 2*4;
+ }
+ bool isBeamAbove(int highestHeightOnStaff, int lowestHeightOnStaff,
+ int weightAbove, int weightBelow) {
+ if (highestHeightOnStaff + lowestHeightOnStaff == 2*4) {
+ return isBeamAboveWeighted(weightAbove,weightBelow);
+ } else {
+ return isBeamAbove(highestHeightOnStaff,lowestHeightOnStaff);
+ }
+ }
+};
+
+}
+
+#endif
diff --git a/src/base/NotationTypes.cpp b/src/base/NotationTypes.cpp
new file mode 100644
index 0000000..ceddf79
--- /dev/null
+++ b/src/base/NotationTypes.cpp
@@ -0,0 +1,2436 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <cstdio> // needed for sprintf()
+#include "NotationRules.h"
+#include "NotationTypes.h"
+#include "BaseProperties.h"
+#include <iostream>
+#include <cstdlib> // for atoi
+#include <limits.h> // for SHRT_MIN
+#include <cassert>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#else
+#include <sstream>
+#endif
+
+//dmm This will make everything excruciatingly slow if defined:
+//#define DEBUG_PITCH
+
+namespace Rosegarden
+{
+using std::string;
+using std::vector;
+using std::cout;
+using std::cerr;
+using std::endl;
+
+// This is the fundamental definition of the resolution used throughout.
+// It must be a multiple of 16, and should ideally be a multiple of 96.
+static const timeT basePPQ = 960;
+
+const int MIN_SUBORDERING = SHRT_MIN;
+
+namespace Accidentals
+{
+ /**
+ * NoAccidental means the accidental will be inferred
+ * based on the performance pitch and current key at the
+ * location of the note.
+ */
+ const Accidental NoAccidental = "no-accidental";
+
+ const Accidental Sharp = "sharp";
+ const Accidental Flat = "flat";
+ const Accidental Natural = "natural";
+ const Accidental DoubleSharp = "double-sharp";
+ const Accidental DoubleFlat = "double-flat";
+
+ AccidentalList getStandardAccidentals() {
+
+ static Accidental a[] = {
+ NoAccidental, Sharp, Flat, Natural, DoubleSharp, DoubleFlat
+ };
+
+ static AccidentalList v;
+ if (v.size() == 0) {
+ for (unsigned int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
+ v.push_back(a[i]);
+ }
+ return v;
+ }
+
+ int getPitchOffset(const Accidental &acc) {
+ if (acc == DoubleSharp) return 2;
+ else if (acc == Sharp) return 1;
+ else if (acc == Flat) return -1;
+ else if (acc == DoubleFlat) return -2;
+ else return 0;
+ }
+
+ Accidental getAccidental(int pitchChange) {
+ if (pitchChange == -2) return DoubleFlat;
+ if (pitchChange == -1) return Flat;
+ // Yielding 'Natural' will add a natural-sign even if not needed, so for now
+ // just return NoAccidental
+ if (pitchChange == 0) return NoAccidental;
+ if (pitchChange == 1) return Sharp;
+ if (pitchChange == 2) return DoubleSharp;
+
+ // if we're getting into triple flats/sharps, we're probably atonal
+ // and don't case if the accidental is simplified
+ return NoAccidental;
+ }
+}
+
+using namespace Accidentals;
+
+
+namespace Marks
+{
+ const Mark NoMark = "no-mark";
+ const Mark Accent = "accent";
+ const Mark Tenuto = "tenuto";
+ const Mark Staccato = "staccato";
+ const Mark Staccatissimo = "staccatissimo";
+ const Mark Marcato = "marcato";
+ const Mark Sforzando = getTextMark("sf");
+ const Mark Rinforzando = getTextMark("rf");
+ const Mark Trill = "trill";
+ const Mark LongTrill = "long-trill";
+ const Mark TrillLine = "trill-line";
+ const Mark Turn = "turn";
+ const Mark Pause = "pause";
+ const Mark UpBow = "up-bow";
+ const Mark DownBow = "down-bow";
+
+ const Mark Mordent = "mordent";
+ const Mark MordentInverted = "mordent-inverted";
+ const Mark MordentLong = "mordent-long";
+ const Mark MordentLongInverted = "mordent-long-inverted";
+
+ string getTextMark(string text) {
+ return string("text_") + text;
+ }
+
+ bool isTextMark(Mark mark) {
+ return string(mark).substr(0, 5) == "text_";
+ }
+
+ string getTextFromMark(Mark mark) {
+ if (!isTextMark(mark)) return string();
+ else return string(mark).substr(5);
+ }
+
+ string getFingeringMark(string fingering) {
+ return string("finger_") + fingering;
+ }
+
+ bool isFingeringMark(Mark mark) {
+ return string(mark).substr(0, 7) == "finger_";
+ }
+
+ string getFingeringFromMark(Mark mark) {
+ if (!isFingeringMark(mark)) return string();
+ else return string(mark).substr(7);
+ }
+
+ int getMarkCount(const Event &e) {
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+ return markCount;
+ }
+
+ std::vector<Mark> getMarks(const Event &e) {
+
+ std::vector<Mark> marks;
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+ if (markCount == 0) return marks;
+
+ for (long j = 0; j < markCount; ++j) {
+
+ Mark mark(Marks::NoMark);
+ (void)e.get<String>(BaseProperties::getMarkPropertyName(j), mark);
+
+ marks.push_back(mark);
+ }
+
+ return marks;
+ }
+
+ Mark getFingeringMark(const Event &e) {
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+ if (markCount == 0) return NoMark;
+
+ for (long j = 0; j < markCount; ++j) {
+
+ Mark mark(Marks::NoMark);
+ (void)e.get<String>(BaseProperties::getMarkPropertyName(j), mark);
+
+ if (isFingeringMark(mark)) return mark;
+ }
+
+ return NoMark;
+ }
+
+ void addMark(Event &e, const Mark &mark, bool unique) {
+ if (unique && hasMark(e, mark)) return;
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+ e.set<Int>(BaseProperties::MARK_COUNT, markCount + 1);
+
+ PropertyName markProperty = BaseProperties::getMarkPropertyName(markCount);
+ e.set<String>(markProperty, mark);
+ }
+
+ bool removeMark(Event &e, const Mark &mark) {
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+
+ for (long j = 0; j < markCount; ++j) {
+ PropertyName pn(BaseProperties::getMarkPropertyName(j));
+ std::string m;
+ if (e.get<String>(pn, m) && m == mark) {
+ e.unset(pn);
+ while (j < markCount - 1) {
+ PropertyName npn(BaseProperties::getMarkPropertyName(j+1));
+ if (e.get<String>(npn, m)) {
+ e.set<String>( pn, m);
+ }
+ pn = npn;
+ ++j;
+ }
+ e.set<Int>(BaseProperties::MARK_COUNT, markCount - 1);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool hasMark(const Event &e, const Mark &mark) {
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+
+ for (long j = 0; j < markCount; ++j) {
+ std::string m;
+ if (e.get<String>(BaseProperties::getMarkPropertyName(j), m) && m == mark) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ std::vector<Mark> getStandardMarks() {
+
+ static Mark a[] = {
+ NoMark, Accent, Tenuto, Staccato, Staccatissimo, Marcato,
+ Sforzando, Rinforzando, Trill, LongTrill, TrillLine,
+ Turn, Pause, UpBow, DownBow,
+ Mordent, MordentInverted, MordentLong, MordentLongInverted
+ };
+
+ static std::vector<Mark> v;
+ if (v.size() == 0) {
+ for (unsigned int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
+ v.push_back(a[i]);
+ }
+ return v;
+ }
+
+}
+
+using namespace Marks;
+
+
+//////////////////////////////////////////////////////////////////////
+// Clef
+//////////////////////////////////////////////////////////////////////
+
+const string Clef::EventType = "clefchange";
+const int Clef::EventSubOrdering = -250;
+const PropertyName Clef::ClefPropertyName = "clef";
+const PropertyName Clef::OctaveOffsetPropertyName = "octaveoffset";
+const string Clef::Treble = "treble";
+const string Clef::French = "french";
+const string Clef::Soprano = "soprano";
+const string Clef::Mezzosoprano = "mezzosoprano";
+const string Clef::Alto = "alto";
+const string Clef::Tenor = "tenor";
+const string Clef::Baritone = "baritone";
+const string Clef::Varbaritone = "varbaritone";
+const string Clef::Bass = "bass";
+const string Clef::Subbass = "subbass";
+
+const Clef Clef::DefaultClef = Clef("treble");
+
+Clef::Clef(const Event &e) :
+ m_clef(DefaultClef.m_clef),
+ m_octaveOffset(0)
+{
+ if (e.getType() != EventType) {
+ std::cerr << Event::BadType
+ ("Clef model event", EventType, e.getType()).getMessage()
+ << std::endl;
+ return;
+ }
+
+ std::string s;
+ e.get<String>(ClefPropertyName, s);
+
+ if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) {
+ std::cerr << BadClefName("No such clef as \"" + s + "\"").getMessage()
+ << std::endl;
+ return;
+ }
+
+ long octaveOffset = 0;
+ (void)e.get<Int>(OctaveOffsetPropertyName, octaveOffset);
+
+ m_clef = s;
+ m_octaveOffset = octaveOffset;
+}
+
+Clef::Clef(const std::string &s, int octaveOffset)
+ // throw (BadClefName)
+{
+ if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) {
+ throw BadClefName("No such clef as \"" + s + "\"");
+ }
+ m_clef = s;
+ m_octaveOffset = octaveOffset;
+}
+
+Clef &Clef::operator=(const Clef &c)
+{
+ if (this != &c) {
+ m_clef = c.m_clef;
+ m_octaveOffset = c.m_octaveOffset;
+ }
+ return *this;
+}
+
+bool Clef::isValid(const Event &e)
+{
+ if (e.getType() != EventType) return false;
+
+ std::string s;
+ e.get<String>(ClefPropertyName, s);
+ if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) return false;
+
+ return true;
+}
+
+int Clef::getTranspose() const
+{
+//!!! plus or minus?
+ return getOctave() * 12 - getPitchOffset();
+}
+
+int Clef::getOctave() const
+{
+ if (m_clef == Treble || m_clef == French) return 0 + m_octaveOffset;
+ else if (m_clef == Bass || m_clef == Varbaritone || m_clef == Subbass) return -2 + m_octaveOffset;
+ else return -1 + m_octaveOffset;
+}
+
+int Clef::getPitchOffset() const
+{
+ if (m_clef == Treble) return 0;
+ else if (m_clef == French) return -2;
+ else if (m_clef == Soprano) return -5;
+ else if (m_clef == Mezzosoprano) return -3;
+ else if (m_clef == Alto) return -1;
+ else if (m_clef == Tenor) return 1;
+ else if (m_clef == Baritone) return 3;
+ else if (m_clef == Varbaritone) return -4;
+ else if (m_clef == Bass) return -2;
+ else if (m_clef == Subbass) return 0;
+ else return -2;
+}
+
+int Clef::getAxisHeight() const
+{
+ if (m_clef == Treble) return 2;
+ else if (m_clef == French) return 0;
+ else if (m_clef == Soprano) return 0;
+ else if (m_clef == Mezzosoprano) return 2;
+ else if (m_clef == Alto) return 4;
+ else if (m_clef == Tenor) return 6;
+ else if (m_clef == Baritone) return 8;
+ else if (m_clef == Varbaritone) return 4;
+ else if (m_clef == Bass) return 6;
+ else if (m_clef == Subbass) return 8;
+ else return 6;
+}
+
+Clef::ClefList
+Clef::getClefs()
+{
+ ClefList clefs;
+ clefs.push_back(Clef(Bass));
+ clefs.push_back(Clef(Varbaritone));
+ clefs.push_back(Clef(Subbass));
+ clefs.push_back(Clef(Baritone));
+ clefs.push_back(Clef(Tenor));
+ clefs.push_back(Clef(Alto));
+ clefs.push_back(Clef(Mezzosoprano));
+ clefs.push_back(Clef(Soprano));
+ clefs.push_back(Clef(French));
+ clefs.push_back(Clef(Treble));
+ return clefs;
+}
+
+Event *Clef::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<String>(ClefPropertyName, m_clef);
+ e->set<Int>(OctaveOffsetPropertyName, m_octaveOffset);
+ return e;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// Key
+//////////////////////////////////////////////////////////////////////
+
+Key::KeyDetailMap Key::m_keyDetailMap = Key::KeyDetailMap();
+
+const string Key::EventType = "keychange";
+const int Key::EventSubOrdering = -200;
+const PropertyName Key::KeyPropertyName = "key";
+const Key Key::DefaultKey = Key("C major");
+
+Key::Key() :
+ m_name(DefaultKey.m_name),
+ m_accidentalHeights(0)
+{
+ checkMap();
+}
+
+
+Key::Key(const Event &e) :
+ m_name(""),
+ m_accidentalHeights(0)
+{
+ checkMap();
+ if (e.getType() != EventType) {
+ std::cerr << Event::BadType
+ ("Key model event", EventType, e.getType()).getMessage()
+ << std::endl;
+ return;
+ }
+ e.get<String>(KeyPropertyName, m_name);
+ if (m_keyDetailMap.find(m_name) == m_keyDetailMap.end()) {
+ std::cerr << BadKeyName
+ ("No such key as \"" + m_name + "\"").getMessage() << std::endl;
+ return;
+ }
+}
+
+Key::Key(const std::string &name) :
+ m_name(name),
+ m_accidentalHeights(0)
+{
+ checkMap();
+ if (m_keyDetailMap.find(m_name) == m_keyDetailMap.end()) {
+ throw BadKeyName("No such key as \"" + m_name + "\"");
+ }
+}
+
+Key::Key(int accidentalCount, bool isSharp, bool isMinor) :
+ m_accidentalHeights(0)
+{
+ checkMap();
+ for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin();
+ i != m_keyDetailMap.end(); ++i) {
+ if ((*i).second.m_sharpCount == accidentalCount &&
+ (*i).second.m_minor == isMinor &&
+ ((*i).second.m_sharps == isSharp ||
+ (*i).second.m_sharpCount == 0)) {
+ m_name = (*i).first;
+ return;
+ }
+ }
+
+#if (__GNUC__ < 3)
+ std::ostrstream os;
+#else
+ std::ostringstream os;
+#endif
+
+ os << "No " << (isMinor ? "minor" : "major") << " key with "
+ << accidentalCount << (isSharp ? " sharp(s)" : " flat(s)");
+
+#if (__GNUC__ < 3)
+ os << std::ends;
+#endif
+
+ throw BadKeySpec(os.str());
+}
+
+// Unfortunately this is ambiguous -- e.g. B major / Cb major.
+// We need an isSharp argument, but we already have a constructor
+// with that signature. Not quite sure what's the best solution.
+
+Key::Key(int tonicPitch, bool isMinor) :
+ m_accidentalHeights(0)
+{
+ checkMap();
+ for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin();
+ i != m_keyDetailMap.end(); ++i) {
+ if ((*i).second.m_tonicPitch == tonicPitch &&
+ (*i).second.m_minor == isMinor) {
+ m_name = (*i).first;
+ return;
+ }
+ }
+
+#if (__GNUC__ < 3)
+ std::ostrstream os;
+#else
+ std::ostringstream os;
+#endif
+
+ os << "No " << (isMinor ? "minor" : "major") << " key with tonic pitch "
+ << tonicPitch;
+
+#if (__GNUC__ < 3)
+ os << std::ends;
+#endif
+
+ throw BadKeySpec(os.str());
+}
+
+
+Key::Key(const Key &kc) :
+ m_name(kc.m_name),
+ m_accidentalHeights(0)
+{
+}
+
+Key& Key::operator=(const Key &kc)
+{
+ m_name = kc.m_name;
+ m_accidentalHeights = 0;
+ return *this;
+}
+
+bool Key::isValid(const Event &e)
+{
+ if (e.getType() != EventType) return false;
+ std::string name;
+ e.get<String>(KeyPropertyName, name);
+ if (m_keyDetailMap.find(name) == m_keyDetailMap.end()) return false;
+ return true;
+}
+
+Key::KeyList Key::getKeys(bool minor)
+{
+ checkMap();
+ KeyList result;
+ for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin();
+ i != m_keyDetailMap.end(); ++i) {
+ if ((*i).second.m_minor == minor) {
+ result.push_back(Key((*i).first));
+ }
+ }
+ return result;
+}
+
+Key::Key Key::transpose(int pitchDelta, int heightDelta)
+{
+ Pitch tonic(getTonicPitch());
+ Pitch newTonic = tonic.transpose(*this, pitchDelta, heightDelta);
+ int newTonicPitch = (newTonic.getPerformancePitch() % 12 + 12) % 12;
+ return Key (newTonicPitch, isMinor());
+}
+
+Accidental Key::getAccidentalAtHeight(int height, const Clef &clef) const
+{
+ checkAccidentalHeights();
+ height = canonicalHeight(height);
+ for (unsigned int i = 0; i < m_accidentalHeights->size(); ++i) {
+ if (height ==static_cast<int>(canonicalHeight((*m_accidentalHeights)[i] +
+ clef.getPitchOffset()))) {
+ return isSharp() ? Sharp : Flat;
+ }
+ }
+ return NoAccidental;
+}
+
+Accidental Key::getAccidentalForStep(int step) const
+{
+ if (isMinor()) {
+ step = (step + 5) % 7;
+ }
+
+ int accidentalCount = getAccidentalCount();
+
+ if (accidentalCount == 0) {
+ return NoAccidental;
+ }
+
+ bool sharp = isSharp();
+
+ int currentAccidentalPosition = sharp ? 6 : 3;
+
+ for (int i = 1; i <= accidentalCount; i++) {
+ if (step == currentAccidentalPosition) {
+ return sharp ? Sharp : Flat;
+ }
+
+ currentAccidentalPosition =
+ (currentAccidentalPosition + (sharp ? 3 : 4)) % 7;
+ }
+
+ return NoAccidental;
+}
+
+vector<int> Key::getAccidentalHeights(const Clef &clef) const
+{
+ // staff positions of accidentals
+ checkAccidentalHeights();
+ vector<int> v(*m_accidentalHeights);
+ int offset = clef.getPitchOffset();
+
+ for (unsigned int i = 0; i < v.size(); ++i) {
+ v[i] += offset;
+ if (offset > 0)
+ if (v[i] > 8) v[i] -= 7;
+ }
+ return v;
+}
+
+void Key::checkAccidentalHeights() const
+{
+ if (m_accidentalHeights) return;
+ m_accidentalHeights = new vector<int>;
+
+ bool sharp = isSharp();
+ int accidentals = getAccidentalCount();
+ int height = sharp ? 8 : 4;
+
+ for (int i = 0; i < accidentals; ++i) {
+ m_accidentalHeights->push_back(height);
+ if (sharp) { height -= 3; if (height < 3) height += 7; }
+ else { height += 3; if (height > 7) height -= 7; }
+ }
+}
+
+int Key::convertFrom(int p, const Key &previousKey,
+ const Accidental &explicitAccidental) const
+{
+ Pitch pitch(p, explicitAccidental);
+ int height = pitch.getHeightOnStaff(Clef(), previousKey);
+ Pitch newPitch(height, Clef(), *this, explicitAccidental);
+ return newPitch.getPerformancePitch();
+}
+
+int Key::transposeFrom(int pitch, const Key &previousKey) const
+{
+ int delta = getTonicPitch() - previousKey.getTonicPitch();
+ if (delta > 6) delta -= 12;
+ if (delta < -6) delta += 12;
+ return pitch + delta;
+}
+
+Event *Key::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<String>(KeyPropertyName, m_name);
+ return e;
+}
+
+
+void Key::checkMap() {
+ if (!m_keyDetailMap.empty()) return;
+
+ m_keyDetailMap["A major" ] = KeyDetails(true, false, 3, "F# minor", "A maj / F# min", 9);
+ m_keyDetailMap["F# minor"] = KeyDetails(true, true, 3, "A major", "A maj / F# min", 6);
+ m_keyDetailMap["Ab major"] = KeyDetails(false, false, 4, "F minor", "Ab maj / F min", 8);
+ m_keyDetailMap["F minor" ] = KeyDetails(false, true, 4, "Ab major", "Ab maj / F min", 5);
+ m_keyDetailMap["B major" ] = KeyDetails(true, false, 5, "G# minor", "B maj / G# min", 11);
+ m_keyDetailMap["G# minor"] = KeyDetails(true, true, 5, "B major", "B maj / G# min", 8);
+ m_keyDetailMap["Bb major"] = KeyDetails(false, false, 2, "G minor", "Bb maj / G min", 10);
+ m_keyDetailMap["G minor" ] = KeyDetails(false, true, 2, "Bb major", "Bb maj / G min", 7);
+ m_keyDetailMap["C major" ] = KeyDetails(true, false, 0, "A minor", "C maj / A min", 0);
+ m_keyDetailMap["A minor" ] = KeyDetails(false, true, 0, "C major", "C maj / A min", 9);
+ m_keyDetailMap["Cb major"] = KeyDetails(false, false, 7, "Ab minor", "Cb maj / Ab min", 11);
+ m_keyDetailMap["Ab minor"] = KeyDetails(false, true, 7, "Cb major", "Cb maj / Ab min", 8);
+ m_keyDetailMap["C# major"] = KeyDetails(true, false, 7, "A# minor", "C# maj / A# min", 1);
+ m_keyDetailMap["A# minor"] = KeyDetails(true, true, 7, "C# major", "C# maj / A# min", 10);
+ m_keyDetailMap["D major" ] = KeyDetails(true, false, 2, "B minor", "D maj / B min", 2);
+ m_keyDetailMap["B minor" ] = KeyDetails(true, true, 2, "D major", "D maj / B min", 11);
+ m_keyDetailMap["Db major"] = KeyDetails(false, false, 5, "Bb minor", "Db maj / Bb min", 1);
+ m_keyDetailMap["Bb minor"] = KeyDetails(false, true, 5, "Db major", "Db maj / Bb min", 10);
+ m_keyDetailMap["E major" ] = KeyDetails(true, false, 4, "C# minor", "E maj / C# min", 4);
+ m_keyDetailMap["C# minor"] = KeyDetails(true, true, 4, "E major", "E maj / C# min", 1);
+ m_keyDetailMap["Eb major"] = KeyDetails(false, false, 3, "C minor", "Eb maj / C min", 3);
+ m_keyDetailMap["C minor" ] = KeyDetails(false, true, 3, "Eb major", "Eb maj / C min", 0);
+ m_keyDetailMap["F major" ] = KeyDetails(false, false, 1, "D minor", "F maj / D min", 5);
+ m_keyDetailMap["D minor" ] = KeyDetails(false, true, 1, "F major", "F maj / D min", 2);
+ m_keyDetailMap["F# major"] = KeyDetails(true, false, 6, "D# minor", "F# maj / D# min", 6);
+ m_keyDetailMap["D# minor"] = KeyDetails(true, true, 6, "F# major", "F# maj / D# min", 3);
+ m_keyDetailMap["G major" ] = KeyDetails(true, false, 1, "E minor", "G maj / E min", 7);
+ m_keyDetailMap["E minor" ] = KeyDetails(true, true, 1, "G major", "G maj / E min", 4);
+ m_keyDetailMap["Gb major"] = KeyDetails(false, false, 6, "Eb minor", "Gb maj / Eb min", 6);
+ m_keyDetailMap["Eb minor"] = KeyDetails(false, true, 6, "Gb major", "Gb maj / Eb min", 3);
+}
+
+
+Key::KeyDetails::KeyDetails()
+ : m_sharps(false), m_minor(false), m_sharpCount(0),
+ m_equivalence(""), m_rg2name(""), m_tonicPitch(0)
+{
+}
+
+Key::KeyDetails::KeyDetails(bool sharps, bool minor, int sharpCount,
+ std::string equivalence, std::string rg2name,
+ int tonicPitch)
+ : m_sharps(sharps), m_minor(minor), m_sharpCount(sharpCount),
+ m_equivalence(equivalence), m_rg2name(rg2name), m_tonicPitch(tonicPitch)
+{
+}
+
+Key::KeyDetails::KeyDetails(const Key::KeyDetails &d)
+ : m_sharps(d.m_sharps), m_minor(d.m_minor),
+ m_sharpCount(d.m_sharpCount), m_equivalence(d.m_equivalence),
+ m_rg2name(d.m_rg2name), m_tonicPitch(d.m_tonicPitch)
+{
+}
+
+Key::KeyDetails& Key::KeyDetails::operator=(const Key::KeyDetails &d)
+{
+ if (&d == this) return *this;
+ m_sharps = d.m_sharps; m_minor = d.m_minor;
+ m_sharpCount = d.m_sharpCount; m_equivalence = d.m_equivalence;
+ m_rg2name = d.m_rg2name; m_tonicPitch = d.m_tonicPitch;
+ return *this;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Indication
+//////////////////////////////////////////////////////////////////////
+
+const std::string Indication::EventType = "indication";
+const int Indication::EventSubOrdering = -50;
+const PropertyName Indication::IndicationTypePropertyName = "indicationtype";
+//const PropertyName Indication::IndicationDurationPropertyName = "indicationduration";
+static const PropertyName IndicationDurationPropertyName = "indicationduration";//!!!
+
+const std::string Indication::Slur = "slur";
+const std::string Indication::PhrasingSlur = "phrasingslur";
+const std::string Indication::Crescendo = "crescendo";
+const std::string Indication::Decrescendo = "decrescendo";
+const std::string Indication::Glissando = "glissando";
+const std::string Indication::QuindicesimaUp = "ottava2up";
+const std::string Indication::OttavaUp = "ottavaup";
+const std::string Indication::OttavaDown = "ottavadown";
+const std::string Indication::QuindicesimaDown = "ottava2down";
+
+Indication::Indication(const Event &e)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("Indication model event", EventType, e.getType());
+ }
+ std::string s;
+ e.get<String>(IndicationTypePropertyName, s);
+ if (!isValid(s)) {
+ throw BadIndicationName("No such indication as \"" + s + "\"");
+ }
+ m_indicationType = s;
+
+ m_duration = e.getDuration();
+ if (m_duration == 0) {
+ e.get<Int>(IndicationDurationPropertyName, m_duration); // obsolete property
+ }
+}
+
+Indication::Indication(const std::string &s, timeT indicationDuration)
+{
+ if (!isValid(s)) {
+ throw BadIndicationName("No such indication as \"" + s + "\"");
+ }
+ m_indicationType = s;
+ m_duration = indicationDuration;
+}
+
+Indication &
+Indication::operator=(const Indication &m)
+{
+ if (&m != this) {
+ m_indicationType = m.m_indicationType;
+ m_duration = m.m_duration;
+ }
+ return *this;
+}
+
+Event *
+Indication::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, m_duration, EventSubOrdering);
+ e->set<String>(IndicationTypePropertyName, m_indicationType);
+
+ // Set this obsolete property as well, as otherwise we could actually
+ // crash earlier versions of RG by loading files exported from this one!
+ e->set<Int>(IndicationDurationPropertyName, m_duration);
+
+ return e;
+}
+
+bool
+Indication::isValid(const std::string &s) const
+{
+ return
+ (s == Slur || s == PhrasingSlur ||
+ s == Crescendo || s == Decrescendo ||
+ s == Glissando ||
+ s == QuindicesimaUp || s == OttavaUp ||
+ s == OttavaDown || s == QuindicesimaDown);
+}
+
+
+
+//////////////////////////////////////////////////////////////////////
+// Text
+//////////////////////////////////////////////////////////////////////
+
+const std::string Text::EventType = "text";
+const int Text::EventSubOrdering = -70;
+const PropertyName Text::TextPropertyName = "text";
+const PropertyName Text::TextTypePropertyName = "type";
+const PropertyName Text::LyricVersePropertyName = "verse";
+
+// text styles
+const std::string Text::UnspecifiedType = "unspecified";
+const std::string Text::StaffName = "staffname";
+const std::string Text::ChordName = "chordname";
+const std::string Text::KeyName = "keyname";
+const std::string Text::Dynamic = "dynamic";
+const std::string Text::Lyric = "lyric";
+const std::string Text::Chord = "chord";
+const std::string Text::Direction = "direction";
+const std::string Text::LocalDirection = "local_direction";
+const std::string Text::Tempo = "tempo";
+const std::string Text::LocalTempo = "local_tempo";
+const std::string Text::Annotation = "annotation";
+const std::string Text::LilyPondDirective = "lilypond_directive";
+
+// special LilyPond directives
+const std::string Text::Segno = "Segno";
+const std::string Text::Coda = "Coda";
+const std::string Text::Alternate1 = "Alt1 ->";
+const std::string Text::Alternate2 = "Alt2 ->";
+const std::string Text::BarDouble = "|| ->";
+const std::string Text::BarEnd = "|. ->";
+const std::string Text::BarDot = ": ->";
+const std::string Text::Gliss = "Gliss.";
+const std::string Text::Arpeggio = "Arp.";
+//const std::string Text::ArpeggioUp = "Arp.^";
+//const std::string Text::ArpeggioDn = "Arp._";
+const std::string Text::Tiny = "tiny ->";
+const std::string Text::Small = "small ->";
+const std::string Text::NormalSize = "norm. ->";
+
+Text::Text(const Event &e) :
+ m_verse(0)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("Text model event", EventType, e.getType());
+ }
+
+ m_text = "";
+ m_type = Text::UnspecifiedType;
+
+ e.get<String>(TextPropertyName, m_text);
+ e.get<String>(TextTypePropertyName, m_type);
+ e.get<Int>(LyricVersePropertyName, m_verse);
+}
+
+Text::Text(const std::string &s, const std::string &type) :
+ m_text(s),
+ m_type(type),
+ m_verse(0)
+{
+ // nothing else
+}
+
+Text::Text(const Text &t) :
+ m_text(t.m_text),
+ m_type(t.m_type),
+ m_verse(t.m_verse)
+{
+ // nothing else
+}
+
+Text &
+Text::operator=(const Text &t)
+{
+ if (&t != this) {
+ m_text = t.m_text;
+ m_type = t.m_type;
+ m_verse = t.m_verse;
+ }
+ return *this;
+}
+
+Text::~Text()
+{
+ // nothing
+}
+
+bool
+Text::isTextOfType(Event *e, std::string type)
+{
+ return (e->isa(EventType) &&
+ e->has(TextTypePropertyName) &&
+ e->get<String>(TextTypePropertyName) == type);
+}
+
+std::vector<std::string>
+Text::getUserStyles()
+{
+ std::vector<std::string> v;
+
+ v.push_back(Dynamic);
+ v.push_back(Direction);
+ v.push_back(LocalDirection);
+ v.push_back(Tempo);
+ v.push_back(LocalTempo);
+ v.push_back(Chord);
+ v.push_back(Lyric);
+ v.push_back(Annotation);
+ v.push_back(LilyPondDirective);
+
+ return v;
+}
+
+std::vector<std::string>
+Text::getLilyPondDirectives()
+{
+ std::vector<std::string> v;
+
+ v.push_back(Alternate1);
+ v.push_back(Alternate2);
+ v.push_back(Segno);
+ v.push_back(Coda);
+ v.push_back(BarDouble);
+ v.push_back(BarEnd);
+ v.push_back(BarDot);
+ v.push_back(Gliss);
+ v.push_back(Arpeggio);
+// v.push_back(ArpeggioUp);
+// v.push_back(ArpeggioDn);
+ v.push_back(Tiny);
+ v.push_back(Small);
+ v.push_back(NormalSize);
+
+ return v;
+}
+
+Event *
+Text::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<String>(TextPropertyName, m_text);
+ e->set<String>(TextTypePropertyName, m_type);
+ if (m_type == Lyric) e->set<Int>(LyricVersePropertyName, m_verse);
+ return e;
+}
+
+bool
+pitchInKey(int pitch, const Key& key)
+{
+ int pitchOffset = (pitch - key.getTonicPitch() + 12) % 12;
+
+ static int pitchInMajor[] =
+ { true, false, true, false, true, true, false, true, false, true, false, true };
+ static int pitchInMinor[] =
+ { true, false, true, true, false, true, false, true, true, false, true, false };
+
+ if (key.isMinor()) {
+ return pitchInMinor[pitchOffset];
+ }
+ else {
+ return pitchInMajor[pitchOffset];
+ }
+}
+
+/**
+ * @param pitch in the range 0..11 (C..B)
+ *
+ * @author Arnout Engelen
+ */
+Accidental
+resolveNoAccidental(int pitch,
+ const Key &key,
+ NoAccidentalStrategy noAccidentalStrategy)
+{
+ Accidental outputAccidental = "";
+
+ // Find out the accidental to use, based on the strategy specified
+ switch (noAccidentalStrategy) {
+ case UseKeySharpness:
+ noAccidentalStrategy =
+ key.isSharp() ? UseSharps : UseFlats;
+ // fall though
+ case UseFlats:
+ // shares code with UseSharps
+ case UseSharps:
+ if (pitchInKey(pitch, key)) {
+ outputAccidental = NoAccidental;
+ }
+ else {
+ if (noAccidentalStrategy == UseSharps) {
+ outputAccidental = Sharp;
+ }
+ else {
+ outputAccidental = Flat;
+ }
+ }
+ break;
+ case UseKey:
+ // the distance of the pitch from the tonic of the current
+ // key
+ int pitchOffset = (pitch - key.getTonicPitch() + 12) % 12;
+ // 0: major, 1: minor
+ int minor = key.isMinor();
+ static int pitchToHeight[2][12] =
+ {
+ { 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 },
+ // a ., b, c, ., d, ., e, f, ., g, .
+ { 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 }
+ };
+
+ // map pitchOffset to the extra correction, on top of any
+ // accidentals in the key. Example: in F major, with a pitchOffset
+ // of 6, the resulting height would be 3 (Bb) and the correction
+ // would be +1, so the resulting note would be B-natural
+ static int pitchToCorrection[2][12] =
+ {
+ { 0, +1, 0, -1, 0, 0, +1, 0, -1, 0, -1, 0 },
+ { 0, -1, 0, 0, +1, 0, -1, 0, 0, +1, 0, +1 }
+ };
+
+ int correction = pitchToCorrection[minor][pitchOffset];
+
+ // Get the accidental normally associated with this height in this
+ // key.
+ Accidental normalAccidental = key.getAccidentalForStep(pitchToHeight[minor][pitchOffset]);
+
+ // Apply the pitchCorrection and get the outputAccidental
+ outputAccidental = Accidentals::getAccidental(
+ getPitchOffset(normalAccidental) + correction);
+
+ }
+
+ return outputAccidental;
+}
+
+/**
+ * @param pitch in the range 0..11 (C..B)
+ *
+ * @author Michael McIntyre
+ */
+void
+resolveSpecifiedAccidental(int pitch,
+ const Clef &clef,
+ const Key &key,
+ int &height,
+ int &octave,
+ Accidental &inputAccidental,
+ Accidental &outputAccidental)
+{
+ // 4. Get info from the Key
+ long accidentalCount = key.getAccidentalCount();
+ bool keyIsSharp = key.isSharp(), keyIsFlat = !keyIsSharp;
+
+ // Calculate the flags needed for resolving accidentals against the key.
+ // First we initialize them false...
+ bool keyHasSharpC = false, keyHasSharpD = false, keyHasSharpE = false,
+ keyHasSharpF = false, keyHasSharpG = false, keyHasSharpA = false,
+ keyHasSharpB = false, keyHasFlatC = false, keyHasFlatD = false,
+ keyHasFlatE = false, keyHasFlatF = false, keyHasFlatG = false,
+ keyHasFlatA = false, keyHasFlatB = false;
+
+ // Then we use "trip points" based on the flat/sharp state of the key and
+ // its number of accidentals to set the flags:
+ if (keyIsSharp) {
+ switch (accidentalCount) {
+ case 7: keyHasSharpB = true;
+ case 6: keyHasSharpE = true;
+ case 5: keyHasSharpA = true;
+ case 4: keyHasSharpD = true;
+ case 3: keyHasSharpG = true;
+ case 2: keyHasSharpC = true;
+ case 1: keyHasSharpF = true;
+ }
+ } else {
+ switch (accidentalCount) {
+ case 7: keyHasFlatF = true;
+ case 6: keyHasFlatC = true;
+ case 5: keyHasFlatG = true;
+ case 4: keyHasFlatD = true;
+ case 3: keyHasFlatA = true;
+ case 2: keyHasFlatE = true;
+ case 1: keyHasFlatB = true;
+ }
+ }
+
+
+ // 5. Determine height on staff and accidental note should display with for key...
+ //
+ // Every position on the staff is one of six accidental states:
+ //
+ // Natural, Sharp, Flat, DoubleSharp, DoubleFlat, NoAccidental
+ //
+ // DoubleSharp and DoubleFlat are always user-specified accidentals, so
+ // they are always used to decide how to draw the note, and they are
+ // always passed along unchanged.
+ //
+ // The Natural state indicates that a note is or might be going against
+ // the key. Since the Natural state will always be attached to a plain
+ // pitch that can never resolve to a "black key" note, it is not necessary
+ // to handle this case differently unless the key has "white key" notes
+ // that are supposed to take accidentals for the key. (eg. Cb Gb B C# major)
+ // For most keys we treat it the same as a NoAccidental, and use the key
+ // to decide where to draw the note, and what accidental to return.
+ //
+ // The Sharp and Flat states indicate that a user has specified an
+ // accidental for the note, and it might be "out of key." We check to see
+ // if that's the case. If the note is "in key" then the extra accidental
+ // property is removed, and we return NoAccidental. If the note is "out of
+ // key" then the Sharp or Flat is used to decide where to draw the note, and
+ // the accidental is passed along unchanged. (Incomplete? Will a failure
+ // to always pass along the accidental cause strange behavior if a user
+ // specifies an explicit Bb in key of F and then transposes to G, wishing
+ // the Bb to remain an explicit Bb? If someone complains, I'll know where
+ // to look.)
+ //
+ // The NoAccidental state is a default state. We have nothing else upon
+ // which to base a decision in this case, so we make the best decisions
+ // possible using only the pitch and key. Notes that are "in key" pass on
+ // with NoAccidental preserved, otherwise we return an appropriate
+ // accidental for the key.
+
+ // We calculate height on a virtual staff, and then make necessary adjustments to
+ // translate them onto a particular Clef later on...
+ //
+ // ---------F--------- Staff Height Note(semitone) for each of five states:
+ // E
+ // ---------D--------- Natural| Sharp | Flat |DblSharp| DblFlat
+ // C | | | |
+ // ---------B--------- height 4 B(11) | B#( 0) | Bb(10) | Bx( 1) | Bbb( 9)
+ // A height 3 A( 9) | A#(10) | Ab( 8) | Ax(11) | Abb( 7)
+ // ---------G--------- height 2 G( 7) | G#( 8) | Gb( 6) | Gx( 9) | Gbb( 5)
+ // F height 1 F( 5) | F#( 6) | Fb( 4) | Fx( 7) | Fbb( 3)
+ // ---------E--------- height 0 E( 4) | E#( 5) | Eb( 3) | Ex( 6) | Ebb( 2)
+ // D height -1 D( 2) | D#( 3) | Db( 1) | Dx( 4) | Dbb( 0)
+ // ---C---- height -2 C( 0) | C#( 1) | Cb(11) | Cx( 2) | Cbb(10)
+
+
+ // use these constants instead of numeric literals in order to reduce the
+ // chance of making incorrect height assignments...
+ const int C = -2, D = -1, E = 0, F = 1, G = 2, A = 3, B = 4;
+
+ // Here we do the actual work of making all the decisions explained above.
+ switch (pitch) {
+ case 0 :
+ if (inputAccidental == Sharp || // B#
+ (inputAccidental == NoAccidental && keyHasSharpB)) {
+ height = B;
+ octave--;
+ outputAccidental = (keyHasSharpB) ? NoAccidental : Sharp;
+ } else if (inputAccidental == DoubleFlat) { // Dbb
+ height = D;
+ outputAccidental = DoubleFlat;
+ } else {
+ height = C; // C or C-Natural
+ outputAccidental = (keyHasFlatC || keyHasSharpC ||
+ (keyHasSharpB &&
+ inputAccidental == Natural)) ? Natural : NoAccidental;
+ }
+ break;
+ case 1 :
+ if (inputAccidental == Sharp || // C#
+ (inputAccidental == NoAccidental && keyIsSharp)) {
+ height = C;
+ outputAccidental = (keyHasSharpC) ? NoAccidental : Sharp;
+ } else if (inputAccidental == Flat || // Db
+ (inputAccidental == NoAccidental && keyIsFlat)) {
+ height = D;
+ outputAccidental = (keyHasFlatD) ? NoAccidental : Flat;
+ } else if (inputAccidental == DoubleSharp) { // Bx
+ height = B;
+ octave--;
+ outputAccidental = DoubleSharp;
+ }
+ break;
+ case 2 :
+ if (inputAccidental == DoubleSharp) { // Cx
+ height = C;
+ outputAccidental = DoubleSharp;
+ } else if (inputAccidental == DoubleFlat) { // Ebb
+ height = E;
+ outputAccidental = DoubleFlat;
+ } else { // D or D-Natural
+ height = D;
+ outputAccidental = (keyHasSharpD || keyHasFlatD) ? Natural : NoAccidental;
+ }
+ break;
+ case 3 :
+ if (inputAccidental == Sharp || // D#
+ (inputAccidental == NoAccidental && keyIsSharp)) {
+ height = D;
+ outputAccidental = (keyHasSharpD) ? NoAccidental : Sharp;
+ } else if (inputAccidental == Flat || // Eb
+ (inputAccidental == NoAccidental && keyIsFlat)) {
+ height = E;
+ outputAccidental = (keyHasFlatE) ? NoAccidental : Flat;
+ } else if (inputAccidental == DoubleFlat) { // Fbb
+ height = F;
+ outputAccidental = DoubleFlat;
+ }
+ break;
+ case 4 :
+ if (inputAccidental == Flat || // Fb
+ (inputAccidental == NoAccidental && keyHasFlatF)) {
+ height = F;
+ outputAccidental = (keyHasFlatF) ? NoAccidental : Flat;
+ } else if (inputAccidental == DoubleSharp) { // Dx
+ height = D;
+ outputAccidental = DoubleSharp;
+ } else { // E or E-Natural
+ height = E;
+ outputAccidental = (keyHasSharpE || keyHasFlatE ||
+ (keyHasFlatF && inputAccidental==Natural)) ?
+ Natural : NoAccidental;
+ }
+ break;
+ case 5 :
+ if (inputAccidental == Sharp || // E#
+ (inputAccidental == NoAccidental && keyHasSharpE)) {
+ height = E;
+ outputAccidental = (keyHasSharpE) ? NoAccidental : Sharp;
+ } else if (inputAccidental == DoubleFlat) { // Gbb
+ height = G;
+ outputAccidental = DoubleFlat;
+ } else { // F or F-Natural
+ height = F;
+ outputAccidental = (keyHasSharpF || keyHasFlatF ||
+ (keyHasSharpE && inputAccidental==Natural))?
+ Natural : NoAccidental;
+ }
+ break;
+ case 6 :
+ if (inputAccidental == Sharp ||
+ (inputAccidental == NoAccidental && keyIsSharp)) { // F#
+ height = F;
+ outputAccidental = (keyHasSharpF) ? NoAccidental : Sharp;
+ } else if (inputAccidental == Flat || // Gb
+ (inputAccidental == NoAccidental && keyIsFlat)) {
+ height = G;
+ outputAccidental = (keyHasFlatG) ? NoAccidental : Flat;
+ } else if (inputAccidental == DoubleSharp) { // Ex
+ height = E;
+ outputAccidental = DoubleSharp;
+ }
+ break;
+ case 7 :
+ if (inputAccidental == DoubleSharp) { // Fx
+ height = F;
+ outputAccidental = DoubleSharp;
+ } else if (inputAccidental == DoubleFlat) { // Abb
+ height = A;
+ outputAccidental = DoubleFlat;
+ } else { // G or G-Natural
+ height = G;
+ outputAccidental = (keyHasSharpG || keyHasFlatG) ? Natural : NoAccidental;
+ }
+ break;
+ case 8 :
+ if (inputAccidental == Sharp ||
+ (inputAccidental == NoAccidental && keyIsSharp)) { // G#
+ height = G;
+ outputAccidental = (keyHasSharpG) ? NoAccidental : Sharp;
+ } else if (inputAccidental == Flat || // Ab
+ (inputAccidental == NoAccidental && keyIsFlat)) {
+ height = A;
+ outputAccidental = (keyHasFlatA) ? NoAccidental : Flat;
+ }
+ break;
+ case 9 :
+ if (inputAccidental == DoubleSharp) { // Gx
+ height = G;
+ outputAccidental = DoubleSharp;
+ } else if (inputAccidental == DoubleFlat) { // Bbb
+ height = B;
+ outputAccidental = DoubleFlat;
+ } else { // A or A-Natural
+ height = A;
+ outputAccidental = (keyHasSharpA || keyHasFlatA) ? Natural : NoAccidental;
+ }
+ break;
+ case 10:
+ if (inputAccidental == DoubleFlat) { // Cbb
+ height = C;
+ octave++; // tweak B/C divide
+ outputAccidental = DoubleFlat;
+ } else if (inputAccidental == Sharp || // A#
+ (inputAccidental == NoAccidental && keyIsSharp)) {
+ height = A;
+ outputAccidental = (keyHasSharpA) ? NoAccidental : Sharp;
+ } else if (inputAccidental == Flat || // Bb
+ (inputAccidental == NoAccidental && keyIsFlat)) {
+ height = B;
+ outputAccidental = (keyHasFlatB) ? NoAccidental : Flat;
+ }
+ break;
+ case 11:
+ if (inputAccidental == DoubleSharp) { // Ax
+ height = A;
+ outputAccidental = DoubleSharp;
+ } else if (inputAccidental == Flat || // Cb
+ (inputAccidental == NoAccidental && keyHasFlatC)) {
+ height = C;
+ octave++; // tweak B/C divide
+ outputAccidental = (keyHasFlatC) ? NoAccidental : Flat;
+ } else { // B or B-Natural
+ height = B;
+ outputAccidental = (keyHasSharpB || keyHasFlatB ||
+ (keyHasFlatC && inputAccidental==Natural)) ?
+ Natural : NoAccidental;
+ }
+ }
+
+ if (outputAccidental == NoAccidental && inputAccidental == Natural) {
+ outputAccidental = Natural;
+ }
+
+}
+
+bool
+Pitch::validAccidental() const
+{
+// std::cout << "Checking whether accidental is valid " << std::endl;
+ if (m_accidental == NoAccidental)
+ {
+ return true;
+ }
+ int naturalPitch = (m_pitch -
+ Accidentals::getPitchOffset(m_accidental) + 12) % 12;
+ switch(naturalPitch)
+ {
+ case 0: //C
+ return true;
+ case 1:
+ return false;
+ case 2: //D
+ return true;
+ case 3:
+ return false;
+ case 4: //E
+ return true;
+ case 5: //F
+ return true;
+ case 6:
+ return false;
+ case 7: //G
+ return true;
+ case 8:
+ return false;
+ case 9: //A
+ return true;
+ case 10:
+ return false;
+ case 11: //B
+ return true;
+ };
+ std::cout << "Internal error in validAccidental" << std::endl;
+ return false;
+}
+
+Event *
+Pitch::getAsNoteEvent(timeT absoluteTime, timeT duration) const
+{
+ Event *e = new Event(Note::EventType, absoluteTime, duration);
+ e->set<Int>(BaseProperties::PITCH, m_pitch);
+ e->set<String>(BaseProperties::ACCIDENTAL, m_accidental);
+ return e;
+}
+
+/**
+ * Converts performance pitch to height on staff + correct accidentals
+ * for current key.
+ *
+ * This method takes a Clef, Key, Accidental and raw performance pitch, then
+ * applies this information to return a height on staff value and an
+ * accidental state. The pitch itself contains a lot of information, but we
+ * need to use the Key and user-specified Accidental to make an accurate
+ * decision just where to put it on the staff, and what accidental it should
+ * display for (or against) the key.
+ *
+ * This function originally written by Chris Cannam for Rosegarden 2.1
+ * Entirely rewritten by Chris Cannam for Rosegarden 4
+ * Entirely rewritten by Hans Kieserman
+ * Entirely rewritten by Michael McIntyre
+ * This version by Michael McIntyre <[email protected]>
+ * Resolving the accidental was refactored out by Arnout Engelen
+ */
+void
+Pitch::rawPitchToDisplayPitch(int rawpitch,
+ const Clef &clef,
+ const Key &key,
+ int &height,
+ Accidental &accidental,
+ NoAccidentalStrategy noAccidentalStrategy)
+{
+
+ // 1. Calculate the octave (for later):
+ int octave = rawpitch / 12;
+
+ // 2. Set initial height to 0
+ height = 0;
+
+ // 3. Calculate raw semitone number, yielding a value between 0 (C) and
+ // 11 (B)
+ int pitch = rawpitch % 12;
+
+ // clear the in-coming accidental so we can trap any failure to re-set
+ // it on the way out:
+ Accidental userAccidental = accidental;
+ accidental = "";
+
+ if (userAccidental == NoAccidental || !Pitch(rawpitch, userAccidental).validAccidental())
+ {
+ userAccidental = resolveNoAccidental(pitch, key, noAccidentalStrategy);
+ //std::cout << "Chose accidental " << userAccidental << " for pitch " << pitch <<
+ // " in key " << key.getName() << std::endl;
+ }
+ //else
+ //{
+ // std::cout << "Accidental was specified, as " << userAccidental << std::endl;
+ //}
+
+ resolveSpecifiedAccidental(pitch, clef, key, height, octave, userAccidental, accidental);
+
+ // Failsafe... If this ever executes, there's trouble to fix...
+// WIP - DMM - munged up to explore #937389, which is temporarily deferred,
+// owing to its non-critical nature, having been hacked around in the LilyPond
+// code
+#ifndef DEBUG_PITCH
+ if (accidental == "") {
+ std::cerr << "Pitch::rawPitchToDisplayPitch(): error! returning null accidental for:"
+#else
+ std::cerr << "Pitch::rawPitchToDisplayPitch(): calculating: "
+#endif
+ << std::endl << "pitch: " << rawpitch << " (" << pitch << " in oct "
+ << octave << ") userAcc: " << userAccidental
+ << " clef: " << clef.getClefType() << " key: " << key.getName() << std::endl;
+#ifndef DEBUG_PITCH
+ }
+#endif
+
+
+ // 6. "Recenter" height in case it's been changed:
+ height = ((height + 2) % 7) - 2;
+
+ height += (octave - 5) * 7;
+ height += clef.getPitchOffset();
+
+
+ // 7. Transpose up or down for the clef:
+ height -= 7 * clef.getOctave();
+}
+
+void
+Pitch::displayPitchToRawPitch(int height,
+ Accidental accidental,
+ const Clef &clef,
+ const Key &key,
+ int &pitch,
+ bool ignoreOffset)
+{
+ int octave = 5;
+
+ // 1. Ask Key for accidental if necessary
+ if (accidental == NoAccidental) {
+ accidental = key.getAccidentalAtHeight(height, clef);
+ }
+
+ // 2. Get pitch and correct octave
+
+ if (!ignoreOffset) height -= clef.getPitchOffset();
+
+ while (height < 0) { octave -= 1; height += 7; }
+ while (height >= 7) { octave += 1; height -= 7; }
+
+ if (height > 4) ++octave;
+
+ // Height is now relative to treble clef lines
+ switch (height) {
+
+ case 0: pitch = 4; break; /* bottom line, treble clef: E */
+ case 1: pitch = 5; break; /* F */
+ case 2: pitch = 7; break; /* G */
+ case 3: pitch = 9; break; /* A, in next octave */
+ case 4: pitch = 11; break; /* B, likewise*/
+ case 5: pitch = 0; break; /* C, moved up an octave (see above) */
+ case 6: pitch = 2; break; /* D, likewise */
+ }
+ // Pitch is now "natural"-ized note at given height
+
+ // 3. Adjust pitch for accidental
+
+ if (accidental != NoAccidental &&
+ accidental != Natural) {
+ if (accidental == Sharp) { pitch++; }
+ else if (accidental == Flat) { pitch--; }
+ else if (accidental == DoubleSharp) { pitch += 2; }
+ else if (accidental == DoubleFlat) { pitch -= 2; }
+ }
+
+ // 4. Adjust for clef
+ octave += clef.getOctave();
+
+ pitch += 12 * octave;
+}
+
+
+
+Pitch::Pitch(const Event &e) :
+ // throw (Event::NoData)
+ m_accidental(NoAccidental)
+{
+ m_pitch = e.get<Int>(BaseProperties::PITCH);
+ e.get<String>(BaseProperties::ACCIDENTAL, m_accidental);
+}
+
+Pitch::Pitch(int performancePitch, const Accidental &explicitAccidental) :
+ m_pitch(performancePitch),
+ m_accidental(explicitAccidental)
+{
+ // nothing
+}
+
+Pitch::Pitch(int pitchInOctave, int octave,
+ const Accidental &explicitAccidental, int octaveBase) :
+ m_pitch((octave - octaveBase) * 12 + pitchInOctave),
+ m_accidental(explicitAccidental)
+{
+ // nothing else
+}
+
+Pitch::Pitch(int noteInScale, int octave, const Key &key,
+ const Accidental &explicitAccidental, int octaveBase) :
+ m_pitch(0),
+ m_accidental(explicitAccidental)
+{
+ m_pitch = (key.getTonicPitch());
+ m_pitch = (octave - octaveBase) * 12 + m_pitch % 12;
+
+ if (key.isMinor()) m_pitch += scale_Cminor_harmonic[noteInScale];
+ else m_pitch += scale_Cmajor[noteInScale];
+
+ m_pitch += Accidentals::getPitchOffset(m_accidental);
+}
+
+Pitch::Pitch(int noteInCMajor, int octave, int pitch,
+ int octaveBase) :
+ m_pitch(pitch)
+{
+ int natural = (octave - octaveBase) * 12 + scale_Cmajor[noteInCMajor];
+ m_accidental = Accidentals::getAccidental(pitch - natural);
+}
+
+
+Pitch::Pitch(char noteName, int octave, const Key &key,
+ const Accidental &explicitAccidental, int octaveBase) :
+ m_pitch(0),
+ m_accidental(explicitAccidental)
+{
+ int height = getIndexForNote(noteName) - 2;
+ displayPitchToRawPitch(height, explicitAccidental,
+ Clef(), key, m_pitch);
+
+ // we now have the pitch within octave 5 (C == 60) -- though it
+ // might have spilled over at either end
+ if (m_pitch < 60) --octave;
+ if (m_pitch > 71) ++octave;
+ m_pitch = (octave - octaveBase) * 12 + m_pitch % 12;
+}
+
+Pitch::Pitch(int heightOnStaff, const Clef &clef, const Key &key,
+ const Accidental &explicitAccidental) :
+ m_pitch(0),
+ m_accidental(explicitAccidental)
+{
+ displayPitchToRawPitch
+ (heightOnStaff, explicitAccidental, clef, key, m_pitch);
+}
+
+Pitch::Pitch(const Pitch &p) :
+ m_pitch(p.m_pitch),
+ m_accidental(p.m_accidental)
+{
+ // nothing else
+}
+
+Pitch &
+Pitch::operator=(const Pitch &p)
+{
+ if (&p != this) {
+ m_pitch = p.m_pitch;
+ m_accidental = p.m_accidental;
+ }
+ return *this;
+}
+
+int
+Pitch::getPerformancePitch() const
+{
+ return m_pitch;
+}
+
+Accidental
+Pitch::getAccidental(bool useSharps) const
+{
+ return getDisplayAccidental(Key("C major"),
+ useSharps ? UseSharps : UseFlats);
+}
+
+Accidental
+Pitch::getAccidental(const Key &key) const
+{
+ if (m_accidental == NoAccidental || !validAccidental())
+ {
+ Accidental retval = resolveNoAccidental(m_pitch, key, UseKey);
+ //std::cout << "Resolved No/invalid accidental: chose " << retval << std::endl;
+ return retval;
+ }
+ else
+ {
+ //std::cout << "Returning specified accidental" << std::endl;
+ return m_accidental;
+ }
+}
+
+Accidental
+Pitch::getDisplayAccidental(const Key &key) const
+{
+ return getDisplayAccidental(key, UseKey);
+}
+
+Accidental
+Pitch::getDisplayAccidental(const Key &key, NoAccidentalStrategy noAccidentalStrategy) const
+{
+ int heightOnStaff;
+ Accidental accidental(m_accidental);
+ rawPitchToDisplayPitch(m_pitch, Clef(), key, heightOnStaff, accidental, noAccidentalStrategy);
+ return accidental;
+}
+
+int
+Pitch::getNoteInScale(const Key &key) const
+{
+ int p = m_pitch;
+ p -= key.getTonicPitch();
+ p -= Accidentals::getPitchOffset(getDisplayAccidental(key));
+ p += 24; // in case these calculations made it -ve
+ p %= 12;
+
+ if (key.isMinor()) return steps_Cminor_harmonic[p];
+ else return steps_Cmajor[p];
+}
+
+char
+Pitch::getNoteName(const Key &key) const
+{
+ int index = (getHeightOnStaff(Clef(Clef::Treble), key) + 72) % 7;
+ return getNoteForIndex(index);
+}
+
+int
+Pitch::getHeightOnStaff(const Clef &clef, const Key &key) const
+{
+ int heightOnStaff;
+ Accidental accidental(m_accidental);
+ rawPitchToDisplayPitch(m_pitch, clef, key, heightOnStaff, accidental, UseKey);
+ return heightOnStaff;
+}
+
+int
+Pitch::getHeightOnStaff(const Clef &clef, bool useSharps) const
+{
+ int heightOnStaff;
+ Accidental accidental(m_accidental);
+ rawPitchToDisplayPitch(m_pitch, clef, Key("C major"), heightOnStaff, accidental,
+ useSharps ? UseSharps : UseFlats);
+ return heightOnStaff;
+}
+
+int
+Pitch::getOctave(int octaveBase) const
+{
+ return m_pitch / 12 + octaveBase;
+}
+
+int
+Pitch::getPitchInOctave() const
+{
+ return m_pitch % 12;
+}
+
+bool
+Pitch::isDiatonicInKey(const Key &key) const
+{
+ if (getDisplayAccidental(key) == Accidentals::NoAccidental) return true;
+
+ // ### as used in the chord identifiers, this calls chords built on
+ // the raised sixth step diatonic -- may be correct, but it's
+ // misleading, as we're really looking for whether chords are
+ // often built on given tone
+
+ if (key.isMinor()) {
+ int stepsFromTonic = ((m_pitch - key.getTonicPitch() + 12) % 12);
+ if (stepsFromTonic == 9 || stepsFromTonic == 11) return true;
+ }
+
+ return false;
+}
+
+std::string
+Pitch::getAsString(bool useSharps, bool inclOctave, int octaveBase) const
+{
+ Accidental acc = getAccidental(useSharps);
+
+ std::string s;
+ s += getNoteName(useSharps ? Key("C major") : Key("A minor"));
+
+ if (acc == Accidentals::Sharp) s += "#";
+ else if (acc == Accidentals::Flat) s += "b";
+
+ if (!inclOctave) return s;
+
+ char tmp[10];
+ sprintf(tmp, "%s%d", s.c_str(), getOctave(octaveBase));
+ return std::string(tmp);
+}
+
+int
+Pitch::getIndexForNote(char noteName)
+{
+ if (islower(noteName)) noteName = toupper(noteName);
+ if (noteName < 'C') {
+ if (noteName < 'A') return 0; // error, really
+ else return noteName - 'A' + 5;
+ } else {
+ if (noteName > 'G') return 0; // error, really
+ else return noteName - 'C';
+ }
+}
+
+char
+Pitch::getNoteForIndex(int index)
+{
+ if (index < 0 || index > 6) return 'C'; // error, really
+ return "CDEFGAB"[index];
+}
+
+int
+Pitch::getPerformancePitchFromRG21Pitch(int heightOnStaff,
+ const Accidental &accidental,
+ const Clef &clef,
+ const Key &)
+{
+ // Rosegarden 2.1 pitches are a bit weird; see
+ // docs/data_struct/units.txt
+
+ // We pass the accidental and clef, a faked key of C major, and a
+ // flag telling displayPitchToRawPitch to ignore the clef offset
+ // and take only its octave into account
+
+ int p = 0;
+ displayPitchToRawPitch(heightOnStaff, accidental, clef, Key(), p, true);
+ return p;
+}
+
+Pitch Pitch::transpose(const Key &key, int pitchDelta, int heightDelta)
+{
+ // get old accidental
+ Accidental oldAccidental = getAccidental(key);
+
+ // get old step
+ // TODO: maybe we should write an oldPitchObj.getOctave(0, key) that takes into account accidentals
+ // properly (e.g. yielding '0' instead of '1' for B#0). For now workaround here.
+ Pitch oldPitchWithoutAccidental(getPerformancePitch() - Accidentals::getPitchOffset(oldAccidental), Natural);
+ Key cmaj = Key();
+ int oldStep = oldPitchWithoutAccidental.getNoteInScale(cmaj) + oldPitchWithoutAccidental.getOctave(0) * 7;
+
+ // calculate new pitch and step
+ int newPitch = getPerformancePitch() + pitchDelta;
+ int newStep = oldStep + heightDelta;
+
+ // could happen for example when transposing the tonic of a key downwards
+ if (newStep < 0 || newPitch < 0) {
+ newStep += 7;
+ newPitch += 12;
+ }
+
+ // should not happen
+ if (newStep < 0 || newPitch < 0) {
+ std::cerr << "Internal error in NotationTypes, Pitch::transpose()"
+ << std::endl;
+ }
+
+ // calculate new accidental for step
+ int pitchWithoutAccidental = ((newStep / 7) * 12 + scale_Cmajor[newStep % 7]);
+ int newAccidentalOffset = newPitch - pitchWithoutAccidental;
+
+ // construct pitch-object to return
+ Pitch newPitchObj(newPitch, Accidentals::getAccidental(newAccidentalOffset));
+ return newPitchObj;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Note
+//////////////////////////////////////////////////////////////////////
+
+const string Note::EventType = "note";
+const string Note::EventRestType = "rest";
+const int Note::EventRestSubOrdering = 10;
+
+const timeT Note::m_shortestTime = basePPQ / 16;
+
+Note& Note::operator=(const Note &n)
+{
+ if (&n == this) return *this;
+ m_type = n.m_type;
+ m_dots = n.m_dots;
+ return *this;
+}
+
+timeT Note::getDurationAux() const
+{
+ int duration = m_shortestTime * (1 << m_type);
+ int extra = duration / 2;
+ for (int dots = m_dots; dots > 0; --dots) {
+ duration += extra;
+ extra /= 2;
+ }
+ return duration;
+}
+
+
+Note Note::getNearestNote(timeT duration, int maxDots)
+{
+ int tag = Shortest - 1;
+ timeT d(duration / m_shortestTime);
+ while (d > 0) { ++tag; d /= 2; }
+
+// cout << "Note::getNearestNote: duration " << duration <<
+// " leading to tag " << tag << endl;
+ if (tag < Shortest) return Note(Shortest);
+ if (tag > Longest) return Note(Longest, maxDots);
+
+ timeT prospective = Note(tag, 0).getDuration();
+ int dots = 0;
+ timeT extra = prospective / 2;
+
+ while (dots <= maxDots &&
+ dots <= tag) { // avoid TooManyDots exception from Note ctor
+ prospective += extra;
+ if (prospective > duration) return Note(tag, dots);
+ extra /= 2;
+ ++dots;
+// cout << "added another dot okay" << endl;
+ }
+
+ if (tag < Longest) return Note(tag + 1, 0);
+ else return Note(tag, std::max(maxDots, tag));
+}
+
+Event *Note::getAsNoteEvent(timeT absoluteTime, int pitch) const
+{
+ Event *e = new Event(EventType, absoluteTime, getDuration());
+ e->set<Int>(BaseProperties::PITCH, pitch);
+ return e;
+}
+
+Event *Note::getAsRestEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventRestType, absoluteTime, getDuration());
+ return e;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////
+// TimeSignature
+//////////////////////////////////////////////////////////////////////
+
+const string TimeSignature::EventType = "timesignature";
+const int TimeSignature::EventSubOrdering = -150;
+const PropertyName TimeSignature::NumeratorPropertyName = "numerator";
+const PropertyName TimeSignature::DenominatorPropertyName = "denominator";
+const PropertyName TimeSignature::ShowAsCommonTimePropertyName = "common";
+const PropertyName TimeSignature::IsHiddenPropertyName = "hidden";
+const PropertyName TimeSignature::HasHiddenBarsPropertyName = "hiddenbars";
+const TimeSignature TimeSignature::DefaultTimeSignature = TimeSignature(4, 4);
+
+TimeSignature::TimeSignature(int numerator, int denominator,
+ bool preferCommon, bool hidden, bool hiddenBars)
+ // throw (BadTimeSignature)
+ : m_numerator(numerator), m_denominator(denominator),
+ m_common(preferCommon &&
+ (m_denominator == m_numerator &&
+ (m_numerator == 2 || m_numerator == 4))),
+ m_hidden(hidden),
+ m_hiddenBars(hiddenBars)
+{
+ if (numerator < 1 || denominator < 1) {
+ throw BadTimeSignature("Numerator and denominator must be positive");
+ }
+}
+
+TimeSignature::TimeSignature(const Event &e)
+ // throw (Event::NoData, Event::BadType, BadTimeSignature)
+{
+ if (e.getType() != EventType) {
+ throw Event::BadType("TimeSignature model event", EventType, e.getType());
+ }
+ m_numerator = 4;
+ m_denominator = 4;
+
+ if (e.has(NumeratorPropertyName)) {
+ m_numerator = e.get<Int>(NumeratorPropertyName);
+ }
+
+ if (e.has(DenominatorPropertyName)) {
+ m_denominator = e.get<Int>(DenominatorPropertyName);
+ }
+
+ m_common = false;
+ e.get<Bool>(ShowAsCommonTimePropertyName, m_common);
+
+ m_hidden = false;
+ e.get<Bool>(IsHiddenPropertyName, m_hidden);
+
+ m_hiddenBars = false;
+ e.get<Bool>(HasHiddenBarsPropertyName, m_hiddenBars);
+
+ if (m_numerator < 1 || m_denominator < 1) {
+ throw BadTimeSignature("Numerator and denominator must be positive");
+ }
+}
+
+TimeSignature& TimeSignature::operator=(const TimeSignature &ts)
+{
+ if (&ts == this) return *this;
+ m_numerator = ts.m_numerator;
+ m_denominator = ts.m_denominator;
+ m_common = ts.m_common;
+ m_hidden = ts.m_hidden;
+ m_hiddenBars = ts.m_hiddenBars;
+ return *this;
+}
+
+timeT TimeSignature::getBarDuration() const
+{
+ setInternalDurations();
+ return m_barDuration;
+}
+
+timeT TimeSignature::getBeatDuration() const
+{
+ setInternalDurations();
+ return m_beatDuration;
+}
+
+timeT TimeSignature::getUnitDuration() const
+{
+ return m_crotchetTime * 4 / m_denominator;
+}
+
+Note::Type TimeSignature::getUnit() const
+{
+ int c, d;
+ for (c = 0, d = m_denominator; d > 1; d /= 2) ++c;
+ return Note::Semibreve - c;
+}
+
+bool TimeSignature::isDotted() const
+{
+ setInternalDurations();
+ return m_dotted;
+}
+
+Event *TimeSignature::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<Int>(NumeratorPropertyName, m_numerator);
+ e->set<Int>(DenominatorPropertyName, m_denominator);
+ e->set<Bool>(ShowAsCommonTimePropertyName, m_common);
+ e->set<Bool>(IsHiddenPropertyName, m_hidden);
+ e->set<Bool>(HasHiddenBarsPropertyName, m_hiddenBars);
+ return e;
+}
+
+// This doesn't consider subdivisions of the bar larger than a beat in
+// any time other than 4/4, but it should handle the usual time signatures
+// correctly (compound time included).
+
+void TimeSignature::getDurationListForInterval(DurationList &dlist,
+ timeT duration,
+ timeT startOffset) const
+{
+ setInternalDurations();
+
+ timeT offset = startOffset;
+ timeT durationRemaining = duration;
+
+ while (durationRemaining > 0) {
+
+ // Everything in this loop is of the form, "if we're on a
+ // [unit] boundary and there's a [unit] of space left to fill,
+ // insert a [unit] of time."
+
+ // See if we can insert a bar of time.
+
+ if (offset % m_barDuration == 0
+ && durationRemaining >= m_barDuration) {
+
+ getDurationListForBar(dlist);
+ durationRemaining -= m_barDuration,
+ offset += m_barDuration;
+
+ }
+
+ // If that fails and we're in 4/4 time, see if we can insert a
+ // half-bar of time.
+
+ //_else_ if!
+ else if (m_numerator == 4 && m_denominator == 4
+ && offset % (m_barDuration/2) == 0
+ && durationRemaining >= m_barDuration/2) {
+
+ dlist.push_back(m_barDuration/2);
+ durationRemaining -= m_barDuration/2;
+ offset += m_barDuration;
+
+ }
+
+ // If that fails, see if we can insert a beat of time.
+
+ else if (offset % m_beatDuration == 0
+ && durationRemaining >= m_beatDuration) {
+
+ dlist.push_back(m_beatDuration);
+ durationRemaining -= m_beatDuration;
+ offset += m_beatDuration;
+
+ }
+
+ // If that fails, see if we can insert a beat-division of time
+ // (half the beat in simple time, a third of the beat in compound
+ // time)
+
+ else if (offset % m_beatDivisionDuration == 0
+ && durationRemaining >= m_beatDivisionDuration) {
+
+ dlist.push_back(m_beatDivisionDuration);
+ durationRemaining -= m_beatDivisionDuration;
+ offset += m_beatDivisionDuration;
+
+ }
+
+ // cc: In practice, if the time we have remaining is shorter
+ // than our shortest note then we should just insert a single
+ // unit of the correct time; we won't be able to do anything
+ // useful with any shorter units anyway.
+
+ else if (durationRemaining <= Note(Note::Shortest).getDuration()) {
+
+ dlist.push_back(durationRemaining);
+ offset += durationRemaining;
+ durationRemaining = 0;
+
+ }
+
+ // If that fails, keep halving the beat division until we
+ // find something to insert. (This could be part of the beat-division
+ // case; it's only in its own place for clarity.)
+
+ else {
+
+ timeT currentDuration = m_beatDivisionDuration;
+
+ while ( !(offset % currentDuration == 0
+ && durationRemaining >= currentDuration) ) {
+
+ if (currentDuration <= Note(Note::Shortest).getDuration()) {
+
+ // okay, this isn't working. If our duration takes
+ // us past the next beat boundary, fill with an exact
+ // rest duration to there and then continue --cc
+
+ timeT toNextBeat =
+ m_beatDuration - (offset % m_beatDuration);
+
+ if (durationRemaining > toNextBeat) {
+ currentDuration = toNextBeat;
+ } else {
+ currentDuration = durationRemaining;
+ }
+ break;
+ }
+
+ currentDuration /= 2;
+ }
+
+ dlist.push_back(currentDuration);
+ durationRemaining -= currentDuration;
+ offset += currentDuration;
+
+ }
+
+ }
+
+}
+
+void TimeSignature::getDurationListForBar(DurationList &dlist) const
+{
+
+ // If the bar's length can be represented with one long symbol, do it.
+ // Otherwise, represent it as individual beats.
+
+ if (m_barDuration == m_crotchetTime ||
+ m_barDuration == m_crotchetTime * 2 ||
+ m_barDuration == m_crotchetTime * 4 ||
+ m_barDuration == m_crotchetTime * 8 ||
+ m_barDuration == m_dottedCrotchetTime ||
+ m_barDuration == m_dottedCrotchetTime * 2 ||
+ m_barDuration == m_dottedCrotchetTime * 4 ||
+ m_barDuration == m_dottedCrotchetTime * 8) {
+
+ dlist.push_back(getBarDuration());
+
+ } else {
+
+ for (int i = 0; i < getBeatsPerBar(); ++i) {
+ dlist.push_back(getBeatDuration());
+ }
+
+ }
+
+}
+
+int TimeSignature::getEmphasisForTime(timeT offset)
+{
+ setInternalDurations();
+
+ if (offset % m_barDuration == 0)
+ return 4;
+ else if (m_numerator == 4 && m_denominator == 4 &&
+ offset % (m_barDuration/2) == 0)
+ return 3;
+ else if (offset % m_beatDuration == 0)
+ return 2;
+ else if (offset % m_beatDivisionDuration == 0)
+ return 1;
+ else
+ return 0;
+}
+
+
+void TimeSignature::getDivisions(int depth, std::vector<int> &divisions) const
+{
+ divisions.clear();
+
+ if (depth <= 0) return;
+ timeT base = getBarDuration(); // calls setInternalDurations
+/*
+ if (m_numerator == 4 && m_denominator == 4) {
+ divisions.push_back(2);
+ base /= 2;
+ --depth;
+ }
+*/
+ if (depth <= 0) return;
+
+ divisions.push_back(base / m_beatDuration);
+ base = m_beatDuration;
+ --depth;
+
+ if (depth <= 0) return;
+
+ if (m_dotted) divisions.push_back(3);
+ else divisions.push_back(2);
+ --depth;
+
+ while (depth > 0) {
+ divisions.push_back(2);
+ --depth;
+ }
+
+ return;
+}
+
+
+void TimeSignature::setInternalDurations() const
+{
+ int unitLength = m_crotchetTime * 4 / m_denominator;
+
+ m_barDuration = m_numerator * unitLength;
+
+ // Is 3/8 dotted time? This will report that it isn't, because of
+ // the check for m_numerator > 3 -- but otherwise we'd get a false
+ // positive with 3/4
+
+ // [rf] That's an acceptable answer, according to my theory book. In
+ // practice, you can say it's dotted time iff it has 6, 9, or 12 on top.
+
+ m_dotted = (m_numerator % 3 == 0 &&
+ m_numerator > 3 &&
+ m_barDuration >= m_dottedCrotchetTime);
+
+ if (m_dotted) {
+ m_beatDuration = unitLength * 3;
+ m_beatDivisionDuration = unitLength;
+ }
+ else {
+ m_beatDuration = unitLength;
+ m_beatDivisionDuration = unitLength / 2;
+ }
+
+}
+
+const timeT TimeSignature::m_crotchetTime = basePPQ;
+const timeT TimeSignature::m_dottedCrotchetTime = basePPQ + basePPQ/2;
+
+
+
+//////////////////////////////////////////////////////////////////////
+// AccidentalTable
+//////////////////////////////////////////////////////////////////////
+
+AccidentalTable::AccidentalTable(const Key &key, const Clef &clef,
+ OctaveType octaves, BarResetType barReset) :
+ m_key(key), m_clef(clef),
+ m_octaves(octaves), m_barReset(barReset)
+{
+ // nothing else
+}
+
+AccidentalTable::AccidentalTable(const AccidentalTable &t) :
+ m_key(t.m_key), m_clef(t.m_clef),
+ m_octaves(t.m_octaves), m_barReset(t.m_barReset),
+ m_accidentals(t.m_accidentals),
+ m_canonicalAccidentals(t.m_canonicalAccidentals),
+ m_newAccidentals(t.m_newAccidentals),
+ m_newCanonicalAccidentals(t.m_newCanonicalAccidentals)
+{
+ // nothing else
+}
+
+AccidentalTable &
+AccidentalTable::operator=(const AccidentalTable &t)
+{
+ if (&t != this) {
+ m_key = t.m_key;
+ m_clef = t.m_clef;
+ m_octaves = t.m_octaves;
+ m_barReset = t.m_barReset;
+ m_accidentals = t.m_accidentals;
+ m_canonicalAccidentals = t.m_canonicalAccidentals;
+ m_newAccidentals = t.m_newAccidentals;
+ m_newCanonicalAccidentals = t.m_newCanonicalAccidentals;
+ }
+ return *this;
+}
+
+Accidental
+AccidentalTable::processDisplayAccidental(const Accidental &acc0, int height,
+ bool &cautionary)
+{
+ Accidental acc = acc0;
+
+ int canonicalHeight = Key::canonicalHeight(height);
+ Accidental keyAcc = m_key.getAccidentalAtHeight(canonicalHeight, m_clef);
+
+ Accidental normalAcc = NoAccidental;
+ Accidental canonicalAcc = NoAccidental;
+ Accidental prevBarAcc = NoAccidental;
+
+ if (m_octaves == OctavesEquivalent ||
+ m_octaves == OctavesCautionary) {
+
+ AccidentalMap::iterator i = m_canonicalAccidentals.find(canonicalHeight);
+ if (i != m_canonicalAccidentals.end() && !i->second.previousBar) {
+ canonicalAcc = i->second.accidental;
+ }
+ }
+
+ if (m_octaves == OctavesEquivalent) {
+ normalAcc = canonicalAcc;
+ } else {
+ AccidentalMap::iterator i = m_accidentals.find(height);
+ if (i != m_accidentals.end() && !i->second.previousBar) {
+ normalAcc = i->second.accidental;
+ }
+ }
+
+ if (m_barReset != BarResetNone) {
+ AccidentalMap::iterator i = m_accidentals.find(height);
+ if (i != m_accidentals.end() && i->second.previousBar) {
+ prevBarAcc = i->second.accidental;
+ }
+ }
+
+// std::cerr << "AccidentalTable::processDisplayAccidental: acc " << acc0 << ", h " << height << ", caut " << cautionary << ", ch " << canonicalHeight << ", keyacc " << keyAcc << " canacc " << canonicalAcc << " noracc " << normalAcc << " oct " << m_octaves << " barReset = " << m_barReset << " pbacc " << prevBarAcc << std::endl;
+
+ if (acc == NoAccidental) acc = keyAcc;
+
+ if (m_octaves == OctavesIndependent ||
+ m_octaves == OctavesEquivalent) {
+
+ if (normalAcc == NoAccidental) {
+ normalAcc = keyAcc;
+ }
+
+ if (acc == normalAcc) {
+ if (!cautionary) acc = NoAccidental;
+ } else if (acc == NoAccidental) {
+ if (normalAcc != Natural) {
+ acc = Natural;
+ }
+ }
+
+ } else {
+
+ if (normalAcc != NoAccidental) {
+ if (acc != normalAcc) {
+ if (acc == NoAccidental) {
+ if (normalAcc != Natural) {
+ acc = Natural;
+ }
+ }
+ } else { // normalAcc != NoAccidental, acc == normalAcc
+ if (canonicalAcc != NoAccidental && canonicalAcc != normalAcc) {
+ cautionary = true;
+ } else { // canonicalAcc == NoAccidental || canonicalAcc == normalAcc
+ if (!cautionary) {
+ acc = NoAccidental;
+ }
+ }
+ }
+ } else { // normalAcc == NoAccidental
+ if (acc != keyAcc && keyAcc != Natural) {
+ if (acc == NoAccidental) {
+ acc = Natural;
+ }
+ } else { // normalAcc == NoAccidental, acc == keyAcc
+ if (canonicalAcc != NoAccidental && canonicalAcc != keyAcc) {
+ cautionary = true;
+ if (acc == NoAccidental) {
+ acc = Natural;
+ }
+ } else { // canonicalAcc == NoAccidental || canonicalAcc == keyAcc
+ if (!cautionary) {
+ acc = NoAccidental;
+ }
+ }
+ }
+ }
+ }
+
+ if (m_barReset != BarResetNone) {
+ if (acc == NoAccidental) {
+ if (prevBarAcc != NoAccidental &&
+ prevBarAcc != keyAcc &&
+ !(prevBarAcc == Natural && keyAcc == NoAccidental)) {
+ cautionary = (m_barReset == BarResetCautionary);
+ if (keyAcc == NoAccidental) {
+ acc = Natural;
+ } else {
+ acc = keyAcc;
+ }
+ }
+ }
+ }
+
+ if (acc != NoAccidental) {
+ m_newAccidentals[height] = AccidentalRec(acc, false);
+ m_newCanonicalAccidentals[canonicalHeight] = AccidentalRec(acc, false);
+ }
+
+ return acc;
+}
+
+void
+AccidentalTable::update()
+{
+ m_accidentals = m_newAccidentals;
+ m_canonicalAccidentals = m_newCanonicalAccidentals;
+}
+
+void
+AccidentalTable::newBar()
+{
+ for (AccidentalMap::iterator i = m_accidentals.begin();
+ i != m_accidentals.end(); ) {
+
+ if (i->second.previousBar) {
+ AccidentalMap::iterator j = i;
+ ++j;
+ m_accidentals.erase(i);
+ i = j;
+ } else {
+ i->second.previousBar = true;
+ ++i;
+ }
+ }
+
+ m_canonicalAccidentals.clear();
+
+ m_newAccidentals = m_accidentals;
+ m_newCanonicalAccidentals.clear();
+}
+
+void
+AccidentalTable::newClef(const Clef &clef)
+{
+ m_clef = clef;
+}
+
+
+} // close namespace
diff --git a/src/base/NotationTypes.h b/src/base/NotationTypes.h
new file mode 100644
index 0000000..9133983
--- /dev/null
+++ b/src/base/NotationTypes.h
@@ -0,0 +1,1342 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _NOTATION_TYPES_H_
+#define _NOTATION_TYPES_H_
+
+#include <list>
+#include <map>
+
+#include "Event.h"
+#include "Instrument.h"
+
+/*
+ * NotationTypes.h
+ *
+ * This file contains definitions of several classes to assist in
+ * creating and manipulating certain event types. The classes are:
+ *
+ * Accidental
+ * Clef
+ * Key
+ * Indication
+ * Pitch
+ * Note
+ * TimeSignature
+ * AccidentalTable
+ *
+ * The classes in this file are _not_ actually used for storing
+ * events. Events are always stored in Event objects (see Event.h).
+ *
+ * These classes are usually constructed on-the-fly when a particular
+ * operation specific to a single sort of event is required, and
+ * usually destroyed as soon as they go out of scope. The most common
+ * usages are for creating events (create an instance of one of these
+ * classes with the data you require, then call getAsEvent on it), for
+ * doing notation-related calculations from existing events (such as
+ * the bar duration of a time signature), and for doing calculations
+ * that are independent of any particular instance of an event (such
+ * as the Note methods that calculate duration-related values without
+ * reference to any specific pitch or other note-event properties; or
+ * everything in Pitch).
+ *
+ * This file also defines the event types and standard property names
+ * for the basic events.
+ */
+
+namespace Rosegarden
+{
+
+extern const int MIN_SUBORDERING;
+
+typedef std::list<int> DurationList;
+
+
+/**
+ * Accidentals are stored in the event as string properties, purely
+ * for clarity. (They aren't manipulated _all_ that often, so this
+ * probably isn't a great inefficiency.) Originally we used an enum
+ * for the Accidental type with conversion functions to and from
+ * strings, but making Accidental a string seems simpler.
+ */
+
+typedef std::string Accidental;
+
+namespace Accidentals
+{
+ extern const Accidental NoAccidental;
+ extern const Accidental Sharp;
+ extern const Accidental Flat;
+ extern const Accidental Natural;
+ extern const Accidental DoubleSharp;
+ extern const Accidental DoubleFlat;
+
+ typedef std::vector<Accidental> AccidentalList;
+
+ /**
+ * When no accidental is specified for a pitch, there are several
+ * strategies to determine what accidental to display for an
+ * out-of-key pitch
+ */
+ enum NoAccidentalStrategy {
+ /** always use sharps */
+ UseSharps,
+ /** always use flats */
+ UseFlats,
+ /** always use sharps or always use flats depending on of what
+ * type of accidentals the current key is made up */
+ UseKeySharpness,
+ /** use the most likely accidental for this key */
+ UseKey
+ };
+
+ /**
+ * Get the predefined accidentals (i.e. the ones listed above)
+ * in their defined order.
+ */
+ extern AccidentalList getStandardAccidentals();
+
+ /**
+ * Get the change in pitch resulting from an accidental: -1 for
+ * flat, 2 for double-sharp, 0 for natural or NoAccidental etc.
+ * This is not as useful as it may seem, as in reality the
+ * effect of an accidental depends on the key as well -- see
+ * the Key and Pitch classes.
+ */
+ extern int getPitchOffset(const Accidental &accidental);
+
+
+ /**
+ * Get the Accidental corresponding to a change in pitch: flat
+ * for -1, double-sharp for 2, natural for 0 etc.
+ *
+ * Useful for tying to code that represents accidentals by
+ * their pitch change.
+ */
+ extern Accidental getAccidental(int pitchChange);
+}
+
+
+/**
+ * Marks, like Accidentals, are stored in the event as string properties.
+ */
+
+typedef std::string Mark;
+
+namespace Marks //!!! This would be better as a class, these days
+{
+ extern const Mark NoMark; // " "
+
+ extern const Mark Accent; // ">"
+ extern const Mark Tenuto; // "-" ("legato" in RG2.1)
+ extern const Mark Staccato; // "."
+ extern const Mark Staccatissimo; // "'"
+ extern const Mark Marcato; // "^"
+ extern const Mark Sforzando; // "sf"
+ extern const Mark Rinforzando; // "rf"
+
+ extern const Mark Trill; // "tr"
+ extern const Mark LongTrill; // with wiggly line
+ extern const Mark TrillLine; // line on its own
+ extern const Mark Turn; // "~"
+
+ extern const Mark Pause; // aka "fermata"
+
+ extern const Mark UpBow; // "v"
+ extern const Mark DownBow; // a square with the bottom side missing
+
+ extern const Mark Mordent;
+ extern const Mark MordentInverted;
+ extern const Mark MordentLong;
+ extern const Mark MordentLongInverted;
+
+ /**
+ * Given a string, return a mark that will be recognised as a
+ * text mark containing that string. For example, the Sforzando
+ * mark is actually defined as getTextMark("sf").
+ */
+ extern Mark getTextMark(std::string text);
+
+ /**
+ * Return true if the given mark is a text mark.
+ */
+ extern bool isTextMark(Mark mark);
+
+ /**
+ * Extract the string from a text mark.
+ */
+ extern std::string getTextFromMark(Mark mark);
+
+ /**
+ * Given a string, return a mark that will be recognised as a
+ * fingering mark containing that string. (We use a string
+ * instead of a number to permit "fingering" marks containing
+ * labels like "+".)
+ */
+ extern Mark getFingeringMark(std::string fingering);
+
+ /**
+ * Return true if the given mark is a fingering mark.
+ */
+ extern bool isFingeringMark(Mark mark);
+
+ /**
+ * Extract the string from a fingering mark.
+ */
+ extern std::string getFingeringFromMark(Mark mark);
+
+ /**
+ * Extract the number of marks from an event.
+ */
+ extern int getMarkCount(const Event &e);
+
+ /**
+ * Extract the marks from an event.
+ */
+ extern std::vector<Mark> getMarks(const Event &e);
+
+ /**
+ * Return the first fingering mark on an event (or NoMark, if none).
+ */
+ extern Mark getFingeringMark(const Event &e);
+
+ /**
+ * Add a mark to an event. If unique is true, add the mark only
+ * if the event does not already have it (otherwise permit
+ * multiple identical marks).
+ */
+ extern void addMark(Event &e, const Mark &mark, bool unique);
+
+ /**
+ * Remove a mark from an event. Returns true if the mark was
+ * there to remove. If the mark was not unique, removes only
+ * the first instance of it.
+ */
+ extern bool removeMark(Event &e, const Mark &mark);
+
+ /**
+ * Returns true if the event has the given mark.
+ */
+ extern bool hasMark(const Event &e, const Mark &mark);
+
+ /**
+ * Get the predefined marks (i.e. the ones listed above) in their
+ * defined order.
+ */
+ extern std::vector<Mark> getStandardMarks();
+}
+
+
+/**
+ * Clefs are represented as one of a set of standard strings, stored
+ * within a clef Event. The Clef class defines those standards and
+ * provides a few bits of information about the clefs.
+ */
+
+class Clef
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+ static const PropertyName ClefPropertyName;
+ static const PropertyName OctaveOffsetPropertyName;
+ static const Clef DefaultClef;
+ typedef Exception BadClefName;
+
+ static const std::string Treble;
+ static const std::string French;
+ static const std::string Soprano;
+ static const std::string Mezzosoprano;
+ static const std::string Alto;
+ static const std::string Tenor;
+ static const std::string Baritone;
+ static const std::string Varbaritone;
+ static const std::string Bass;
+ static const std::string Subbass;
+
+ /**
+ * Construct the default clef (treble).
+ */
+ Clef() : m_clef(DefaultClef.m_clef), m_octaveOffset(0) { }
+
+ /**
+ * Construct a Clef from the clef data in the given event. If the
+ * event is not of clef type or contains insufficient data, this
+ * returns the default clef (with a warning). You should normally
+ * test Clef::isValid() to catch that before construction.
+ */
+ Clef(const Event &e);
+
+ /**
+ * Construct a Clef from the given data. Throws a BadClefName
+ * exception if the given string does not match one of the above
+ * clef name constants.
+ */
+ Clef(const std::string &s, int octaveOffset = 0);
+
+ Clef(const Clef &c) : m_clef(c.m_clef), m_octaveOffset(c.m_octaveOffset) {
+ }
+
+ Clef &operator=(const Clef &c);
+
+ bool operator==(const Clef &c) const {
+ return c.m_clef == m_clef && c.m_octaveOffset == m_octaveOffset;
+ }
+
+ bool operator!=(const Clef &c) const {
+ return !(c == *this);
+ }
+
+ ~Clef() { }
+
+ /**
+ * Test whether the given event is a valid Clef event.
+ */
+ static bool isValid(const Event &e);
+
+ /**
+ * Return the basic clef type (Treble, French, Soprano, Mezzosoprano, Alto, Tenor, Baritone, Varbaritone, Bass, Subbass)
+ */
+ std::string getClefType() const { return m_clef; }
+
+ /**
+ * Return any additional octave offset, that is, return 1 for
+ * a clef shifted an 8ve up, etc
+ */
+ int getOctaveOffset() const { return m_octaveOffset; }
+
+ /**
+ * Return the number of semitones a pitch in the treble clef would
+ * have to be lowered by in order to be drawn with the same height
+ * and accidental in this clef
+ */
+ int getTranspose() const;
+
+ /**
+ * Return the octave component of getTranspose(), i.e. the number
+ * of octaves difference in pitch between this clef and the treble
+ */
+ int getOctave() const;
+
+ /**
+ * Return the intra-octave component of getTranspose(), i.e. the
+ * number of semitones this clef is distinct in pitch from the treble
+ * besides the difference in octaves
+ */
+ int getPitchOffset() const;
+
+ /**
+ * Return the height-on-staff (in Pitch terminology)
+ * of the clef's axis -- the line around which the clef is drawn.
+ */
+ int getAxisHeight() const;
+
+ typedef std::vector<Clef> ClefList;
+
+ /**
+ * Return all the clefs, in ascending order of pitch
+ */
+ static ClefList getClefs();
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ std::string m_clef;
+ int m_octaveOffset;
+};
+
+/**
+ * All we store in a key Event is the name of the key. A Key object
+ * can be constructed from such an Event or just from its name, and
+ * will return all the properties of the key. The Key class also
+ * provides some useful mechanisms for getting information about and
+ * transposing between keys.
+ */
+
+class Key
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+ static const PropertyName KeyPropertyName;
+ static const Key DefaultKey;
+ typedef Exception BadKeyName;
+ typedef Exception BadKeySpec;
+
+ /**
+ * Construct the default key (C major).
+ */
+ Key();
+
+ /**
+ * Construct a Key from the key data in the given event. If the
+ * event is not of key type or contains insufficient data, this
+ * returns the default key (with a warning). You should normally
+ * test Key::isValid() to catch that before construction.
+ */
+ Key(const Event &e);
+
+ /**
+ * Construct the named key. Throws a BadKeyName exception if the
+ * given string does not match one of the known key names.
+ */
+ Key(const std::string &name);
+
+ /**
+ * Construct a key from signature and mode. May throw a
+ * BadKeySpec exception.
+ */
+ Key(int accidentalCount, bool isSharp, bool isMinor);
+
+ /**
+ * Construct the key with the given tonic and mode. (Ambiguous.)
+ * May throw a BadKeySpec exception.
+ */
+ Key(int tonicPitch, bool isMinor);
+
+ Key(const Key &kc);
+
+ ~Key() {
+ delete m_accidentalHeights;
+ }
+
+ Key &operator=(const Key &kc);
+
+ bool operator==(const Key &k) const {
+ return k.m_name == m_name;
+ }
+
+ bool operator!=(const Key &k) const {
+ return !(k == *this);
+ }
+
+ /**
+ * Test whether the given event is a valid Key event.
+ */
+ static bool isValid(const Event &e);
+
+ /**
+ * Return true if this is a minor key. Unlike in RG2.1,
+ * we distinguish between major and minor keys with the
+ * same signature.
+ */
+ bool isMinor() const {
+ return m_keyDetailMap[m_name].m_minor;
+ }
+
+ /**
+ * Return true if this key's signature is made up of
+ * sharps, false if flats.
+ */
+ bool isSharp() const {
+ return m_keyDetailMap[m_name].m_sharps;
+ }
+
+ /**
+ * Return the pitch of the tonic note in this key, as a
+ * MIDI (or RG4) pitch modulo 12 (i.e. in the range 0-11).
+ * This is the pitch of the note named in the key's name,
+ * e.g. 0 for the C in C major.
+ */
+ int getTonicPitch() const {
+ return m_keyDetailMap[m_name].m_tonicPitch;
+ }
+
+ /**
+ * Return the number of sharps or flats in the key's signature.
+ */
+ int getAccidentalCount() const {
+ return m_keyDetailMap[m_name].m_sharpCount;
+ }
+
+ /**
+ * Return the key with the same signature but different
+ * major/minor mode. For example if called on C major,
+ * returns A minor.
+ */
+ Key getEquivalent() const {
+ return Key(m_keyDetailMap[m_name].m_equivalence);
+ }
+
+ /**
+ * Return the name of the key, in a human-readable form
+ * also suitable for passing to the Key constructor.
+ */
+ std::string getName() const {
+ return m_name;
+ }
+
+ /**
+ * Return the name of the key, in the form used by RG2.1.
+ */
+ std::string getRosegarden2Name() const {
+ return m_keyDetailMap[m_name].m_rg2name;
+ }
+
+ /**
+ * Return the accidental at the given height-on-staff
+ * (in Pitch terminology) in the given clef.
+ */
+ Accidental getAccidentalAtHeight(int height, const Clef &clef) const;
+
+ /**
+ * Return the accidental for the the given number of steps
+ * from the tonic. For example: for F major, step '3' is the
+ * Bb, so getAccidentalForStep(3) will yield a Flat.
+ */
+ Accidental getAccidentalForStep(int steps) const;
+
+ /**
+ * Return the heights-on-staff (in Pitch
+ * terminology) of all accidentals in the key's signature,
+ * in the given clef.
+ */
+ std::vector<int> getAccidentalHeights(const Clef &clef) const;
+
+ /**
+ * Return the result of applying this key to the given
+ * pitch, that is, modifying the pitch so that it has the
+ * same status in terms of accidentals as it had when
+ * found in the given previous key.
+ */
+ int convertFrom(int pitch, const Key &previousKey,
+ const Accidental &explicitAccidental =
+ Accidentals::NoAccidental) const;
+
+ /**
+ * Return the result of transposing the given pitch into
+ * this key, that is, modifying the pitch by the difference
+ * between the tonic pitches of this and the given previous
+ * key.
+ */
+ int transposeFrom(int pitch, const Key &previousKey) const;
+
+ /**
+ * Reduce a height-on-staff to a single octave, so that it
+ * can be compared against the accidental heights returned
+ * by the preceding method.
+ */
+ static inline unsigned int canonicalHeight(int height) {
+ return (height > 0) ? (height % 7) : ((7 - (-height % 7)) % 7);
+ }
+
+ typedef std::vector<Key> KeyList;
+
+ /**
+ * Return all the keys in the given major/minor mode, in
+ * no particular order.
+ */
+ static KeyList getKeys(bool minor = false);
+
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+ /**
+ * Transpose this key by the specified interval given in pitch and steps
+ *
+ * For example: transposing F major by a major triad (4,2) yields
+ * A major.
+ */
+ Key transpose(int pitchDelta, int heightDelta);
+
+private:
+ std::string m_name;
+ mutable std::vector<int> *m_accidentalHeights;
+
+ struct KeyDetails {
+ bool m_sharps;
+ bool m_minor;
+ int m_sharpCount;
+ std::string m_equivalence;
+ std::string m_rg2name;
+ int m_tonicPitch;
+
+ KeyDetails(); // ctor needed in order to live in a map
+
+ KeyDetails(bool sharps, bool minor, int sharpCount,
+ std::string equivalence, std::string rg2name,
+ int m_tonicPitch);
+
+ KeyDetails(const KeyDetails &d);
+
+ KeyDetails &operator=(const KeyDetails &d);
+ };
+
+
+ typedef std::map<std::string, KeyDetails> KeyDetailMap;
+ static KeyDetailMap m_keyDetailMap;
+ static void checkMap();
+ void checkAccidentalHeights() const;
+
+};
+
+
+/**
+ * Indication is a collective name for graphical marks that span a
+ * series of events, such as slurs, dynamic marks etc. These are
+ * stored in indication Events with a type and duration. The
+ * Indication class gives a basic set of indication types.
+ */
+
+class Indication
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+ static const PropertyName IndicationTypePropertyName;
+ typedef Exception BadIndicationName;
+
+ static const std::string Slur;
+ static const std::string PhrasingSlur;
+ static const std::string Crescendo;
+ static const std::string Decrescendo;
+ static const std::string Glissando;
+
+ static const std::string QuindicesimaUp;
+ static const std::string OttavaUp;
+ static const std::string OttavaDown;
+ static const std::string QuindicesimaDown;
+
+ Indication(const Event &e)
+ /* throw (Event::NoData, Event::BadType) */;
+ Indication(const std::string &s, timeT indicationDuration)
+ /* throw (BadIndicationName) */;
+
+ Indication(const Indication &m) : m_indicationType(m.m_indicationType),
+ m_duration(m.m_duration) { }
+
+ Indication &operator=(const Indication &m);
+
+ ~Indication() { }
+
+ std::string getIndicationType() const { return m_indicationType; }
+ timeT getIndicationDuration() const { return m_duration; }
+
+ bool isOttavaType() const {
+ return
+ m_indicationType == QuindicesimaUp ||
+ m_indicationType == OttavaUp ||
+ m_indicationType == OttavaDown ||
+ m_indicationType == QuindicesimaDown;
+ }
+
+ int getOttavaShift() const {
+ return (m_indicationType == QuindicesimaUp ? 2 :
+ m_indicationType == OttavaUp ? 1 :
+ m_indicationType == OttavaDown ? -1 :
+ m_indicationType == QuindicesimaDown ? -2 : 0);
+ }
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ bool isValid(const std::string &s) const;
+
+ std::string m_indicationType;
+ timeT m_duration;
+};
+
+
+
+/**
+ * Definitions for use in the text Event type.
+ */
+
+class Text
+{
+public:
+ static const std::string EventType;
+ static const int EventSubOrdering;
+ static const PropertyName TextPropertyName;
+ static const PropertyName TextTypePropertyName;
+ static const PropertyName LyricVersePropertyName;
+
+ /**
+ * Text styles
+ */
+ static const std::string UnspecifiedType;
+ static const std::string StaffName;
+ static const std::string ChordName;
+ static const std::string KeyName;
+ static const std::string Lyric;
+ static const std::string Chord;
+ static const std::string Dynamic;
+ static const std::string Direction;
+ static const std::string LocalDirection;
+ static const std::string Tempo;
+ static const std::string LocalTempo;
+ static const std::string Annotation;
+ static const std::string LilyPondDirective;
+
+ /**
+ * Special LilyPond directives
+ */
+ static const std::string Segno; // print segno here
+ static const std::string Coda; // print coda sign here
+ static const std::string Alternate1; // first alternative ending
+ static const std::string Alternate2; // second alternative ending
+ static const std::string BarDouble; // next barline is double
+ static const std::string BarEnd; // next barline is final double
+ static const std::string BarDot; // next barline is dotted
+ static const std::string Gliss; // \glissando on this note (to next note)
+ static const std::string Arpeggio; // \arpeggio on this chord
+// static const std::string ArpeggioUp; // \ArpeggioUp on this chord
+// static const std::string ArpeggioDn; // \ArpeggioDown on this chord
+ static const std::string Tiny; // begin \tiny font section
+ static const std::string Small; // begin \small font section
+ static const std::string NormalSize; // begin \normalsize font section
+
+ Text(const Event &e)
+ /* throw (Event::NoData, Event::BadType) */;
+ Text(const std::string &text,
+ const std::string &textType = UnspecifiedType);
+ Text(const Text &);
+ Text &operator=(const Text &);
+ ~Text();
+
+ std::string getText() const { return m_text; }
+ std::string getTextType() const { return m_type; }
+
+ int getVerse() const { return m_verse; } // only relevant for lyrics
+ void setVerse(int verse) { m_verse = verse; }
+
+ static bool isTextOfType(Event *, std::string type);
+
+ /**
+ * Return those text types that the user should be allowed to
+ * specify directly and visually
+ */
+ static std::vector<std::string> getUserStyles();
+
+ /**
+ * Return a list of available special LilyPond directives
+ */
+ static std::vector<std::string> getLilyPondDirectives();
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ std::string m_text;
+ std::string m_type;
+ long m_verse;
+};
+
+
+
+/**
+ * Pitch stores a note's pitch and provides information about it in
+ * various different ways, notably in terms of the position of the
+ * note on the staff and its associated accidental.
+ *
+ * (See docs/discussion/units.txt for explanation of pitch units.)
+ *
+ * This completely replaces the older NotationDisplayPitch class.
+ */
+
+class Pitch
+{
+public:
+ /**
+ * Construct a Pitch object based on the given Event, which must
+ * have a BaseProperties::PITCH property. If the property is
+ * absent, NoData is thrown. The BaseProperties::ACCIDENTAL
+ * property will also be used if present.
+ */
+ Pitch(const Event &e)
+ /* throw Event::NoData */;
+
+ /**
+ * Construct a Pitch object based on the given performance (MIDI) pitch.
+ */
+ Pitch(int performancePitch,
+ const Accidental &explicitAccidental = Accidentals::NoAccidental);
+
+ /**
+ * Construct a Pitch based on octave and pitch in octave. The
+ * lowest permissible octave number is octaveBase, and middle C is
+ * in octave octaveBase + 5. pitchInOctave must be in the range
+ * 0-11 where 0 is C, 1 is C sharp, etc.
+ */
+ Pitch(int pitchInOctave, int octave,
+ const Accidental &explicitAccidental = Accidentals::NoAccidental,
+ int octaveBase = -2);
+
+ /**
+ * Construct a Pitch based on octave and note in scale. The
+ * lowest permissible octave number is octaveBase, and middle C is
+ * in octave octaveBase + 5. The octave supplied should be that
+ * of the root note in the given key, which may be in a different
+ * MIDI octave from the resulting pitch (as MIDI octaves always
+ * begin at C). noteInScale must be in the range 0-6 where 0 is
+ * the root of the key and so on. The accidental is relative to
+ * noteInScale: if there is an accidental in the key for this note
+ * already, explicitAccidental will be "added" to it.
+ *
+ * For minor keys, the harmonic scale is used.
+ */
+ Pitch(int noteInScale, int octave, const Key &key,
+ const Accidental &explicitAccidental = Accidentals::NoAccidental,
+ int octaveBase = -2);
+
+ /**
+ * Construct a Pitch based on (MIDI) octave, note in the C major scale and
+ * performance pitch. The accidental is calculated based on these
+ * properties.
+ */
+ Pitch(int noteInCMajor, int octave, int pitch,
+ int octaveBase = -2);
+
+ /**
+ * Construct a Pitch based on octave and note name. The lowest
+ * permissible octave number is octaveBase, and middle C is in
+ * octave octaveBase + 5. noteName must be a character in the
+ * range [CDEFGAB] or lower-case equivalents. The key is supplied
+ * so that we know how to interpret the NoAccidental case.
+ */
+ Pitch(char noteName, int octave, const Key &key,
+ const Accidental &explicitAccidental = Accidentals::NoAccidental,
+ int octaveBase = -2);
+
+ /**
+ * Construct a Pitch corresponding a staff line or space on a
+ * classical 5-line staff. The bottom staff line has height 0,
+ * the top has height 8, and both positive and negative values are
+ * permissible.
+ */
+ Pitch(int heightOnStaff, const Clef &clef, const Key &key,
+ const Accidental &explicitAccidental = Accidentals::NoAccidental);
+
+ Pitch(const Pitch &);
+ Pitch &operator=(const Pitch &);
+
+ /**
+ * Return the MIDI pitch for this Pitch object.
+ */
+ int getPerformancePitch() const;
+
+ /**
+ * Return the accidental for this pitch using a bool to prefer sharps over
+ * flats if there is any doubt. This is the accidental
+ * that would be used to display this pitch outside of the context
+ * of any key; that is, it may duplicate an accidental actually in
+ * the current key. This should not be used if you need to get an
+ * explicit accidental returned for E#, Fb, B# or Cb.
+ *
+ * This version of the function exists to avoid breaking old code.
+ */
+ Accidental getAccidental(bool useSharps) const;
+
+ /**
+ * Return the accidental for this pitch, using a key. This should be used
+ * if you need an explicit accidental returned for E#, Fb, B# or Cb, which
+ * can't be resolved correctly without knowing that their key requires
+ * them to take an accidental. The provided key will also be used to
+ * determine whether to prefer sharps over flats.
+ */
+ Accidental getAccidental(const Key &key) const;
+
+ /**
+ * Return the accidental that should be used to display this pitch
+ * in a given key. For example, if the pitch is F-sharp in a key
+ * in which F has a sharp, NoAccidental will be returned. (This
+ * is in contrast to getAccidental, which would return Sharp.)
+ * This obviously can't take into account things like which
+ * accidentals have already been displayed in the bar, etc.
+ */
+ Accidental getDisplayAccidental(const Key &key) const;
+
+ /**
+ * Return the accidental that should be used to display this pitch
+ * in a given key, using the given strategy to resolve pitches where
+ * an accidental is needed but not specified.
+ */
+ Accidental getDisplayAccidental(const Key &key, Accidentals::NoAccidentalStrategy) const;
+
+ /**
+ * Return the position in the scale for this pitch, as a number in
+ * the range 0 to 6 where 0 is the root of the key.
+ */
+ int getNoteInScale(const Key &key) const;
+
+ /**
+ * Return the note name for this pitch, as a single character in
+ * the range A to G. (This is a reference value that should not
+ * normally be shown directly to the user, for i18n reasons.)
+ */
+ char getNoteName(const Key &key) const;
+
+ /**
+ * Return the height at which this pitch should display on a
+ * conventional 5-line staff. 0 is the bottom line, 1 the first
+ * space, etc., so for example middle-C in the treble clef would
+ * return -2.
+ *
+ * Chooses the most likely accidental for this pitch in this key.
+ */
+ int getHeightOnStaff(const Clef &clef, const Key &key) const;
+
+ /**
+ * Return the height at which this pitch should display on a
+ * conventional 5-line staff. 0 is the bottom line, 1 the first
+ * space, etc., so for example middle-C in the treble clef would
+ * return -2.
+ *
+ * Chooses the accidental specified by the 'useSharps' parameter
+ */
+ int getHeightOnStaff(const Clef &clef, bool useSharps) const;
+
+ /**
+ * Return the octave containing this pitch. The octaveBase argument
+ * specifies the octave containing MIDI pitch 0; middle-C is in octave
+ * octaveBase + 5.
+ */
+ int getOctave(int octaveBase = -2) const;
+
+ /**
+ * Return the pitch within the octave, in the range 0 to 11.
+ */
+ int getPitchInOctave() const;
+
+ /**
+ * Return whether this pitch is diatonic in the given key.
+ */
+ bool isDiatonicInKey(const Key &key) const;
+
+ /**
+ * Return a reference name for this pitch. (C4, Bb2, etc...)
+ * according to http://www.harmony-central.com/MIDI/Doc/table2.html
+ *
+ * Note that this does not take into account the stored accidental
+ * -- this string is purely an encoding of the MIDI pitch, with
+ * the accidental in the string selected according to the
+ * useSharps flag (which may be expected to have come from a call
+ * to Key::isSharp).
+ *
+ * If inclOctave is false, this will return C, Bb, etc.
+ */
+ std::string getAsString(bool useSharps,
+ bool inclOctave = true,
+ int octaveBase = -2) const;
+
+ /**
+ * Return a number 0-6 corresponding to the given note name, which
+ * must be in the range [CDEFGAB] or lower-case equivalents. The
+ * return value is in the range 0-6 with 0 for C, 1 for D etc.
+ */
+ static int getIndexForNote(char noteName);
+
+ /**
+ * Return a note name corresponding to the given note index, which
+ * must be in the range 0-6 with 0 for C, 1 for D etc.
+ */
+ static char getNoteForIndex(int index);
+
+ /**
+ * Calculate and return the performance (MIDI) pitch corresponding
+ * to the stored height and accidental, interpreting them as
+ * Rosegarden-2.1-style values (for backward compatibility use),
+ * in the given clef and key
+ */
+ static int getPerformancePitchFromRG21Pitch(int heightOnStaff,
+ const Accidental &accidental,
+ const Clef &clef,
+ const Key &key);
+
+ /**
+ * return the result of transposing the given pitch by the
+ * specified interval in the given key. The key is left unchanged,
+ * only the pitch is transposed.
+ */
+ Pitch transpose(const Key &key, int pitchDelta, int heightDelta);
+
+ /**
+ * checks whether the accidental specified for this pitch (if any)
+ * is valid - for example, a Sharp for pitch 11 is invalid, as
+ * it's between A# and B#.
+ */
+ bool validAccidental() const;
+
+ /**
+ * Returned event is on heap; caller takes responsibility for ownership
+ */
+ Event *getAsNoteEvent(timeT absoluteTime, timeT duration) const;
+
+private:
+ int m_pitch;
+ Accidental m_accidental;
+
+ static void rawPitchToDisplayPitch
+ (int, const Clef &, const Key &, int &, Accidental &,
+ Accidentals::NoAccidentalStrategy);
+
+ static void displayPitchToRawPitch
+ (int, Accidental, const Clef &, const Key &,
+ int &, bool ignoreOffset = false);
+};
+
+
+
+class TimeSignature;
+
+
+/**
+ * The Note class represents note durations only, not pitch or
+ * accidental; it's therefore just as relevant to rest events as to
+ * note events. You can construct one of these from either.
+ */
+
+class Note
+{
+public:
+ static const std::string EventType;
+ static const std::string EventRestType;
+ static const int EventRestSubOrdering;
+
+ typedef int Type; // not an enum, too much arithmetic at stake
+
+ // define both sorts of names; some people prefer the American
+ // names, but I just can't remember which of them is which
+
+ static const Type
+
+ SixtyFourthNote = 0,
+ ThirtySecondNote = 1,
+ SixteenthNote = 2,
+ EighthNote = 3,
+ QuarterNote = 4,
+ HalfNote = 5,
+ WholeNote = 6,
+ DoubleWholeNote = 7,
+
+ Hemidemisemiquaver = 0,
+ Demisemiquaver = 1,
+ Semiquaver = 2,
+ Quaver = 3,
+ Crotchet = 4,
+ Minim = 5,
+ Semibreve = 6,
+ Breve = 7,
+
+ Shortest = 0,
+ Longest = 7;
+
+
+ /**
+ * Create a Note object of the given type, representing a
+ * particular sort of duration. Note objects are strictly
+ * durational; they don't represent pitch, and may be as
+ * relevant to rests as actual notes.
+ */
+ Note(Type type, int dots = 0) :
+ m_type(type < Shortest ? Shortest :
+ type > Longest ? Longest :
+ type),
+ m_dots(dots) { }
+
+ Note(const Note &n) : m_type(n.m_type), m_dots(n.m_dots) { }
+ ~Note() { }
+
+ Note &operator=(const Note &n);
+
+ Type getNoteType() const { return m_type; }
+ int getDots() const { return m_dots; }
+
+ /**
+ * Return the duration of this note type.
+ */
+ timeT getDuration() const {
+ return m_dots ? getDurationAux() : (m_shortestTime * (1 << m_type));
+ }
+
+ /**
+ * Return the Note whose duration is closest to (but shorter than or
+ * equal to) the given duration, permitting at most maxDots dots.
+ */
+ static Note getNearestNote(timeT duration, int maxDots = 2);
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsNoteEvent(timeT absoluteTime, int pitch) const;
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsRestEvent(timeT absoluteTime) const;
+
+
+private:
+ Type m_type;
+ int m_dots;
+
+ timeT getDurationAux() const;
+
+ // a time & effort saving device; if changing this, change
+ // TimeSignature::m_crotchetTime etc too
+ static const timeT m_shortestTime;
+};
+
+
+
+/**
+ * TimeSignature contains arithmetic methods relevant to time
+ * signatures and bar durations, including code for splitting long
+ * rest intervals into bite-sized chunks. Although there is a time
+ * signature Event type, these Events don't appear in regular Segments
+ * but only in the Composition's reference segment.
+ */
+
+class TimeSignature
+{
+public:
+ static const TimeSignature DefaultTimeSignature;
+ typedef Exception BadTimeSignature;
+
+ TimeSignature() :
+ m_numerator(DefaultTimeSignature.m_numerator),
+ m_denominator(DefaultTimeSignature.m_denominator),
+ m_common(false), m_hidden(false), m_hiddenBars(false) { }
+
+ /**
+ * Construct a TimeSignature object describing a time signature
+ * with the given numerator and denominator. If preferCommon is
+ * true and the time signature is a common or cut-common time, the
+ * constructed object will return true for isCommon; if hidden is
+ * true, the time signature is intended not to be displayed and
+ * isHidden will return true; if hiddenBars is true, the bar lines
+ * between this time signature and the next will not be shown.
+ */
+ TimeSignature(int numerator, int denominator,
+ bool preferCommon = false,
+ bool hidden = false,
+ bool hiddenBars = false)
+ /* throw (BadTimeSignature) */;
+
+ TimeSignature(const TimeSignature &ts) :
+ m_numerator(ts.m_numerator),
+ m_denominator(ts.m_denominator),
+ m_common(ts.m_common),
+ m_hidden(ts.m_hidden),
+ m_hiddenBars(ts.m_hiddenBars) { }
+
+ ~TimeSignature() { }
+
+ TimeSignature &operator=(const TimeSignature &ts);
+
+ bool operator==(const TimeSignature &ts) const {
+ return ts.m_numerator == m_numerator && ts.m_denominator == m_denominator;
+ }
+ bool operator!=(const TimeSignature &ts) const {
+ return !operator==(ts);
+ }
+
+ int getNumerator() const { return m_numerator; }
+ int getDenominator() const { return m_denominator; }
+
+ bool isCommon() const { return m_common; }
+ bool isHidden() const { return m_hidden; }
+ bool hasHiddenBars() const { return m_hiddenBars; }
+
+ timeT getBarDuration() const;
+
+ /**
+ * Return the unit of the time signature. This is the note
+ * implied by the denominator. For example, the unit of 4/4 time
+ * is the crotchet, and that of 6/8 is the quaver. (The numerator
+ * of the time signature gives the number of units per bar.)
+ */
+ Note::Type getUnit() const;
+
+ /**
+ * Return the duration of the unit of the time signature.
+ * See also getUnit(). In most cases getBeatDuration() gives
+ * a more meaningful value.
+ */
+ timeT getUnitDuration() const;
+
+ /**
+ * Return true if this time signature indicates dotted time.
+ */
+ bool isDotted() const;
+
+ /**
+ * Return the duration of the beat of the time signature. For
+ * example, the beat of 4/4 time is the crotchet, the same as its
+ * unit, but that of 6/8 is the dotted crotchet (there are only
+ * two beats in a 6/8 bar). The beat therefore depends on whether
+ * the signature indicates dotted or undotted time.
+ */
+ timeT getBeatDuration() const;
+
+ /**
+ * Return the number of beats in a complete bar.
+ */
+ int getBeatsPerBar() const {
+ return getBarDuration() / getBeatDuration();
+ }
+
+ /**
+ * Get the "optimal" list of rest durations to make up a bar in
+ * this time signature.
+ */
+ void getDurationListForBar(DurationList &dlist) const;
+
+ /**
+ * Get the "optimal" list of rest durations to make up a time
+ * interval of the given total duration, starting at the given
+ * offset after the start of a bar, assuming that the interval
+ * is entirely in this time signature.
+ */
+ void getDurationListForInterval(DurationList &dlist,
+ timeT intervalDuration,
+ timeT startOffset = 0) const;
+
+ /**
+ * Get the level of emphasis for a position in a bar. 4 is lots
+ * of emphasis, 0 is none.
+ */
+ int getEmphasisForTime(timeT offset);
+
+ /**
+ * Return a list of divisions, subdivisions, subsubdivisions
+ * etc of a bar in this time, up to the given depth. For example,
+ * if the time signature is 6/8 and the depth is 3, return a list
+ * containing 2, 3, and 2 (there are 2 beats to the bar, each of
+ * which is best subdivided into 3 subdivisions, each of which
+ * divides most neatly into 2).
+ */
+ void getDivisions(int depth, std::vector<int> &divisions) const;
+
+private:
+ friend class Composition;
+ friend class TimeTempoSelection;
+
+ TimeSignature(const Event &e)
+ /* throw (Event::NoData, Event::BadType, BadTimeSignature) */;
+
+ static const std::string EventType;
+ static const int EventSubOrdering;
+ static const PropertyName NumeratorPropertyName;
+ static const PropertyName DenominatorPropertyName;
+ static const PropertyName ShowAsCommonTimePropertyName;
+ static const PropertyName IsHiddenPropertyName;
+ static const PropertyName HasHiddenBarsPropertyName;
+
+ /// Returned event is on heap; caller takes responsibility for ownership
+ Event *getAsEvent(timeT absoluteTime) const;
+
+private:
+ int m_numerator;
+ int m_denominator;
+
+ bool m_common;
+ bool m_hidden;
+ bool m_hiddenBars;
+
+ mutable int m_barDuration;
+ mutable int m_beatDuration;
+ mutable int m_beatDivisionDuration;
+ mutable bool m_dotted;
+ void setInternalDurations() const;
+
+ // a time & effort saving device
+ static const timeT m_crotchetTime;
+ static const timeT m_dottedCrotchetTime;
+};
+
+
+
+/**
+ * AccidentalTable represents a set of accidentals in force at a
+ * given time.
+ *
+ * Keep an AccidentalTable variable on-hand as you track through a
+ * staff; then when reading a chord, call processDisplayAccidental
+ * on the accidentals found in the chord to obtain the actual
+ * displayed accidentals and to tell the AccidentalTable to
+ * remember the accidentals that have been found in the chord.
+ * Then when the chord ends, call update() on the AccidentalTable
+ * so that that chord's accidentals are taken into account for the
+ * next one.
+ *
+ * Create a new AccidentalTable whenever a new key is encountered,
+ * and call newBar() or newClef() when a new bar happens or a new
+ * clef is encountered.
+ */
+class AccidentalTable
+{
+public:
+ enum OctaveType {
+ OctavesIndependent, // if c' and c'' sharp, mark them both sharp
+ OctavesCautionary, // if c' and c'' sharp, put the second one in brackets
+ OctavesEquivalent // if c' and c'' sharp, only mark the first one
+ };
+
+ enum BarResetType {
+ BarResetNone, // c# | c -> omit natural
+ BarResetCautionary, // c# | c -> add natural to c in brackets
+ BarResetExplicit // c# | c -> add natural to c
+ };
+
+ AccidentalTable(const Key &, const Clef &,
+ OctaveType = OctavesCautionary,
+ BarResetType = BarResetCautionary);
+
+ AccidentalTable(const AccidentalTable &);
+ AccidentalTable &operator=(const AccidentalTable &);
+
+ Accidental processDisplayAccidental(const Accidental &displayAcc,
+ int heightOnStaff,
+ bool &cautionary);
+
+ void update();
+
+ void newBar();
+ void newClef(const Clef &);
+
+private:
+ Key m_key;
+ Clef m_clef;
+ OctaveType m_octaves;
+ BarResetType m_barReset;
+
+ struct AccidentalRec {
+ AccidentalRec() : accidental(Accidentals::NoAccidental), previousBar(false) { }
+ AccidentalRec(Accidental a, bool p) : accidental(a), previousBar(p) { }
+ Accidental accidental;
+ bool previousBar;
+ };
+
+ typedef std::map<int, AccidentalRec> AccidentalMap;
+
+ AccidentalMap m_accidentals;
+ AccidentalMap m_canonicalAccidentals;
+
+ AccidentalMap m_newAccidentals;
+ AccidentalMap m_newCanonicalAccidentals;
+};
+
+
+}
+
+
+#endif
diff --git a/src/base/Profiler.cpp b/src/base/Profiler.cpp
new file mode 100644
index 0000000..4f3ab42
--- /dev/null
+++ b/src/base/Profiler.cpp
@@ -0,0 +1,187 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <iostream>
+#include "Profiler.h"
+
+#include <vector>
+#include <algorithm>
+
+//#define NO_TIMING 1
+
+#ifdef NDEBUG
+#define NO_TIMING 1
+#endif
+
+using std::cerr;
+using std::endl;
+
+namespace Rosegarden
+{
+
+Profiles* Profiles::m_instance = 0;
+
+Profiles* Profiles::getInstance()
+{
+ if (!m_instance) m_instance = new Profiles();
+
+ return m_instance;
+}
+
+Profiles::Profiles()
+{
+}
+
+Profiles::~Profiles()
+{
+ dump();
+}
+
+void Profiles::accumulate(const char* id, clock_t time, RealTime rt)
+{
+#ifndef NO_TIMING
+ ProfilePair &pair(m_profiles[id]);
+ ++pair.first;
+ pair.second.first += time;
+ pair.second.second = pair.second.second + rt;
+
+ TimePair &timePair(m_lastCalls[id]);
+ timePair.first = time;
+ timePair.second = rt;
+#endif
+}
+
+void Profiles::dump()
+{
+#ifndef NO_TIMING
+ cerr << "Profiles::dump() :\n";
+
+ // I'm finding these two confusing dumped out in random order,
+ // so I'm going to sort them alphabetically:
+
+ std::vector<const char *> profileNames;
+ for (ProfileMap::iterator i = m_profiles.begin();
+ i != m_profiles.end(); ++i) {
+ profileNames.push_back((*i).first);
+ }
+
+ std::sort(profileNames.begin(), profileNames.end());
+
+ for (std::vector<const char *>::iterator i = profileNames.begin();
+ i != profileNames.end(); ++i) {
+
+ cerr << "-> " << *i << ": CPU: "
+ << m_profiles[*i].first << " calls, "
+ << int((m_profiles[*i].second.first * 1000.0) / CLOCKS_PER_SEC) << "ms, "
+ << (((double)m_profiles[*i].second.first * 1000000.0 /
+ (double)m_profiles[*i].first) / CLOCKS_PER_SEC) << "us/call"
+ << endl;
+
+ cerr << "-> " << *i << ": real: "
+ << m_profiles[*i].first << " calls, "
+ << m_profiles[*i].second.second << ", "
+ << (m_profiles[*i].second.second / m_profiles[*i].first)
+ << "/call"
+ << endl;
+
+ cerr << "-> " << *i << ": last: CPU: "
+ << int((m_lastCalls[*i].first * 1000.0) / CLOCKS_PER_SEC) << "ms, "
+ << " real: "
+ << m_lastCalls[*i].second << endl;
+ }
+
+ cerr << "Profiles::dump() finished\n";
+#endif
+}
+
+Profiler::Profiler(const char* c, bool showOnDestruct)
+ : m_c(c),
+ m_showOnDestruct(showOnDestruct)
+{
+#ifndef NO_TIMING
+ m_startCPU = clock();
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ m_startTime = RealTime(tv.tv_sec, tv.tv_usec * 1000);
+#endif
+}
+
+void
+Profiler::update()
+{
+#ifndef NO_TIMING
+ clock_t elapsedCPU = clock() - m_startCPU;
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ RealTime elapsedTime = RealTime(tv.tv_sec, tv.tv_usec * 1000) - m_startTime;
+
+ cerr << "Profiler : id = " << m_c
+ << " - elapsed so far = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC)
+ << "ms CPU, " << elapsedTime << " real" << endl;
+#endif
+}
+
+Profiler::~Profiler()
+{
+#ifndef NO_TIMING
+ clock_t elapsedCPU = clock() - m_startCPU;
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ RealTime elapsedTime = RealTime(tv.tv_sec, tv.tv_usec * 1000) - m_startTime;
+
+ Profiles::getInstance()->accumulate(m_c, elapsedCPU, elapsedTime);
+
+ if (m_showOnDestruct)
+ cerr << "Profiler : id = " << m_c
+ << " - elapsed = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC)
+ << "ms CPU, " << elapsedTime << " real" << endl;
+#endif
+}
+
+}
+
+/* A little usage demo
+
+int main()
+{
+ {
+ Profiler foo("test");
+ sleep(1);
+ }
+
+ {
+ Profiler foo("test");
+ sleep(1);
+ }
+
+ {
+ Profiler foo("test2");
+ sleep(1);
+ }
+
+ Profiles::getInstance()->dump();
+
+ return 0;
+}
+*/
diff --git a/src/base/Profiler.h b/src/base/Profiler.h
new file mode 100644
index 0000000..4ba033b
--- /dev/null
+++ b/src/base/Profiler.h
@@ -0,0 +1,84 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _PROFILER_H_
+#define _PROFILER_H_
+
+#include <ctime>
+#include <sys/time.h>
+#include <map>
+
+#include "RealTime.h"
+
+
+namespace Rosegarden
+{
+
+/**
+ * Profiling classes
+ */
+
+/**
+ * The class holding all profiling data
+ *
+ * This class is a singleton
+ */
+class Profiles
+{
+public:
+ static Profiles* getInstance();
+ ~Profiles();
+
+ void accumulate(const char* id, clock_t time, RealTime rt);
+ void dump();
+
+protected:
+ Profiles();
+
+ typedef std::pair<clock_t, RealTime> TimePair;
+ typedef std::pair<int, TimePair> ProfilePair;
+ typedef std::map<const char *, ProfilePair> ProfileMap;
+ typedef std::map<const char *, TimePair> LastCallMap;
+ ProfileMap m_profiles;
+ LastCallMap m_lastCalls;
+
+ static Profiles* m_instance;
+};
+
+class Profiler
+{
+public:
+ Profiler(const char*, bool showOnDestruct = false);
+ ~Profiler();
+
+ void update();
+
+protected:
+ const char* m_c;
+ clock_t m_startCPU;
+ RealTime m_startTime;
+ bool m_showOnDestruct;
+};
+
+}
+
+#endif
diff --git a/src/base/Property.cpp b/src/base/Property.cpp
new file mode 100644
index 0000000..45e818b
--- /dev/null
+++ b/src/base/Property.cpp
@@ -0,0 +1,169 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Property.h"
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+namespace Rosegarden
+{
+using std::string;
+
+string
+PropertyDefn<UInt>::typeName()
+{
+ return "UInt";
+}
+
+PropertyDefn<UInt>::basic_type
+PropertyDefn<UInt>::parse(string s)
+{
+ return atoi(s.c_str());
+}
+
+string
+PropertyDefn<UInt>::unparse(PropertyDefn<UInt>::basic_type i)
+{
+ static char buffer[20]; sprintf(buffer, "%ld", i);
+ return buffer;
+}
+
+
+string
+PropertyDefn<Int>::typeName()
+{
+ return "Int";
+}
+
+PropertyDefn<Int>::basic_type
+PropertyDefn<Int>::parse(string s)
+{
+ return atoi(s.c_str());
+}
+
+string
+PropertyDefn<Int>::unparse(PropertyDefn<Int>::basic_type i)
+{
+ static char buffer[20]; sprintf(buffer, "%ld", i);
+ return buffer;
+}
+
+string
+PropertyDefn<String>::typeName()
+{
+ return "String";
+}
+
+PropertyDefn<String>::basic_type
+PropertyDefn<String>::parse(string s)
+{
+ return s;
+}
+
+string
+PropertyDefn<String>::unparse(PropertyDefn<String>::basic_type i)
+{
+ return i;
+}
+
+string
+PropertyDefn<Bool>::typeName()
+{
+ return "Bool";
+}
+
+PropertyDefn<Bool>::basic_type
+PropertyDefn<Bool>::parse(string s)
+{
+ return s == "true";
+}
+
+string
+PropertyDefn<Bool>::unparse(PropertyDefn<Bool>::basic_type i)
+{
+ return (i ? "true" : "false");
+}
+
+string
+PropertyDefn<RealTimeT>::typeName()
+{
+ return "RealTimeT";
+}
+
+PropertyDefn<RealTimeT>::basic_type
+PropertyDefn<RealTimeT>::parse(string s)
+{
+ string sec = s.substr(0, s.find('/')),
+ nsec = s.substr(s.find('/') + 1);
+
+ return RealTime(atoi(sec.c_str()), atoi(nsec.c_str()));
+}
+
+string
+PropertyDefn<RealTimeT>::unparse(PropertyDefn<RealTimeT>::basic_type i)
+{
+ static char buffer[256]; sprintf(buffer, "%d/%d", i.sec, i.nsec);
+ return buffer;
+}
+
+PropertyStoreBase::~PropertyStoreBase()
+{
+}
+
+template <>
+size_t
+PropertyStore<UInt>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+template <>
+size_t
+PropertyStore<Int>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+template <>
+size_t
+PropertyStore<String>::getStorageSize() const
+{
+ return sizeof(*this) + m_data.size();
+}
+
+template <>
+size_t
+PropertyStore<Bool>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+template <>
+size_t
+PropertyStore<RealTimeT>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+}
+
diff --git a/src/base/Property.h b/src/base/Property.h
new file mode 100644
index 0000000..32b6226
--- /dev/null
+++ b/src/base/Property.h
@@ -0,0 +1,225 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _PROPERTY_H_
+#define _PROPERTY_H_
+
+#include <string>
+
+#include "RealTime.h"
+
+namespace Rosegarden
+{
+
+enum PropertyType { Int, String, Bool, RealTimeT, UInt };
+
+template <PropertyType P>
+class PropertyDefn
+{
+public:
+ struct PropertyDefnNotDefined {
+ PropertyDefnNotDefined() { throw(0); }
+ };
+ typedef PropertyDefnNotDefined basic_type;
+
+ static std::string typeName() { return "Undefined"; }
+ static basic_type parse(std::string);
+ static std::string unparse(basic_type);
+};
+
+template <PropertyType P>
+typename PropertyDefn<P>::basic_type
+PropertyDefn<P>::parse(std::string)
+{
+ throw(0);
+ return "";
+}
+
+template <PropertyType P>
+std::string
+PropertyDefn<P>::unparse(PropertyDefn<P>::basic_type)
+{
+ throw(0);
+ return "";
+}
+
+
+template <>
+class PropertyDefn<Int>
+{
+public:
+ typedef long basic_type;
+
+ static std::string typeName();
+ static basic_type parse(std::string s);
+ static std::string unparse(basic_type i);
+};
+
+
+template <>
+class PropertyDefn<String>
+{
+public:
+ typedef std::string basic_type;
+
+ static std::string typeName();
+ static basic_type parse(std::string s);
+ static std::string unparse(basic_type i);
+};
+
+template <>
+class PropertyDefn<Bool>
+{
+public:
+ typedef bool basic_type;
+
+ static std::string typeName();
+ static basic_type parse(std::string s);
+ static std::string unparse(basic_type i);
+};
+
+template <>
+class PropertyDefn<RealTimeT>
+{
+public:
+ typedef RealTime basic_type;
+
+ static std::string typeName();
+ static basic_type parse(std::string s);
+ static std::string unparse(basic_type i);
+};
+
+template <>
+class PropertyDefn<UInt>
+{
+public:
+ typedef unsigned int basic_type;
+
+ static std::string typeName();
+ static basic_type parse(std::string s);
+ static std::string unparse(basic_type i);
+};
+
+
+class PropertyStoreBase {
+public:
+ virtual ~PropertyStoreBase();
+
+ virtual PropertyType getType() const = 0;
+ virtual std::string getTypeName() const = 0;
+ virtual PropertyStoreBase *clone() = 0;
+ virtual std::string unparse() const = 0;
+
+ virtual size_t getStorageSize() const = 0; // for debugging
+
+#ifndef NDEBUG
+ virtual void dump(std::ostream&) const = 0;
+#else
+ virtual void dump(std::ostream&) const { }
+#endif
+};
+
+#ifndef NDEBUG
+inline std::ostream& operator<<(std::ostream &out, PropertyStoreBase &e)
+{ e.dump(out); return out; }
+#endif
+
+template <PropertyType P>
+class PropertyStore : public PropertyStoreBase
+{
+public:
+ PropertyStore(typename PropertyDefn<P>::basic_type d) :
+ m_data(d) { }
+ PropertyStore(const PropertyStore<P> &p) :
+ PropertyStoreBase(p), m_data(p.m_data) { }
+ PropertyStore &operator=(const PropertyStore<P> &p);
+
+ virtual PropertyType getType() const;
+ virtual std::string getTypeName() const;
+
+ virtual PropertyStoreBase* clone();
+
+ virtual std::string unparse() const;
+
+ typename PropertyDefn<P>::basic_type getData() { return m_data; }
+ void setData(typename PropertyDefn<P>::basic_type data) { m_data = data; }
+
+ virtual size_t getStorageSize() const;
+
+#ifndef NDEBUG
+ void dump(std::ostream&) const;
+#endif
+
+private:
+ typename PropertyDefn<P>::basic_type m_data;
+};
+
+template <PropertyType P>
+PropertyStore<P>&
+PropertyStore<P>::operator=(const PropertyStore<P> &p) {
+ if (this != &p) {
+ m_data = p.m_data;
+ }
+ return *this;
+}
+
+template <PropertyType P>
+PropertyType
+PropertyStore<P>::getType() const
+{
+ return P;
+}
+
+template <PropertyType P>
+std::string
+PropertyStore<P>::getTypeName() const
+{
+ return PropertyDefn<P>::typeName();
+}
+
+template <PropertyType P>
+PropertyStoreBase*
+PropertyStore<P>::clone()
+{
+ return new PropertyStore<P>(*this);
+}
+
+template <PropertyType P>
+std::string
+PropertyStore<P>::unparse() const
+{
+ return PropertyDefn<P>::unparse(m_data);
+}
+
+#ifndef NDEBUG
+template <PropertyType P>
+void
+PropertyStore<P>::dump(std::ostream &out) const
+{
+ out << getTypeName() << " - " << unparse();
+}
+#endif
+
+}
+
+
+#endif
diff --git a/src/base/PropertyMap.cpp b/src/base/PropertyMap.cpp
new file mode 100644
index 0000000..5958dc2
--- /dev/null
+++ b/src/base/PropertyMap.cpp
@@ -0,0 +1,101 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <cstdio>
+#include <iostream>
+#include <string>
+#include "PropertyMap.h"
+#include "XmlExportable.h"
+
+namespace Rosegarden
+{
+using std::string;
+
+#ifdef PROPERTY_MAP_IS_HASH_MAP
+PropertyMap::PropertyMap() :
+ __HASH_NS::hash_map<PropertyName,
+ PropertyStoreBase *,
+ PropertyNameHash,
+ PropertyNamesEqual>(50, PropertyNameHash())
+{
+ // nothing
+}
+#endif
+
+PropertyMap::PropertyMap(const PropertyMap &pm) :
+
+#ifdef PROPERTY_MAP_IS_HASH_MAP
+
+ __HASH_NS::hash_map<PropertyName,
+ PropertyStoreBase *,
+ PropertyNameHash,
+ PropertyNamesEqual>(50, PropertyNameHash())
+#else
+
+ std::map<PropertyName, PropertyStoreBase *>()
+
+#endif
+
+{
+ for (const_iterator i = pm.begin(); i != pm.end(); ++i) {
+ insert(PropertyPair(i->first, i->second->clone()));
+ }
+}
+
+PropertyMap::~PropertyMap()
+{
+ for (iterator i = begin(); i != end(); ++i) delete i->second;
+}
+
+void
+PropertyMap::clear()
+{
+ for (iterator i = begin(); i != end(); ++i) delete i->second;
+ erase(begin(), end());
+}
+
+
+// We could derive from XmlExportable and make this a virtual method
+// overriding XmlExportable's pure virtual. We don't, because this
+// class has no other virtual methods and for such a core class we
+// could do without the overhead (given that it wouldn't really gain
+// us anything anyway).
+
+string
+PropertyMap::toXmlString()
+{
+ string xml;
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+
+ xml +=
+ "<property name=\"" + XmlExportable::encode(i->first.getName()) +
+ "\" " + i->second->getTypeName() +
+ "=\"" + XmlExportable::encode(i->second->unparse()) +
+ "\"/>";
+
+ }
+
+ return xml;
+}
+
+}
+
diff --git a/src/base/PropertyMap.h b/src/base/PropertyMap.h
new file mode 100644
index 0000000..fca603c
--- /dev/null
+++ b/src/base/PropertyMap.h
@@ -0,0 +1,50 @@
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _PROPERTY_MAP_H_
+#define _PROPERTY_MAP_H_
+
+#include "Property.h"
+#include "PropertyName.h"
+
+#include <map>
+
+namespace Rosegarden {
+
+class PropertyMap : public std::map<PropertyName, PropertyStoreBase *>
+{
+public:
+ PropertyMap() { }
+ PropertyMap(const PropertyMap &pm);
+
+ ~PropertyMap();
+
+ void clear();
+
+ std::string toXmlString();
+
+private:
+ PropertyMap &operator=(const PropertyMap &); // not provided
+};
+
+typedef PropertyMap::value_type PropertyPair;
+
+}
+
+#endif
diff --git a/src/base/PropertyName.cpp b/src/base/PropertyName.cpp
new file mode 100644
index 0000000..11ff019
--- /dev/null
+++ b/src/base/PropertyName.cpp
@@ -0,0 +1,86 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <iostream>
+#include <string>
+
+#include "PropertyName.h"
+#include "Exception.h"
+
+
+namespace Rosegarden
+{
+using std::string;
+
+PropertyName::intern_map *PropertyName::m_interns = 0;
+PropertyName::intern_reverse_map *PropertyName::m_internsReversed = 0;
+int PropertyName::m_nextValue = 0;
+
+int PropertyName::intern(const string &s)
+{
+ if (!m_interns) {
+ m_interns = new intern_map;
+ m_internsReversed = new intern_reverse_map;
+ }
+
+ intern_map::iterator i(m_interns->find(s));
+
+ if (i != m_interns->end()) {
+ return i->second;
+ } else {
+ int nv = ++m_nextValue;
+ m_interns->insert(intern_pair(s, nv));
+ m_internsReversed->insert(intern_reverse_pair(nv, s));
+ return nv;
+ }
+}
+
+string PropertyName::getName() const
+{
+ intern_reverse_map::iterator i(m_internsReversed->find(m_value));
+ if (i != m_internsReversed->end()) return i->second;
+
+ // dump some informative data, even if we aren't in debug mode,
+ // because this really shouldn't be happening
+ std::cerr << "ERROR: PropertyName::getName: value corrupted!\n";
+ std::cerr << "PropertyName's internal value is " << m_value << std::endl;
+ std::cerr << "Reverse interns are ";
+ i = m_internsReversed->begin();
+ if (i == m_internsReversed->end()) std::cerr << "(none)";
+ else while (i != m_internsReversed->end()) {
+ if (i != m_internsReversed->begin()) {
+ std::cerr << ", ";
+ }
+ std::cerr << i->first << "=" << i->second;
+ ++i;
+ }
+ std::cerr << std::endl;
+
+ throw Exception
+ ("Serious problem in PropertyName::getName(): property "
+ "name's internal value is corrupted -- see stderr for details");
+}
+
+const PropertyName PropertyName::EmptyPropertyName = "";
+
+}
+
diff --git a/src/base/PropertyName.h b/src/base/PropertyName.h
new file mode 100644
index 0000000..f9e8c20
--- /dev/null
+++ b/src/base/PropertyName.h
@@ -0,0 +1,158 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _PROPERTY_NAME_H_
+#define _PROPERTY_NAME_H_
+
+#include <string>
+#include <map>
+#include <iostream>
+
+namespace Rosegarden
+{
+
+/**
+
+ A PropertyName is something that can be constructed from a string,
+ compared quickly as an int, hashed as a key in a hash map, and
+ streamed out again as a string. It must have accompanying functors
+ PropertyNamesEqual and PropertyNameHash which compare and hash
+ PropertyName objects.
+
+ The simplest implementation is a string:
+
+ typedef std::string PropertyName;
+
+ struct PropertyNamesEqual {
+ bool operator() (const PropertyName &s1, const PropertyName &s2) const {
+ return s1 == s2;
+ }
+ };
+
+ struct PropertyNameHash {
+ static std::hash<const char *> _H;
+ size_t operator() (const PropertyName &s) const {
+ return _H(s.c_str());
+ }
+ };
+
+ std::hash<const char *> PropertyNameHash::_H;
+
+ but our implementation is faster in practice: while it behaves
+ outwardly like a string, for the Event that makes use of it,
+ it performs much like a machine integer. It also shares
+ strings, reducing storage sizes if there are many names in use.
+
+ A big caveat with this class is that it is _not_ safe to persist
+ the values of PropertyNames and assume that the original strings
+ can be recovered; they can't. The values are assigned on demand,
+ and there's no guarantee that a given string will always map to
+ the same value (on separate invocations of the program). This
+ is why there's no PropertyName(int) constructor and no mechanism
+ for storing PropertyNames in properties. (Of course, you can
+ store the string representation of a PropertyName in a property;
+ but that's slow.)
+
+*/
+
+class PropertyName
+{
+public:
+ PropertyName() : m_value(-1) { }
+ PropertyName(const char *cs) { std::string s(cs); m_value = intern(s); }
+ PropertyName(const std::string &s) : m_value(intern(s)) { }
+ PropertyName(const PropertyName &p) : m_value(p.m_value) { }
+ ~PropertyName() { }
+
+ PropertyName &operator=(const char *cs) {
+ std::string s(cs);
+ m_value = intern(s);
+ return *this;
+ }
+ PropertyName &operator=(const std::string &s) {
+ m_value = intern(s);
+ return *this;
+ }
+ PropertyName &operator=(const PropertyName &p) {
+ m_value = p.m_value;
+ return *this;
+ }
+
+ bool operator==(const PropertyName &p) const {
+ return m_value == p.m_value;
+ }
+ bool operator< (const PropertyName &p) const {
+ return m_value < p.m_value;
+ }
+
+ const char *c_str() const {
+ return getName().c_str();
+ }
+
+ std::string getName() const /* throw (CorruptedValue) */;
+
+ int getValue() const { return m_value; }
+
+ static const PropertyName EmptyPropertyName;
+
+private:
+ typedef std::map<std::string, int> intern_map;
+ typedef intern_map::value_type intern_pair;
+
+ typedef std::map<int, std::string> intern_reverse_map;
+ typedef intern_reverse_map::value_type intern_reverse_pair;
+
+ static intern_map *m_interns;
+ static intern_reverse_map *m_internsReversed;
+ static int m_nextValue;
+
+ int m_value;
+
+ static int intern(const std::string &s);
+};
+
+inline std::ostream& operator<<(std::ostream &out, const PropertyName &n) {
+ out << n.getName();
+ return out;
+}
+
+inline std::string operator+(const std::string &s, const PropertyName &n) {
+ return s + n.getName();
+}
+
+struct PropertyNamesEqual
+{
+ bool operator() (const PropertyName &s1, const PropertyName &s2) const {
+ return s1 == s2;
+ }
+};
+
+struct PropertyNameHash
+{
+ size_t operator() (const PropertyName &s) const {
+ return static_cast<size_t>(s.getValue());
+ }
+};
+
+}
+
+#endif
diff --git a/src/base/Quantizer.cpp b/src/base/Quantizer.cpp
new file mode 100644
index 0000000..c0e4d1b
--- /dev/null
+++ b/src/base/Quantizer.cpp
@@ -0,0 +1,496 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Quantizer.h"
+#include "BaseProperties.h"
+#include "NotationTypes.h"
+#include "Selection.h"
+#include "Composition.h"
+#include "Sets.h"
+#include "Profiler.h"
+
+#include <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_NOTATION_QUANTIZER 1
+
+namespace Rosegarden {
+
+Quantizer::Quantizer(std::string source,
+ std::string target) :
+ m_source(source), m_target(target)
+{
+ makePropertyNames();
+}
+
+
+Quantizer::Quantizer(std::string target) :
+ m_target(target)
+{
+ if (target == RawEventData) {
+ m_source = GlobalSource;
+ } else {
+ m_source = RawEventData;
+ }
+
+ makePropertyNames();
+}
+
+
+Quantizer::~Quantizer()
+{
+ // nothing
+}
+
+void
+Quantizer::quantize(Segment *s) const
+{
+ quantize(s, s->begin(), s->getEndMarker());
+}
+
+void
+Quantizer::quantize(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ assert(m_toInsert.size() == 0);
+
+ quantizeRange(s, from, to);
+
+ insertNewEvents(s);
+}
+
+void
+Quantizer::quantize(EventSelection *selection)
+{
+ assert(m_toInsert.size() == 0);
+
+ Segment &segment = selection->getSegment();
+
+ // Attempt to handle non-contiguous selections.
+
+ // We have to be a bit careful here, because the rest-
+ // normalisation that's carried out as part of a quantize
+ // process is liable to replace the event that follows
+ // the quantized range. (moved here from editcommands.cpp)
+
+ EventSelection::RangeList ranges(selection->getRanges());
+
+ // So that we can retrieve a list of new events we cheat and stop
+ // the m_toInsert vector from being cleared automatically. Remember
+ // to turn it back on.
+ //
+
+ EventSelection::RangeList::iterator r = ranges.end();
+ while (r-- != ranges.begin()) {
+
+/*
+ cerr << "Quantizer: quantizing range ";
+ if (r->first == segment.end()) {
+ cerr << "end";
+ } else {
+ cerr << (*r->first)->getAbsoluteTime();
+ }
+ cerr << " to ";
+ if (r->second == segment.end()) {
+ cerr << "end";
+ } else {
+ cerr << (*r->second)->getAbsoluteTime();
+ }
+ cerr << endl;
+*/
+
+ quantizeRange(&segment, r->first, r->second);
+ }
+
+ // Push the new events to the selection
+ for (int i = 0; i < m_toInsert.size(); ++i) {
+ selection->addEvent(m_toInsert[i]);
+ }
+
+ // and then to the segment
+ insertNewEvents(&segment);
+}
+
+
+void
+Quantizer::fixQuantizedValues(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ assert(m_toInsert.size() == 0);
+
+ quantize(s, from, to);
+
+ if (m_target == RawEventData) return;
+
+ for (Segment::iterator nextFrom = from; from != to; from = nextFrom) {
+
+ ++nextFrom;
+
+ timeT t = getFromTarget(*from, AbsoluteTimeValue);
+ timeT d = getFromTarget(*from, DurationValue);
+ Event *e = new Event(**from, t, d);
+ s->erase(from);
+ m_toInsert.push_back(e);
+ }
+
+ insertNewEvents(s);
+}
+
+
+timeT
+Quantizer::getQuantizedDuration(const Event *e) const
+{
+ if (m_target == RawEventData) {
+ return e->getDuration();
+ } else if (m_target == NotationPrefix) {
+ return e->getNotationDuration();
+ } else {
+ timeT d = e->getDuration();
+ e->get<Int>(m_targetProperties[DurationValue], d);
+ return d;
+ }
+}
+
+timeT
+Quantizer::getQuantizedAbsoluteTime(const Event *e) const
+{
+ if (m_target == RawEventData) {
+ return e->getAbsoluteTime();
+ } else if (m_target == NotationPrefix) {
+ return e->getNotationAbsoluteTime();
+ } else {
+ timeT t = e->getAbsoluteTime();
+ e->get<Int>(m_targetProperties[AbsoluteTimeValue], t);
+ return t;
+ }
+}
+
+timeT
+Quantizer::getUnquantizedAbsoluteTime(Event *e) const
+{
+ return getFromSource(e, AbsoluteTimeValue);
+}
+
+timeT
+Quantizer::getUnquantizedDuration(Event *e) const
+{
+ return getFromSource(e, DurationValue);
+}
+
+void
+Quantizer::quantizeRange(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ //!!! It is vital that ordering is maintained after quantization.
+ // That is, an event whose absolute time quantizes to a time t must
+ // appear in the original segment before all events whose times
+ // quantize to greater than t. This means we must quantize the
+ // absolute times of non-note events as well as notes.
+
+ // We don't need to worry about quantizing rests, however; they're
+ // only used for notation and will be explicitly recalculated when
+ // the notation quantization values change.
+
+ for (Segment::iterator nextFrom = from; from != to; from = nextFrom) {
+
+ ++nextFrom;
+ quantizeSingle(s, from);
+ }
+}
+
+void
+Quantizer::unquantize(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to) const
+{
+ assert(m_toInsert.size() == 0);
+
+ for (Segment::iterator nextFrom = from; from != to; from = nextFrom) {
+ ++nextFrom;
+
+ if (m_target == RawEventData || m_target == NotationPrefix) {
+ setToTarget(s, from,
+ getFromSource(*from, AbsoluteTimeValue),
+ getFromSource(*from, DurationValue));
+
+ } else {
+ removeTargetProperties(*from);
+ }
+ }
+
+ insertNewEvents(s);
+}
+
+void
+Quantizer::unquantize(EventSelection *selection) const
+{
+ assert(m_toInsert.size() == 0);
+
+ Segment *s = &selection->getSegment();
+
+ Rosegarden::EventSelection::eventcontainer::iterator it
+ = selection->getSegmentEvents().begin();
+
+ for (; it != selection->getSegmentEvents().end(); it++) {
+
+ if (m_target == RawEventData || m_target == NotationPrefix) {
+
+ Segment::iterator from = s->findSingle(*it);
+ Segment::iterator to = s->findSingle(*it);
+ setToTarget(s, from,
+ getFromSource(*from, AbsoluteTimeValue),
+ getFromSource(*to, DurationValue));
+
+ } else {
+ removeTargetProperties(*it);
+ }
+ }
+
+ insertNewEvents(&selection->getSegment());
+}
+
+
+
+timeT
+Quantizer::getFromSource(Event *e, ValueType v) const
+{
+ Profiler profiler("Quantizer::getFromSource");
+
+// cerr << "Quantizer::getFromSource: source is \"" << m_source << "\"" << endl;
+
+ if (m_source == RawEventData) {
+
+ if (v == AbsoluteTimeValue) return e->getAbsoluteTime();
+ else return e->getDuration();
+
+ } else if (m_source == NotationPrefix) {
+
+ if (v == AbsoluteTimeValue) return e->getNotationAbsoluteTime();
+ else return e->getNotationDuration();
+
+ } else {
+
+ // We need to write the source from the target if the
+ // source doesn't exist (and the target does)
+
+ bool haveSource = e->has(m_sourceProperties[v]);
+ bool haveTarget = ((m_target == RawEventData) ||
+ (e->has(m_targetProperties[v])));
+ timeT t = 0;
+
+ if (!haveSource && haveTarget) {
+ t = getFromTarget(e, v);
+ e->setMaybe<Int>(m_sourceProperties[v], t);
+ return t;
+ }
+
+ e->get<Int>(m_sourceProperties[v], t);
+ return t;
+ }
+}
+
+timeT
+Quantizer::getFromTarget(Event *e, ValueType v) const
+{
+ Profiler profiler("Quantizer::getFromTarget");
+
+ if (m_target == RawEventData) {
+
+ if (v == AbsoluteTimeValue) return e->getAbsoluteTime();
+ else return e->getDuration();
+
+ } else if (m_target == NotationPrefix) {
+
+ if (v == AbsoluteTimeValue) return e->getNotationAbsoluteTime();
+ else return e->getNotationDuration();
+
+ } else {
+ timeT value;
+ if (v == AbsoluteTimeValue) value = e->getAbsoluteTime();
+ else value = e->getDuration();
+ e->get<Int>(m_targetProperties[v], value);
+ return value;
+ }
+}
+
+void
+Quantizer::setToTarget(Segment *s, Segment::iterator i,
+ timeT absTime, timeT duration) const
+{
+ Profiler profiler("Quantizer::setToTarget");
+
+ //cerr << "Quantizer::setToTarget: target is \"" << m_target << "\", absTime is " << absTime << ", duration is " << duration << " (unit is " << m_unit << ", original values are absTime " << (*i)->getAbsoluteTime() << ", duration " << (*i)->getDuration() << ")" << endl;
+
+ timeT st = 0, sd = 0;
+ bool haveSt = false, haveSd = false;
+ if (m_source != RawEventData && m_target == RawEventData) {
+ haveSt = (*i)->get<Int>(m_sourceProperties[AbsoluteTimeValue], st);
+ haveSd = (*i)->get<Int>(m_sourceProperties[DurationValue], sd);
+ }
+
+ Event *e;
+ if (m_target == RawEventData) {
+ e = new Event(**i, absTime, duration);
+ } else if (m_target == NotationPrefix) {
+ // Setting the notation absolute time on an event without
+ // recreating it would be dodgy, just as setting the absolute
+ // time would, because it could change the ordering of events
+ // that are already being referred to in ViewElementLists,
+ // preventing us from locating them in the ViewElementLists
+ // because their ordering would have silently changed
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "Quantizer: setting " << absTime << " to notation absolute time and "
+ << duration << " to notation duration"
+ << endl;
+#endif
+ e = new Event(**i, (*i)->getAbsoluteTime(), (*i)->getDuration(),
+ (*i)->getSubOrdering(), absTime, duration);
+ } else {
+ e = *i;
+ e->clearNonPersistentProperties();
+ }
+
+ if (m_target == NotationPrefix) {
+ timeT normalizeStart = std::min(absTime, (*i)->getAbsoluteTime());
+ timeT normalizeEnd = std::max(absTime + duration,
+ (*i)->getAbsoluteTime() +
+ (*i)->getDuration()) + 1;
+
+ if (m_normalizeRegion.first != m_normalizeRegion.second) {
+ normalizeStart = std::min(normalizeStart, m_normalizeRegion.first);
+ normalizeEnd = std::max(normalizeEnd, m_normalizeRegion.second);
+ }
+
+ m_normalizeRegion = std::pair<timeT, timeT>
+ (normalizeStart, normalizeEnd);
+ }
+
+ if (haveSt) e->setMaybe<Int>(m_sourceProperties[AbsoluteTimeValue],st);
+ if (haveSd) e->setMaybe<Int>(m_sourceProperties[DurationValue], sd);
+
+ if (m_target != RawEventData && m_target != NotationPrefix) {
+ e->setMaybe<Int>(m_targetProperties[AbsoluteTimeValue], absTime);
+ e->setMaybe<Int>(m_targetProperties[DurationValue], duration);
+ } else {
+ s->erase(i);
+ m_toInsert.push_back(e);
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "m_toInsert.size() is now " << m_toInsert.size() << endl;
+#endif
+}
+
+void
+Quantizer::removeProperties(Event *e) const
+{
+ if (m_source != RawEventData) {
+ e->unset(m_sourceProperties[AbsoluteTimeValue]);
+ e->unset(m_sourceProperties[DurationValue]);
+ }
+
+ if (m_target != RawEventData && m_target != NotationPrefix) {
+ e->unset(m_targetProperties[AbsoluteTimeValue]);
+ e->unset(m_targetProperties[DurationValue]);
+ }
+}
+
+void
+Quantizer::removeTargetProperties(Event *e) const
+{
+ if (m_target != RawEventData) {
+ e->unset(m_targetProperties[AbsoluteTimeValue]);
+ e->unset(m_targetProperties[DurationValue]);
+ }
+}
+
+void
+Quantizer::makePropertyNames()
+{
+ if (m_source != RawEventData && m_source != NotationPrefix) {
+ m_sourceProperties[AbsoluteTimeValue] = m_source + "AbsoluteTimeSource";
+ m_sourceProperties[DurationValue] = m_source + "DurationSource";
+ }
+
+ if (m_target != RawEventData && m_target != NotationPrefix) {
+ m_targetProperties[AbsoluteTimeValue] = m_target + "AbsoluteTimeTarget";
+ m_targetProperties[DurationValue] = m_target + "DurationTarget";
+ }
+}
+
+void
+Quantizer::insertNewEvents(Segment *s) const
+{
+ unsigned int sz = m_toInsert.size();
+
+ timeT minTime = m_normalizeRegion.first,
+ maxTime = m_normalizeRegion.second;
+
+ for (unsigned int i = 0; i < sz; ++i) {
+
+ timeT myTime = m_toInsert[i]->getAbsoluteTime();
+ timeT myDur = m_toInsert[i]->getDuration();
+ if (i == 0 || myTime < minTime) minTime = myTime;
+ if (i == 0 || myTime + myDur > maxTime) maxTime = myTime + myDur;
+
+ s->insert(m_toInsert[i]);
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "Quantizer::insertNewEvents: sz is " << sz
+ << ", minTime " << minTime << ", maxTime " << maxTime
+ << endl;
+#endif
+
+ if (m_target == NotationPrefix || m_target == RawEventData) {
+
+ if (m_normalizeRegion.first == m_normalizeRegion.second) {
+ if (sz > 0) {
+ s->normalizeRests(minTime, maxTime);
+ }
+ } else {
+ s->normalizeRests(minTime, maxTime);
+ m_normalizeRegion = std::pair<timeT, timeT>(0, 0);
+ }
+ }
+
+#ifdef DEBUG_NOTATION_QUANTIZER
+ cout << "Quantizer: calling normalizeRests("
+ << minTime << ", " << maxTime << ")" << endl;
+#endif
+
+ m_toInsert.clear();
+}
+
+
+
+
+}
diff --git a/src/base/Quantizer.h b/src/base/Quantizer.h
new file mode 100644
index 0000000..407b651
--- /dev/null
+++ b/src/base/Quantizer.h
@@ -0,0 +1,249 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef QUANTIZER_H
+#define QUANTIZER_H
+
+#include "Segment.h"
+#include "Event.h"
+#include "NotationTypes.h"
+#include "FastVector.h"
+#include <string>
+
+namespace Rosegarden {
+
+class EventSelection;
+
+/**
+ The Quantizer class rounds the starting times and durations of note
+ and rest events according to one of a set of possible criteria.
+*/
+
+class Quantizer
+{
+ // define the Quantizer API
+
+public:
+ virtual ~Quantizer();
+
+ /**
+ * Quantize a Segment.
+ */
+ void quantize(Segment *) const;
+
+ /**
+ * Quantize a section of a Segment.
+ */
+ void quantize(Segment *,
+ Segment::iterator from,
+ Segment::iterator to) const;
+
+ /**
+ * Quantize an EventSelection.
+ */
+ void quantize(EventSelection *);
+
+ /**
+ * Quantize a section of a Segment, and force the quantized
+ * results into the formal absolute time and duration of
+ * the events. This is a destructive operation that should
+ * not be carried out except on a user's explicit request.
+ * (If target is RawEventData, this will do nothing besides
+ * quantize. In this case, but no other, unquantize will
+ * still work afterwards.)
+ */
+ void fixQuantizedValues(Segment *,
+ Segment::iterator from,
+ Segment::iterator to) const;
+
+ /**
+ * Return the quantized duration of the event if it has been
+ * quantized -- otherwise just return the unquantized duration.
+ * Do not modify the event.
+ */
+ virtual timeT getQuantizedDuration(const Event *e) const;
+
+ /**
+ * Return the quantized absolute time of the event if it has been
+ * quantized -- otherwise just return the unquantized time. Do
+ * not modify the event.
+ */
+ virtual timeT getQuantizedAbsoluteTime(const Event *e) const;
+
+ /**
+ * Return the unquantized absolute time of the event --
+ * the absolute time that would be restored by a call to
+ * unquantize.
+ */
+ virtual timeT getUnquantizedAbsoluteTime(Event *e) const;
+
+ /**
+ * Return the unquantized absolute time of the event --
+ * the absolute time that would be restored by a call to
+ * unquantize.
+ */
+ virtual timeT getUnquantizedDuration(Event *e) const;
+
+ /**
+ * Unquantize all events in the given range, for this
+ * quantizer. Properties set by other quantizers with
+ * different propertyNamePrefix values will remain.
+ */
+ void unquantize(Segment *,
+ Segment::iterator from, Segment::iterator to) const;
+
+ /**
+ * Unquantize a selection of Events
+ */
+ void unquantize(EventSelection *) const;
+
+ static const std::string RawEventData;
+ static const std::string DefaultTarget;
+ static const std::string GlobalSource;
+ static const std::string NotationPrefix;
+
+protected:
+ /**
+ * \arg source, target : Description of where to find the
+ * times to be quantized, and where to put the quantized results.
+ *
+ * These may be strings, specifying a prefix for the names
+ * of properties to contain the timings, or may be the special
+ * value RawEventData in which case the event's absolute time
+ * and duration will be used instead of properties.
+ *
+ * If source specifies a property prefix for properties that are
+ * found not to exist, they will be pre-filled from the original
+ * timings in the target values before being quantized and then
+ * set back into the target. (This permits a quantizer to write
+ * directly into the Event's absolute time and duration without
+ * losing the original values, because they are backed up
+ * automatically into the source properties.)
+ *
+ * Note that because it's impossible to modify the duration or
+ * absolute time of an event after construction, if target is
+ * RawEventData the quantizer must re-construct each event in
+ * order to adjust its timings. This operation (deliberately)
+ * loses any non-persistent properties in the events. This
+ * does not happen if target is a property prefix.
+ *
+ * Examples:
+ *
+ * -- if source == RawEventData and target == "MyPrefix",
+ * values will be read from the event's absolute time and
+ * duration, quantized, and written into MyPrefixAbsoluteTime
+ * and MyPrefixDuration properties on the event. A call to
+ * unquantize will simply delete these properties.
+ *
+ * -- if source == "MyPrefix" and target == RawEventData,
+ * the MyPrefixAbsoluteTime and MyPrefixDuration will be
+ * populated if necessary from the event's absolute time and
+ * duration, and then quantized and written back into the
+ * event's values. A call to unquantize will write the
+ * MyPrefix-property timings back into the event's values,
+ * and delete the MyPrefix properties.
+ *
+ * -- if source == "YourPrefix" and target == "MyPrefix",
+ * values will be read from YourPrefixAbsoluteTime and
+ * YourPrefixDuration, quantized, and written into the
+ * MyPrefix-properties. This may be useful for piggybacking
+ * onto another quantizer's output.
+ *
+ * -- if source == RawEventData and target == RawEventData,
+ * values will be read from the event's absolute time and
+ * duration, quantized, and written back to these values.
+ */
+ Quantizer(std::string source, std::string target);
+
+ /**
+ * If only target is supplied, source is deduced appropriately
+ * as GlobalSource if target == RawEventData and RawEventData
+ * otherwise.
+ */
+ Quantizer(std::string target);
+
+ /**
+ * To implement a subclass of Quantizer, you should
+ * override either quantizeSingle (if your quantizer is simple
+ * enough only to have to look at a single event at a time) or
+ * quantizeRange. The default implementation of quantizeRange
+ * simply calls quantizeSingle on each non-rest event in turn.
+ * The default implementation of quantizeSingle, as you see,
+ * does nothing.
+ *
+ * Note that implementations of these methods should call
+ * getFromSource and setToTarget to get and set the unquantized
+ * and quantized data; they should not query the event properties
+ * or timings directly.
+ *
+ * NOTE: It is vital that ordering is maintained after
+ * quantization. That is, an event whose absolute time quantizes
+ * to a time t must appear in the original segment before all
+ * events whose times quantize to greater than t. This means you
+ * must quantize the absolute times of non-note events as well as
+ * notes. You don't need to worry about quantizing rests,
+ * however; they're only used for notation and will be
+ * automatically recalculated if the notation quantization values
+ * are seen to change.
+ */
+ virtual void quantizeSingle(Segment *,
+ Segment::iterator) const { }
+
+ /**
+ * See note for quantizeSingle.
+ */
+ virtual void quantizeRange(Segment *,
+ Segment::iterator,
+ Segment::iterator) const;
+
+ std::string m_source;
+ std::string m_target;
+ mutable std::pair<timeT, timeT> m_normalizeRegion;
+
+ enum ValueType { AbsoluteTimeValue = 0, DurationValue = 1 };
+
+ PropertyName m_sourceProperties[2];
+ PropertyName m_targetProperties[2];
+
+public: // should be protected, but gcc-2.95 doesn't like allowing NotationQuantizer::m_impl to access them
+ timeT getFromSource(Event *, ValueType) const;
+ timeT getFromTarget(Event *, ValueType) const;
+ void setToTarget(Segment *, Segment::iterator, timeT t, timeT d) const;
+ mutable FastVector<Event *> m_toInsert;
+
+protected:
+ void removeProperties(Event *) const;
+ void removeTargetProperties(Event *) const;
+ void makePropertyNames();
+
+ void insertNewEvents(Segment *) const;
+
+private: // not provided
+ Quantizer(const Quantizer &);
+ Quantizer &operator=(const Quantizer &);
+ bool operator==(const Quantizer &) const;
+ bool operator!=(const Quantizer & c) const;
+};
+
+
+}
+
+#endif
diff --git a/src/base/RealTime.cpp b/src/base/RealTime.cpp
new file mode 100644
index 0000000..8f8125f
--- /dev/null
+++ b/src/base/RealTime.cpp
@@ -0,0 +1,236 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <iostream>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include "RealTime.h"
+#include "sys/time.h"
+
+namespace Rosegarden {
+
+// A RealTime consists of two ints that must be at least 32 bits each.
+// A signed 32-bit int can store values exceeding +/- 2 billion. This
+// means we can safely use our lower int for nanoseconds, as there are
+// 1 billion nanoseconds in a second and we need to handle double that
+// because of the implementations of addition etc that we use.
+//
+// The maximum valid RealTime on a 32-bit system is somewhere around
+// 68 years: 999999999 nanoseconds longer than the classic Unix epoch.
+
+#define ONE_BILLION 1000000000
+
+RealTime::RealTime(int s, int n) :
+ sec(s), nsec(n)
+{
+ if (sec == 0) {
+ while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
+ while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
+ } else if (sec < 0) {
+ while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
+ while (nsec > 0) { nsec -= ONE_BILLION; ++sec; }
+ } else {
+ while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
+ while (nsec < 0) { nsec += ONE_BILLION; --sec; }
+ }
+}
+
+RealTime
+RealTime::fromSeconds(double sec)
+{
+ return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
+}
+
+RealTime
+RealTime::fromMilliseconds(int msec)
+{
+ return RealTime(msec / 1000, (msec % 1000) * 1000000);
+}
+
+RealTime
+RealTime::fromTimeval(const struct timeval &tv)
+{
+ return RealTime(tv.tv_sec, tv.tv_usec * 1000);
+}
+
+std::ostream &operator<<(std::ostream &out, const RealTime &rt)
+{
+ if (rt < RealTime::zeroTime) {
+ out << "-";
+ } else {
+ out << " ";
+ }
+
+ int s = (rt.sec < 0 ? -rt.sec : rt.sec);
+ int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec);
+
+ out << s << ".";
+
+ int nn(n);
+ if (nn == 0) out << "00000000";
+ else while (nn < (ONE_BILLION / 10)) {
+ out << "0";
+ nn *= 10;
+ }
+
+ out << n << "R";
+ return out;
+}
+
+std::string
+RealTime::toString(bool align) const
+{
+ std::stringstream out;
+ out << *this;
+
+#if (__GNUC__ < 3)
+ out << std::ends;
+#endif
+
+ std::string s = out.str();
+
+ if (!align && *this >= RealTime::zeroTime) {
+ // remove leading " "
+ s = s.substr(1, s.length() - 1);
+ }
+
+ // remove trailing R
+ return s.substr(0, s.length() - 1);
+}
+
+std::string
+RealTime::toText(bool fixedDp) const
+{
+ if (*this < RealTime::zeroTime) return "-" + (-*this).toText();
+
+ std::stringstream out;
+
+ if (sec >= 3600) {
+ out << (sec / 3600) << ":";
+ }
+
+ if (sec >= 60) {
+ out << (sec % 3600) / 60 << ":";
+ }
+
+ if (sec >= 10) {
+ out << ((sec % 60) / 10);
+ }
+
+ out << (sec % 10);
+
+ int ms = msec();
+
+ if (ms != 0) {
+ out << ".";
+ out << (ms / 100);
+ ms = ms % 100;
+ if (ms != 0) {
+ out << (ms / 10);
+ ms = ms % 10;
+ } else if (fixedDp) {
+ out << "0";
+ }
+ if (ms != 0) {
+ out << ms;
+ } else if (fixedDp) {
+ out << "0";
+ }
+ } else if (fixedDp) {
+ out << ".000";
+ }
+
+#if (__GNUC__ < 3)
+ out << std::ends;
+#endif
+
+ std::string s = out.str();
+
+ return s;
+}
+
+RealTime
+RealTime::operator*(double m) const
+{
+ double t = (double(nsec) / ONE_BILLION) * m;
+ t += sec * m;
+ return fromSeconds(t);
+}
+
+RealTime
+RealTime::operator/(int d) const
+{
+ int secdiv = sec / d;
+ int secrem = sec % d;
+
+ double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d;
+
+ return RealTime(secdiv, int(nsecdiv + 0.5));
+}
+
+double
+RealTime::operator/(const RealTime &r) const
+{
+ double lTotal = double(sec) * ONE_BILLION + double(nsec);
+ double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec);
+
+ if (rTotal == 0) return 0.0;
+ else return lTotal/rTotal;
+}
+
+long
+RealTime::realTime2Frame(const RealTime &time, unsigned int sampleRate)
+{
+ if (time < zeroTime) return -realTime2Frame(-time, sampleRate);
+
+ // We like integers. The last term is always zero unless the
+ // sample rate is greater than 1MHz, but hell, you never know...
+
+ long frame =
+ time.sec * sampleRate +
+ (time.msec() * sampleRate) / 1000 +
+ ((time.usec() - 1000 * time.msec()) * sampleRate) / 1000000 +
+ ((time.nsec - 1000 * time.usec()) * sampleRate) / 1000000000;
+
+ return frame;
+}
+
+RealTime
+RealTime::frame2RealTime(long frame, unsigned int sampleRate)
+{
+ if (frame < 0) return -frame2RealTime(-frame, sampleRate);
+
+ RealTime rt;
+ rt.sec = frame / sampleRate;
+ frame -= rt.sec * sampleRate;
+ rt.nsec = (int)(((float(frame) * 1000000) / sampleRate) * 1000);
+ return rt;
+}
+
+const RealTime RealTime::zeroTime(0,0);
+
+}
diff --git a/src/base/RealTime.h b/src/base/RealTime.h
new file mode 100644
index 0000000..3ebef26
--- /dev/null
+++ b/src/base/RealTime.h
@@ -0,0 +1,124 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _REAL_TIME_H_
+#define _REAL_TIME_H_
+
+#include <iostream>
+#include <string>
+
+struct timeval;
+
+namespace Rosegarden
+{
+
+struct RealTime
+{
+ int sec;
+ int nsec;
+
+ int usec() const { return nsec / 1000; }
+ int msec() const { return nsec / 1000000; }
+
+ RealTime(): sec(0), nsec(0) {}
+ RealTime(int s, int n);
+
+ RealTime(const RealTime &r) :
+ sec(r.sec), nsec(r.nsec) { }
+
+ static RealTime fromSeconds(double sec);
+ static RealTime fromMilliseconds(int msec);
+ static RealTime fromTimeval(const struct timeval &);
+
+ RealTime &operator=(const RealTime &r) {
+ sec = r.sec; nsec = r.nsec; return *this;
+ }
+
+ RealTime operator+(const RealTime &r) const {
+ return RealTime(sec + r.sec, nsec + r.nsec);
+ }
+ RealTime operator-(const RealTime &r) const {
+ return RealTime(sec - r.sec, nsec - r.nsec);
+ }
+ RealTime operator-() const {
+ return RealTime(-sec, -nsec);
+ }
+
+ bool operator <(const RealTime &r) const {
+ if (sec == r.sec) return nsec < r.nsec;
+ else return sec < r.sec;
+ }
+
+ bool operator >(const RealTime &r) const {
+ if (sec == r.sec) return nsec > r.nsec;
+ else return sec > r.sec;
+ }
+
+ bool operator==(const RealTime &r) const {
+ return (sec == r.sec && nsec == r.nsec);
+ }
+
+ bool operator!=(const RealTime &r) const {
+ return !(r == *this);
+ }
+
+ bool operator>=(const RealTime &r) const {
+ if (sec == r.sec) return nsec >= r.nsec;
+ else return sec >= r.sec;
+ }
+
+ bool operator<=(const RealTime &r) const {
+ if (sec == r.sec) return nsec <= r.nsec;
+ else return sec <= r.sec;
+ }
+
+ RealTime operator*(double m) const;
+ RealTime operator/(int d) const;
+
+ // Find the fractional difference between times
+ //
+ double operator/(const RealTime &r) const;
+
+ // Return a human-readable debug-type string to full precision
+ // (probably not a format to show to a user directly). If align
+ // is true, prepend " " to the start of positive values so that
+ // they line up with negative ones (which start with "-").
+ //
+ std::string toString(bool align = false) const;
+
+ // Return a user-readable string to the nearest millisecond
+ // in a form like HH:MM:SS.mmm
+ //
+ std::string toText(bool fixedDp = false) const;
+
+ // Convenience functions for handling sample frames
+ //
+ static long realTime2Frame(const RealTime &r, unsigned int sampleRate);
+ static RealTime frame2RealTime(long frame, unsigned int sampleRate);
+
+ static const RealTime zeroTime;
+};
+
+std::ostream &operator<<(std::ostream &out, const RealTime &rt);
+
+}
+
+#endif
diff --git a/src/base/RefreshStatus.h b/src/base/RefreshStatus.h
new file mode 100644
index 0000000..4c39a18
--- /dev/null
+++ b/src/base/RefreshStatus.h
@@ -0,0 +1,76 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _REFRESH_STATUS_H_
+#define _REFRESH_STATUS_H_
+
+namespace Rosegarden
+{
+
+class RefreshStatus
+{
+public:
+ RefreshStatus() : m_needsRefresh(true) {}
+
+ bool needsRefresh() { return m_needsRefresh; }
+ void setNeedsRefresh(bool s) { m_needsRefresh = s; }
+
+protected:
+ bool m_needsRefresh;
+};
+
+template<class RS>
+class RefreshStatusArray
+{
+public:
+ unsigned int getNewRefreshStatusId();
+ size_t size() { return m_refreshStatuses.size(); }
+ RS& getRefreshStatus(unsigned int id) { return m_refreshStatuses[id]; }
+ void updateRefreshStatuses();
+
+protected:
+ std::vector<RS> m_refreshStatuses;
+};
+
+template<class RS>
+unsigned int RefreshStatusArray<RS>::getNewRefreshStatusId()
+{
+ m_refreshStatuses.push_back(RS());
+ unsigned int res = m_refreshStatuses.size() - 1;
+ return res;
+}
+
+void breakpoint();
+
+template<class RS>
+void RefreshStatusArray<RS>::updateRefreshStatuses()
+{
+ // breakpoint(); // for debug purposes, so one can set a breakpoint
+ // in this template code (set it in breakpoint() itself).
+ for(unsigned int i = 0; i < m_refreshStatuses.size(); ++i)
+ m_refreshStatuses[i].setNeedsRefresh(true);
+}
+
+
+}
+
+#endif
diff --git a/src/base/RulerScale.cpp b/src/base/RulerScale.cpp
new file mode 100644
index 0000000..510a0a5
--- /dev/null
+++ b/src/base/RulerScale.cpp
@@ -0,0 +1,243 @@
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <cmath>
+#include "RulerScale.h"
+#include "Composition.h"
+
+namespace Rosegarden {
+
+
+//////////////////////////////////////////////////////////////////////
+// RulerScale
+//////////////////////////////////////////////////////////////////////
+
+RulerScale::RulerScale(Composition *c) :
+ m_composition(c)
+{
+ // nothing
+}
+
+RulerScale::~RulerScale()
+{
+ // nothing
+}
+
+int
+RulerScale::getFirstVisibleBar() const
+{
+ return m_composition->getBarNumber(m_composition->getStartMarker());
+}
+
+int
+RulerScale::getLastVisibleBar() const
+{
+ return m_composition->getBarNumber(m_composition->getEndMarker());
+}
+
+double
+RulerScale::getBarWidth(int n) const
+{
+ return getBarPosition(n + 1) - getBarPosition(n);
+}
+
+double
+RulerScale::getBeatWidth(int n) const
+{
+ std::pair<timeT, timeT> barRange = m_composition->getBarRange(n);
+ timeT barDuration = barRange.second - barRange.first;
+ if (barDuration == 0) return 0;
+
+ bool isNew;
+ TimeSignature timeSig = m_composition->getTimeSignatureInBar(n, isNew);
+
+ // cope with partial bars
+ double theoreticalWidth =
+ (getBarWidth(n) * timeSig.getBarDuration()) / barDuration;
+
+ return theoreticalWidth / timeSig.getBeatsPerBar();
+}
+
+int
+RulerScale::getBarForX(double x) const
+{
+ // binary search
+
+ int minBar = getFirstVisibleBar(),
+ maxBar = getLastVisibleBar();
+
+ while (maxBar > minBar) {
+ int middle = minBar + (maxBar - minBar) / 2;
+ if (x > getBarPosition(middle)) minBar = middle + 1;
+ else maxBar = middle;
+ }
+
+ // we've just done equivalent of lower_bound -- we're one bar too
+ // far into the list
+
+ if (minBar > getFirstVisibleBar()) return minBar - 1;
+ else return minBar;
+}
+
+timeT
+RulerScale::getTimeForX(double x) const
+{
+ int n = getBarForX(x);
+
+ double barWidth = getBarWidth(n);
+ std::pair<timeT, timeT> barRange = m_composition->getBarRange(n);
+
+ if (barWidth < 1.0) {
+
+ return barRange.first;
+
+ } else {
+
+ timeT barDuration = barRange.second - barRange.first;
+ x -= getBarPosition(n);
+
+ return barRange.first + (timeT)nearbyint(((double)(x * barDuration) / barWidth));
+ }
+}
+
+double
+RulerScale::getXForTime(timeT time) const
+{
+ int n = m_composition->getBarNumber(time);
+
+ double barWidth = getBarWidth(n);
+ std::pair<timeT, timeT> barRange = m_composition->getBarRange(n);
+ timeT barDuration = barRange.second - barRange.first;
+
+ if (barDuration == 0) {
+
+ return getBarPosition(n);
+
+ } else {
+
+ time -= barRange.first;
+ return getBarPosition(n) + (double)(time * barWidth) / barDuration;
+ }
+}
+
+timeT
+RulerScale::getDurationForWidth(double x, double width) const
+{
+ return getTimeForX(x + width) - getTimeForX(x);
+}
+
+double
+RulerScale::getWidthForDuration(timeT startTime, timeT duration) const
+{
+ return getXForTime(startTime + duration) - getXForTime(startTime);
+}
+
+double
+RulerScale::getTotalWidth() const
+{
+ int n = getLastVisibleBar();
+ return getBarPosition(n) + getBarWidth(n);
+}
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// SimpleRulerScale
+//////////////////////////////////////////////////////////////////////
+
+
+SimpleRulerScale::SimpleRulerScale(Composition *composition,
+ double origin, double ratio) :
+ RulerScale(composition),
+ m_origin(origin),
+ m_ratio(ratio)
+{
+ // nothing
+}
+
+SimpleRulerScale::SimpleRulerScale(const SimpleRulerScale &ruler):
+ RulerScale(ruler.getComposition()),
+ m_origin(ruler.getOrigin()),
+ m_ratio(ruler.getUnitsPerPixel())
+{
+ // nothing
+}
+
+
+SimpleRulerScale::~SimpleRulerScale()
+{
+ // nothing
+}
+
+double
+SimpleRulerScale::getBarPosition(int n) const
+{
+ timeT barStart = m_composition->getBarRange(n).first;
+ return getXForTime(barStart);
+}
+
+double
+SimpleRulerScale::getBarWidth(int n) const
+{
+ std::pair<timeT, timeT> range = m_composition->getBarRange(n);
+ return (double)(range.second - range.first) / m_ratio;
+}
+
+double
+SimpleRulerScale::getBeatWidth(int n) const
+{
+ bool isNew;
+ TimeSignature timeSig(m_composition->getTimeSignatureInBar(n, isNew));
+ return (double)(timeSig.getBeatDuration()) / m_ratio;
+}
+
+int
+SimpleRulerScale::getBarForX(double x) const
+{
+ return m_composition->getBarNumber(getTimeForX(x));
+}
+
+timeT
+SimpleRulerScale::getTimeForX(double x) const
+{
+ timeT t = (timeT)(nearbyint((double)(x - m_origin) * m_ratio));
+
+ int firstBar = getFirstVisibleBar();
+ if (firstBar != 0) {
+ t += m_composition->getBarRange(firstBar).first;
+ }
+
+ return t;
+}
+
+double
+SimpleRulerScale::getXForTime(timeT time) const
+{
+ int firstBar = getFirstVisibleBar();
+ if (firstBar != 0) {
+ time -= m_composition->getBarRange(firstBar).first;
+ }
+
+ return m_origin + (double)time / m_ratio;
+}
+
+
+}
diff --git a/src/base/RulerScale.h b/src/base/RulerScale.h
new file mode 100644
index 0000000..763ca13
--- /dev/null
+++ b/src/base/RulerScale.h
@@ -0,0 +1,166 @@
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _RULER_SCALE_H_
+#define _RULER_SCALE_H_
+
+#include "Event.h"
+
+namespace Rosegarden {
+
+class Composition;
+
+/**
+ * RulerScale is a base for classes that may be queried in order to
+ * discover the correct x-coordinates for bar lines and bar
+ * subdivisions.
+ *
+ * RulerScale does not contain any methods that relate bar numbers
+ * to times, time signature or duration -- those are in Composition.
+ *
+ * The methods in RulerScale should return extrapolated (but valid)
+ * results even when passed a bar number outside the theoretically
+ * visible or existant bar range.
+ *
+ * Apart from getBarPosition, every method in this class has a
+ * default implementation, which should work correctly provided
+ * the subclass maintains spacing proportional to time within a
+ * bar, but which may not be an efficient implementation for any
+ * given subclass.
+ *
+ * (Potential to-do: At the moment all our RulerScales are used in
+ * contexts where spacing proportional to time within a bar is the
+ * only interpretation that makes sense, so this is okay. In
+ * theory though we should probably subclass out these "default"
+ * implementations into an intermediate abstract class.)
+ */
+
+class RulerScale
+{
+public:
+ virtual ~RulerScale();
+ Composition *getComposition() const { return m_composition; }
+
+ /**
+ * Return the number of the first visible bar.
+ */
+ virtual int getFirstVisibleBar() const;
+
+ /**
+ * Return the number of the last visible bar. (The last
+ * visible bar_line_ will be at the end of this bar.)
+ */
+ virtual int getLastVisibleBar() const;
+
+ /**
+ * Return the x-coordinate at which bar number n starts.
+ */
+ virtual double getBarPosition(int n) const = 0;
+
+ /**
+ * Return the width of bar number n.
+ */
+ virtual double getBarWidth(int n) const;
+
+ /**
+ * Return the width of each beat subdivision in bar n.
+ */
+ virtual double getBeatWidth(int n) const;
+
+ /**
+ * Return the number of the bar containing the given x-coord.
+ */
+ virtual int getBarForX(double x) const;
+
+ /**
+ * Return the nearest time value to the given x-coord.
+ */
+ virtual timeT getTimeForX(double x) const;
+
+ /**
+ * Return the x-coord corresponding to the given time value.
+ */
+ virtual double getXForTime(timeT time) const;
+
+ /**
+ * Return the duration corresponding to the given delta-x
+ * starting at the given x-coord.
+ */
+ virtual timeT getDurationForWidth(double x, double width) const;
+
+ /**
+ * Return the width corresponding to the given duration
+ * starting at the given time.
+ */
+ virtual double getWidthForDuration(timeT startTime, timeT duration) const;
+
+ /**
+ * Return the width of the entire scale.
+ */
+ virtual double getTotalWidth() const;
+
+protected:
+ RulerScale(Composition *c);
+ Composition *m_composition;
+};
+
+
+/**
+ * SimpleRulerScale is an implementation of RulerScale that maintains
+ * a strict proportional correspondence between x-coordinate and time.
+ */
+
+class SimpleRulerScale : public RulerScale
+{
+public:
+ /**
+ * Construct a SimpleRulerScale for the given Composition, with a
+ * given origin and x-coord/time ratio. (For example, a ratio of
+ * 10 means that one pixel equals 10 time units.)
+ */
+ SimpleRulerScale(Composition *composition,
+ double origin, double unitsPerPixel);
+ virtual ~SimpleRulerScale();
+
+ double getOrigin() const { return m_origin; }
+ void setOrigin(double origin) { m_origin = origin; }
+
+ double getUnitsPerPixel() const { return m_ratio; }
+ void setUnitsPerPixel(double ratio) { m_ratio = ratio; }
+
+ virtual double getBarPosition(int n) const;
+ virtual double getBarWidth(int n) const;
+ virtual double getBeatWidth(int n) const;
+ virtual int getBarForX(double x) const;
+ virtual timeT getTimeForX(double x) const;
+ virtual double getXForTime(timeT time) const;
+
+protected:
+ double m_origin;
+ double m_ratio;
+
+private:
+ SimpleRulerScale(const SimpleRulerScale &ruler);
+ SimpleRulerScale &operator=(const SimpleRulerScale &ruler);
+};
+
+}
+
+#endif
diff --git a/src/base/ScriptAPI.cpp b/src/base/ScriptAPI.cpp
new file mode 100644
index 0000000..be01550
--- /dev/null
+++ b/src/base/ScriptAPI.cpp
@@ -0,0 +1,85 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2004
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "ScriptAPI.h"
+
+#include "Composition.h"
+#include "Segment.h"
+#include "Event.h"
+#include "Sets.h"
+
+#include <map>
+
+namespace Rosegarden
+{
+
+class ScriptRep
+{
+public:
+
+ //!!! Needs to be a SegmentObserver _and_ CompositionObserver.
+ // If an event is removed from a segment, we have to drop it too.
+ // If a segment is removed from a composition likewise
+
+ Event *getEvent(ScriptInterface::EventId id);
+
+
+protected:
+ Composition *m_composition;
+ CompositionTimeSliceAdapter *m_adapter;
+ GlobalChord *m_chord;
+ std::map<ScriptInterface::EventId, Event *> m_events;
+};
+
+Event *
+ScriptRep::getEvent(ScriptInterface::EventId id)
+{
+ return m_events[id];
+}
+
+class ScriptInterface::ScriptContainer :
+ public std::map<ScriptId, ScriptRep *> { };
+
+ScriptInterface::ScriptInterface(Rosegarden::Composition *composition) :
+ m_composition(composition),
+ m_scripts(new ScriptContainer())
+{
+}
+
+ScriptInterface::~ScriptInterface()
+{
+}
+
+std::string
+ScriptInterface::getEventType(ScriptId id, EventId eventId)
+{
+ ScriptRep *rep = (*m_scripts)[id];
+ if (!rep) return "";
+
+ Event *event = rep->getEvent(eventId);
+ if (!event) return "";
+
+ return event->getType();
+}
+
+
+}
+
diff --git a/src/base/ScriptAPI.h b/src/base/ScriptAPI.h
new file mode 100644
index 0000000..8d721a4
--- /dev/null
+++ b/src/base/ScriptAPI.h
@@ -0,0 +1,128 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2004
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SCRIPT_API_H_
+#define _SCRIPT_API_H_
+
+#include "Segment.h"
+
+namespace Rosegarden
+{
+
+class Composition;
+
+class ScriptInterface
+{
+public:
+ typedef int ScriptId;
+ typedef int SegmentId;
+ typedef int EventId;
+ typedef int ScriptTime;
+
+ // Resolution defines the meaning of ScriptTime units. If set to
+ // the QuantizedNN values, each ScriptTime unit will correspond to
+ // the duration of an NN-th note. If Unquantized, ScriptTime will
+ // correspond to timeT, i.e. 960 to a quarter note.
+ // And Notation is like Quantized64 except that the times are
+ // obtained from the notation time and duration properties of each
+ // event instead of the raw ones.
+
+ enum Resolution {
+ Unquantized,
+ Notation,
+ Quantized64,
+ Quantized32,
+ Quantized16
+ };
+
+ enum Scope {
+ Global,
+ Segment
+ };
+
+ class ScriptEvent {
+ EventId id;
+ int bar; // number, 1-based
+ ScriptTime time; // within bar
+ ScriptTime duration;
+ int pitch; // 0-127 if note, -1 otherwise
+ };
+
+ class ScriptTimeSignature {
+ int numerator;
+ int denominator;
+ ScriptTime duration;
+ };
+
+ class ScriptKeySignature {
+ int accidentals;
+ bool sharps;
+ bool minor;
+ };
+
+ ScriptInterface(Composition *composition);
+ virtual ~ScriptInterface();
+
+ ScriptId createScript(SegmentId target, Resolution resolution, Scope scope);
+ void destroyScript(ScriptId id);
+
+ // A script can only proceed forwards. The getEvent and getNote
+ // methods get the next event (including notes) or note within the
+ // current chord or timeslice; the advance method moves forwards
+ // to the next chord or other event. So to process through all
+ // events, call advance() followed by a loop of getEvent() calls
+ // before the next advance(), and so on. An event with id -1
+ // marks the end of a slice. ( -1 is an out-of-range value for
+ // all types of id.)
+
+ ScriptEvent getEvent(ScriptId id);
+ ScriptEvent getNote(ScriptId id);
+
+ bool advance(ScriptId id);
+
+ ScriptTimeSignature getTimeSignature(ScriptId id);
+ ScriptKeySignature getKeySignature(ScriptId id);
+
+ EventId addNote(ScriptId id,
+ int bar, ScriptTime time, ScriptTime duration, int pitch);
+
+ EventId addEvent(ScriptId id,
+ std::string type, int bar, ScriptTime time, ScriptTime duration);
+
+ void deleteEvent(ScriptId id, EventId event);
+
+ std::string getEventType(ScriptId id, EventId event);
+ std::string getProperty(ScriptId id, EventId event, std::string property);
+ void setProperty(ScriptId id, EventId event, std::string property, std::string value);
+
+protected:
+ Composition *m_composition;
+
+ class ScriptContainer;
+ ScriptContainer *m_scripts;
+};
+
+}
+
+
+#endif
+
+
diff --git a/src/base/Segment.cpp b/src/base/Segment.cpp
new file mode 100644
index 0000000..2f65acd
--- /dev/null
+++ b/src/base/Segment.cpp
@@ -0,0 +1,1294 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Segment.h"
+#include "NotationTypes.h"
+#include "BaseProperties.h"
+#include "Composition.h"
+#include "BasicQuantizer.h"
+#include "Profiler.h"
+
+#include <iostream>
+#include <algorithm>
+#include <iterator>
+#include <cstdio>
+#include <typeinfo>
+
+namespace Rosegarden
+{
+using std::cerr;
+using std::endl;
+using std::string;
+
+//#define DEBUG_NORMALIZE_RESTS 1
+
+static int _runtimeSegmentId = 0;
+
+Segment::Segment(SegmentType segmentType, timeT startTime) :
+ std::multiset<Event*, Event::EventCmp>(),
+ m_composition(0),
+ m_startTime(startTime),
+ m_endMarkerTime(0),
+ m_endTime(startTime),
+ m_track(0),
+ m_type(segmentType),
+ m_colourIndex(0),
+ m_id(0),
+ m_audioFileId(0),
+ m_unstretchedFileId(0),
+ m_stretchRatio(1.0),
+ m_audioStartTime(0, 0),
+ m_audioEndTime(0, 0),
+ m_repeating(false),
+ m_quantizer(new BasicQuantizer()),
+ m_quantize(false),
+ m_transpose(0),
+ m_delay(0),
+ m_realTimeDelay(0, 0),
+ m_clefKeyList(0),
+ m_runtimeSegmentId(_runtimeSegmentId++),
+ m_snapGridSize(-1),
+ m_viewFeatures(0),
+ m_autoFade(false),
+ m_fadeInTime(Rosegarden::RealTime::zeroTime),
+ m_fadeOutTime(Rosegarden::RealTime::zeroTime),
+ m_highestPlayable(127),
+ m_lowestPlayable(0)
+{
+}
+
+Segment::Segment(const Segment &segment):
+ std::multiset<Event*, Event::EventCmp>(),
+ m_composition(0), // Composition should decide what's in it and what's not
+ m_startTime(segment.getStartTime()),
+ m_endMarkerTime(segment.m_endMarkerTime ?
+ new timeT(*segment.m_endMarkerTime) : 0),
+ m_endTime(segment.getEndTime()),
+ m_track(segment.getTrack()),
+ m_type(segment.getType()),
+ m_label(segment.getLabel()),
+ m_colourIndex(segment.getColourIndex()),
+ m_id(0),
+ m_audioFileId(segment.getAudioFileId()),
+ m_unstretchedFileId(segment.getUnstretchedFileId()),
+ m_stretchRatio(segment.getStretchRatio()),
+ m_audioStartTime(segment.getAudioStartTime()),
+ m_audioEndTime(segment.getAudioEndTime()),
+ m_repeating(segment.isRepeating()),
+ m_quantizer(new BasicQuantizer(segment.m_quantizer->getUnit(),
+ segment.m_quantizer->getDoDurations())),
+ m_quantize(segment.hasQuantization()),
+ m_transpose(segment.getTranspose()),
+ m_delay(segment.getDelay()),
+ m_realTimeDelay(segment.getRealTimeDelay()),
+ m_clefKeyList(0),
+ m_runtimeSegmentId(_runtimeSegmentId++),
+ m_snapGridSize(-1),
+ m_viewFeatures(0),
+ m_autoFade(segment.isAutoFading()),
+ m_fadeInTime(segment.getFadeInTime()),
+ m_fadeOutTime(segment.getFadeOutTime()),
+ m_highestPlayable(127),
+ m_lowestPlayable(0)
+{
+ for (const_iterator it = segment.begin();
+ segment.isBeforeEndMarker(it); ++it) {
+ insert(new Event(**it));
+ }
+}
+
+Segment::~Segment()
+{
+ if (!m_observers.empty()) {
+ cerr << "Warning: Segment::~Segment() with " << m_observers.size()
+ << " observers still extant" << endl;
+ cerr << "Observers are:";
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ cerr << " " << (void *)(*i);
+ cerr << " [" << typeid(**i).name() << "]";
+ }
+ cerr << endl;
+ }
+
+ notifySourceDeletion();
+
+ if (m_composition) m_composition->detachSegment(this);
+
+ if (m_clefKeyList) {
+ // don't delete contents of m_clefKeyList: the pointers
+ // are just aliases for events in the main segment
+ delete m_clefKeyList;
+ }
+
+ // Clear EventRulers
+ //
+ EventRulerListIterator it;
+ for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it) delete *it;
+ m_eventRulerList.clear();
+
+ // delete content
+ for (iterator it = begin(); it != end(); ++it) delete (*it);
+
+ delete m_endMarkerTime;
+}
+
+
+void
+Segment::setTrack(TrackId id)
+{
+ Composition *c = m_composition;
+ if (c) c->weakDetachSegment(this); // sets m_composition to 0
+ TrackId oldTrack = m_track;
+ m_track = id;
+ if (c) {
+ c->weakAddSegment(this);
+ c->updateRefreshStatuses();
+ c->notifySegmentTrackChanged(this, oldTrack, id);
+ }
+}
+
+timeT
+Segment::getStartTime() const
+{
+ return m_startTime;
+}
+
+timeT
+Segment::getEndMarkerTime() const
+{
+ timeT endTime;
+
+ if (m_type == Audio && m_composition) {
+
+ RealTime startRT = m_composition->getElapsedRealTime(m_startTime);
+ RealTime endRT = startRT - m_audioStartTime + m_audioEndTime;
+ endTime = m_composition->getElapsedTimeForRealTime(endRT);
+
+ } else {
+
+ if (m_endMarkerTime) {
+ endTime = *m_endMarkerTime;
+ } else {
+ endTime = getEndTime();
+ }
+
+ if (m_composition) {
+ endTime = std::min(endTime, m_composition->getEndMarker());
+ }
+ }
+
+ return endTime;
+}
+
+timeT
+Segment::getEndTime() const
+{
+ if (m_type == Audio && m_composition) {
+ RealTime startRT = m_composition->getElapsedRealTime(m_startTime);
+ RealTime endRT = startRT - m_audioStartTime + m_audioEndTime;
+ return m_composition->getElapsedTimeForRealTime(endRT);
+ } else {
+ return m_endTime;
+ }
+}
+
+void
+Segment::setStartTime(timeT t)
+{
+ int dt = t - m_startTime;
+ if (dt == 0) return;
+
+ // reset the time of all events. can't just setAbsoluteTime on these,
+ // partly 'cos we're not allowed, partly 'cos it might screw up the
+ // quantizer (which is why we're not allowed)
+
+ // still, this is rather unsatisfactory
+
+ FastVector<Event *> events;
+
+ for (iterator i = begin(); i != end(); ++i) {
+ events.push_back((*i)->copyMoving(dt));
+ }
+
+ timeT previousEndTime = m_endTime;
+
+ erase(begin(), end());
+
+ m_endTime = previousEndTime + dt;
+ if (m_endMarkerTime) *m_endMarkerTime += dt;
+
+ if (m_composition) m_composition->setSegmentStartTime(this, t);
+ else m_startTime = t;
+
+ for (int i = 0; i < events.size(); ++i) {
+ insert(events[i]);
+ }
+
+ notifyStartChanged(m_startTime);
+ updateRefreshStatuses(m_startTime, m_endTime);
+}
+
+void
+Segment::setEndMarkerTime(timeT t)
+{
+ if (t < m_startTime) t = m_startTime;
+
+ if (m_type == Audio) {
+ if (m_endMarkerTime) *m_endMarkerTime = t;
+ else m_endMarkerTime = new timeT(t);
+ RealTime oldAudioEndTime = m_audioEndTime;
+ if (m_composition) {
+ m_audioEndTime = m_audioStartTime +
+ m_composition->getRealTimeDifference(m_startTime, t);
+ if (oldAudioEndTime != m_audioEndTime) {
+ notifyEndMarkerChange(m_audioEndTime < oldAudioEndTime);
+ }
+ }
+ } else {
+
+ timeT endTime = getEndTime();
+ timeT oldEndMarker = getEndMarkerTime();
+ bool shorten = (t < oldEndMarker);
+
+ if (t > endTime) {
+ fillWithRests(endTime, t);
+ if (oldEndMarker < endTime) {
+ updateRefreshStatuses(oldEndMarker, t);
+ }
+ } else {
+ // only need to do this if we aren't inserting or
+ // deleting any actual events
+ if (oldEndMarker < t) {
+ updateRefreshStatuses(oldEndMarker, t);
+ }
+ updateRefreshStatuses(t, endTime);
+ }
+
+ if (m_endMarkerTime) *m_endMarkerTime = t;
+ else m_endMarkerTime = new timeT(t);
+ notifyEndMarkerChange(shorten);
+ }
+}
+
+void
+Segment::setEndTime(timeT t)
+{
+ timeT endTime = getEndTime();
+ if (t < m_startTime) t = m_startTime;
+
+ if (m_type == Audio) {
+ setEndMarkerTime(t);
+ } else {
+ if (t < endTime) {
+ erase(findTime(t), end());
+ endTime = getEndTime();
+ if (m_endMarkerTime && endTime < *m_endMarkerTime) {
+ *m_endMarkerTime = endTime;
+ notifyEndMarkerChange(true);
+ }
+ } else if (t > endTime) {
+ fillWithRests(endTime, t);
+ }
+ }
+}
+
+Segment::iterator
+Segment::getEndMarker()
+{
+ if (m_endMarkerTime) {
+ return findTime(*m_endMarkerTime);
+ } else {
+ return end();
+ }
+}
+
+bool
+Segment::isBeforeEndMarker(const_iterator i) const
+{
+ if (i == end()) return false;
+
+ timeT absTime = (*i)->getAbsoluteTime();
+ timeT endTime = getEndMarkerTime();
+
+ return ((absTime < endTime) ||
+ (absTime == endTime && (*i)->getDuration() == 0));
+}
+
+void
+Segment::clearEndMarker()
+{
+ delete m_endMarkerTime;
+ m_endMarkerTime = 0;
+ notifyEndMarkerChange(false);
+}
+
+const timeT *
+Segment::getRawEndMarkerTime() const
+{
+ return m_endMarkerTime;
+}
+
+
+void
+Segment::updateRefreshStatuses(timeT startTime, timeT endTime)
+{
+ for(unsigned int i = 0; i < m_refreshStatusArray.size(); ++i)
+ m_refreshStatusArray.getRefreshStatus(i).push(startTime, endTime);
+}
+
+
+Segment::iterator
+Segment::insert(Event *e)
+{
+ assert(e);
+
+ timeT t0 = e->getAbsoluteTime();
+ timeT t1 = t0 + e->getDuration();
+
+ if (t0 < m_startTime ||
+ (begin() == end() && t0 > m_startTime)) {
+
+ if (m_composition) m_composition->setSegmentStartTime(this, t0);
+ else m_startTime = t0;
+ notifyStartChanged(m_startTime);
+ }
+
+ if (t1 > m_endTime ||
+ begin() == end()) {
+ timeT oldTime = m_endTime;
+ m_endTime = t1;
+ notifyEndMarkerChange(m_endTime < oldTime);
+ }
+
+ iterator i = std::multiset<Event*, Event::EventCmp>::insert(e);
+ notifyAdd(e);
+ updateRefreshStatuses(e->getAbsoluteTime(),
+ e->getAbsoluteTime() + e->getDuration());
+ return i;
+}
+
+
+void
+Segment::updateEndTime()
+{
+ m_endTime = m_startTime;
+ for (iterator i = begin(); i != end(); ++i) {
+ timeT t = (*i)->getAbsoluteTime() + (*i)->getDuration();
+ if (t > m_endTime) m_endTime = t;
+ }
+}
+
+
+void
+Segment::erase(iterator pos)
+{
+ Event *e = *pos;
+
+ assert(e);
+
+ timeT t0 = e->getAbsoluteTime();
+ timeT t1 = t0 + e->getDuration();
+
+ std::multiset<Event*, Event::EventCmp>::erase(pos);
+ notifyRemove(e);
+ delete e;
+ updateRefreshStatuses(t0, t1);
+
+ if (t0 == m_startTime && begin() != end()) {
+ timeT startTime = (*begin())->getAbsoluteTime();
+ if (m_composition) m_composition->setSegmentStartTime(this, startTime);
+ else m_startTime = startTime;
+ notifyStartChanged(m_startTime);
+ }
+ if (t1 == m_endTime) {
+ updateEndTime();
+ }
+}
+
+
+void
+Segment::erase(iterator from, iterator to)
+{
+ timeT startTime = 0, endTime = m_endTime;
+ if (from != end()) startTime = (*from)->getAbsoluteTime();
+ if (to != end()) endTime = (*to)->getAbsoluteTime() + (*to)->getDuration();
+
+ // Not very efficient, but without an observer event for
+ // multiple erase we can't do any better.
+
+ for (Segment::iterator i = from; i != to; ) {
+
+ Segment::iterator j(i);
+ ++j;
+
+ Event *e = *i;
+ assert(e);
+
+ std::multiset<Event*, Event::EventCmp>::erase(i);
+ notifyRemove(e);
+ delete e;
+
+ i = j;
+ }
+
+ if (startTime == m_startTime && begin() != end()) {
+ timeT startTime = (*begin())->getAbsoluteTime();
+ if (m_composition) m_composition->setSegmentStartTime(this, startTime);
+ else m_startTime = startTime;
+ notifyStartChanged(m_startTime);
+ }
+
+ if (endTime == m_endTime) {
+ updateEndTime();
+ }
+
+ updateRefreshStatuses(startTime, endTime);
+}
+
+
+bool
+Segment::eraseSingle(Event* e)
+{
+ iterator elPos = findSingle(e);
+
+ if (elPos != end()) {
+
+ erase(elPos);
+ return true;
+
+ } else return false;
+
+}
+
+
+Segment::iterator
+Segment::findSingle(Event* e)
+{
+ iterator res = end();
+
+ std::pair<iterator, iterator> interval = equal_range(e);
+
+ for (iterator i = interval.first; i != interval.second; ++i) {
+ if (*i == e) {
+ res = i;
+ break;
+ }
+ }
+ return res;
+}
+
+
+Segment::iterator
+Segment::findTime(timeT t)
+{
+ Event dummy("dummy", t, 0, MIN_SUBORDERING);
+ return lower_bound(&dummy);
+}
+
+
+Segment::iterator
+Segment::findNearestTime(timeT t)
+{
+ iterator i = findTime(t);
+ if (i == end() || (*i)->getAbsoluteTime() > t) {
+ if (i == begin()) return end();
+ else --i;
+ }
+ return i;
+}
+
+
+timeT
+Segment::getBarStartForTime(timeT t) const
+{
+ if (t < getStartTime()) t = getStartTime();
+ return getComposition()->getBarStartForTime(t);
+}
+
+
+timeT
+Segment::getBarEndForTime(timeT t) const
+{
+ if (t > getEndMarkerTime()) t = getEndMarkerTime();
+ return getComposition()->getBarEndForTime(t);
+}
+
+
+int Segment::getNextId() const
+{
+ return m_id++;
+}
+
+
+void
+Segment::fillWithRests(timeT endTime)
+{
+ fillWithRests(getEndTime(), endTime);
+}
+
+void
+Segment::fillWithRests(timeT startTime, timeT endTime)
+{
+ if (startTime < m_startTime) {
+ if (m_composition) m_composition->setSegmentStartTime(this, startTime);
+ else m_startTime = startTime;
+ notifyStartChanged(m_startTime);
+ }
+
+ TimeSignature ts;
+ timeT sigTime = 0;
+
+ if (getComposition()) {
+ sigTime = getComposition()->getTimeSignatureAt(startTime, ts);
+ }
+
+ timeT restDuration = endTime - startTime;
+ if (restDuration <= 0) return;
+
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "fillWithRests (" << startTime << "->" << endTime << "), composition "
+ << (getComposition() ? "exists" : "does not exist") << ", sigTime "
+ << sigTime << ", timeSig duration " << ts.getBarDuration() << ", restDuration " << restDuration << endl;
+#endif
+
+ DurationList dl;
+ ts.getDurationListForInterval(dl, restDuration, startTime - sigTime);
+
+ timeT acc = startTime;
+
+ for (DurationList::iterator i = dl.begin(); i != dl.end(); ++i) {
+ Event *e = new Event(Note::EventRestType, acc, *i,
+ Note::EventRestSubOrdering);
+ insert(e);
+ acc += *i;
+ }
+}
+
+void
+Segment::normalizeRests(timeT startTime, timeT endTime)
+{
+ Profiler profiler("Segment::normalizeRests");
+
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests (" << startTime << "->" << endTime << "), segment starts at " << m_startTime << endl;
+#endif
+
+ if (startTime < m_startTime) {
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: pulling start time back from "
+ << m_startTime << " to " << startTime << endl;
+#endif
+ if (m_composition) m_composition->setSegmentStartTime(this, startTime);
+ else m_startTime = startTime;
+ notifyStartChanged(m_startTime);
+ }
+
+ //!!! Need to remove the rests then relocate the start time
+ // and get the notation end time for the nearest note before that
+ // (?)
+
+ //!!! We need to insert rests at fictitious unquantized times that
+ //are broadly correct, so as to maintain ordering of notes and
+ //rests in the unquantized segment. The quantized times should go
+ //in notation-prefix properties.
+
+ // Preliminary: If there are any time signature changes between
+ // the start and end times, consider separately each of the sections
+ // they divide the range up into.
+
+ Composition *composition = getComposition();
+ if (composition) {
+ int timeSigNo = composition->getTimeSignatureNumberAt(startTime);
+ if (timeSigNo < composition->getTimeSignatureCount() - 1) {
+ timeT nextSigTime =
+ composition->getTimeSignatureChange(timeSigNo + 1).first;
+ if (nextSigTime < endTime) {
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: divide-and-conquer on timesig at " << nextSigTime << endl;
+#endif
+ normalizeRests(startTime, nextSigTime);
+ normalizeRests(nextSigTime, endTime);
+ return;
+ }
+ }
+ }
+
+ // First stage: erase all existing non-tupleted rests in this range.
+
+ timeT segmentEndTime = m_endTime;
+
+ iterator ia = findNearestTime(startTime);
+ if (ia == end()) ia = begin();
+ if (ia == end()) { // the segment is empty
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: empty segment" << endl;
+#endif
+ fillWithRests(startTime, endTime);
+ return;
+ } else {
+ if (startTime > (*ia)->getNotationAbsoluteTime()) {
+ startTime = (*ia)->getNotationAbsoluteTime();
+ }
+ }
+
+ iterator ib = findTime(endTime);
+ if (ib == end()) {
+ if (ib != begin()) {
+ --ib;
+ // if we're pointing at the real-end-time of the last event,
+ // use its notation-end-time instead
+ if (endTime == (*ib)->getAbsoluteTime() + (*ib)->getDuration()) {
+ endTime =
+ (*ib)->getNotationAbsoluteTime() +
+ (*ib)->getNotationDuration();
+ }
+ ++ib;
+ }
+ } else {
+ endTime = (*ib)->getNotationAbsoluteTime();
+ }
+
+ // If there's a rest preceding the start time, with no notes
+ // between us and it, and if it doesn't have precisely the
+ // right duration, then we need to normalize it too
+
+ //!!! needs modification for new scheme
+
+ iterator scooter = ia;
+ while (scooter-- != begin()) {
+// if ((*scooter)->isa(Note::EventRestType)) { //!!! experimental
+ if ((*scooter)->getDuration() > 0) {
+ if ((*scooter)->getNotationAbsoluteTime() +
+ (*scooter)->getNotationDuration() !=
+ startTime) {
+ startTime = (*scooter)->getNotationAbsoluteTime();
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: scooting back to " << startTime << endl;
+#endif
+ ia = scooter;
+ }
+ break;
+/*!!!
+ } else if ((*scooter)->getDuration() > 0) {
+ break;
+*/
+ }
+ }
+
+ for (iterator i = ia, j = i; i != ib && i != end(); i = j) {
+ ++j;
+ if ((*i)->isa(Note::EventRestType) &&
+ !(*i)->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE)) {
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: erasing rest at " << (*i)->getAbsoluteTime() << endl;
+#endif
+ erase(i);
+ }
+ }
+
+ // It's possible we've just removed all the events between here
+ // and the end of the segment, if they were all rests. Check.
+
+ if (endTime < segmentEndTime && m_endTime < segmentEndTime) {
+ endTime = segmentEndTime;
+ }
+
+ // Second stage: find the gaps that need to be filled with
+ // rests. We don't mind about the case where two simultaneous
+ // notes end at different times -- we're only interested in
+ // the one ending sooner. Each time an event ends, we start
+ // a candidate gap.
+
+ std::vector<std::pair<timeT, timeT> > gaps;
+
+ timeT lastNoteStarts = startTime;
+ timeT lastNoteEnds = startTime;
+
+ // Re-find this, as it might have been erased
+ ia = findNearestTime(startTime);
+
+ if (ia == end()) {
+ // already have good lastNoteStarts, lastNoteEnds
+ ia = begin();
+ } else {
+ lastNoteStarts = (*ia)->getNotationAbsoluteTime();
+ lastNoteEnds = lastNoteStarts;
+ }
+
+ if (ib != end()) {
+ //!!! This and related code really need to get a quantized
+ // absolute time of a note event that has the same unquantized
+ // time as ib, not necessarily of ib itself... or else the
+ // quantizer needs to set the quantized times of all non-note
+ // events that happen at the same unquantized time as a note
+ // event to the same as that of the note event... yeah, that's
+ // probably the right thing
+ endTime = (*ib)->getNotationAbsoluteTime();
+
+ // was this just a nasty hack?
+ ++ib;
+ }
+
+ iterator i = ia;
+
+ for (; i != ib && i != end(); ++i) {
+
+ // Boundary events for sets of rests may be notes (obviously),
+ // text events (because they need to be "attached" to
+ // something that has the correct timing), or rests (any
+ // remaining rests in this area have tuplet data so should be
+ // treated as "hard" rests);
+ if (!((*i)->isa(Note::EventType) ||
+ (*i)->isa(Text::EventType) ||
+ (*i)->isa(Note::EventRestType))) {
+ continue;
+ }
+
+ timeT thisNoteStarts = (*i)->getNotationAbsoluteTime();
+
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: scanning: thisNoteStarts " << thisNoteStarts
+ << ", lastNoteStarts " << lastNoteStarts
+ << ", lastNoteEnds " << lastNoteEnds << endl;
+#endif
+
+ /* BR #988185: "Notation: Rest can be simultaneous with note but follow it"
+
+ This conditional tested whether a note started before the
+ preceding note ended, and if so inserted rests simultaneous
+ with the preceding note to make up the gap. Without the
+ ability to lay out those rests partwise, this is never any
+ better than plain confusing. Revert the change.
+
+ if (thisNoteStarts < lastNoteEnds &&
+ thisNoteStarts > lastNoteStarts) {
+ gaps.push_back(std::pair<timeT, timeT>
+ (lastNoteStarts,
+ thisNoteStarts - lastNoteStarts));
+ }
+ */
+
+ if (thisNoteStarts > lastNoteEnds) {
+ gaps.push_back(std::pair<timeT, timeT>
+ (lastNoteEnds,
+ thisNoteStarts - lastNoteEnds));
+ }
+
+ lastNoteStarts = thisNoteStarts;
+ lastNoteEnds = thisNoteStarts + (*i)->getNotationDuration();
+ }
+
+ if (endTime > lastNoteEnds) {
+ gaps.push_back(std::pair<timeT, timeT>
+ (lastNoteEnds, endTime - lastNoteEnds));
+ }
+
+ timeT duration;
+
+ for (unsigned int gi = 0; gi < gaps.size(); ++gi) {
+
+#ifdef DEBUG_NORMALIZE_RESTS
+ cerr << "normalizeRests: gap " << gi << ": " << gaps[gi].first << " -> " << (gaps[gi].first + gaps[gi].second) << endl;
+#endif
+
+ startTime = gaps[gi].first;
+ duration = gaps[gi].second;
+
+ if (duration >= Note(Note::Shortest).getDuration()) {
+ fillWithRests(startTime, startTime + duration);
+ }
+ }
+}
+
+
+
+void Segment::getTimeSlice(timeT absoluteTime, iterator &start, iterator &end)
+{
+ Event dummy("dummy", absoluteTime, 0, MIN_SUBORDERING);
+
+ // No, this won't work -- we need to include things that don't
+ // compare equal because they have different suborderings, as long
+ // as they have the same times
+
+// std::pair<iterator, iterator> res = equal_range(&dummy);
+
+// start = res.first;
+// end = res.second;
+
+ // Got to do this instead:
+
+ start = end = lower_bound(&dummy);
+
+ while (end != this->end() &&
+ (*end)->getAbsoluteTime() == (*start)->getAbsoluteTime())
+ ++end;
+}
+
+void Segment::getTimeSlice(timeT absoluteTime, const_iterator &start, const_iterator &end)
+ const
+{
+ Event dummy("dummy", absoluteTime, 0, MIN_SUBORDERING);
+
+ start = end = lower_bound(&dummy);
+
+ while (end != this->end() &&
+ (*end)->getAbsoluteTime() == (*start)->getAbsoluteTime())
+ ++end;
+}
+
+void
+Segment::setQuantization(bool quantize)
+{
+ if (m_quantize != quantize) {
+ m_quantize = quantize;
+ if (m_quantize) {
+ m_quantizer->quantize(this, begin(), end());
+ } else {
+ m_quantizer->unquantize(this, begin(), end());
+ }
+ }
+}
+
+bool
+Segment::hasQuantization() const
+{
+ return m_quantize;
+}
+
+void
+Segment::setQuantizeLevel(timeT unit)
+{
+ if (m_quantizer->getUnit() == unit) return;
+
+ m_quantizer->setUnit(unit);
+ if (m_quantize) m_quantizer->quantize(this, begin(), end());
+}
+
+const BasicQuantizer *
+Segment::getQuantizer() const
+{
+ return m_quantizer;
+}
+
+
+void
+Segment::setRepeating(bool value)
+{
+ m_repeating = value;
+ if (m_composition) {
+ m_composition->updateRefreshStatuses();
+ m_composition->notifySegmentRepeatChanged(this, value);
+ }
+}
+
+void
+Segment::setDelay(timeT delay)
+{
+ m_delay = delay;
+ if (m_composition) {
+ // don't updateRefreshStatuses() - affects playback only
+ m_composition->notifySegmentEventsTimingChanged(this, delay, RealTime::zeroTime);
+ }
+}
+
+void
+Segment::setRealTimeDelay(RealTime delay)
+{
+ m_realTimeDelay = delay;
+ if (m_composition) {
+ // don't updateRefreshStatuses() - affects playback only
+ m_composition->notifySegmentEventsTimingChanged(this, 0, delay);
+ }
+}
+
+void
+Segment::setTranspose(int transpose)
+{
+ m_transpose = transpose;
+ if (m_composition) {
+ // don't updateRefreshStatuses() - affects playback only
+ m_composition->notifySegmentTransposeChanged(this, transpose);
+ }
+}
+
+void
+Segment::setAudioFileId(unsigned int id)
+{
+ m_audioFileId = id;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+}
+
+void
+Segment::setUnstretchedFileId(unsigned int id)
+{
+ m_unstretchedFileId = id;
+}
+
+void
+Segment::setStretchRatio(float ratio)
+{
+ m_stretchRatio = ratio;
+}
+
+void
+Segment::setAudioStartTime(const RealTime &time)
+{
+ m_audioStartTime = time;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+}
+
+void
+Segment::setAudioEndTime(const RealTime &time)
+{
+ RealTime oldAudioEndTime = m_audioEndTime;
+ m_audioEndTime = time;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+ notifyEndMarkerChange(time < oldAudioEndTime);
+}
+
+void
+Segment::setAutoFade(bool value)
+{
+ m_autoFade = value;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+}
+
+void
+Segment::setFadeInTime(const RealTime &time)
+{
+ m_fadeInTime = time;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+}
+
+void
+Segment::setFadeOutTime(const RealTime &time)
+{
+ m_fadeOutTime = time;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+}
+
+void
+Segment::setLabel(const std::string &label)
+{
+ m_label = label;
+ if (m_composition) m_composition->updateRefreshStatuses();
+ notifyAppearanceChange();
+}
+
+bool
+Segment::ClefKeyCmp::operator()(const Event *e1, const Event *e2) const
+{
+ if (e1->getType() == e2->getType()) return Event::EventCmp()(e1, e2);
+ else return e1->getType() < e2->getType();
+}
+
+Clef
+Segment::getClefAtTime(timeT time) const
+{
+ timeT ctime;
+ return getClefAtTime(time, ctime);
+}
+
+Clef
+Segment::getClefAtTime(timeT time, timeT &ctime) const
+{
+ if (!m_clefKeyList) return Clef();
+
+ Event ec(Clef::EventType, time);
+ ClefKeyList::iterator i = m_clefKeyList->lower_bound(&ec);
+
+ while (i == m_clefKeyList->end() ||
+ (*i)->getAbsoluteTime() > time ||
+ (*i)->getType() != Clef::EventType) {
+
+ if (i == m_clefKeyList->begin()) {
+ ctime = getStartTime();
+ return Clef();
+ }
+ --i;
+ }
+
+ try {
+ ctime = (*i)->getAbsoluteTime();
+ return Clef(**i);
+ } catch (const Exception &e) {
+ std::cerr << "Segment::getClefAtTime(" << time
+ << "): bogus clef in ClefKeyList: event dump follows:"
+ << std::endl;
+ (*i)->dump(std::cerr);
+ return Clef();
+ }
+}
+
+Key
+Segment::getKeyAtTime(timeT time) const
+{
+ timeT ktime;
+ return getKeyAtTime(time, ktime);
+}
+
+Key
+Segment::getKeyAtTime(timeT time, timeT &ktime) const
+{
+ if (!m_clefKeyList) return Key();
+
+ Event ek(Key::EventType, time);
+ ClefKeyList::iterator i = m_clefKeyList->lower_bound(&ek);
+
+ while (i == m_clefKeyList->end() ||
+ (*i)->getAbsoluteTime() > time ||
+ (*i)->getType() != Key::EventType) {
+
+ if (i == m_clefKeyList->begin()) {
+ ktime = getStartTime();
+ return Key();
+ }
+ --i;
+ }
+
+ try {
+ ktime = (*i)->getAbsoluteTime();
+ return Key(**i);
+ } catch (const Exception &e) {
+ std::cerr << "Segment::getClefAtTime(" << time
+ << "): bogus key in ClefKeyList: event dump follows:"
+ << std::endl;
+ (*i)->dump(std::cerr);
+ return Key();
+ }
+}
+
+void
+Segment::getFirstClefAndKey(Clef &clef, Key &key)
+{
+ bool keyFound = false;
+ bool clefFound = false;
+ clef = Clef(); // Default clef
+ key = Key(); // Default key signature
+
+ iterator i = begin();
+ while (i!=end()) {
+ // Keep current clef and key as soon as a note or rest event is found
+ if ((*i)->isa(Note::EventRestType) || (*i)->isa(Note::EventType)) return;
+
+ // Remember the first clef event found
+ if ((*i)->isa(Clef::EventType)) {
+ clef = Clef(*(*i));
+ // and return if a key has already been found
+ if (keyFound) return;
+ clefFound = true;
+ }
+
+ // Remember the first key event found
+ if ((*i)->isa(Key::EventType)) {
+ key = Key(*(*i));
+ // and return if a clef has already been found
+ if (clefFound) return;
+ keyFound = true;
+ }
+
+ ++i;
+ }
+}
+
+timeT
+Segment::getRepeatEndTime() const
+{
+ timeT endMarker = getEndMarkerTime();
+
+ if (m_repeating && m_composition) {
+ Composition::iterator i(m_composition->findSegment(this));
+ assert(i != m_composition->end());
+ ++i;
+ if (i != m_composition->end() && (*i)->getTrack() == getTrack()) {
+ timeT t = (*i)->getStartTime();
+ if (t < endMarker) return endMarker;
+ else return t;
+ } else {
+ return m_composition->getEndMarker();
+ }
+ }
+
+ return endMarker;
+}
+
+
+void
+Segment::notifyAdd(Event *e) const
+{
+ if (e->isa(Clef::EventType) || e->isa(Key::EventType)) {
+ if (!m_clefKeyList) m_clefKeyList = new ClefKeyList;
+ m_clefKeyList->insert(e);
+ }
+
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->eventAdded(this, e);
+ }
+}
+
+
+void
+Segment::notifyRemove(Event *e) const
+{
+ if (m_clefKeyList && (e->isa(Clef::EventType) || e->isa(Key::EventType))) {
+ ClefKeyList::iterator i;
+ for (i = m_clefKeyList->find(e); i != m_clefKeyList->end(); ++i) {
+ // fix for bug#1485643 (crash erasing a duplicated key signature)
+ if ((*i) == e) {
+ m_clefKeyList->erase(i);
+ break;
+ }
+ }
+ }
+
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->eventRemoved(this, e);
+ }
+}
+
+
+void
+Segment::notifyAppearanceChange() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->appearanceChanged(this);
+ }
+}
+
+void
+Segment::notifyStartChanged(timeT newTime)
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->startChanged(this, newTime);
+ }
+ if (m_composition) {
+ m_composition->notifySegmentStartChanged(this, newTime);
+ }
+}
+
+
+void
+Segment::notifyEndMarkerChange(bool shorten)
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->endMarkerTimeChanged(this, shorten);
+ }
+ if (m_composition) {
+ m_composition->notifySegmentEndMarkerChange(this, shorten);
+ }
+}
+
+
+void
+Segment::notifySourceDeletion() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->segmentDeleted(this);
+ }
+}
+
+
+void
+Segment::setColourIndex(const unsigned int input)
+{
+ m_colourIndex = input;
+ updateRefreshStatuses(getStartTime(), getEndTime());
+ if (m_composition) m_composition->updateRefreshStatuses();
+ notifyAppearanceChange();
+}
+
+void
+Segment::addEventRuler(const std::string &type, int controllerValue, bool active)
+{
+ EventRulerListConstIterator it;
+
+ for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it)
+ if ((*it)->m_type == type && (*it)->m_controllerValue == controllerValue)
+ return;
+
+ m_eventRulerList.push_back(new EventRuler(type, controllerValue, active));
+}
+
+bool
+Segment::deleteEventRuler(const std::string &type, int controllerValue)
+{
+ EventRulerListIterator it;
+
+ for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it)
+ {
+ if ((*it)->m_type == type && (*it)->m_controllerValue == controllerValue)
+ {
+ delete *it;
+ m_eventRulerList.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Segment::EventRuler*
+Segment::getEventRuler(const std::string &type, int controllerValue)
+{
+ EventRulerListConstIterator it;
+ for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it)
+ if ((*it)->m_type == type && (*it)->m_controllerValue == controllerValue)
+ return *it;
+
+ return 0;
+}
+
+
+
+SegmentHelper::~SegmentHelper() { }
+
+
+void
+SegmentRefreshStatus::push(timeT from, timeT to)
+{
+ if (!needsRefresh()) { // don't do anything subtle - just erase the old data
+
+ m_from = from;
+ m_to = to;
+
+ } else { // accumulate on what was already there
+
+ if (from < m_from) m_from = from;
+ if (to > m_to) m_to = to;
+
+ }
+
+ if (m_to < m_from) std::swap(m_from, m_to);
+
+ setNeedsRefresh(true);
+}
+
+
+
+
+}
diff --git a/src/base/Segment.h b/src/base/Segment.h
new file mode 100644
index 0000000..564d118
--- /dev/null
+++ b/src/base/Segment.h
@@ -0,0 +1,783 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SEGMENT_H_
+#define _SEGMENT_H_
+
+#include <set>
+#include <list>
+#include <string>
+
+#include "Track.h"
+#include "Event.h"
+#include "NotationTypes.h"
+#include "RefreshStatus.h"
+#include "RealTime.h"
+#include "MidiProgram.h"
+
+namespace Rosegarden
+{
+
+class SegmentRefreshStatus : public RefreshStatus
+{
+public:
+ SegmentRefreshStatus() : m_from(0), m_to(0) {}
+
+ void push(timeT from, timeT to);
+
+ timeT from() const { return m_from; }
+ timeT to() const { return m_to; }
+
+protected:
+ timeT m_from;
+ timeT m_to;
+};
+
+
+/**
+ * Segment is the container for a set of Events that are all played on
+ * the same track. Each event has an absolute starting time,
+ * which is used as the index within the segment. Multiple events may
+ * have the same absolute time.
+ *
+ * (For example, chords are represented simply as a sequence of notes
+ * that share a starting time. The Segment can contain counterpoint --
+ * notes that overlap, rather than starting and ending together -- but
+ * in practice it's probably too hard to display so we should make
+ * more than one Segment if we want to represent true counterpoint.)
+ *
+ * If you want to carry out notation-related editing operations on
+ * a Segment, take a look at SegmentNotationHelper. If you want to play a
+ * Segment, try SegmentPerformanceHelper for duration calculations.
+ *
+ * The Segment owns the Events its items are pointing at.
+ */
+
+class SegmentObserver;
+class Quantizer;
+class BasicQuantizer;
+class Composition;
+
+class Segment : public std::multiset<Event*, Event::EventCmp>
+{
+public:
+ /// A Segment contains either Internal representation or Audio
+ typedef enum {
+ Internal,
+ Audio
+ } SegmentType;
+
+ /**
+ * Construct a Segment of a given type with a given formal starting time.
+ */
+ Segment(SegmentType segmentType = Internal,
+ timeT startTime = 0);
+ /**
+ * Copy constructor
+ */
+ Segment(const Segment&);
+
+ virtual ~Segment();
+
+
+ //////
+ //
+ // BASIC SEGMENT ATTRIBUTES
+
+ /**
+ * Get the Segment type (Internal or Audio)
+ */
+ SegmentType getType() const { return m_type; }
+
+ /**
+ * Note that a Segment does not have to be in a Composition;
+ * if it isn't, this will return zero
+ */
+ Composition *getComposition() const {
+ return m_composition;
+ }
+
+ /**
+ * Get the track number this Segment is associated with.
+ */
+ TrackId getTrack() const { return m_track; }
+
+ /**
+ * Set the track number this Segment is associated with.
+ */
+ void setTrack(TrackId i);
+
+ // label
+ //
+ void setLabel(const std::string &label);
+ std::string getLabel() const { return m_label; }
+
+ // Colour information
+ void setColourIndex(const unsigned int input);
+ unsigned int getColourIndex() const { return m_colourIndex; }
+
+ /**
+ * Returns a numeric id of some sort
+ * The id is guaranteed to be unique within the segment, but not to
+ * have any other interesting properties
+ */
+ int getNextId() const;
+
+ /**
+ * Returns a MIDI pitch representing the highest suggested playable note for
+ * notation contained in this segment, as a convenience reminder to composers.
+ *
+ * This property, and its corresponding lowest note counterpart, initialize by
+ * default such that no limitation is imposed. (lowest = 0, highest = 127)
+ */
+ int getHighestPlayable() { return m_highestPlayable; }
+
+ /**
+ * Set the highest suggested playable note for this segment
+ */
+ void setHighestPlayable(int pitch) { m_highestPlayable = pitch; }
+
+ /**
+ * Returns a MIDI pitch representing the lowest suggested playable note for
+ * notation contained in this segment, as a convenience reminder to composers
+ */
+ int getLowestPlayable() { return m_lowestPlayable; }
+
+ /**
+ * Set the highest suggested playable note for this segment
+ */
+ void setLowestPlayable(int pitch) { m_lowestPlayable = pitch; }
+
+
+
+ //////
+ //
+ // TIME & DURATION VALUES
+
+ /**
+ * Return the start time of the Segment. For a non-audio
+ * Segment, this is the start time of the first event in it.
+ */
+ timeT getStartTime() const;
+
+ /**
+ * Return the nominal end time of the Segment. This must
+ * be the same as or earlier than the getEndTime() value.
+ * The return value will not necessarily be that last set
+ * with setEndMarkerTime, as if there is a Composition its
+ * end marker will also be used for clipping.
+ */
+ timeT getEndMarkerTime() const;
+
+ /**
+ * Return the time of the end of the last event stored in the
+ * Segment. This time may be outside the audible/editable
+ * range of the Segment, depending on the location of the end
+ * marker.
+ */
+ timeT getEndTime() const;
+
+ /**
+ * Shift the start time of the Segment by moving the start
+ * times of all the events in the Segment.
+ */
+ void setStartTime(timeT);
+
+ /**
+ * DO NOT USE THIS METHOD
+ * Simple accessor for the m_startTime member. Used by
+ * Composition#setSegmentStartTime
+ */
+ void setStartTimeDataMember(timeT t) { m_startTime = t; }
+
+ /**
+ * Set the end marker (nominal end time) of this Segment.
+ *
+ * If the given time is later than the current end of the
+ * Segment's storage, extend the Segment by filling it with
+ * rests; if earlier, simply move the end marker. The end
+ * marker time may not precede the start time.
+ */
+ void setEndMarkerTime(timeT);
+
+ /**
+ * Set the end time of the Segment.
+ *
+ * If the given time is later than the current end of the
+ * Segment's storage, extend the Segment by filling it with
+ * rests; if earlier, shorten it by throwing away events as
+ * necessary (though do not truncate any events) and also move
+ * the end marker to the given time. The end time may not
+ * precede the start time.
+ *
+ * Note that simply inserting an event beyond the end of the
+ * Segment will also change the end time, although it does
+ * not fill with rests in the desirable way.
+ *
+ * Consider using setEndMarkerTime in preference to this.
+ */
+ void setEndTime(timeT);
+
+ /**
+ * Return an iterator pointing to the nominal end of the
+ * Segment. This may be earlier than the end() iterator.
+ */
+ iterator getEndMarker();
+
+ /**
+ * Return true if the given iterator points earlier in the
+ * Segment than the nominal end marker. You can use this
+ * as an extent test in code such as
+ *
+ * while (segment.isBeforeEndMarker(my_iterator)) {
+ * // ...
+ * ++my_iterator;
+ * }
+ *
+ * It is not generally safe to write
+ *
+ * while (my_iterator != segment.getEndMarker()) {
+ * // ...
+ * ++my_iterator;
+ * }
+ *
+ * as the loop will not terminate if my_iterator's initial
+ * value is already beyond the end marker. (Also takes the
+ * Composition's end marker into account.)
+ */
+ bool isBeforeEndMarker(const_iterator) const;
+
+ /**
+ * Remove the end marker, thus making the Segment end
+ * at its storage end time (unless the Composition's
+ * end marker is earlier).
+ */
+ void clearEndMarker();
+
+ /**
+ * Return the end marker in raw form, that is, a pointer to
+ * its value or null if none is set. Does not take the
+ * composition's end marker into account.
+ */
+ const timeT *getRawEndMarkerTime() const;
+
+
+ //////
+ //
+ // QUANTIZATION
+
+ /**
+ * Switch quantization on or off.
+ */
+ void setQuantization(bool quantize);
+
+ /**
+ * Find out whether quantization is on or off.
+ */
+ bool hasQuantization() const;
+
+ /**
+ * Set the quantization level.
+ * (This does not switch quantization on, if it's currently off,
+ * it only changes the level that will be used when it's next
+ * switched on.)
+ */
+ void setQuantizeLevel(timeT unit);
+
+ /**
+ * Get the quantizer currently in (or not in) use.
+ */
+ const BasicQuantizer *getQuantizer() const;
+
+
+
+ //////
+ //
+ // EVENT MANIPULATION
+
+ /**
+ * Inserts a single Event
+ */
+ iterator insert(Event *e);
+
+ /**
+ * Erases a single Event
+ */
+ void erase(iterator pos);
+
+ /**
+ * Erases a set of Events
+ */
+ void erase(iterator from, iterator to);
+
+ /**
+ * Clear the segment.
+ */
+ void clear() { erase(begin(), end()); }
+
+ /**
+ * Looks up an Event and if it finds it, erases it.
+ * @return true if the event was found and erased, false otherwise.
+ */
+ bool eraseSingle(Event*);
+
+ /**
+ * Returns an iterator pointing to that specific element,
+ * end() otherwise
+ */
+ iterator findSingle(Event*);
+
+ const_iterator findSingle(Event *e) const {
+ return const_iterator(((Segment *)this)->findSingle(e));
+ }
+
+ /**
+ * Returns an iterator pointing to the first element starting at
+ * or beyond the given absolute time
+ */
+ iterator findTime(timeT time);
+
+ const_iterator findTime(timeT time) const {
+ return const_iterator(((Segment *)this)->findTime(time));
+ }
+
+ /**
+ * Returns an iterator pointing to the first element starting at
+ * or before the given absolute time (so returns end() if the
+ * time precedes the first event, not if it follows the last one)
+ */
+ iterator findNearestTime(timeT time);
+
+ const_iterator findNearestTime(timeT time) const {
+ return const_iterator(((Segment *)this)->findNearestTime(time));
+ }
+
+
+ //////
+ //
+ // ADVANCED, ESOTERIC, or PLAIN STUPID MANIPULATION
+
+ /**
+ * Returns the range [start, end[ of events which are at absoluteTime
+ */
+ void getTimeSlice(timeT absoluteTime, iterator &start, iterator &end);
+
+ /**
+ * Returns the range [start, end[ of events which are at absoluteTime
+ */
+ void getTimeSlice(timeT absoluteTime, const_iterator &start, const_iterator &end) const;
+
+ /**
+ * Return the starting time of the bar that contains time t. This
+ * differs from Composition's bar methods in that it will truncate
+ * to the start and end times of this Segment, and is guaranteed
+ * to return the start time of a bar that is at least partially
+ * within this Segment.
+ *
+ * (See Composition for most of the generally useful bar methods.)
+ */
+ timeT getBarStartForTime(timeT t) const;
+
+ /**
+ * Return the ending time of the bar that contains time t. This
+ * differs from Composition's bar methods in that it will truncate
+ * to the start and end times of this Segment, and is guaranteed
+ * to return the end time of a bar that is at least partially
+ * within this Segment.
+ *
+ * (See Composition for most of the generally useful bar methods.)
+ */
+ timeT getBarEndForTime(timeT t) const;
+
+ /**
+ * Fill up the segment with rests, from the end of the last event
+ * currently on the segment to the endTime given. Actually, this
+ * does much the same as setEndTime does when it extends a segment.
+ */
+ void fillWithRests(timeT endTime);
+
+ /**
+ * Fill up a section within a segment with rests, from the
+ * startTime given to the endTime given. This may be useful if
+ * you have a pathological segment that contains notes already but
+ * not rests, but it is is likely to be dangerous unless you're
+ * quite careful about making sure the given range doesn't overlap
+ * any notes.
+ */
+ void fillWithRests(timeT startTime, timeT endTime);
+
+ /**
+ * For each series of contiguous rests found between the start and
+ * end time, replace the series of rests with another series of
+ * the same duration but composed of the theoretically "correct"
+ * rest durations to fill the gap, in the current time signature.
+ * The start and end time should be the raw absolute times of the
+ * events, not the notation-quantized versions, although the code
+ * will use the notation quantizations if it finds them.
+ */
+ void normalizeRests(timeT startTime, timeT endTime);
+
+ /**
+ * Return the clef in effect at the given time. This is a
+ * reasonably quick call.
+ */
+ Clef getClefAtTime(timeT time) const;
+
+ /**
+ * Return the clef in effect at the given time, and set ctime to
+ * the time of the clef change. This is a reasonably quick call.
+ */
+ Clef getClefAtTime(timeT time, timeT &ctime) const;
+
+ /**
+ * Return the key signature in effect at the given time. This is
+ * a reasonably quick call.
+ */
+ Key getKeyAtTime(timeT time) const;
+
+ /**
+ * Return the key signature in effect at the given time, and set
+ * ktime to the time of the key change. This is a reasonably
+ * quick call.
+ */
+ Key getKeyAtTime(timeT time, timeT &ktime) const;
+
+ /**
+ * Return the clef and key signature in effect at the beginning of the
+ * segment using the following rules :
+ *
+ * - Return the default clef if no clef change is preceding the first
+ * note or rest event,
+ * - else return the first clef event in the segment,
+ * - else return the default clef if the segment has no note event nor
+ * clef change in it.
+ *
+ * - Use the same rules with the key signature.
+ */
+ void getFirstClefAndKey(Clef &clef, Key &key);
+
+
+ //////
+ //
+ // REPEAT, DELAY, TRANSPOSE
+
+ // Is this Segment repeating?
+ //
+ bool isRepeating() const { return m_repeating; }
+ void setRepeating(bool value);
+
+ /**
+ * If this Segment is repeating, calculate and return the time at
+ * which the repeating stops. This is the start time of the
+ * following Segment on the same Track, if any, or else the end
+ * time of the Composition. If this Segment does not repeat, or
+ * the time calculated would precede the end time of the Segment,
+ * instead return the end time of the Segment.
+ */
+ timeT getRepeatEndTime() const;
+
+ timeT getDelay() const { return m_delay; }
+ void setDelay(timeT delay);
+
+ RealTime getRealTimeDelay() const { return m_realTimeDelay; }
+ void setRealTimeDelay(RealTime delay);
+
+ int getTranspose() const { return m_transpose; }
+ void setTranspose(int transpose);
+
+
+
+ //////
+ //
+ // AUDIO
+
+ // Get and set Audio file Id (see the AudioFileManager)
+ //
+ unsigned int getAudioFileId() const { return m_audioFileId; }
+ void setAudioFileId(unsigned int id);
+
+ unsigned int getUnstretchedFileId() const { return m_unstretchedFileId; }
+ void setUnstretchedFileId(unsigned int id);
+
+ float getStretchRatio() const { return m_stretchRatio; }
+ void setStretchRatio(float ratio);
+
+ // The audio start and end times tell us how far into
+ // audio file "m_audioFileId" this Segment starts and
+ // how far into the sample the Segment finishes.
+ //
+ RealTime getAudioStartTime() const { return m_audioStartTime; }
+ RealTime getAudioEndTime() const { return m_audioEndTime; }
+ void setAudioStartTime(const RealTime &time);
+ void setAudioEndTime(const RealTime &time);
+
+ bool isAutoFading() const { return m_autoFade; }
+ void setAutoFade(bool value);
+
+ RealTime getFadeInTime() const { return m_fadeInTime; }
+ void setFadeInTime(const RealTime &time);
+
+ RealTime getFadeOutTime() const { return m_fadeOutTime; }
+ void setFadeOutTime(const RealTime &time);
+
+ //////
+ //
+ // MISCELLANEOUS
+
+ /// Should only be called by Composition
+ void setComposition(Composition *composition) {
+ m_composition = composition;
+ }
+
+ // The runtime id for this segment
+ //
+ int getRuntimeId() const { return m_runtimeSegmentId; }
+
+ // Grid size for matrix view (and others probably)
+ //
+ void setSnapGridSize(int size) { m_snapGridSize = size; }
+ int getSnapGridSize() const { return m_snapGridSize; }
+
+ // Other view features we might want to set on this Segment
+ //
+ void setViewFeatures(int features) { m_viewFeatures = features; }
+ int getViewFeatures() const { return m_viewFeatures; }
+
+ /**
+ * The compare class used by Composition
+ */
+ struct SegmentCmp
+ {
+ bool operator()(const Segment* a, const Segment* b) const
+ {
+ if (a->getTrack() == b->getTrack())
+ return a->getStartTime() < b->getStartTime();
+
+ return a->getTrack() < b->getTrack();
+ }
+ };
+
+
+ /// For use by SegmentObserver objects like Composition & Staff
+ void addObserver(SegmentObserver *obs) { m_observers.push_back(obs); }
+
+ /// For use by SegmentObserver objects like Composition & Staff
+ void removeObserver(SegmentObserver *obs) { m_observers.remove(obs); }
+
+ // List of visible EventRulers attached to this segment
+ //
+ class EventRuler
+ {
+ public:
+ EventRuler(const std::string &type, int controllerValue, bool active):
+ m_type(type), m_controllerValue(controllerValue), m_active(active) {;}
+
+ std::string m_type; // Event Type
+ int m_controllerValue; // if controller event, then which value
+ bool m_active; // is this Ruler active?
+ };
+
+ typedef std::vector<EventRuler*> EventRulerList;
+ typedef std::vector<EventRuler*>::iterator EventRulerListIterator;
+ typedef std::vector<EventRuler*>::const_iterator EventRulerListConstIterator;
+
+ EventRulerList& getEventRulerList() { return m_eventRulerList; }
+ EventRuler* getEventRuler(const std::string &type, int controllerValue = -1);
+
+ void addEventRuler(const std::string &type, int controllerValue = -1, bool active = 0);
+ bool deleteEventRuler(const std::string &type, int controllerValue = -1);
+
+ //////
+ //
+ // REFRESH STATUS
+
+ // delegate part of the RefreshStatusArray API
+
+ unsigned int getNewRefreshStatusId() {
+ return m_refreshStatusArray.getNewRefreshStatusId();
+ }
+
+ SegmentRefreshStatus &getRefreshStatus(unsigned int id) {
+ return m_refreshStatusArray.getRefreshStatus(id);
+ }
+
+ void updateRefreshStatuses(timeT startTime, timeT endTime);
+
+private:
+ Composition *m_composition; // owns me, if it exists
+
+ timeT m_startTime;
+ timeT *m_endMarkerTime; // points to end time, or null if none
+ timeT m_endTime;
+
+ void updateEndTime(); // called after erase of item at end
+
+ TrackId m_track;
+ SegmentType m_type; // identifies Segment type
+ std::string m_label; // segment label
+
+ unsigned int m_colourIndex; // identifies Colour Index (default == 0)
+
+ mutable int m_id; // not id of Segment, but a value for return by getNextId
+
+ unsigned int m_audioFileId; // audio file ID (see AudioFileManager)
+ unsigned int m_unstretchedFileId;
+ float m_stretchRatio;
+ RealTime m_audioStartTime; // start time relative to start of audio file
+ RealTime m_audioEndTime; // end time relative to start of audio file
+
+ bool m_repeating; // is this segment repeating?
+
+ BasicQuantizer *const m_quantizer;
+ bool m_quantize;
+
+ int m_transpose; // all Events tranpose
+ timeT m_delay; // all Events delay
+ RealTime m_realTimeDelay; // all Events delay (the delays are cumulative)
+
+ int m_highestPlayable; // suggestion for highest playable note (notation)
+ int m_lowestPlayable; // suggestion for lowest playable note (notation)
+
+ RefreshStatusArray<SegmentRefreshStatus> m_refreshStatusArray;
+
+ struct ClefKeyCmp {
+ bool operator()(const Event *e1, const Event *e2) const;
+ };
+ typedef std::multiset<Event*, ClefKeyCmp> ClefKeyList;
+ mutable ClefKeyList *m_clefKeyList;
+
+ // EventRulers currently selected as visible on this segment
+ //
+ EventRulerList m_eventRulerList;
+
+private: // stuff to support SegmentObservers
+
+ typedef std::list<SegmentObserver *> ObserverSet;
+ ObserverSet m_observers;
+
+ void notifyAdd(Event *) const;
+ void notifyRemove(Event *) const;
+ void notifyAppearanceChange() const;
+ void notifyStartChanged(timeT);
+ void notifyEndMarkerChange(bool shorten);
+ void notifySourceDeletion() const;
+
+private: // assignment operator not provided
+
+ Segment &operator=(const Segment &);
+
+ // Used for mapping the segment to runtime things like PlayableAudioFiles at
+ // the sequencer.
+ //
+ int m_runtimeSegmentId;
+
+ // Remember the last used snap grid size for this segment
+ //
+ int m_snapGridSize;
+
+ // Switch for other view-specific features we want to remember in the segment
+ //
+ int m_viewFeatures;
+
+ // Audio autofading
+ //
+ bool m_autoFade;
+ RealTime m_fadeInTime;
+ RealTime m_fadeOutTime;
+
+};
+
+
+class SegmentObserver
+{
+public:
+ virtual ~SegmentObserver() {}
+
+ /**
+ * Called after the event has been added to the segment
+ */
+ virtual void eventAdded(const Segment *, Event *) { }
+
+ /**
+ * Called after the event has been removed from the segment,
+ * and just before it is deleted
+ */
+ virtual void eventRemoved(const Segment *, Event *) { }
+
+ /**
+ * Called after a change in the segment that will change the way its displays,
+ * like a label change for instance
+ */
+ virtual void appearanceChanged(const Segment *) { }
+
+ /**
+ * Called after a change that affects the start time of the segment
+ */
+ virtual void startChanged(const Segment *, timeT) { }
+
+ /**
+ * Called after the segment's end marker time has been
+ * changed
+ *
+ * @param shorten true if the marker change shortens the segment's duration
+ */
+ virtual void endMarkerTimeChanged(const Segment *, bool /*shorten*/) { }
+
+ /**
+ * Called from the segment dtor
+ * MUST BE IMPLEMENTED BY ALL OBSERVERS
+ */
+ virtual void segmentDeleted(const Segment *) = 0;
+};
+
+
+
+// an abstract base
+
+class SegmentHelper
+{
+protected:
+ SegmentHelper(Segment &t) : m_segment(t) { }
+ virtual ~SegmentHelper();
+
+ typedef Segment::iterator iterator;
+
+ Segment &segment() { return m_segment; }
+
+ Segment::iterator begin() { return segment().begin(); }
+ Segment::iterator end() { return segment().end(); }
+
+ bool isBeforeEndMarker(Segment::const_iterator i) {
+ return segment().isBeforeEndMarker(i);
+ }
+
+ Segment::iterator insert(Event *e) { return segment().insert(e); }
+ void erase(Segment::iterator i) { segment().erase(i); }
+
+private:
+ Segment &m_segment;
+};
+
+}
+
+
+#endif
diff --git a/src/base/SegmentMatrixHelper.cpp b/src/base/SegmentMatrixHelper.cpp
new file mode 100644
index 0000000..d9af52c
--- /dev/null
+++ b/src/base/SegmentMatrixHelper.cpp
@@ -0,0 +1,56 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "SegmentMatrixHelper.h"
+#include "BaseProperties.h"
+
+namespace Rosegarden
+{
+
+Segment::iterator SegmentMatrixHelper::insertNote(Event* e)
+{
+ Segment::iterator i = segment().insert(e);
+ segment().normalizeRests(e->getAbsoluteTime(),
+ e->getAbsoluteTime() + e->getDuration());
+ return i;
+}
+
+bool
+SegmentMatrixHelper::isDrumColliding(Event* e)
+{
+ long pitch = 0;
+ if (!e->get<Int>(BaseProperties::PITCH, pitch))
+ return false;
+
+ timeT evTime = e->getAbsoluteTime();
+
+ Segment::iterator it;
+ for (it = segment().findTime(evTime); it != end(); ++it) {
+ if ((*it) == e) continue;
+ if ((*it)->getAbsoluteTime() != evTime) break;
+ long p = 0;
+ if (!(*it)->get<Int>(BaseProperties::PITCH, p)) continue;
+ if (p == pitch) return true;
+ }
+ return false;
+}
+
+}
diff --git a/src/base/SegmentMatrixHelper.h b/src/base/SegmentMatrixHelper.h
new file mode 100644
index 0000000..1790496
--- /dev/null
+++ b/src/base/SegmentMatrixHelper.h
@@ -0,0 +1,53 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SEGMENT_MATRIX_HELPER_H_
+#define _SEGMENT_MATRIX_HELPER_H_
+
+#include "SegmentNotationHelper.h"
+
+namespace Rosegarden
+{
+
+class SegmentMatrixHelper : protected SegmentNotationHelper
+{
+public:
+ SegmentMatrixHelper(Segment &t) : SegmentNotationHelper(t) { }
+
+ iterator insertNote(Event *);
+
+ /**
+ * Returns true if event is colliding another note in percussion
+ * matrix (ie event is a note and has the same start time and the
+ * same pitch as another note).
+ */
+ bool isDrumColliding(Event *);
+
+ using SegmentHelper::segment;
+ using SegmentNotationHelper::deleteEvent;
+ using SegmentNotationHelper::deleteNote;
+
+};
+
+
+}
+
+#endif
diff --git a/src/base/SegmentNotationHelper.cpp b/src/base/SegmentNotationHelper.cpp
new file mode 100644
index 0000000..a6c8ab8
--- /dev/null
+++ b/src/base/SegmentNotationHelper.cpp
@@ -0,0 +1,2129 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "SegmentNotationHelper.h"
+#include "NotationTypes.h"
+#include "Quantizer.h"
+#include "BasicQuantizer.h"
+#include "NotationQuantizer.h"
+#include "BaseProperties.h"
+#include "Composition.h"
+
+#include <iostream>
+#include <algorithm>
+#include <iterator>
+#include <list>
+
+namespace Rosegarden
+{
+using std::cerr;
+using std::endl;
+using std::string;
+using std::list;
+
+using namespace BaseProperties;
+
+
+SegmentNotationHelper::~SegmentNotationHelper() { }
+
+
+const Quantizer &
+SegmentNotationHelper::basicQuantizer() {
+ return *(segment().getComposition()->getBasicQuantizer());
+}
+
+const Quantizer &
+SegmentNotationHelper::notationQuantizer() {
+ return *(segment().getComposition()->getNotationQuantizer());
+}
+
+
+//!!! we need to go very carefully through this file and check calls
+//to getAbsoluteTime/getDuration -- the vast majority should almost
+//certainly now be using getNotationAbsoluteTime/getNotationDuration
+
+Segment::iterator
+SegmentNotationHelper::findNotationAbsoluteTime(timeT t)
+{
+ iterator i(segment().findTime(t));
+
+ // We don't know whether the notation absolute time t will appear
+ // before or after the real absolute time t. First scan backwards
+ // until we find a notation absolute time prior to (or equal to)
+ // t, and then scan forwards until we find the first one that
+ // isn't prior to t
+
+ while (i != begin() &&
+ ((i == end() ? t + 1 : (*i)->getNotationAbsoluteTime()) > t))
+ --i;
+
+ while (i != end() &&
+ ((*i)->getNotationAbsoluteTime() < t))
+ ++i;
+
+ return i;
+}
+
+Segment::iterator
+SegmentNotationHelper::findNearestNotationAbsoluteTime(timeT t)
+{
+ iterator i(segment().findTime(t));
+
+ // Exactly findNotationAbsoluteTime, only with the two scan loops
+ // in the other order
+
+ while (i != end() &&
+ ((*i)->getNotationAbsoluteTime() < t))
+ ++i;
+
+ while (i != begin() &&
+ ((i == end() ? t + 1 : (*i)->getNotationAbsoluteTime()) > t))
+ --i;
+
+ return i;
+}
+
+void
+SegmentNotationHelper::setNotationProperties(timeT startTime, timeT endTime)
+{
+ Segment::iterator from = begin();
+ Segment::iterator to = end();
+
+ if (startTime != endTime) {
+ from = segment().findTime(startTime);
+ to = segment().findTime(endTime);
+ }
+/*!!!
+ bool justSeenGraceNote = false;
+ timeT graceNoteStart = 0;
+*/
+ for (Segment::iterator i = from;
+ i != to && segment().isBeforeEndMarker(i); ++i) {
+
+ if ((*i)->has(NOTE_TYPE) /*!!! && !(*i)->has(IS_GRACE_NOTE) */) continue;
+
+ timeT duration = (*i)->getNotationDuration();
+
+ if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ int tcount = (*i)->get<Int>(BEAMED_GROUP_TUPLED_COUNT);
+ int ucount = (*i)->get<Int>(BEAMED_GROUP_UNTUPLED_COUNT);
+
+ if (tcount == 0) {
+ std::cerr << "WARNING: SegmentNotationHelper::setNotationProperties: zero tuplet count:" << std::endl;
+ (*i)->dump(std::cerr);
+ } else {
+ // nominal duration is longer than actual (sounding) duration
+ duration = (duration / tcount) * ucount;
+ }
+ }
+
+ if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType)) {
+
+ if ((*i)->isa(Note::EventType)) {
+/*!!!
+ if ((*i)->has(IS_GRACE_NOTE) &&
+ (*i)->get<Bool>(IS_GRACE_NOTE)) {
+
+ if (!justSeenGraceNote) {
+ graceNoteStart = (*i)->getNotationAbsoluteTime();
+ justSeenGraceNote = true;
+ }
+
+ } else if (justSeenGraceNote) {
+
+ duration += (*i)->getNotationAbsoluteTime() - graceNoteStart;
+ justSeenGraceNote = false;
+ }
+*/
+ }
+
+ Note n(Note::getNearestNote(duration));
+
+ (*i)->setMaybe<Int>(NOTE_TYPE, n.getNoteType());
+ (*i)->setMaybe<Int>(NOTE_DOTS, n.getDots());
+ }
+ }
+}
+
+timeT
+SegmentNotationHelper::getNotationEndTime(Event *e)
+{
+ return e->getNotationAbsoluteTime() + e->getNotationDuration();
+}
+
+
+Segment::iterator
+SegmentNotationHelper::getNextAdjacentNote(iterator i,
+ bool matchPitch,
+ bool allowOverlap)
+{
+ iterator j(i);
+ if (!isBeforeEndMarker(i)) return i;
+ if (!(*i)->isa(Note::EventType)) return end();
+
+ timeT iEnd = getNotationEndTime(*i);
+ long ip = 0, jp = 0;
+ if (!(*i)->get<Int>(PITCH, ip) && matchPitch) return end();
+
+ while (true) {
+ if (!isBeforeEndMarker(j) || !isBeforeEndMarker(++j)) return end();
+ if (!(*j)->isa(Note::EventType)) continue;
+
+ timeT jStart = (*j)->getNotationAbsoluteTime();
+ if (jStart > iEnd) return end();
+
+ if (matchPitch) {
+ if (!(*j)->get<Int>(PITCH, jp) || (jp != ip)) continue;
+ }
+
+ if (allowOverlap || (jStart == iEnd)) return j;
+ }
+}
+
+
+Segment::iterator
+SegmentNotationHelper::getPreviousAdjacentNote(iterator i,
+ timeT rangeStart,
+ bool matchPitch,
+ bool allowOverlap)
+{
+ iterator j(i);
+ if (!isBeforeEndMarker(i)) return i;
+ if (!(*i)->isa(Note::EventType)) return end();
+
+ timeT iStart = (*i)->getNotationAbsoluteTime();
+ timeT iEnd = getNotationEndTime(*i);
+ long ip = 0, jp = 0;
+ if (!(*i)->get<Int>(PITCH, ip) && matchPitch) return end();
+
+ while (true) {
+ if (j == begin()) return end(); else --j;
+ if (!(*j)->isa(Note::EventType)) continue;
+ if ((*j)->getAbsoluteTime() < rangeStart) return end();
+
+ timeT jEnd = getNotationEndTime(*j);
+
+ // don't consider notes that end after i ends or before i begins
+
+ if (jEnd > iEnd || jEnd < iStart) continue;
+
+ if (matchPitch) {
+ if (!(*j)->get<Int>(PITCH, jp) || (jp != ip)) continue;
+ }
+
+ if (allowOverlap || (jEnd == iStart)) return j;
+ }
+}
+
+
+Segment::iterator
+SegmentNotationHelper::findContiguousNext(iterator el)
+{
+ std::string elType = (*el)->getType(),
+ reject, accept;
+
+ if (elType == Note::EventType) {
+ accept = Note::EventType;
+ reject = Note::EventRestType;
+ } else if (elType == Note::EventRestType) {
+ accept = Note::EventRestType;
+ reject = Note::EventType;
+ } else {
+ accept = elType;
+ reject = "";
+ }
+
+ bool success = false;
+
+ iterator i = ++el;
+
+ for(; isBeforeEndMarker(i); ++i) {
+ std::string iType = (*i)->getType();
+
+ if (iType == reject) {
+ success = false;
+ break;
+ }
+ if (iType == accept) {
+ success = true;
+ break;
+ }
+ }
+
+ if (success) return i;
+ else return end();
+
+}
+
+Segment::iterator
+SegmentNotationHelper::findContiguousPrevious(iterator el)
+{
+ if (el == begin()) return end();
+
+ std::string elType = (*el)->getType(),
+ reject, accept;
+
+ if (elType == Note::EventType) {
+ accept = Note::EventType;
+ reject = Note::EventRestType;
+ } else if (elType == Note::EventRestType) {
+ accept = Note::EventRestType;
+ reject = Note::EventType;
+ } else {
+ accept = elType;
+ reject = "";
+ }
+
+ bool success = false;
+
+ iterator i = --el;
+
+ while (true) {
+ std::string iType = (*i)->getType();
+
+ if (iType == reject) {
+ success = false;
+ break;
+ }
+ if (iType == accept) {
+ success = true;
+ break;
+ }
+ if (i == begin()) break;
+ --i;
+ }
+
+ if (success) return i;
+ else return end();
+}
+
+
+bool
+SegmentNotationHelper::noteIsInChord(Event *note)
+{
+ iterator i = segment().findSingle(note);
+ timeT t = note->getNotationAbsoluteTime();
+
+ for (iterator j = i; j != end(); ++j) { // not isBeforeEndMarker, unnecessary here
+ if (j == i) continue;
+ if ((*j)->isa(Note::EventType)) {
+ timeT tj = (*j)->getNotationAbsoluteTime();
+ if (tj == t) return true;
+ else if (tj > t) break;
+ }
+ }
+
+ for (iterator j = i; ; ) {
+ if (j == begin()) break;
+ --j;
+ if ((*j)->isa(Note::EventType)) {
+ timeT tj = (*j)->getNotationAbsoluteTime();
+ if (tj == t) return true;
+ else if (tj < t) break;
+ }
+ }
+
+ return false;
+
+/*!!!
+ iterator first, second;
+ segment().getTimeSlice(note->getAbsoluteTime(), first, second);
+
+ int noteCount = 0;
+ for (iterator i = first; i != second; ++i) {
+ if ((*i)->isa(Note::EventType)) ++noteCount;
+ }
+
+ return noteCount > 1;
+*/
+}
+
+
+//!!! This doesn't appear to be used any more and may well not work.
+// Ties are calculated in several different places, and it's odd that
+// we don't have a decent API for them
+Segment::iterator
+SegmentNotationHelper::getNoteTiedWith(Event *note, bool forwards)
+{
+ bool tied = false;
+
+ if (!note->get<Bool>(forwards ?
+ BaseProperties::TIED_FORWARD :
+ BaseProperties::TIED_BACKWARD, tied) || !tied) {
+ return end();
+ }
+
+ timeT myTime = note->getAbsoluteTime();
+ timeT myDuration = note->getDuration();
+ int myPitch = note->get<Int>(BaseProperties::PITCH);
+
+ iterator i = segment().findSingle(note);
+ if (!isBeforeEndMarker(i)) return end();
+
+ for (;;) {
+ i = forwards ? findContiguousNext(i) : findContiguousPrevious(i);
+
+ if (!isBeforeEndMarker(i)) return end();
+ if ((*i)->getAbsoluteTime() == myTime) continue;
+
+ if (forwards && ((*i)->getAbsoluteTime() != myTime + myDuration)) {
+ return end();
+ }
+ if (!forwards &&
+ (((*i)->getAbsoluteTime() + (*i)->getDuration()) != myTime)) {
+ return end();
+ }
+
+ if (!(*i)->get<Bool>(forwards ?
+ BaseProperties::TIED_BACKWARD :
+ BaseProperties::TIED_FORWARD, tied) || !tied) {
+ continue;
+ }
+
+ if ((*i)->get<Int>(BaseProperties::PITCH) == myPitch) return i;
+ }
+
+ return end();
+}
+
+
+bool
+SegmentNotationHelper::collapseRestsIfValid(Event* e, bool& collapseForward)
+{
+ iterator elPos = segment().findSingle(e);
+ if (elPos == end()) return false;
+
+ timeT myDuration = (*elPos)->getNotationDuration();
+
+ // findContiguousNext won't return an iterator beyond the end marker
+ iterator nextEvent = findContiguousNext(elPos),
+ previousEvent = findContiguousPrevious(elPos);
+
+ // Remark: findContiguousXXX is inadequate for notes, we would
+ // need to check adjacency using e.g. getNextAdjacentNote if this
+ // method were to work for notes as well as rests.
+
+ // collapse to right if (a) not at end...
+ if (nextEvent != end() &&
+ // ...(b) rests can be merged to a single, valid unit
+ isCollapseValid((*nextEvent)->getNotationDuration(), myDuration) &&
+ // ...(c) event is in same bar (no cross-bar collapsing)
+ (*nextEvent)->getAbsoluteTime() <
+ segment().getBarEndForTime(e->getAbsoluteTime())) {
+
+ // collapse right is OK; collapse e with nextEvent
+ Event *e1(new Event(*e, e->getAbsoluteTime(),
+ e->getDuration() + (*nextEvent)->getDuration()));
+
+ collapseForward = true;
+ erase(elPos);
+ erase(nextEvent);
+ insert(e1);
+ return true;
+ }
+
+ // logic is exactly backwards from collapse to right logic above
+ if (previousEvent != end() &&
+ isCollapseValid((*previousEvent)->getNotationDuration(), myDuration) &&
+ (*previousEvent)->getAbsoluteTime() >
+ segment().getBarStartForTime(e->getAbsoluteTime())) {
+
+ // collapse left is OK; collapse e with previousEvent
+ Event *e1(new Event(**previousEvent,
+ (*previousEvent)->getAbsoluteTime(),
+ e->getDuration() +
+ (*previousEvent)->getDuration()));
+
+ collapseForward = false;
+ erase(elPos);
+ erase(previousEvent);
+ insert(e1);
+ return true;
+ }
+
+ return false;
+}
+
+
+bool
+SegmentNotationHelper::isCollapseValid(timeT a, timeT b)
+{
+ return (isViable(a + b));
+}
+
+
+bool
+SegmentNotationHelper::isSplitValid(timeT a, timeT b)
+{
+ return (isViable(a) && isViable(b));
+}
+
+Segment::iterator
+SegmentNotationHelper::splitIntoTie(iterator &i, timeT baseDuration)
+{
+ if (i == end()) return end();
+ iterator i2;
+ segment().getTimeSlice((*i)->getAbsoluteTime(), i, i2);
+ return splitIntoTie(i, i2, baseDuration);
+}
+
+Segment::iterator
+SegmentNotationHelper::splitIntoTie(iterator &from, iterator to,
+ timeT baseDuration)
+{
+ // so long as we do the quantization checks for validity before
+ // calling this method, we should be fine splitting precise times
+ // in this method. only problem is deciding not to split something
+ // if its duration is very close to requested duration, but that's
+ // probably not a task for this function
+
+ timeT eventDuration = (*from)->getDuration();
+ timeT baseTime = (*from)->getAbsoluteTime();
+
+ long firstGroupId = -1;
+ (*from)->get<Int>(BEAMED_GROUP_ID, firstGroupId);
+
+ long nextGroupId = -1;
+ iterator ni(to);
+
+ if (segment().isBeforeEndMarker(ni) && segment().isBeforeEndMarker(++ni)) {
+ (*ni)->get<Int>(BEAMED_GROUP_ID, nextGroupId);
+ }
+
+ list<Event *> toInsert;
+ list<iterator> toErase;
+
+ // Split all the note and rest events in range [from, to[
+ //
+ for (iterator i = from; i != to; ++i) {
+
+ if (!(*i)->isa(Note::EventType) &&
+ !(*i)->isa(Note::EventRestType)) continue;
+
+ if ((*i)->getAbsoluteTime() != baseTime) {
+ // no way to really cope with an error, because at this
+ // point we may already have splut some events. Best to
+ // skip this event
+ cerr << "WARNING: SegmentNotationHelper::splitIntoTie(): (*i)->getAbsoluteTime() != baseTime (" << (*i)->getAbsoluteTime() << " vs " << baseTime << "), ignoring this event\n";
+ continue;
+ }
+
+ if ((*i)->getDuration() != eventDuration) {
+ if ((*i)->getDuration() == 0) continue;
+ cerr << "WARNING: SegmentNotationHelper::splitIntoTie(): (*i)->getDuration() != eventDuration (" << (*i)->getDuration() << " vs " << eventDuration << "), changing eventDuration to match\n";
+ eventDuration = (*i)->getDuration();
+ }
+
+ if (baseDuration >= eventDuration) {
+// cerr << "SegmentNotationHelper::splitIntoTie() : baseDuration >= eventDuration, ignoring event\n";
+ continue;
+ }
+
+ std::pair<Event *, Event *> split =
+ splitPreservingPerformanceTimes(*i, baseDuration);
+
+ Event *eva = split.first;
+ Event *evb = split.second;
+
+ if (!eva || !evb) {
+ cerr << "WARNING: SegmentNotationHelper::splitIntoTie(): No valid split for event of duration " << eventDuration << " at " << baseTime << " (baseDuration " << baseDuration << "), ignoring this event\n";
+ continue;
+ }
+
+ // we only want to tie Note events:
+
+ if (eva->isa(Note::EventType)) {
+
+ // if the first event was already tied forward, the
+ // second one will now be marked as tied forward
+ // (which is good). set up the relationship between
+ // the original (now shorter) event and the new one.
+
+ evb->set<Bool>(TIED_BACKWARD, true);
+ eva->set<Bool>(TIED_FORWARD, true);
+ }
+
+ // we may also need to change some group information: if
+ // the first event is in a beamed group but the event
+ // following the insertion is not or is in a different
+ // group, then the new second event should not be in a
+ // group. otherwise, it should inherit the grouping info
+ // from the first event (as it already does, because it
+ // was created using the copy constructor).
+
+ // (this doesn't apply to tupled groups, which we want
+ // to persist wherever possible.)
+
+ if (firstGroupId != -1 &&
+ nextGroupId != firstGroupId &&
+ !evb->has(BEAMED_GROUP_TUPLET_BASE)) {
+ evb->unset(BEAMED_GROUP_ID);
+ evb->unset(BEAMED_GROUP_TYPE);
+ }
+
+ toInsert.push_back(eva);
+ toInsert.push_back(evb);
+ toErase.push_back(i);
+ }
+
+ // erase the old events
+ for (list<iterator>::iterator i = toErase.begin();
+ i != toErase.end(); ++i) {
+ segment().erase(*i);
+ }
+
+ from = end();
+ iterator last = end();
+
+ // now insert the new events
+ for (list<Event *>::iterator i = toInsert.begin();
+ i != toInsert.end(); ++i) {
+ last = insert(*i);
+ if (from == end()) from = last;
+ }
+
+ return last;
+}
+
+bool
+SegmentNotationHelper::isViable(timeT duration, int dots)
+{
+ bool viable;
+
+/*!!!
+ duration = basicQuantizer().quantizeDuration(duration);
+
+ if (dots >= 0) {
+ viable = (duration == Quantizer(Quantizer::RawEventData,
+ Quantizer::DefaultTarget,
+ Quantizer::NoteQuantize, 1, dots).
+ quantizeDuration(duration));
+ } else {
+ viable = (duration == notationQuantizer().quantizeDuration(duration));
+ }
+*/
+
+ //!!! what to do about this?
+
+ timeT nearestDuration =
+ Note::getNearestNote(duration, dots >= 0 ? dots : 2).getDuration();
+
+// std::cerr << "SegmentNotationHelper::isViable: nearestDuration is " << nearestDuration << ", duration is " << duration << std::endl;
+ viable = (nearestDuration == duration);
+
+ return viable;
+}
+
+
+void
+SegmentNotationHelper::makeRestViable(iterator i)
+{
+ timeT absTime = (*i)->getAbsoluteTime();
+ timeT duration = (*i)->getDuration();
+ erase(i);
+ segment().fillWithRests(absTime, absTime + duration);
+}
+
+
+void
+SegmentNotationHelper::makeNotesViable(iterator from, iterator to,
+ bool splitAtBars)
+{
+ // We don't use quantized values here; we want a precise division.
+ // Even if it doesn't look precise on the score (because the score
+ // is quantized), we want any playback to produce exactly the same
+ // duration of note as was originally recorded
+
+ std::vector<Event *> toInsert;
+
+ for (Segment::iterator i = from, j = i;
+ segment().isBeforeEndMarker(i) && i != to; i = j) {
+
+ ++j;
+
+ if (!(*i)->isa(Note::EventType) && !(*i)->isa(Note::EventRestType)) {
+ continue;
+ }
+
+ if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ continue;
+ }
+
+ DurationList dl;
+
+ // Behaviour differs from TimeSignature::getDurationListForInterval
+
+ timeT acc = 0;
+ timeT required = (*i)->getNotationDuration();
+
+ while (acc < required) {
+ timeT remaining = required - acc;
+ if (splitAtBars) {
+ timeT thisNoteStart = (*i)->getNotationAbsoluteTime() + acc;
+ timeT toNextBar =
+ segment().getBarEndForTime(thisNoteStart) - thisNoteStart;
+ if (toNextBar > 0 && remaining > toNextBar) remaining = toNextBar;
+ }
+ timeT component = Note::getNearestNote(remaining).getDuration();
+ if (component > (required - acc)) dl.push_back(required - acc);
+ else dl.push_back(component);
+ acc += component;
+ }
+
+ if (dl.size() < 2) {
+ // event is already of the correct duration
+ continue;
+ }
+
+ acc = (*i)->getNotationAbsoluteTime();
+ Event *e = new Event(*(*i));
+
+ bool lastTiedForward = false;
+ e->get<Bool>(TIED_FORWARD, lastTiedForward);
+
+ e->set<Bool>(TIED_FORWARD, true);
+ erase(i);
+
+ for (DurationList::iterator dli = dl.begin(); dli != dl.end(); ++dli) {
+
+ DurationList::iterator dlj(dli);
+ if (++dlj == dl.end()) {
+ // end of duration list
+ if (!lastTiedForward) e->unset(TIED_FORWARD);
+ toInsert.push_back(e);
+ e = 0;
+ break;
+ }
+
+ std::pair<Event *, Event *> splits =
+ splitPreservingPerformanceTimes(e, *dli);
+
+ if (!splits.first || !splits.second) {
+ cerr << "WARNING: SegmentNotationHelper::makeNoteViable(): No valid split for event of duration " << e->getDuration() << " at " << e->getAbsoluteTime() << " (split duration " << *dli << "), ignoring remainder\n";
+ cerr << "WARNING: This is probably a bug; fix required" << std::endl;
+ toInsert.push_back(e);
+ e = 0;
+ break;
+ }
+
+ toInsert.push_back(splits.first);
+ delete e;
+ e = splits.second;
+
+ acc += *dli;
+
+ e->set<Bool>(TIED_BACKWARD, true);
+ }
+
+ delete e;
+ }
+
+ for (std::vector<Event *>::iterator ei = toInsert.begin();
+ ei != toInsert.end(); ++ei) {
+ insert(*ei);
+ }
+}
+
+void
+SegmentNotationHelper::makeNotesViable(timeT startTime, timeT endTime,
+ bool splitAtBars)
+{
+ Segment::iterator from = segment().findTime(startTime);
+ Segment::iterator to = segment().findTime(endTime);
+
+ makeNotesViable(from, to, splitAtBars);
+}
+
+
+Segment::iterator
+SegmentNotationHelper::insertNote(timeT absoluteTime, Note note, int pitch,
+ Accidental explicitAccidental)
+{
+ Event *e = new Event(Note::EventType, absoluteTime, note.getDuration());
+ e->set<Int>(PITCH, pitch);
+ e->set<String>(ACCIDENTAL, explicitAccidental);
+ iterator i = insertNote(e);
+ delete e;
+ return i;
+}
+
+Segment::iterator
+SegmentNotationHelper::insertNote(Event *modelEvent)
+{
+ timeT absoluteTime = modelEvent->getAbsoluteTime();
+ iterator i = segment().findNearestTime(absoluteTime);
+
+ // If our insertion time doesn't match up precisely with any
+ // existing event, and if we're inserting over a rest, split the
+ // rest at the insertion time first.
+
+ if (i != end() &&
+ (*i)->getAbsoluteTime() < absoluteTime &&
+ (*i)->getAbsoluteTime() + (*i)->getDuration() > absoluteTime &&
+ (*i)->isa(Note::EventRestType)) {
+ i = splitIntoTie(i, absoluteTime - (*i)->getAbsoluteTime());
+ }
+
+ timeT duration = modelEvent->getDuration();
+
+ if (i != end() && (*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ duration = duration * (*i)->get<Int>(BEAMED_GROUP_TUPLED_COUNT) /
+ (*i)->get<Int>(BEAMED_GROUP_UNTUPLED_COUNT);
+ }
+
+ //!!! Deal with end-of-bar issues!
+
+ return insertSomething(i, duration, modelEvent, false);
+}
+
+
+Segment::iterator
+SegmentNotationHelper::insertRest(timeT absoluteTime, Note note)
+{
+ iterator i, j;
+ segment().getTimeSlice(absoluteTime, i, j);
+
+ //!!! Deal with end-of-bar issues!
+
+ timeT duration(note.getDuration());
+
+ if (i != end() && (*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ duration = duration * (*i)->get<Int>(BEAMED_GROUP_TUPLED_COUNT) /
+ (*i)->get<Int>(BEAMED_GROUP_UNTUPLED_COUNT);
+ }
+
+ Event *modelEvent = new Event(Note::EventRestType, absoluteTime,
+ note.getDuration(),
+ Note::EventRestSubOrdering);
+
+ i = insertSomething(i, duration, modelEvent, false);
+ delete modelEvent;
+ return i;
+}
+
+
+// return an iterator pointing to the "same" event as the original
+// iterator (which will have been replaced)
+
+Segment::iterator
+SegmentNotationHelper::collapseRestsForInsert(iterator i,
+ timeT desiredDuration)
+{
+ // collapse at most once, then recurse
+
+ if (!segment().isBeforeEndMarker(i) ||
+ !(*i)->isa(Note::EventRestType)) return i;
+
+ timeT d = (*i)->getDuration();
+ iterator j = findContiguousNext(i); // won't return itr after end marker
+ if (d >= desiredDuration || j == end()) return i;
+
+ Event *e(new Event(**i, (*i)->getAbsoluteTime(), d + (*j)->getDuration()));
+ iterator ii(insert(e));
+ erase(i);
+ erase(j);
+
+ return collapseRestsForInsert(ii, desiredDuration);
+}
+
+
+Segment::iterator
+SegmentNotationHelper::insertSomething(iterator i, int duration,
+ Event *modelEvent, bool tiedBack)
+{
+ // Rules:
+ //
+ // 1. If we hit a bar line in the course of the intended inserted
+ // note, we should split the note rather than make the bar the
+ // wrong length. (Not implemented yet)
+ //
+ // 2. If there's nothing at the insertion point but rests (and
+ // enough of them to cover the entire duration of the new note),
+ // then we should insert the new note/rest literally and remove
+ // rests as appropriate. Rests should never prevent us from
+ // inserting what the user asked for.
+ //
+ // 3. If there are notes in the way of an inserted note, however,
+ // we split whenever "reasonable" and truncate our user's note if
+ // not reasonable to split. We can't always give users the Right
+ // Thing here, so to hell with them.
+
+ while (i != end() &&
+ ((*i)->getDuration() == 0 ||
+ !((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType))))
+ ++i;
+
+ if (i == end()) {
+ return insertSingleSomething(i, duration, modelEvent, tiedBack);
+ }
+
+ // If there's a rest at the insertion position, merge it with any
+ // following rests, if available, until we have at least the
+ // duration of the new note.
+ i = collapseRestsForInsert(i, duration);
+
+ timeT existingDuration = (*i)->getNotationDuration();
+
+// cerr << "SegmentNotationHelper::insertSomething: asked to insert duration " << duration
+// << " over event of duration " << existingDuration << ":" << endl;
+ (*i)->dump(cerr);
+
+ if (duration == existingDuration) {
+
+ // 1. If the new note or rest is the same length as an
+ // existing note or rest at that position, chord the existing
+ // note or delete the existing rest and insert.
+
+// cerr << "Durations match; doing simple insert" << endl;
+
+ return insertSingleSomething(i, duration, modelEvent, tiedBack);
+
+ } else if (duration < existingDuration) {
+
+ // 2. If the new note or rest is shorter than an existing one,
+ // split the existing one and chord or replace the first part.
+
+ if ((*i)->isa(Note::EventType)) {
+
+ if (!isSplitValid(duration, existingDuration - duration)) {
+
+// cerr << "Bad split, coercing new note" << endl;
+
+ // not reasonable to split existing note, so force new one
+ // to same duration instead
+ duration = (*i)->getNotationDuration();
+
+ } else {
+// cerr << "Good split, splitting old event" << endl;
+ splitIntoTie(i, duration);
+ }
+ } else if ((*i)->isa(Note::EventRestType)) {
+
+// cerr << "Found rest, splitting" << endl;
+ iterator last = splitIntoTie(i, duration);
+
+ // Recover viability for the second half of any split rest
+ // (we duck out of this if we find we're in a tupleted zone)
+
+ if (last != end() && !(*last)->has(BEAMED_GROUP_TUPLET_BASE)) {
+ makeRestViable(last);
+ }
+ }
+
+ return insertSingleSomething(i, duration, modelEvent, tiedBack);
+
+ } else { // duration > existingDuration
+
+ // 3. If the new note is longer, split the new note so that
+ // the first part is the same duration as the existing note or
+ // rest, and recurse to step 1 with both the first and the
+ // second part in turn.
+
+ bool needToSplit = true;
+
+ // special case: existing event is a rest, and it's at the end
+ // of the segment
+
+ if ((*i)->isa(Note::EventRestType)) {
+ iterator j;
+ for (j = i; j != end(); ++j) {
+ if ((*j)->isa(Note::EventType)) break;
+ }
+ if (j == end()) needToSplit = false;
+ }
+
+ if (needToSplit) {
+
+ //!!! This is not quite right for rests. Because they
+ //replace (rather than chording with) any events already
+ //present, they don't need to be split in the case where
+ //their duration spans several note-events. Worry about
+ //that later, I guess. We're actually getting enough
+ //is-note/is-rest decisions here to make it possibly worth
+ //splitting this method into note and rest versions again
+
+// cerr << "Need to split new note" << endl;
+
+ i = insertSingleSomething
+ (i, existingDuration, modelEvent, tiedBack);
+
+ if (modelEvent->isa(Note::EventType))
+ (*i)->set<Bool>(TIED_FORWARD, true);
+
+ timeT insertedTime = (*i)->getAbsoluteTime();
+ while (i != end() &&
+ ((*i)->getNotationAbsoluteTime() <
+ (insertedTime + existingDuration))) ++i;
+
+ return insertSomething
+ (i, duration - existingDuration, modelEvent, true);
+
+ } else {
+// cerr << "No need to split new note" << endl;
+ return insertSingleSomething(i, duration, modelEvent, tiedBack);
+ }
+ }
+}
+
+Segment::iterator
+SegmentNotationHelper::insertSingleSomething(iterator i, int duration,
+ Event *modelEvent, bool tiedBack)
+{
+ timeT time;
+ timeT notationTime;
+ bool eraseI = false;
+ timeT effectiveDuration(duration);
+
+ if (i == end()) {
+ time = segment().getEndTime();
+ notationTime = time;
+ } else {
+ time = (*i)->getAbsoluteTime();
+ notationTime = (*i)->getNotationAbsoluteTime();
+ if (modelEvent->isa(Note::EventRestType) ||
+ (*i)->isa(Note::EventRestType)) eraseI = true;
+ }
+
+ Event *e = new Event(*modelEvent, time, effectiveDuration,
+ modelEvent->getSubOrdering(), notationTime);
+
+ // If the model event already has group info, I guess we'd better use it!
+ if (!e->has(BEAMED_GROUP_ID)) {
+ setInsertedNoteGroup(e, i);
+ }
+
+ if (tiedBack && e->isa(Note::EventType)) {
+ e->set<Bool>(TIED_BACKWARD, true);
+ }
+
+ if (eraseI) {
+ // erase i and all subsequent events with the same type and
+ // absolute time
+ timeT time((*i)->getAbsoluteTime());
+ std::string type((*i)->getType());
+ iterator j(i);
+ while (j != end() && (*j)->getAbsoluteTime() == time) {
+ ++j;
+ if ((*i)->isa(type)) erase(i);
+ i = j;
+ }
+ }
+
+ return insert(e);
+}
+
+void
+SegmentNotationHelper::setInsertedNoteGroup(Event *e, iterator i)
+{
+ // Formerly this was posited on the note being inserted between
+ // two notes in the same group, but that's quite wrong-headed: we
+ // want to place it in the same group as any existing note at the
+ // same time, and otherwise leave it alone.
+
+ e->unset(BEAMED_GROUP_ID);
+ e->unset(BEAMED_GROUP_TYPE);
+
+ while (isBeforeEndMarker(i) &&
+ (!((*i)->isa(Note::EventRestType)) ||
+ (*i)->has(BEAMED_GROUP_TUPLET_BASE)) &&
+ (*i)->getNotationAbsoluteTime() == e->getAbsoluteTime()) {
+
+ if ((*i)->has(BEAMED_GROUP_ID)) {
+
+ string type = (*i)->get<String>(BEAMED_GROUP_TYPE);
+ if (type != GROUP_TYPE_TUPLED && !(*i)->isa(Note::EventType)) {
+ if ((*i)->isa(Note::EventRestType)) return;
+ else {
+ ++i;
+ continue;
+ }
+ }
+
+ e->set<Int>(BEAMED_GROUP_ID, (*i)->get<Int>(BEAMED_GROUP_ID));
+ e->set<String>(BEAMED_GROUP_TYPE, type);
+
+ if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+
+ e->set<Int>(BEAMED_GROUP_TUPLET_BASE,
+ (*i)->get<Int>(BEAMED_GROUP_TUPLET_BASE));
+ e->set<Int>(BEAMED_GROUP_TUPLED_COUNT,
+ (*i)->get<Int>(BEAMED_GROUP_TUPLED_COUNT));
+ e->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT,
+ (*i)->get<Int>(BEAMED_GROUP_UNTUPLED_COUNT));
+ }
+
+ return;
+ }
+
+ ++i;
+ }
+}
+
+
+Segment::iterator
+SegmentNotationHelper::insertClef(timeT absoluteTime, Clef clef)
+{
+ return insert(clef.getAsEvent(absoluteTime));
+}
+
+
+Segment::iterator
+SegmentNotationHelper::insertKey(timeT absoluteTime, Key key)
+{
+ return insert(key.getAsEvent(absoluteTime));
+}
+
+
+Segment::iterator
+SegmentNotationHelper::insertText(timeT absoluteTime, Text text)
+{
+ return insert(text.getAsEvent(absoluteTime));
+}
+
+
+void
+SegmentNotationHelper::deleteNote(Event *e, bool collapseRest)
+{
+ iterator i = segment().findSingle(e);
+
+ if (i == end()) return;
+
+ if ((*i)->has(TIED_BACKWARD) && (*i)->get<Bool>(TIED_BACKWARD)) {
+ iterator j = getPreviousAdjacentNote(i, segment().getStartTime(),
+ true, false);
+ if (j != end()) {
+ (*j)->unset(TIED_FORWARD); // don't even check if it has it set
+ }
+ }
+
+ if ((*i)->has(TIED_FORWARD) && (*i)->get<Bool>(TIED_FORWARD)) {
+ iterator j = getNextAdjacentNote(i, true, false);
+ if (j != end()) {
+ (*j)->unset(TIED_BACKWARD); // don't even check if it has it set
+ }
+ }
+
+ // If any notes start at the same time as this one but end first,
+ // or start after this one starts but before it ends, then we go
+ // for the delete-event-and-normalize-rests option. Otherwise
+ // (the notationally simpler case) we go for the
+ // replace-note-by-rest option. We still lose in the case where
+ // another note starts before this one, overlaps it, but then also
+ // ends before it does -- but I think we can live with that.
+
+ iterator j = i;
+ timeT endTime = (*i)->getAbsoluteTime() + (*i)->getDuration();
+
+ while (j != end() && (*j)->getAbsoluteTime() < endTime) {
+
+ bool complicatedOverlap = false;
+
+ if ((*j)->getAbsoluteTime() != (*i)->getAbsoluteTime()) {
+ complicatedOverlap = true;
+ } else if (((*j)->getAbsoluteTime() + (*j)->getDuration()) < endTime) {
+ complicatedOverlap = true;
+ }
+
+ if (complicatedOverlap) {
+ timeT startTime = (*i)->getAbsoluteTime();
+ segment().erase(i);
+ segment().normalizeRests(startTime, endTime);
+ return;
+ }
+
+ ++j;
+ }
+
+ if (noteIsInChord(e)) {
+
+ erase(i);
+
+ } else {
+
+ // replace with a rest
+ Event *newRest = new Event(Note::EventRestType,
+ e->getAbsoluteTime(), e->getDuration(),
+ Note::EventRestSubOrdering);
+ insert(newRest);
+ erase(i);
+
+ // collapse the new rest
+ if (collapseRest) {
+ bool dummy;
+ collapseRestsIfValid(newRest, dummy);
+ }
+
+ }
+}
+
+bool
+SegmentNotationHelper::deleteRest(Event *e)
+{
+ bool collapseForward;
+ return collapseRestsIfValid(e, collapseForward);
+}
+
+bool
+SegmentNotationHelper::deleteEvent(Event *e, bool collapseRest)
+{
+ bool res = true;
+
+ if (e->isa(Note::EventType)) deleteNote(e, collapseRest);
+ else if (e->isa(Note::EventRestType)) res = deleteRest(e);
+ else {
+ // just plain delete
+ iterator i = segment().findSingle(e);
+ if (i != end()) erase(i);
+ }
+
+ return res;
+}
+
+
+bool
+SegmentNotationHelper::hasEffectiveDuration(iterator i)
+{
+ bool hasDuration = ((*i)->getDuration() > 0);
+
+ if ((*i)->isa(Note::EventType)) {
+ iterator i0(i);
+ if (++i0 != end() &&
+ (*i0)->isa(Note::EventType) &&
+ (*i0)->getNotationAbsoluteTime() ==
+ (*i)->getNotationAbsoluteTime()) {
+ // we're in a chord or something
+ hasDuration = false;
+ }
+ }
+
+ return hasDuration;
+}
+
+
+void
+SegmentNotationHelper::makeBeamedGroup(timeT from, timeT to, string type)
+{
+ makeBeamedGroupAux(segment().findTime(from), segment().findTime(to),
+ type, false);
+}
+
+void
+SegmentNotationHelper::makeBeamedGroup(iterator from, iterator to, string type)
+{
+ makeBeamedGroupAux
+ ((from == end()) ? from : segment().findTime((*from)->getAbsoluteTime()),
+ (to == end()) ? to : segment().findTime((*to )->getAbsoluteTime()),
+ type, false);
+}
+
+void
+SegmentNotationHelper::makeBeamedGroupExact(iterator from, iterator to, string type)
+{
+ makeBeamedGroupAux(from, to, type, true);
+}
+
+void
+SegmentNotationHelper::makeBeamedGroupAux(iterator from, iterator to,
+ string type, bool groupGraces)
+{
+// cerr << "SegmentNotationHelper::makeBeamedGroupAux: type " << type << endl;
+// if (from == to) cerr << "from == to" <<endl;
+
+ int groupId = segment().getNextId();
+ bool beamedSomething = false;
+
+ for (iterator i = from; i != to; ++i) {
+// std::cerr << "looking at " << (*i)->getType() << " at " << (*i)->getAbsoluteTime() << std::endl;
+
+ // don't permit ourselves to change the type of an
+ // already-grouped event here
+ if ((*i)->has(BEAMED_GROUP_TYPE) &&
+ (*i)->get<String>(BEAMED_GROUP_TYPE) != GROUP_TYPE_BEAMED) {
+ continue;
+ }
+
+ if (!groupGraces) {
+ if ((*i)->has(IS_GRACE_NOTE) &&
+ (*i)->get<Bool>(IS_GRACE_NOTE)) {
+ continue;
+ }
+ }
+
+ // don't beam anything longer than a quaver unless it's
+ // between beamed quavers -- in which case marking it as
+ // beamed will ensure that it gets re-stemmed appropriately
+
+ if ((*i)->isa(Note::EventType) &&
+ (*i)->getNotationDuration() >= Note(Note::Crotchet).getDuration()) {
+// std::cerr << "too long" <<std::endl;
+ if (!beamedSomething) continue;
+ iterator j = i;
+ bool somethingLeft = false;
+ while (++j != to) {
+ if ((*j)->getType() == Note::EventType &&
+ (*j)->getNotationAbsoluteTime() > (*i)->getNotationAbsoluteTime() &&
+ (*j)->getNotationDuration() < Note(Note::Crotchet).getDuration()) {
+ somethingLeft = true;
+ break;
+ }
+ }
+ if (!somethingLeft) continue;
+ }
+
+// std::cerr << "beaming it" <<std::endl;
+ (*i)->set<Int>(BEAMED_GROUP_ID, groupId);
+ (*i)->set<String>(BEAMED_GROUP_TYPE, type);
+ }
+}
+
+void
+SegmentNotationHelper::makeTupletGroup(timeT t, int untupled, int tupled,
+ timeT unit)
+{
+ int groupId = segment().getNextId();
+
+ cerr << "SegmentNotationHelper::makeTupletGroup: time " << t << ", unit "<< unit << ", params " << untupled << "/" << tupled << ", id " << groupId << endl;
+
+ list<Event *> toInsert;
+ list<iterator> toErase;
+ timeT notationTime = t;
+ timeT fillWithRestsTo = t;
+ bool haveStartNotationTime = false;
+
+ for (iterator i = segment().findTime(t); i != end(); ++i) {
+
+ if (!haveStartNotationTime) {
+ notationTime = (*i)->getNotationAbsoluteTime();
+ fillWithRestsTo = notationTime + (untupled * unit);
+ haveStartNotationTime = true;
+ }
+
+ if ((*i)->getNotationAbsoluteTime() >=
+ notationTime + (untupled * unit)) break;
+
+ timeT offset = (*i)->getNotationAbsoluteTime() - notationTime;
+ timeT duration = (*i)->getNotationDuration();
+
+ if ((*i)->isa(Note::EventRestType) &&
+ ((offset + duration) > (untupled * unit))) {
+ fillWithRestsTo = std::max(fillWithRestsTo,
+ notationTime + offset + duration);
+ duration = (untupled * unit) - offset;
+ if (duration <= 0) {
+ toErase.push_back(i);
+ continue;
+ }
+ }
+
+ Event *e = new Event(**i,
+ notationTime + (offset * tupled / untupled),
+ duration * tupled / untupled);
+
+ cerr << "SegmentNotationHelper::makeTupletGroup: made event at time " << e->getAbsoluteTime() << ", duration " << e->getDuration() << endl;
+
+ e->set<Int>(BEAMED_GROUP_ID, groupId);
+ e->set<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
+
+ e->set<Int>(BEAMED_GROUP_TUPLET_BASE, unit);
+ e->set<Int>(BEAMED_GROUP_TUPLED_COUNT, tupled);
+ e->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT, untupled);
+
+ toInsert.push_back(e);
+ toErase.push_back(i);
+ }
+
+ for (list<iterator>::iterator i = toErase.begin();
+ i != toErase.end(); ++i) {
+ segment().erase(*i);
+ }
+
+ for (list<Event *>::iterator i = toInsert.begin();
+ i != toInsert.end(); ++i) {
+ segment().insert(*i);
+ }
+
+ if (haveStartNotationTime) {
+ segment().fillWithRests(notationTime + (tupled * unit),
+ fillWithRestsTo);
+ }
+}
+
+
+
+
+void
+SegmentNotationHelper::unbeam(timeT from, timeT to)
+{
+ unbeamAux(segment().findTime(from), segment().findTime(to));
+}
+
+void
+SegmentNotationHelper::unbeam(iterator from, iterator to)
+{
+ unbeamAux
+ ((from == end()) ? from : segment().findTime((*from)->getAbsoluteTime()),
+ (to == end()) ? to : segment().findTime((*to )->getAbsoluteTime()));
+}
+
+void
+SegmentNotationHelper::unbeamAux(iterator from, iterator to)
+{
+ for (iterator i = from; i != to; ++i) {
+ (*i)->unset(BEAMED_GROUP_ID);
+ (*i)->unset(BEAMED_GROUP_TYPE);
+ (*i)->clearNonPersistentProperties();
+ }
+}
+
+
+
+/*
+
+ Auto-beaming code derived from Rosegarden 2.1's ItemListAutoBeam
+ and ItemListAutoBeamSub in editor/src/ItemList.c.
+
+*/
+
+void
+SegmentNotationHelper::autoBeam(timeT from, timeT to, string type)
+{
+ /*
+ std::cerr << "autoBeam from " << from << " to " << to << " on segment start time " << segment().getStartTime() << ", end time " << segment().getEndTime() << ", end marker " << segment().getEndMarkerTime() << std::endl;
+ */
+
+ autoBeam(segment().findTime(from), segment().findTime(to), type);
+}
+
+void
+SegmentNotationHelper::autoBeam(iterator from, iterator to, string type)
+{
+ // This can only manage whole bars at a time, and it will split
+ // the from-to range out to encompass the whole bars in which they
+ // each occur
+
+ if (!segment().getComposition()) {
+ cerr << "WARNING: SegmentNotationHelper::autoBeam requires Segment be in a Composition" << endl;
+ return;
+ }
+
+ if (!segment().isBeforeEndMarker(from)) return;
+
+ Composition *comp = segment().getComposition();
+
+ int fromBar = comp->getBarNumber((*from)->getAbsoluteTime());
+ int toBar = comp->getBarNumber(segment().isBeforeEndMarker(to) ?
+ (*to)->getAbsoluteTime() :
+ segment().getEndMarkerTime());
+
+ for (int barNo = fromBar; barNo <= toBar; ++barNo) {
+
+ std::pair<timeT, timeT> barRange = comp->getBarRange(barNo);
+ iterator barStart = segment().findTime(barRange.first);
+ iterator barEnd = segment().findTime(barRange.second);
+
+ // Make sure we're examining the notes defined to be within
+ // the bar in notation terms rather than raw terms
+
+ while (barStart != segment().end() &&
+ (*barStart)->getNotationAbsoluteTime() < barRange.first) ++barStart;
+
+ iterator scooter = barStart;
+ if (barStart != segment().end()) {
+ while (scooter != segment().begin()) {
+ --scooter;
+ if ((*scooter)->getNotationAbsoluteTime() < barRange.first) break;
+ barStart = scooter;
+ }
+ }
+
+ while (barEnd != segment().end() &&
+ (*barEnd)->getNotationAbsoluteTime() < barRange.second) ++barEnd;
+
+ scooter = barEnd;
+ if (barEnd != segment().end()) {
+ while (scooter != segment().begin()) {
+ --scooter;
+ if ((*scooter)->getNotationAbsoluteTime() < barRange.second) break;
+ barEnd = scooter;
+ }
+ }
+
+ TimeSignature timeSig =
+ segment().getComposition()->getTimeSignatureAt(barRange.first);
+
+ autoBeamBar(barStart, barEnd, timeSig, type);
+ }
+}
+
+
+/*
+
+ Derived from (and no less mystifying than) Rosegarden 2.1's
+ ItemListAutoBeamSub in editor/src/ItemList.c.
+
+ "Today I want to celebrate "Montreal" by Autechre, because of
+ its sleep-disturbing aura, because it sounds like the sort of music
+ which would be going around in the gunman's head as he trains a laser
+ sight into your bedroom through the narrow gap in your curtains and
+ dances the little red dot around nervously on your wall."
+
+*/
+
+void
+SegmentNotationHelper::autoBeamBar(iterator from, iterator to,
+ TimeSignature tsig, string type)
+{
+ int num = tsig.getNumerator();
+ int denom = tsig.getDenominator();
+
+ timeT average;
+ timeT minimum = 0;
+
+ // If the denominator is 2 or 4, beam in twos (3/4, 6/2 etc).
+
+ if (denom == 2 || denom == 4) {
+
+ if (num % 3) {
+ average = Note(Note::Quaver).getDuration();
+ } else {
+ average = Note(Note::Semiquaver).getDuration();
+ minimum = average;
+ }
+
+ } else {
+
+ if (num == 6 && denom == 8) { // special hack for 6/8
+ average = 3 * Note(Note::Quaver).getDuration();
+
+ } else {
+ // find a divisor (at least 2) for the numerator
+ int n = 2;
+ while (num >= n && num % n != 0) ++n;
+ average = n * Note(Note::Semiquaver).getDuration();
+ }
+ }
+
+ if (minimum == 0) minimum = average / 2;
+ if (denom > 4) average /= 2;
+
+ autoBeamBar(from, to, average, minimum, average * 4, type);
+}
+
+
+void
+SegmentNotationHelper::autoBeamBar(iterator from, iterator to,
+ timeT average, timeT minimum,
+ timeT maximum, string type)
+{
+ timeT accumulator = 0;
+ timeT crotchet = Note(Note::Crotchet).getDuration();
+ timeT semiquaver = Note(Note::Semiquaver).getDuration();
+
+ iterator e = end();
+
+ for (iterator i = from; i != to && i != e; ++i) {
+
+ // only look at one note in each chord, and at rests
+ if (!hasEffectiveDuration(i)) continue;
+ timeT idur = (*i)->getNotationDuration();
+
+ if (accumulator % average == 0 && // "beamable duration" threshold
+ idur < crotchet) {
+
+ // This could be the start of a beamed group. We maintain
+ // two sorts of state as we scan along here: data about
+ // the best group we've found so far (beamDuration,
+ // prospective, k etc), and data about the items we're
+ // looking at (count, beamable, longerThanDemi etc) just
+ // in case we find a better candidate group before the
+ // eight-line conditional further down makes us give up
+ // the search, beam our best shot, and start again.
+
+ // I hope this is clear.
+
+ iterator k = end(); // best-so-far last item in group;
+ // end() indicates that we've found nothing
+
+ timeT tmin = minimum;
+ timeT count = 0;
+ timeT prospective = 0;
+ timeT beamDuration = 0;
+
+ int beamable = 0;
+ int longerThanDemi = 0;
+
+ for (iterator j = i; j != to; ++j) {
+
+ if (!hasEffectiveDuration(j)) continue;
+ timeT jdur = (*j)->getNotationDuration();
+
+ if ((*j)->isa(Note::EventType)) {
+ if (jdur < crotchet) ++beamable;
+ if (jdur >= semiquaver) ++longerThanDemi;
+ }
+
+ count += jdur;
+
+ if (count % tmin == 0) {
+
+ k = j;
+ beamDuration = count;
+ prospective = accumulator + count;
+
+ // found a group; now accept only double this
+ // group's length for a better one
+ tmin *= 2;
+ }
+
+ // Stop scanning and make the group if our scan has
+ // reached the maximum length of beamed group, we have
+ // more than 4 semis or quavers, we're at the end of
+ // our run, the next chord is longer than the current
+ // one, or there's a rest ahead. (We used to check
+ // that the rest had non-zero duration, but the new
+ // quantization regime should ensure that this doesn't
+ // happen unless we really are displaying completely
+ // unquantized data in which case anything goes.)
+
+ iterator jnext(j);
+
+ if ((count > maximum)
+ || (longerThanDemi > 4)
+ || (++jnext == to)
+ || ((*j )->isa(Note::EventType) &&
+ (*jnext)->isa(Note::EventType) &&
+ (*jnext)->getNotationDuration() > jdur)
+ || ((*jnext)->isa(Note::EventRestType))) {
+
+ if (k != end() && beamable >= 2) {
+
+ iterator knext(k);
+ ++knext;
+
+ makeBeamedGroup(i, knext, type);
+ }
+
+ // If this group is at least as long as the check
+ // threshold ("average"), its length must be a
+ // multiple of the threshold and hence we can
+ // continue scanning from the end of the group
+ // without losing the modulo properties of the
+ // accumulator.
+
+ if (k != end() && beamDuration >= average) {
+
+ i = k;
+ accumulator = prospective;
+
+ } else {
+
+ // Otherwise, we continue from where we were.
+ // (This must be safe because we can't get
+ // another group starting half-way through, as
+ // we know the last group is shorter than the
+ // check threshold.)
+
+ accumulator += idur;
+ }
+
+ break;
+ }
+ }
+ } else {
+
+ accumulator += idur;
+ }
+ }
+}
+
+
+// based on Rosegarden 2.1's GuessItemListClef in editor/src/MidiIn.c
+
+Clef
+SegmentNotationHelper::guessClef(iterator from, iterator to)
+{
+ long totalHeight = 0;
+ int noteCount = 0;
+
+ // just the defaults:
+ Clef clef;
+ Key key;
+
+ for (iterator i = from; i != to; ++i) {
+ if ((*i)->isa(Note::EventType)) {
+//!!! NotationDisplayPitch p((*i)->get<Int>(PITCH), clef, key);
+ try {
+ Pitch p(**i);
+ totalHeight += p.getHeightOnStaff(clef, key);
+ ++noteCount;
+ } catch (Exception e) {
+ // no pitch in note
+ }
+ }
+ }
+
+ if (noteCount == 0) return Clef(Clef::Treble);
+
+ int average = totalHeight / noteCount;
+
+ if (average < -6) return Clef(Clef::Bass);
+ else if (average < -3) return Clef(Clef::Tenor);
+ else if (average < 1) return Clef(Clef::Alto);
+ else return Clef(Clef::Treble);
+}
+
+
+bool
+SegmentNotationHelper::removeRests(timeT time, timeT &duration, bool testOnly)
+{
+ Event dummy("dummy", time, 0, MIN_SUBORDERING);
+
+ std::cerr << "SegmentNotationHelper::removeRests(" << time
+ << ", " << duration << ")" << std::endl;
+
+ iterator from = segment().lower_bound(&dummy);
+
+ // ignore any number of zero-duration events at the start
+ while (from != segment().end() &&
+ (*from)->getAbsoluteTime() == time &&
+ (*from)->getDuration() == 0) ++from;
+ if (from == segment().end()) return false;
+
+ iterator to = from;
+
+ timeT eventTime = time;
+ timeT finalTime = time + duration;
+
+ //!!! We should probably not use an accumulator, but instead
+ // calculate based on each event's absolute time + duration --
+ // in case we've somehow ended up with overlapping rests
+
+ // Iterate on events, checking if all are rests
+ //
+ while ((eventTime < finalTime) && (to != end())) {
+
+ if (!(*to)->isa(Note::EventRestType)) {
+ // a non-rest was found
+ duration = (*to)->getAbsoluteTime() - time;
+ return false;
+ }
+
+ timeT nextEventDuration = (*to)->getDuration();
+
+ if ((eventTime + nextEventDuration) <= finalTime) {
+ eventTime += nextEventDuration;
+ duration = eventTime - time;
+ } else break;
+
+ ++to;
+ }
+
+ bool checkLastRest = false;
+ iterator lastEvent = to;
+
+ if (eventTime < finalTime) {
+ // shorten last event's duration, if possible
+
+
+ if (lastEvent == end()) {
+ duration = segment().getEndTime() - time;
+ return false;
+ }
+
+ if (!testOnly) {
+ // can't safely change the absolute time of an event in a segment
+ Event *newEvent = new Event(**lastEvent, finalTime,
+ (*lastEvent)->getDuration() -
+ (finalTime - eventTime));
+ duration = finalTime + (*lastEvent)->getDuration() - time;
+ bool same = (from == to);
+ segment().erase(lastEvent);
+ to = lastEvent = segment().insert(newEvent);
+ if (same) from = to;
+ checkLastRest = true;
+ }
+ }
+
+ if (testOnly) return true;
+
+ segment().erase(from, to);
+
+ // we must defer calling makeRestViable() until after erase,
+ // because it will invalidate 'to'
+ //
+ if (checkLastRest) makeRestViable(lastEvent);
+
+ return true;
+}
+
+
+void
+SegmentNotationHelper::collapseRestsAggressively(timeT startTime,
+ timeT endTime)
+{
+ reorganizeRests(startTime, endTime,
+ &SegmentNotationHelper::mergeContiguousRests);
+}
+
+
+void
+SegmentNotationHelper::reorganizeRests(timeT startTime, timeT endTime,
+ Reorganizer reorganizer)
+{
+ iterator ia = segment().findTime(startTime);
+ iterator ib = segment().findTime(endTime);
+
+ if (ia == end()) return;
+
+ std::vector<iterator> erasable;
+ std::vector<Event *> insertable;
+
+// cerr << "SegmentNotationHelper::reorganizeRests (" << startTime << ","
+// << endTime << ")" << endl;
+
+ for (iterator i = ia; i != ib; ++i) {
+
+ if ((*i)->isa(Note::EventRestType)) {
+
+ timeT startTime = (*i)->getAbsoluteTime();
+ timeT duration = 0;
+ iterator j = i;
+
+ for ( ; j != ib; ++j) {
+
+ if ((*j)->isa(Note::EventRestType)) {
+ duration += (*j)->getDuration();
+ erasable.push_back(j);
+ } else break;
+ }
+
+ (this->*reorganizer)(startTime, duration, insertable);
+ if (j == ib) break;
+ i = j;
+ }
+ }
+
+ for (unsigned int ei = 0; ei < erasable.size(); ++ei)
+ segment().erase(erasable[ei]);
+
+ for (unsigned int ii = 0; ii < insertable.size(); ++ii)
+ segment().insert(insertable[ii]);
+}
+
+
+void
+SegmentNotationHelper::normalizeContiguousRests(timeT startTime,
+ timeT duration,
+ std::vector<Event *> &toInsert)
+{
+ TimeSignature ts;
+ timeT sigTime =
+ segment().getComposition()->getTimeSignatureAt(startTime, ts);
+
+// cerr << "SegmentNotationHelper::normalizeContiguousRests:"
+// << " startTime = " << startTime << ", duration = "
+// << duration << endl;
+
+ DurationList dl;
+ ts.getDurationListForInterval(dl, duration, startTime - sigTime);
+
+ timeT acc = startTime;
+
+ for (DurationList::iterator i = dl.begin(); i != dl.end(); ++i) {
+ Event *e = new Event(Note::EventRestType, acc, *i,
+ Note::EventRestSubOrdering);
+ toInsert.push_back(e);
+ acc += *i;
+ }
+}
+
+
+void
+SegmentNotationHelper::mergeContiguousRests(timeT startTime,
+ timeT duration,
+ std::vector<Event *> &toInsert)
+{
+ while (duration > 0) {
+
+ timeT d = Note::getNearestNote(duration).getDuration();
+
+ Event *e = new Event(Note::EventRestType, startTime, d,
+ Note::EventRestSubOrdering);
+ toInsert.push_back(e);
+
+ startTime += d;
+ duration -= d;
+ }
+}
+
+
+Segment::iterator
+SegmentNotationHelper::collapseNoteAggressively(Event *note,
+ timeT rangeEnd)
+{
+ iterator i = segment().findSingle(note);
+ if (i == end()) return end();
+
+ iterator j = getNextAdjacentNote(i, true, true);
+ if (j == end() || (*j)->getAbsoluteTime() >= rangeEnd) return end();
+
+ timeT iEnd = (*i)->getAbsoluteTime() + (*i)->getDuration();
+ timeT jEnd = (*j)->getAbsoluteTime() + (*j)->getDuration();
+
+ Event *newEvent = new Event
+ (**i, (*i)->getAbsoluteTime(),
+ (std::max(iEnd, jEnd) - (*i)->getAbsoluteTime()));
+
+ newEvent->unset(TIED_BACKWARD);
+ newEvent->unset(TIED_FORWARD);
+
+ segment().erase(i);
+ segment().erase(j);
+ return segment().insert(newEvent);
+}
+
+std::pair<Event *, Event *>
+SegmentNotationHelper::splitPreservingPerformanceTimes(Event *e, timeT q1)
+{
+ timeT ut = e->getAbsoluteTime();
+ timeT ud = e->getDuration();
+ timeT qt = e->getNotationAbsoluteTime();
+ timeT qd = e->getNotationDuration();
+
+ timeT u1 = (qt + q1) - ut;
+ timeT u2 = (ut + ud) - (qt + q1);
+
+// std::cerr << "splitPreservingPerformanceTimes: (ut,ud) (" << ut << "," << ud << "), (qt,qd) (" << qt << "," << qd << ") q1 " << q1 << ", u1 " << u1 << ", u2 " << u2 << std::endl;
+
+ if (u1 <= 0 || u2 <= 0) { // can't do a meaningful split
+ return std::pair<Event *, Event *>(0, 0);
+ }
+
+ Event *e1 = new Event(*e, ut, u1, e->getSubOrdering(), qt, q1);
+ Event *e2 = new Event(*e, ut + u1, u2, e->getSubOrdering(), qt + q1, qd - q1);
+
+ e1->set<Bool>(TIED_FORWARD, true);
+ e2->set<Bool>(TIED_BACKWARD, true);
+
+ return std::pair<Event *, Event *>(e1, e2);
+}
+
+void
+SegmentNotationHelper::deCounterpoint(timeT startTime, timeT endTime)
+{
+ // How this should work: scan through the range and, for each
+ // note "n" found, if the next following note "m" not at the same
+ // absolute time as n starts before n ends, then split n at m-n.
+
+ // also, if m starts at the same time as n but has a different
+ // duration, we should split the longer of n and m at the shorter
+ // one's duration.
+
+ for (Segment::iterator i = segment().findTime(startTime);
+ segment().isBeforeEndMarker(i); ) {
+
+ timeT t = (*i)->getAbsoluteTime();
+ if (t >= endTime) break;
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr << "SegmentNotationHelper::deCounterpoint: event at " << (*i)->getAbsoluteTime() << " notation " << (*i)->getNotationAbsoluteTime() << ", duration " << (*i)->getNotationDuration() << ", type " << (*i)->getType() << std::endl;
+#endif
+
+ if (!(*i)->isa(Note::EventType)) { ++i; continue; }
+
+ timeT ti = (*i)->getNotationAbsoluteTime();
+ timeT di = (*i)->getNotationDuration();
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"looking for k"<<std::endl;
+#endif
+
+ // find next event that's either at a different time or (if a
+ // note) has a different duration
+ Segment::iterator k = i;
+ while (segment().isBeforeEndMarker(k)) {
+ if ((*k)->isa(Note::EventType)) {
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"abstime "<<(*k)->getAbsoluteTime()<< std::endl;
+#endif
+ if ((*k)->getNotationAbsoluteTime() > ti ||
+ (*k)->getNotationDuration() != di) break;
+ }
+ ++k;
+ }
+
+ if (!segment().isBeforeEndMarker(k)) break; // no split, no more notes
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr << "k is at " << (k == segment().end() ? -1 : (*k)->getAbsoluteTime()) << ", notation " << (*k)->getNotationAbsoluteTime() << ", duration " << (*k)->getNotationDuration() << std::endl;
+#endif
+
+ timeT tk = (*k)->getNotationAbsoluteTime();
+ timeT dk = (*k)->getNotationDuration();
+
+ Event *e1 = 0, *e2 = 0;
+ std::pair<Event *, Event *> splits;
+ Segment::iterator toGo = segment().end();
+
+ if (tk == ti && dk != di) {
+ // do the same-time-different-durations case
+ if (di > dk) { // split *i
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr << "splitting i into " << dk << " and "<< (di-dk) << std::endl;
+#endif
+ splits = splitPreservingPerformanceTimes(*i, dk);
+
+ toGo = i;
+ } else { // split *k
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr << "splitting k into " << di << " and "<< (dk-di) << std::endl;
+#endif
+ splits = splitPreservingPerformanceTimes(*k, di);
+
+ toGo = k;
+ }
+ } else if (tk - ti > 0 && tk - ti < di) { // split *i
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr << "splitting i[*] into " << (tk-ti) << " and "<< (di-(tk-ti)) << std::endl;
+#endif
+ splits = splitPreservingPerformanceTimes(*i, tk - ti);
+
+ toGo = i;
+ }
+
+ e1 = splits.first;
+ e2 = splits.second;
+
+ if (e1 && e2) { // e2 is the new note
+
+ e1->set<Bool>(TIED_FORWARD, true);
+ e2->set<Bool>(TIED_BACKWARD, true);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"Erasing:"<<std::endl;
+ (*toGo)->dump(std::cerr);
+#endif
+
+ segment().erase(toGo);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"Inserting:"<<std::endl;
+ e1->dump(std::cerr);
+#endif
+
+ segment().insert(e1);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"Inserting:"<<std::endl;
+ e2->dump(std::cerr);
+#endif
+
+ segment().insert(e2);
+
+ i = segment().findTime(t);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"resync at " << t << ":" << std::endl;
+ if (i != segment().end()) (*i)->dump(std::cerr);
+ else std::cerr << "(end)" << std::endl;
+#endif
+
+ } else {
+
+ // no split here
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"no split"<<std::endl;
+#endif
+ ++i;
+ }
+ }
+
+ segment().normalizeRests(startTime, endTime);
+}
+
+
+void
+SegmentNotationHelper::autoSlur(timeT startTime, timeT endTime, bool legatoOnly)
+{
+ iterator from = segment().findTime(startTime);
+ iterator to = segment().findTime(endTime);
+
+ timeT potentialStart = segment().getEndTime();
+ long groupId = -1;
+ timeT prevTime = startTime;
+ int count = 0;
+ bool thisLegato = false, prevLegato = false;
+
+ for (iterator i = from; i != to && segment().isBeforeEndMarker(i); ++i) {
+
+ timeT t = (*i)->getNotationAbsoluteTime();
+
+ long newGroupId = -1;
+ if ((*i)->get<Int>(BEAMED_GROUP_ID, newGroupId)) {
+ if (groupId == newGroupId) { // group continuing
+ if (t > prevTime) {
+ ++count;
+ prevLegato = thisLegato;
+ thisLegato = Marks::hasMark(**i, Marks::Tenuto);
+ }
+ prevTime = t;
+ continue;
+ }
+ } else {
+ if (groupId == -1) continue; // no group
+ }
+
+ // a group has ended (and a new one might have begun)
+
+ if (groupId >= 0 && count > 1 && (!legatoOnly || prevLegato)) {
+ Indication ind(Indication::Slur, t - potentialStart);
+ segment().insert(ind.getAsEvent(potentialStart));
+ if (legatoOnly) {
+ for (iterator j = segment().findTime(potentialStart); j != i; ++j) {
+ Marks::removeMark(**j, Marks::Tenuto);
+ }
+ }
+ }
+
+ potentialStart = t;
+ groupId = newGroupId;
+ prevTime = t;
+ count = 0;
+ thisLegato = false;
+ prevLegato = false;
+ }
+
+ if (groupId >= 0 && count > 1 && (!legatoOnly || prevLegato)) {
+ Indication ind(Indication::Slur, endTime - potentialStart);
+ segment().insert(ind.getAsEvent(potentialStart));
+ if (legatoOnly) {
+ for (iterator j = segment().findTime(potentialStart);
+ segment().isBeforeEndMarker(j) && j != to; ++j) {
+ Marks::removeMark(**j, Marks::Tenuto);
+ }
+ }
+ }
+}
+
+
+} // end of namespace
+
diff --git a/src/base/SegmentNotationHelper.h b/src/base/SegmentNotationHelper.h
new file mode 100644
index 0000000..5094929
--- /dev/null
+++ b/src/base/SegmentNotationHelper.h
@@ -0,0 +1,591 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SEGMENT_NOTATION_HELPER_H_
+#define _SEGMENT_NOTATION_HELPER_H_
+
+#include "Segment.h"
+
+namespace Rosegarden
+{
+
+class SegmentNotationHelper : protected SegmentHelper
+{
+public:
+ SegmentNotationHelper(Segment &t) : SegmentHelper(t) { }
+ virtual ~SegmentNotationHelper();
+
+ SegmentHelper::segment;
+
+ /**
+ * Set the NOTE_TYPE and NOTE_DOTS properties on the events
+ * in the segment. If startTime and endTime are equal, operates
+ * on the whole segment.
+ */
+ void setNotationProperties(timeT startTime = 0, timeT endTime = 0);
+
+ /**
+ * Return the notation absolute time plus the notation duration.
+ */
+ timeT getNotationEndTime(Event *e);
+
+ /**
+ * Return an iterator pointing at the first event in the segment
+ * to have an absolute time of t or later. (Most of the time, the
+ * non-notation absolute times should be used as reference
+ * timings; this and the next function are provided for
+ * completeness, but in most cases if you're about to call them
+ * you should ask yourself why.)
+ */
+ iterator findNotationAbsoluteTime(timeT t);
+
+ /**
+ * Return an iterator pointing at the last event in the segment
+ * to have an absolute time of t or earlier. (Most of the time,
+ * the non-notation absolute times should be used as reference
+ * timings; this and the previous function are provided for
+ * completeness, but in most cases if you're about to call them
+ * you should ask yourself why.)
+ */
+ iterator findNearestNotationAbsoluteTime(timeT t);
+
+
+ /**
+ * Looks for another note immediately following the one pointed to
+ * by the given iterator, and (if matchPitch is true) of the same
+ * pitch, and returns an iterator pointing to that note. Returns
+ * end() if there is no such note.
+ *
+ * The notes are considered "adjacent" if the quantized start
+ * time of one matches the quantized end time of the other, unless
+ * allowOverlap is true in which case overlapping notes are also
+ * considered adjacent so long as one does not completely enclose
+ * the other.
+ */
+ iterator getNextAdjacentNote(iterator i,
+ bool matchPitch = true,
+ bool allowOverlap = true);
+
+
+ /**
+ * Looks for another note immediately preceding the one pointed to
+ * by the given iterator, and (if matchPitch is true) of the same
+ * pitch, and returns an iterator pointing to that note. Returns
+ * end() if there is no such note.
+ *
+ * rangeStart gives a bound to the distance that will be scanned
+ * to find events -- no event with starting time earlier than that
+ * will be considered. (This method has no other way to know when
+ * to stop scanning; potentially the very first note in the segment
+ * could turn out to be adjacent to the very last one.)
+ *
+ * The notes are considered "adjacent" if the quantized start
+ * time of one matches the quantized end time of the other, unless
+ * allowOverlap is true in which case overlapping notes are also
+ * considered adjacent so long as one does not completely enclose
+ * the other.
+ */
+ iterator getPreviousAdjacentNote(iterator i,
+ timeT rangeStart = 0,
+ bool matchPitch = true,
+ bool allowOverlap = true);
+
+
+ /**
+ * Returns an iterator pointing to the next contiguous element of
+ * the same type (note or rest) as the one passed as argument, if
+ * any. Returns end() otherwise.
+ *
+ * (for instance if the argument points to a note and the next
+ * element is a rest, end() will be returned)
+ *
+ * Note that if the iterator points to a note, the "contiguous"
+ * iterator returned may point to a note that follows the first
+ * one, overlaps with it, shares a starting time (i.e. they're
+ * both in the same chord) or anything else. "Contiguous" refers
+ * only to their locations in the segment's event container,
+ * which normally means what you expect for rests but not notes.
+ *
+ * See also SegmentNotationHelper::getNextAdjacentNote.
+ */
+ iterator findContiguousNext(iterator);
+
+ /**
+ * Returns an iterator pointing to the previous contiguous element
+ * of the same type (note or rest) as the one passed as argument,
+ * if any. Returns end() otherwise.
+ *
+ * (for instance if the argument points to a note and the previous
+ * element is a rest, end() will be returned)
+ *
+ * Note that if the iterator points to a note, the "contiguous"
+ * iterator returned may point to a note that precedes the first
+ * one, overlaps with it, shares a starting time (i.e. they're
+ * both in the same chord) or anything else. "Contiguous" refers
+ * only to their locations in the segment's event container,
+ * which normally means what you expect for rests but not notes.
+ *
+ * See also SegmentNotationHelper::getPreviousAdjacentNote.
+ */
+ iterator findContiguousPrevious(iterator);
+
+ /**
+ * Returns true if the iterator points at a note in a chord
+ * e.g. if there are more notes at the same absolute time
+ */
+ bool noteIsInChord(Event *note);
+
+ /**
+ * Returns an iterator pointing to the note that this one is tied
+ * with, in the forward direction if goForwards or back otherwise.
+ * Returns end() if none.
+ *
+ * Untested and probably marked-for-expiry -- prefer
+ * SegmentPerformanceHelper::getTiedNotes
+ */
+ iterator getNoteTiedWith(Event *note, bool goForwards);
+
+
+ /**
+ * Checks whether it's reasonable to split a single event
+ * of duration a+b into two events of durations a and b, for some
+ * working definition of "reasonable".
+ *
+ * You should pass note-quantized durations into this method
+ */
+ bool isSplitValid(timeT a, timeT b);
+
+
+ /**
+ * Splits events in the [from, to[ interval into
+ * tied events of duration baseDuration + events of duration R,
+ * with R being equal to the events' initial duration minus baseDuration
+ *
+ * The events in [from, to[ must all be at the same absolute time
+ *
+ * Does not check "reasonableness" of expansion first
+ *
+ * Events may be notes or rests (rests will obviously not be tied)
+ *
+ * @return iterator pointing at the last inserted event. Also
+ * modifies from to point at the first split event (the original
+ * iterator would have been invalidated).
+ */
+ iterator splitIntoTie(iterator &from, iterator to, timeT baseDuration);
+
+
+ /**
+ * Splits (splits) events in the same timeslice as that pointed
+ * to by i into tied events of duration baseDuration + events of
+ * duration R, with R being equal to the events' initial duration
+ * minus baseDuration
+ *
+ * Does not check "reasonableness" of expansion first
+ *
+ * Events may be notes or rests (rests will obviously not be tied)
+ *
+ * @return iterator pointing at the last inserted event. Also
+ * modifies i to point at the first split event (the original
+ * iterator would have been invalidated).
+ */
+ iterator splitIntoTie(iterator &i, timeT baseDuration);
+
+
+ /**
+ * Returns true if Events of durations a and b can reasonably be
+ * collapsed into a single one of duration a+b, for some
+ * definition of "reasonably". For use by collapseRestsIfValid
+ *
+ * You should pass note-quantized durations into this method
+ */
+ bool isCollapseValid(timeT a, timeT b);
+
+ /**
+ * If possible, collapses the rest event with the following or
+ * previous one.
+ *
+ * @return true if collapse was done, false if it wasn't reasonable
+ *
+ * collapseForward is set to true if the collapse was with the
+ * following element, false if it was with the previous one
+ */
+ bool collapseRestsIfValid(Event*, bool& collapseForward);
+
+ /**
+ * Inserts a note, doing all the clever split/merge stuff as
+ * appropriate. Requires segment to be in a composition. Returns
+ * iterator pointing to last event inserted (there may be more
+ * than one, as note may have had to be split)
+ *
+ * This method will only work correctly if there is a note or
+ * rest event already starting at absoluteTime.
+ */
+ iterator insertNote(timeT absoluteTime, Note note, int pitch,
+ Accidental explicitAccidental);
+
+ /**
+ * Inserts a note, doing all the clever split/merge stuff as
+ * appropriate. Requires segment to be in a composition. Returns
+ * iterator pointing to last event inserted (there may be more
+ * than one, as note may have had to be split)
+ *
+ * This method will only work correctly if there is a note or
+ * rest event already starting at the model event's absoluteTime.
+ *
+ * Passing a model event has the advantage over the previous
+ * method of allowing additional properties to be supplied. The
+ * model event will be copied but not itself used; the caller
+ * continues to own it and should release it after return.
+ */
+ iterator insertNote(Event *modelEvent);
+
+ /**
+ * Inserts a rest, doing all the clever split/merge stuff as
+ * appropriate. Requires segment to be in a composition.
+ * Returns iterator pointing to last event inserted (there
+ * may be more than one, as rest may have had to be split)
+ *
+ * This method will only work correctly if there is a note or
+ * rest event already starting at absoluteTime.
+ */
+ iterator insertRest(timeT absoluteTime, Note note);
+
+ /**
+ * Insert a clef.
+ * Returns iterator pointing to clef.
+ */
+ iterator insertClef(timeT absoluteTime, Clef clef);
+
+ /**
+ * Insert a key.
+ * Returns iterator pointing to key.
+ */
+ iterator insertKey(timeT absoluteTime, Key key);
+
+ /**
+ * Insert a text event.
+ * Returns iterator pointing to text event.
+ */
+ iterator insertText(timeT absoluteTime, Text text);
+
+ /**
+ * Deletes a note, doing all the clever split/merge stuff as
+ * appropriate. Requires segment to be in a composition.
+ */
+ void deleteNote(Event *e, bool collapseRest = false);
+
+ /**
+ * Deletes a rest, doing all the clever split/merge stuff as
+ * appropriate. Requires segment to be in a composition.
+ *
+ * @return whether the rest could be deleted -- a rest can only
+ * be deleted if there's a suitable rest next to it to merge it
+ * with.
+ */
+ bool deleteRest(Event *e);
+
+ /**
+ * Deletes an event. If the event is a note or a rest, calls
+ * deleteNote or deleteRest.
+ *
+ * @return whether the event was deleted (always true, unless the
+ * event is a rest).
+ *
+ * @see deleteRest, deleteNote
+ */
+ bool deleteEvent(Event *e, bool collapseRest = false);
+
+ /**
+ * Check whether a note or rest event has a duration that can be
+ * represented by a single note-type. (If not, the code that's
+ * doing the check might wish to split the event.)
+ *
+ * If dots is specified, a true value will only be returned if the
+ * best-fit note has no more than that number of dots. e.g. if
+ * dots = 0, only notes that are viable without the use of dots
+ * will be acceptable. The default is whatever the segment's
+ * quantizer considers acceptable (probably either 1 or 2 dots).
+ */
+ bool isViable(Event *e, int dots = -1) {
+ return isViable(e->getDuration(), dots);
+ }
+
+ /**
+ * Check whether a duration can be represented by a single
+ * note-type. (If not, the code that's doing the check might wish
+ * to split the duration.)
+ *
+ * If dots is specified, a true value will only be returned if the
+ * best-fit note has no more than that number of dots. e.g. if
+ * dots = 0, only notes that are viable without the use of dots
+ * will be acceptable. The default is whatever the segment's
+ * quantizer considers acceptable (probably either 1 or 2 dots).
+ */
+ bool isViable(timeT duration, int dots = -1);
+
+
+ /**
+ * Given an iterator pointing to a rest, split that rest up
+ * according to the durations returned by TimeSignature's
+ * getDurationListForInterval
+ */
+ void makeRestViable(iterator i);
+
+
+ /**
+ * Split notes and rests up into tied notes or shorter rests of
+ * viable lengths (longest possible viable duration first, then
+ * longest possible viable component of remainder &c). Also
+ * optionally splits notes and rests at barlines -- this is
+ * actually the most common user-visible use of this function.
+ */
+ void makeNotesViable(iterator i, iterator j, bool splitAtBars = true);
+
+
+ /**
+ * As above but given a range in time rather than iterators.
+ */
+ void makeNotesViable(timeT startTime, timeT endTime,
+ bool splitAtBars = true);
+
+
+ /**
+ * Give all events between the start of the timeslice containing
+ * from and the start of the timeslice containing to the same new
+ * group id and the given type.
+ *
+ * Do not use this for making tuplet groups, unless the events
+ * in the group already have the other tuplet properties or you
+ * intend to add those yourself. Use makeTupletGroup instead.
+ */
+ void makeBeamedGroup(timeT from, timeT to, std::string type);
+
+ /**
+ * Give all events between the start of the timeslice containing
+ * from and the start of the timeslice containing to the same new
+ * group id and the given type.
+ *
+ * Do not use this for making tuplet groups, unless the events
+ * in the group already have the other tuplet properties or you
+ * intend to add those yourself. Use makeTupletGroup instead.
+ */
+ void makeBeamedGroup(iterator from, iterator to, std::string type);
+
+ /**
+ * Give all events between from and to the same new group id and
+ * the given type.
+ *
+ * Use makeBeamedGroup for normal notes. This function is usually
+ * used for groups of grace notes, which are equal in time and
+ * distinguished by subordering.
+ *
+ * Do not use this for making tuplet groups, unless the events
+ * in the group already have the other tuplet properties or you
+ * intend to add those yourself.
+ */
+ void makeBeamedGroupExact(iterator from, iterator to, std::string type);
+
+
+ /**
+ * Make a beamed group of tuplet type, whose tuplet properties are
+ * specified as "(untupled-count) notes of duration (unit) played
+ * in the time of (tupled-count)". For example, a quaver triplet
+ * group could be specified with untupled = 3, tupled = 2, unit =
+ * (the duration of a quaver).
+ *
+ * The group will start at the beginning of the timeslice containing
+ * the time t, and will be constructed by compressing the appropriate
+ * number of following notes into the tuplet time, and filling the
+ * space that this compression left behind (after the group) with
+ * rests. The results may be unexpected if overlapping events are
+ * present.
+ */
+ void makeTupletGroup(timeT t, int untupled, int tupled, timeT unit);
+
+
+ /**
+ * Divide the notes between the start of the bar containing
+ * from and the end of the bar containing to up into sensible
+ * beamed groups and give each group the right group properties
+ * using makeBeamedGroup. Requires segment to be in a composition.
+ */
+ void autoBeam(timeT from, timeT to, std::string type);
+
+ /**
+ * Divide the notes between the start of the bar containing
+ * from and the end of the bar containing to up into sensible
+ * beamed groups and give each group the right group properties
+ * using makeBeamedGroup. Requires segment to be in a composition.
+ */
+ void autoBeam(iterator from, iterator to, std::string type);
+
+
+ /**
+ * Clear the group id and group type from all events between the
+ * start of the timeslice containing from and the start of the
+ * timeslice containing to
+ */
+ void unbeam(timeT from, timeT to);
+
+ /**
+ * Clear the group id and group type from all events between the
+ * start of the timeslice containing from and the start of the
+ * timeslice containing to
+ */
+ void unbeam(iterator from, iterator to);
+
+ /**
+ * Guess which clef a section of music is supposed to be in,
+ * ignoring any clef events actually found in the section.
+ */
+ Clef guessClef(iterator from, iterator to);
+
+
+ /**
+ * Removes all rests starting at \a time for \a duration,
+ * splitting the last rest if needed.
+ *
+ * Modifies duration to the actual duration of the series
+ * of rests that has been changed by this action (i.e. if
+ * the last rest was split, duration will be extended to
+ * include the second half of this rest). This is intended
+ * to be of use when calculating the extents of a command
+ * for undo/refresh purposes.
+ *
+ * If there's an event which is not a rest in this interval,
+ * returns false and sets duration to the maximum duration
+ * that would have succeeded.
+ *
+ * If testOnly is true, does not actually remove any rests;
+ * just checks whether the rests can be removed and sets
+ * duration and the return value appropriately.
+ *
+ * (Used for Event pasting.)
+ */
+ bool removeRests(timeT time, timeT &duration, bool testOnly = false);
+
+
+ /**
+ * For each series of contiguous rests found between the start and
+ * end time, replace the series of rests with another series of
+ * the same duration but composed of the longest possible valid
+ * rest plus the remainder
+ */
+ void collapseRestsAggressively(timeT startTime, timeT endTime);
+
+
+ /**
+ * Locate the given event and, if it's a note, collapse it with
+ * any following adjacent note of the same pitch, so long as its
+ * start time is before the the given limit. Does not care
+ * whether the resulting note is viable.
+ *
+ * Returns an iterator pointing to the event that replaced the
+ * original one if a collapse happened, segment.end() if no
+ * collapse or event not found
+ */
+ iterator collapseNoteAggressively(Event *, timeT rangeEnd);
+
+
+
+ std::pair<Event *, Event *> splitPreservingPerformanceTimes(Event *e,
+ timeT q1);
+
+ /**
+ * Look for examples of overlapping notes within the given range,
+ * and split each into chords with some tied notes.
+ */
+ void deCounterpoint(timeT startTime, timeT endTime);
+
+ /**
+ * A rather specialised function: Add a slur to every beamed group.
+ * If legatoOnly is true, add a slur only to those beamed groups
+ * in which every note except the last has a tenuto mark already
+ * (and remove that mark).
+ * This is basically intended as a post-notation-quantization-auto-
+ * beam step.
+ */
+ void autoSlur(timeT startTime, timeT endTime, bool legatoOnly);
+
+
+protected:
+ const Quantizer &basicQuantizer();
+ const Quantizer &notationQuantizer();
+
+ /**
+ * Collapse multiple consecutive rests into one, in preparation
+ * for insertion of a note (whose duration may exceed that of the
+ * first rest) at the given position. The resulting rest event
+ * may have a duration that is not expressible as a single note
+ * type, and may therefore require splitting again after the
+ * insertion.
+ *
+ * Returns position at which the collapse ended (i.e. the first
+ * uncollapsed event)
+ */
+ iterator collapseRestsForInsert(iterator firstRest, timeT desiredDuration);
+
+
+ /// for use by insertNote and insertRest
+ iterator insertSomething(iterator position, int duration,
+ Event *modelEvent, bool tiedBack);
+
+ /// for use by insertSomething
+ iterator insertSingleSomething(iterator position, int duration,
+ Event *modelEvent, bool tiedBack);
+
+ /// for use by insertSingleSomething
+ void setInsertedNoteGroup(Event *e, iterator i);
+
+ /// for use by makeBeamedGroup
+ void makeBeamedGroupAux(iterator from, iterator to, std::string type,
+ bool groupGraces);
+
+ /// for use by unbeam
+ void unbeamAux(iterator from, iterator to);
+
+ /// for use by autoBeam
+
+ void autoBeamBar(iterator from, iterator to, TimeSignature timesig,
+ std::string type);
+
+ void autoBeamBar(iterator from, iterator to, timeT average,
+ timeT minimum, timeT maximum, std::string type);
+
+ /// used by autoBeamAux (duplicate of private method in Segment)
+ bool hasEffectiveDuration(iterator i);
+
+ typedef void (SegmentNotationHelper::*Reorganizer)(timeT, timeT,
+ std::vector<Event *>&);
+
+ void reorganizeRests(timeT, timeT, Reorganizer);
+
+ /// for use by normalizeRests
+ void normalizeContiguousRests(timeT, timeT, std::vector<Event *>&);
+
+ /// for use by collapseRestsAggressively
+ void mergeContiguousRests(timeT, timeT, std::vector<Event *>&);
+};
+
+}
+
+#endif
diff --git a/src/base/SegmentPerformanceHelper.cpp b/src/base/SegmentPerformanceHelper.cpp
new file mode 100644
index 0000000..930a794
--- /dev/null
+++ b/src/base/SegmentPerformanceHelper.cpp
@@ -0,0 +1,472 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "SegmentPerformanceHelper.h"
+#include "BaseProperties.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+using std::endl;
+using std::string;
+
+using namespace BaseProperties;
+
+SegmentPerformanceHelper::~SegmentPerformanceHelper() { }
+
+
+SegmentPerformanceHelper::iteratorcontainer
+SegmentPerformanceHelper::getTiedNotes(iterator i)
+{
+ iteratorcontainer c;
+ c.push_back(i);
+
+ Event *e = *i;
+ if (!e->isa(Note::EventType)) return c;
+ Segment::iterator j(i);
+
+ bool tiedBack = false, tiedForward = false;
+ e->get<Bool>(TIED_BACKWARD, tiedBack);
+ e->get<Bool>(TIED_FORWARD, tiedForward);
+
+ timeT d = e->getNotationDuration();
+ timeT t = e->getNotationAbsoluteTime();
+
+ if (!e->has(PITCH)) return c;
+ int pitch = e->get<Int>(PITCH);
+
+ bool valid = false;
+
+ if (tiedBack) {
+ // #1171463: If we can find no preceding TIED_FORWARD event,
+ // then we remove this property
+
+ while (j != begin()) {
+
+ --j;
+ if (!(*j)->isa(Note::EventType)) continue;
+ e = *j; // can reuse e because this branch always returns
+
+ timeT t2 = e->getNotationAbsoluteTime() + e->getNotationDuration();
+ if (t2 < t) break;
+
+ if (t2 > t || !e->has(PITCH) ||
+ e->get<Int>(PITCH) != pitch) continue;
+
+ bool prevTiedForward = false;
+ if (!e->get<Bool>(TIED_FORWARD, prevTiedForward) ||
+ !prevTiedForward) break;
+
+ valid = true;
+ break;
+ }
+
+ if (valid) {
+ return iteratorcontainer();
+ } else {
+ (*i)->unset(TIED_BACKWARD);
+ return c;
+ }
+ }
+ else if (!tiedForward) return c;
+
+ for (;;) {
+ while (++j != end() && !(*j)->isa(Note::EventType));
+ if (j == end()) return c;
+
+ e = *j;
+
+ timeT t2 = e->getNotationAbsoluteTime();
+
+ if (t2 > t + d) break;
+ else if (t2 < t + d || !e->has(PITCH) ||
+ e->get<Int>(PITCH) != pitch) continue;
+
+ if (!e->get<Bool>(TIED_BACKWARD, tiedBack) ||
+ !tiedBack) break;
+
+ d += e->getNotationDuration();
+ c.push_back(j);
+ valid = true;
+
+ if (!e->get<Bool>(TIED_FORWARD, tiedForward) ||
+ !tiedForward) return c;
+ }
+
+ if (!valid) {
+ // Related to #1171463: If we can find no following
+ // TIED_BACKWARD event, then we remove this property
+ (*i)->unset(TIED_FORWARD);
+ }
+
+ return c;
+}
+
+
+bool
+SegmentPerformanceHelper::getGraceAndHostNotes(iterator i,
+ iteratorcontainer &graceNotes,
+ iteratorcontainer &hostNotes,
+ bool &isHostNote)
+{
+ if (i == end() || !(*i)->isa(Note::EventType)) return false;
+
+ Segment::iterator j = i;
+ Segment::iterator firstGraceNote = i;
+ Segment::iterator firstHostNote = i;
+
+ if ((*i)->has(IS_GRACE_NOTE) && (*i)->get<Bool>(IS_GRACE_NOTE)) {
+
+ // i is a grace note. Find the first host note following it
+
+ j = i;
+ while (++j != end()) {
+ if ((*j)->getNotationAbsoluteTime() >
+ (*i)->getNotationAbsoluteTime()) break;
+ if ((*j)->getSubOrdering() < 0) continue;
+ if ((*j)->isa(Note::EventType)) {
+ firstHostNote = j;
+ break;
+ }
+ }
+
+ if (firstHostNote == i) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note" << std::endl;
+ return false;
+ }
+ } else {
+
+ // i is a host note, but we need to ensure we have the first
+ // one, not just any one
+
+ j = i;
+
+ while (j != begin()) {
+ --j;
+ if ((*j)->getNotationAbsoluteTime() <
+ (*i)->getNotationAbsoluteTime()) break;
+ if ((*j)->getSubOrdering() <
+ (*i)->getSubOrdering()) break;
+ if ((*j)->isa(Note::EventType)) {
+ firstHostNote = j;
+ break;
+ }
+ }
+ }
+
+ // firstHostNote now points to the first host note, which is
+ // either the first non-grace note after i (if i was a grace note)
+ // or the first note with the same time and subordering as i (if i
+ // was not a grace note).
+
+ if ((*firstHostNote)->getSubOrdering() < 0) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: WARNING: Note at " << (*firstHostNote)->getAbsoluteTime() << " has subordering " << (*i)->getSubOrdering() << " but is not a grace note" << std::endl;
+ return false;
+ }
+
+ j = firstHostNote;
+
+ while (j != begin()) {
+ --j;
+ if ((*j)->getNotationAbsoluteTime() <
+ (*firstHostNote)->getNotationAbsoluteTime()) break;
+ if ((*j)->getSubOrdering() >= 0) continue;
+ if (!(*j)->isa(Note::EventType)) continue;
+ if (!(*j)->has(IS_GRACE_NOTE) || !(*j)->get<Bool>(IS_GRACE_NOTE)) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: WARNING: Note at " << (*j)->getAbsoluteTime() << " (in trackback) has subordering " << (*j)->getSubOrdering() << " but is not a grace note" << std::endl;
+ break;
+ }
+ firstGraceNote = j;
+ }
+
+ if (firstGraceNote == firstHostNote) {
+ std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: REMARK: Note at " << (*firstHostNote)->getAbsoluteTime() << " has no grace notes" << std::endl;
+ return false;
+ }
+
+ j = firstGraceNote;
+
+ // push all of the grace notes, and notes with the same time as
+ // the first host note, onto the container
+
+ isHostNote = false;
+
+ while (j != end()) {
+ if ((*j)->isa(Note::EventType)) {
+ if ((*j)->getSubOrdering() < 0) {
+ if ((*j)->has(IS_GRACE_NOTE) && (*j)->get<Bool>(IS_GRACE_NOTE)) {
+ graceNotes.push_back(j);
+ }
+ } else {
+ hostNotes.push_back(j);
+ if (j == i) isHostNote = true;
+ }
+ }
+ if ((*j)->getNotationAbsoluteTime() >
+ (*firstHostNote)->getNotationAbsoluteTime()) break;
+ ++j;
+ }
+
+ return true;
+}
+
+
+timeT
+SegmentPerformanceHelper::getSoundingAbsoluteTime(iterator i)
+{
+ timeT t = 0;
+
+ timeT discard;
+
+// std::cerr << "SegmentPerformanceHelper::getSoundingAbsoluteTime at " << (*i)->getAbsoluteTime() << std::endl;
+
+ if ((*i)->has(IS_GRACE_NOTE)) {
+// std::cerr << "it's a grace note" << std::endl;
+ if (getGraceNoteTimeAndDuration(false, i, t, discard)) return t;
+ }
+ if ((*i)->has(MAY_HAVE_GRACE_NOTES)) {
+// std::cerr << "it's a candidate host note" << std::endl;
+ if (getGraceNoteTimeAndDuration(true, i, t, discard)) return t;
+ }
+
+ return (*i)->getAbsoluteTime();
+}
+
+timeT
+SegmentPerformanceHelper::getSoundingDuration(iterator i)
+{
+ timeT d = 0;
+
+ timeT discard;
+
+// std::cerr << "SegmentPerformanceHelper::getSoundingDuration at " << (*i)->getAbsoluteTime() << std::endl;
+
+ if ((*i)->has(IS_GRACE_NOTE)) {
+// std::cerr << "it's a grace note" << std::endl;
+ if (getGraceNoteTimeAndDuration(false, i, discard, d)) return d;
+ }
+ if ((*i)->has(MAY_HAVE_GRACE_NOTES)) {
+// std::cerr << "it's a candidate host note" << std::endl;
+ if (getGraceNoteTimeAndDuration(true, i, discard, d)) return d;
+ }
+
+ if ((*i)->has(TIED_BACKWARD)) {
+
+ // Formerly we just returned d in this case, but now we check
+ // with getTiedNotes so as to remove any bogus backward ties
+ // that have no corresponding forward tie. Unfortunately this
+ // is quite a bit slower.
+
+ //!!! optimize. at least we should add a marker property to
+ //anything we've already processed from this helper this time
+ //around.
+
+ iteratorcontainer c(getTiedNotes(i));
+
+ if (c.empty()) { // the tie back is valid
+ return 0;
+ }
+ }
+
+ if (!(*i)->has(TIED_FORWARD) || !(*i)->isa(Note::EventType)) {
+
+ d = (*i)->getDuration();
+
+ } else {
+
+ // tied forward but not back
+
+ iteratorcontainer c(getTiedNotes(i));
+
+ for (iteratorcontainer::iterator ci = c.begin();
+ ci != c.end(); ++ci) {
+ d += (**ci)->getDuration();
+ }
+ }
+
+ return d;
+}
+
+
+// In theory we can do better with tuplets, because real time has
+// finer precision than timeT time. With a timeT resolution of 960ppq
+// however the difference is probably not audible
+
+RealTime
+SegmentPerformanceHelper::getRealAbsoluteTime(iterator i)
+{
+ return segment().getComposition()->getElapsedRealTime
+ (getSoundingAbsoluteTime(i));
+}
+
+
+// In theory we can do better with tuplets, because real time has
+// finer precision than timeT time. With a timeT resolution of 960ppq
+// however the difference is probably not audible
+//
+// (If we did want to do this, it'd help to have abstime->realtime
+// conversion methods that accept double args in Composition)
+
+RealTime
+SegmentPerformanceHelper::getRealSoundingDuration(iterator i)
+{
+ timeT t0 = getSoundingAbsoluteTime(i);
+ timeT t1 = t0 + getSoundingDuration(i);
+
+ if (t1 > segment().getEndMarkerTime()) {
+ t1 = segment().getEndMarkerTime();
+ }
+
+ return segment().getComposition()->getRealTimeDifference(t0, t1);
+}
+
+
+bool
+SegmentPerformanceHelper::getGraceNoteTimeAndDuration(bool host, iterator i,
+ timeT &t, timeT &d)
+{
+ // [This code currently assumes appoggiatura. Acciaccatura later.]
+
+ // For our present purposes, we will assume that grace notes start
+ // at the same time as their host note was intended to, and
+ // "steal" a proportion of the duration of their host note. This
+ // causes the host note to start later, and be shorter, by that
+ // same proportion.
+
+ // If a host note has more than one (consecutive) grace note, they
+ // should take a single cut from the grace note and divide it
+ // amongst themselves.
+
+ // To begin with we will set the proportion to 1/4, but we will
+ // probably want it to be (a) something different [because I don't
+ // really know what I'm doing], (b) adaptive [e.g. shorter host
+ // note or more grace notes = longer proportion], (c)
+ // configurable, or (d) all of the above.
+
+ // Of course we also ought to be taking into account the notated
+ // duration of the grace notes -- though in my working examples it
+ // generally doesn't seem to be the case that we can always just
+ // follow those. I wonder if we can always use the grace notes'
+ // notated duration if the ratio of grace note duration to host
+ // note duration is less than some value? Whatever we do, we
+ // should be dividing the grace note duration up in proportion to
+ // the durations of the grace notes, in situations where we have
+ // more than one grace note consecutively of different durations;
+ // that isn't handled at all here.
+
+ if (i == end()) return false;
+
+ iteratorcontainer graceNotes, hostNotes;
+ bool isHostNote;
+
+ if (!getGraceAndHostNotes(i, graceNotes, hostNotes, isHostNote)) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Note at " << (*i)->getAbsoluteTime() << " is not a grace note, or has no grace notes" << std::endl;
+ return false;
+ }
+
+ if (!isHostNote) {
+
+ if (!(*i)->has(IS_GRACE_NOTE) || !(*i)->get<Bool>(IS_GRACE_NOTE)) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: WARNING: Note at " << (*i)->getAbsoluteTime() << " is neither grace nor host note, but was reported as suitable by getGraceAndHostNotes" << std::endl;
+ return false;
+ }
+ }
+
+ if (hostNotes.empty()) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note" << std::endl;
+ return false;
+ }
+
+ if (graceNotes.empty()) {
+ std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Note at " << (*i)->getAbsoluteTime() << " has no grace notes" << std::endl;
+ return false;
+ }
+
+ timeT hostNoteEarliestTime = 0;
+ timeT hostNoteShortestDuration = 0;
+ timeT hostNoteNotationDuration = 0;
+
+ for (iteratorcontainer::iterator j = hostNotes.begin();
+ j != hostNotes.end(); ++j) {
+
+ if (j == hostNotes.begin() ||
+ (**j)->getAbsoluteTime() < hostNoteEarliestTime) {
+ hostNoteEarliestTime = (**j)->getAbsoluteTime();
+ }
+ if (j == hostNotes.begin() ||
+ (**j)->getDuration() < hostNoteShortestDuration) {
+ hostNoteShortestDuration = (**j)->getDuration();
+ }
+ if (j == hostNotes.begin() ||
+ (**j)->getNotationDuration() > hostNoteNotationDuration) {
+ hostNoteNotationDuration = (**j)->getNotationDuration();
+ }
+ (**j)->set<Bool>(MAY_HAVE_GRACE_NOTES, true);
+ }
+
+ timeT graceNoteTime = hostNoteEarliestTime;
+ timeT graceNoteDuration = hostNoteNotationDuration / 4;
+ if (graceNoteDuration > hostNoteShortestDuration / 2) {
+ graceNoteDuration = hostNoteShortestDuration / 2;
+ }
+
+ if (isHostNote) {
+ t = (*i)->getAbsoluteTime() + graceNoteDuration;
+ d = (*i)->getDuration() - graceNoteDuration;
+ } else {
+
+ int count = 0, index = 0;
+ bool found = false;
+ int prevSubOrdering = 0;
+
+ for (iteratorcontainer::iterator j = graceNotes.begin();
+ j != graceNotes.end(); ++j) {
+
+ bool newChord = false;
+
+ if ((**j)->getSubOrdering() != prevSubOrdering) {
+ newChord = true;
+ prevSubOrdering = (**j)->getSubOrdering();
+ }
+
+ if (newChord) ++count;
+
+ if (*j == i) found = true;
+
+ if (!found) {
+ if (newChord) ++index;
+ }
+ }
+
+ if (index == count) index = 0;
+ if (count == 0) count = 1; // should not happen
+
+ d = graceNoteDuration / count;
+ t = hostNoteEarliestTime + d * index;
+ }
+
+ return true;
+
+
+}
+
+
+}
diff --git a/src/base/SegmentPerformanceHelper.h b/src/base/SegmentPerformanceHelper.h
new file mode 100644
index 0000000..e0ce745
--- /dev/null
+++ b/src/base/SegmentPerformanceHelper.h
@@ -0,0 +1,126 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SEGMENT_PERFORMANCE_HELPER_H_
+#define _SEGMENT_PERFORMANCE_HELPER_H_
+
+#include "Segment.h"
+#include "Composition.h" // for RealTime
+
+namespace Rosegarden
+{
+
+class SegmentPerformanceHelper : protected SegmentHelper
+{
+public:
+ SegmentPerformanceHelper(Segment &t) : SegmentHelper(t) { }
+ virtual ~SegmentPerformanceHelper();
+
+ typedef std::vector<iterator> iteratorcontainer;
+
+ /**
+ * Returns a sequence of iterators pointing to the note events
+ * that are tied with the given event. If the given event is not
+ * a note event or is not tied, its iterator will be the only one
+ * in the sequence. If the given event is tied but is not the
+ * first in the tied chain, the returned sequence will be empty.
+ */
+ iteratorcontainer getTiedNotes(iterator i);
+
+ /**
+ * Returns two sequences of iterators pointing to the note events
+ * that are grace notes, or host notes for grace notes, associated
+ * with the given event, which is itself either a grace note or a
+ * host note for a grace note. The grace note iterators are
+ * returned in the graceNotes sequence, and the host note
+ * iterators in hostNotes. isHostNote is set to true if the
+ * given event is a host note, false otherwise.
+ *
+ * If the given event is not a grace note, is a grace note with no
+ * host note, or is a potential host note without any grace notes,
+ * the sequences will both be empty and the function will return
+ * false.
+ */
+ bool getGraceAndHostNotes(iterator i,
+ iteratorcontainer &graceNotes,
+ iteratorcontainer &hostNotes,
+ bool &isHostNote);
+
+ /**
+ * Returns the absolute time of the note event pointed to by i.
+ */
+ timeT getSoundingAbsoluteTime(iterator i);
+
+ /**
+ * Returns the duration of the note event pointed to by i, taking
+ * into account any ties the note may have etc.
+ *
+ * If the note is the first of two or more tied notes, this will
+ * return the accumulated duration of the whole series of notes
+ * it's tied to.
+ *
+ * If the note is in a tied series but is not the first, this will
+ * return zero, because the note's duration is presumed to have
+ * been accounted for by a previous call to this method when
+ * examining the first note in the tied series.
+ *
+ * If the note is not tied, or if i does not point to a note
+ * event, this will just return the duration of the event at i.
+ *
+ * This method may return an incorrect duration for any note
+ * event that is tied but lacks a pitch property. This is
+ * expected behaviour; don't create tied notes without pitches.
+ */
+ timeT getSoundingDuration(iterator i);
+
+ /**
+ * Returns the absolute time of the event pointed to by i,
+ * in microseconds elapsed since the start of the Composition.
+ * This method exploits the Composition's getElapsedRealTime
+ * method to take into account any tempo changes that appear
+ * in the section of the composition preceding i.
+ */
+ RealTime getRealAbsoluteTime(iterator i);
+
+ /**
+ * Returns the duration of the note event pointed to by i,
+ * in microseconds. This takes into account the tempo in
+ * force at i's position within the composition, as well as
+ * any tempo changes occurring during the event at i.
+ */
+ RealTime getRealSoundingDuration(iterator i);
+
+ /**
+ * Return a sounding duration (estimated) and start time for the
+ * note event pointed to by i. If host is true, i is expected to
+ * be the "host" note for one or more grace notes; if host is
+ * false, i is expected to point to a grace note. If the relevant
+ * expectation is not met, this function returns false. Otherwise
+ * the sounding time and duration are returned through t and d and
+ * the function returns true.
+ */
+ bool getGraceNoteTimeAndDuration(bool host, iterator i, timeT &t, timeT &d);
+};
+
+}
+
+#endif
diff --git a/src/base/Selection.cpp b/src/base/Selection.cpp
new file mode 100644
index 0000000..6e5ca2f
--- /dev/null
+++ b/src/base/Selection.cpp
@@ -0,0 +1,318 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Selection.h"
+#include "Segment.h"
+#include "SegmentNotationHelper.h"
+#include "BaseProperties.h"
+
+namespace Rosegarden {
+
+EventSelection::EventSelection(Segment& t) :
+ m_originalSegment(t),
+ m_beginTime(0),
+ m_endTime(0),
+ m_haveRealStartTime(false)
+{
+ t.addObserver(this);
+}
+
+EventSelection::EventSelection(Segment& t, timeT beginTime, timeT endTime, bool overlap) :
+ m_originalSegment(t),
+ m_beginTime(0),
+ m_endTime(0),
+ m_haveRealStartTime(false)
+{
+ t.addObserver(this);
+
+ Segment::iterator i = t.findTime(beginTime);
+ Segment::iterator j = t.findTime(endTime);
+
+ if (i != t.end()) {
+ m_beginTime = (*i)->getAbsoluteTime();
+ while (i != j) {
+ m_endTime = (*i)->getAbsoluteTime() + (*i)->getDuration();
+ m_segmentEvents.insert(*i);
+ ++i;
+ }
+ m_haveRealStartTime = true;
+ }
+
+ // Find events overlapping the beginning
+ //
+ if (overlap) {
+ i = t.findTime(beginTime);
+
+ while (i != t.begin() && i != t.end() && i != j) {
+
+ if ((*i)->getAbsoluteTime() + (*i)->getDuration() > beginTime)
+ {
+ m_segmentEvents.insert(*i); // duplicates are filtered automatically
+ m_beginTime = (*i)->getAbsoluteTime();
+ }
+ else
+ break;
+
+ --i;
+ }
+
+ }
+
+}
+
+EventSelection::EventSelection(const EventSelection &sel) :
+ SegmentObserver(),
+ m_originalSegment(sel.m_originalSegment),
+ m_segmentEvents(sel.m_segmentEvents),
+ m_beginTime(sel.m_beginTime),
+ m_endTime(sel.m_endTime),
+ m_haveRealStartTime(sel.m_haveRealStartTime)
+{
+ m_originalSegment.addObserver(this);
+}
+
+EventSelection::~EventSelection()
+{
+ m_originalSegment.removeObserver(this);
+}
+
+void EventSelection::addEvent(Event *e)
+{
+ timeT eventDuration = e->getDuration();
+ if (eventDuration == 0) eventDuration = 1;
+
+ if (contains(e)) return;
+
+ if (e->getAbsoluteTime() < m_beginTime || !m_haveRealStartTime) {
+ m_beginTime = e->getAbsoluteTime();
+ m_haveRealStartTime = true;
+ }
+ if (e->getAbsoluteTime() + eventDuration > m_endTime) {
+ m_endTime = e->getAbsoluteTime() + eventDuration;
+ }
+ m_segmentEvents.insert(e);
+}
+
+void EventSelection::addFromSelection(EventSelection *sel)
+{
+ for (eventcontainer::iterator i = sel->getSegmentEvents().begin();
+ i != sel->getSegmentEvents().end(); ++i) {
+ if (!contains(*i)) addEvent(*i);
+ }
+}
+
+void EventSelection::removeEvent(Event *e)
+{
+ std::pair<eventcontainer::iterator, eventcontainer::iterator>
+ interval = m_segmentEvents.equal_range(e);
+
+ for (eventcontainer::iterator it = interval.first;
+ it != interval.second; it++)
+ {
+ if (*it == e) {
+ m_segmentEvents.erase(it);
+ return;
+ }
+ }
+}
+
+bool EventSelection::contains(Event *e) const
+{
+ std::pair<eventcontainer::const_iterator, eventcontainer::const_iterator>
+ interval = m_segmentEvents.equal_range(e);
+
+ for (eventcontainer::const_iterator it = interval.first;
+ it != interval.second; ++it)
+ {
+ if (*it == e) return true;
+ }
+
+ return false;
+}
+
+bool EventSelection::contains(const std::string &type) const
+{
+ for (eventcontainer::const_iterator i = m_segmentEvents.begin();
+ i != m_segmentEvents.end(); ++i) {
+ if ((*i)->isa(type)) return true;
+ }
+ return false;
+}
+
+timeT EventSelection::getTotalDuration() const
+{
+ return getEndTime() - getStartTime();
+}
+
+EventSelection::RangeList
+EventSelection::getRanges() const
+{
+ RangeList ranges;
+
+ Segment::iterator i = m_originalSegment.findTime(getStartTime());
+ Segment::iterator j = i;
+ Segment::iterator k = m_originalSegment.findTime(getEndTime());
+
+ while (j != k) {
+
+ for (j = i; j != k && contains(*j); ++j);
+
+ if (j != i) {
+ ranges.push_back(RangeList::value_type(i, j));
+ }
+
+ for (i = j; i != k && !contains(*i); ++i);
+ j = i;
+ }
+
+ return ranges;
+}
+
+EventSelection::RangeTimeList
+EventSelection::getRangeTimes() const
+{
+ RangeList ranges(getRanges());
+ RangeTimeList rangeTimes;
+
+ for (RangeList::iterator i = ranges.begin(); i != ranges.end(); ++i) {
+ timeT startTime = m_originalSegment.getEndTime();
+ timeT endTime = m_originalSegment.getEndTime();
+ if (i->first != m_originalSegment.end()) {
+ startTime = (*i->first)->getAbsoluteTime();
+ }
+ if (i->second != m_originalSegment.end()) {
+ endTime = (*i->second)->getAbsoluteTime();
+ }
+ rangeTimes.push_back(RangeTimeList::value_type(startTime, endTime));
+ }
+
+ return rangeTimes;
+}
+
+void
+EventSelection::eventRemoved(const Segment *s, Event *e)
+{
+ if (s == &m_originalSegment /*&& contains(e)*/) {
+ removeEvent(e);
+ }
+}
+
+void
+EventSelection::segmentDeleted(const Segment *)
+{
+ /*
+ std::cerr << "WARNING: EventSelection notified of segment deletion: this is probably a bug "
+ << "(selection should have been deleted before segment)" << std::endl;
+ */
+}
+
+bool SegmentSelection::hasNonAudioSegment() const
+{
+ for (const_iterator i = begin(); i != end(); ++i) {
+ if ((*i)->getType() == Segment::Internal)
+ return true;
+ }
+ return false;
+}
+
+
+TimeSignatureSelection::TimeSignatureSelection() { }
+
+TimeSignatureSelection::TimeSignatureSelection(Composition &composition,
+ timeT beginTime,
+ timeT endTime,
+ bool includeOpeningTimeSig)
+{
+ int n = composition.getTimeSignatureNumberAt(endTime);
+
+ for (int i = composition.getTimeSignatureNumberAt(beginTime);
+ i <= n;
+ ++i) {
+
+ if (i < 0) continue;
+
+ std::pair<timeT, TimeSignature> sig =
+ composition.getTimeSignatureChange(i);
+
+ if (sig.first < endTime) {
+ if (sig.first < beginTime) {
+ if (includeOpeningTimeSig) {
+ sig.first = beginTime;
+ } else {
+ continue;
+ }
+ }
+ addTimeSignature(sig.first, sig.second);
+ }
+ }
+}
+
+TimeSignatureSelection::~TimeSignatureSelection() { }
+
+void
+TimeSignatureSelection::addTimeSignature(timeT t, TimeSignature timeSig)
+{
+ m_timeSignatures.insert(timesigcontainer::value_type(t, timeSig));
+}
+
+TempoSelection::TempoSelection() { }
+
+TempoSelection::TempoSelection(Composition &composition,
+ timeT beginTime,
+ timeT endTime,
+ bool includeOpeningTempo)
+{
+ int n = composition.getTempoChangeNumberAt(endTime);
+
+ for (int i = composition.getTempoChangeNumberAt(beginTime);
+ i <= n;
+ ++i) {
+
+ if (i < 0) continue;
+
+ std::pair<timeT, tempoT> change = composition.getTempoChange(i);
+
+ if (change.first < endTime) {
+ if (change.first < beginTime) {
+ if (includeOpeningTempo) {
+ change.first = beginTime;
+ } else {
+ continue;
+ }
+ }
+ std::pair<bool, tempoT> ramping =
+ composition.getTempoRamping(i, false);
+ addTempo(change.first, change.second,
+ ramping.first ? ramping.second : -1);
+ }
+ }
+}
+
+TempoSelection::~TempoSelection() { }
+
+void
+TempoSelection::addTempo(timeT t, tempoT tempo, tempoT targetTempo)
+{
+ m_tempos.insert(tempocontainer::value_type
+ (t, tempochange(tempo, targetTempo)));
+}
+
+}
diff --git a/src/base/Selection.h b/src/base/Selection.h
new file mode 100644
index 0000000..93ce4b4
--- /dev/null
+++ b/src/base/Selection.h
@@ -0,0 +1,263 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef SELECTION_H
+#define SELECTION_H
+
+#include <set>
+#include "PropertyName.h"
+#include "Event.h"
+#include "Segment.h"
+#include "NotationTypes.h"
+#include "Composition.h"
+
+namespace Rosegarden {
+
+/**
+ * EventSelection records a (possibly non-contiguous) selection
+ * of Events in a single Segment, used for cut'n paste operations.
+ * It does not take a copy of those Events, it just remembers
+ * which ones they are.
+ */
+
+class EventSelection : public SegmentObserver
+{
+public:
+ typedef std::multiset<Event*, Event::EventCmp> eventcontainer;
+
+ /**
+ * Construct an empty EventSelection based on the given Segment.
+ */
+ EventSelection(Segment &);
+
+ /**
+ * Construct an EventSelection selecting all the events in the
+ * given range of the given Segment. Set overlap if you want
+ * to include Events overlapping the selection edges.
+ */
+ EventSelection(Segment &, timeT beginTime, timeT endTime, bool overlap = false);
+
+ EventSelection(const EventSelection&);
+
+ virtual ~EventSelection();
+
+ /**
+ * Add an Event to the selection. The Event should come from
+ * the Segment that was passed to the constructor. Will
+ * silently drop any event that is already in the selection.
+ */
+ void addEvent(Event* e);
+
+ /**
+ * Add all the Events in the given Selection to this one.
+ * Will silently drop any events that are already in the
+ * selection.
+ */
+ void addFromSelection(EventSelection *sel);
+
+ /**
+ * If the given Event is in the selection, take it out.
+ */
+ void removeEvent(Event *e);
+
+ /**
+ * Test whether a given Event (in the Segment) is part of
+ * this selection.
+ */
+ bool contains(Event *e) const;
+
+ /**
+ * Return true if there are any events of the given type in
+ * this selection. Slow.
+ */
+ bool contains(const std::string &eventType) const;
+
+ /**
+ * Return the time at which the first Event in the selection
+ * begins.
+ */
+ timeT getStartTime() const { return m_beginTime; }
+
+ /**
+ * Return the time at which the last Event in the selection ends.
+ */
+ timeT getEndTime() const { return m_endTime; }
+
+ /**
+ * Return the total duration spanned by the selection.
+ */
+ timeT getTotalDuration() const;
+
+ typedef std::vector<std::pair<Segment::iterator,
+ Segment::iterator> > RangeList;
+ /**
+ * Return a set of ranges spanned by the selection, such that
+ * each range covers only events within the selection.
+ */
+ RangeList getRanges() const;
+
+ typedef std::vector<std::pair<timeT, timeT> > RangeTimeList;
+ /**
+ * Return a set of times spanned by the selection, such that
+ * each time range covers only events within the selection.
+ */
+ RangeTimeList getRangeTimes() const;
+
+ /**
+ * Return the number of events added to this selection.
+ */
+ unsigned int getAddedEvents() const { return m_segmentEvents.size(); }
+
+ const eventcontainer &getSegmentEvents() const { return m_segmentEvents; }
+ eventcontainer &getSegmentEvents() { return m_segmentEvents; }
+
+ const Segment &getSegment() const { return m_originalSegment; }
+ Segment &getSegment() { return m_originalSegment; }
+
+ // SegmentObserver methods
+ virtual void eventAdded(const Segment *, Event *) { }
+ virtual void eventRemoved(const Segment *, Event *);
+ virtual void endMarkerTimeChanged(const Segment *, bool) { }
+ virtual void segmentDeleted(const Segment *);
+
+private:
+ EventSelection &operator=(const EventSelection &);
+
+protected:
+ //--------------- Data members ---------------------------------
+
+ Segment& m_originalSegment;
+
+ /// pointers to Events in the original Segment
+ eventcontainer m_segmentEvents;
+
+ timeT m_beginTime;
+ timeT m_endTime;
+ bool m_haveRealStartTime;
+};
+
+
+/**
+ * SegmentSelection is much simpler than EventSelection, we don't
+ * need to do much with this really
+ */
+
+class SegmentSelection : public std::set<Segment *>
+{
+public:
+ bool hasNonAudioSegment() const;
+};
+
+
+/**
+ * A selection that includes (only) time signatures. Unlike
+ * EventSelection, this does copy its contents, not just refer to
+ * them.
+ */
+class TimeSignatureSelection
+{
+public:
+ /**
+ * Construct an empty TimeSignatureSelection.
+ */
+ TimeSignatureSelection();
+
+ /**
+ * Construct a TimeSignatureSelection containing all the time
+ * signatures in the given range of the given Composition.
+ *
+ * If includeOpeningTimeSig is true, the selection will start with
+ * a duplicate of the time signature (if any) that is already in
+ * force at beginTime. Otherwise the selection will only start
+ * with a time signature at beginTime if there is an explicit
+ * signature there in the source composition.
+ */
+ TimeSignatureSelection(Composition &, timeT beginTime, timeT endTime,
+ bool includeOpeningTimeSig);
+
+ virtual ~TimeSignatureSelection();
+
+ /**
+ * Add a time signature to the selection.
+ */
+ void addTimeSignature(timeT t, TimeSignature timeSig);
+
+ typedef std::multimap<timeT, TimeSignature> timesigcontainer;
+
+ const timesigcontainer &getTimeSignatures() const { return m_timeSignatures; }
+ timesigcontainer::const_iterator begin() const { return m_timeSignatures.begin(); }
+ timesigcontainer::const_iterator end() const { return m_timeSignatures.end(); }
+ bool empty() const { return begin() == end(); }
+
+protected:
+ timesigcontainer m_timeSignatures;
+};
+
+
+/**
+ * A selection that includes (only) tempo changes.
+ */
+
+class TempoSelection
+{
+public:
+ /**
+ * Construct an empty TempoSelection.
+ */
+ TempoSelection();
+
+ /**
+ * Construct a TempoSelection containing all the time
+ * signatures in the given range of the given Composition.
+ *
+ * If includeOpeningTempo is true, the selection will start with a
+ * duplicate of the tempo (if any) that is already in force at
+ * beginTime. Otherwise the selection will only start with a
+ * tempo at beginTime if there is an explicit tempo change there
+ * in the source composition.
+ */
+ TempoSelection(Composition &, timeT beginTime, timeT endTime,
+ bool includeOpeningTempo);
+
+ virtual ~TempoSelection();
+
+ /**
+ * Add a time signature to the selection.
+ */
+ void addTempo(timeT t, tempoT tempo, tempoT targetTempo = -1);
+
+ typedef std::pair<tempoT, tempoT> tempochange;
+ typedef std::multimap<timeT, tempochange> tempocontainer;
+
+ const tempocontainer &getTempos() const { return m_tempos; }
+ tempocontainer::const_iterator begin() const { return m_tempos.begin(); }
+ tempocontainer::const_iterator end() const { return m_tempos.end(); }
+ bool empty() const { return begin() == end(); }
+
+protected:
+ tempocontainer m_tempos;
+};
+
+
+
+}
+
+#endif
diff --git a/src/base/Sets.cpp b/src/base/Sets.cpp
new file mode 100644
index 0000000..5111f37
--- /dev/null
+++ b/src/base/Sets.cpp
@@ -0,0 +1,108 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Sets.h"
+
+#include "Event.h"
+#include "BaseProperties.h"
+#include "Quantizer.h"
+
+namespace Rosegarden {
+
+template <>
+Event *
+AbstractSet<Event, Segment>::getAsEvent(const Segment::iterator &i)
+{
+ return *i;
+}
+
+template <>
+Event *
+AbstractSet<Event, CompositionTimeSliceAdapter>::getAsEvent(const CompositionTimeSliceAdapter::iterator &i)
+{
+ return *i;
+}
+
+/*
+ * This ridiculous shit appears to be necessary to please gcc.
+ * Compiler bug? My own misunderstanding of some huge crock of crap
+ * in the C++ standard? No idea. If you know, tell me. Anyway, as
+ * it stands I can't get any calls to get<> or set<> from the Set or
+ * Chord methods to compile -- the compiler appears to parse the
+ * opening < of the template arguments as an operator<. Hence this.
+ */
+
+extern long
+get__Int(Event *e, const PropertyName &name)
+{
+ return e->get<Int>(name);
+}
+
+extern bool
+get__Bool(Event *e, const PropertyName &name)
+{
+ return e->get<Bool>(name);
+}
+
+extern std::string
+get__String(Event *e, const PropertyName &name)
+{
+ return e->get<String>(name);
+}
+
+extern bool
+get__Int(Event *e, const PropertyName &name, long &ref)
+{
+ return e->get<Int>(name, ref);
+}
+
+extern bool
+get__Bool(Event *e, const PropertyName &name, bool &ref)
+{
+ return e->get<Bool>(name, ref);
+}
+
+extern bool
+get__String(Event *e, const PropertyName &name, std::string &ref)
+{
+ return e->get<String>(name, ref);
+}
+
+extern bool
+isPersistent__Bool(Event *e, const PropertyName &name)
+{
+ return e->isPersistent<Bool>(name);
+}
+
+extern void
+setMaybe__Int(Event *e, const PropertyName &name, long value)
+{
+ e->setMaybe<Int>(name, value);
+}
+
+extern void
+setMaybe__String(Event *e, const PropertyName &name, const std::string &value)
+{
+ e->setMaybe<String>(name, value);
+}
+
+}
+
diff --git a/src/base/Sets.h b/src/base/Sets.h
new file mode 100644
index 0000000..4fe14d1
--- /dev/null
+++ b/src/base/Sets.h
@@ -0,0 +1,698 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SETS_H_
+#define _SETS_H_
+
+#include <vector>
+#include <algorithm>
+
+#include "Event.h"
+#include "Segment.h"
+#include "CompositionTimeSliceAdapter.h"
+#include "BaseProperties.h"
+#include "NotationTypes.h"
+#include "MidiTypes.h"
+#include "Quantizer.h"
+
+namespace Rosegarden
+{
+
+class Quantizer;
+
+/**
+ * A "set" in Rosegarden terminology is a collection of elements found
+ * in a container (indeed, a subset of that container) all of which
+ * share a particular property and are located near to one another:
+ * generally either contiguous or within the same bar. The elements
+ * are most usually Events and the container most usually a Segment,
+ * and although this does not have to be the case (for other examples
+ * see gui/notationsets.h), the elements do have to be convertible to
+ * Events somehow.
+ *
+ * To construct a set requires (at least) a container reference plus
+ * an iterator into that container. The constructor (or more
+ * precisely the initialise() method called by the constructor) then
+ * scans the surrounding area of the list for the maximal set of
+ * contiguous or within-the-same-bar elements before and after the
+ * passed-in iterator such that all elements are in the same set
+ * (i.e. Chord, BeamedGroup etc) as the one that the passed-in
+ * iterator pointed to.
+ *
+ * The extents of the set within the list can then be discovered via
+ * getInitialElement() and getFinalElement(). If the iterator passed
+ * in to the constructor was at end() or did not point to an element
+ * that could be a member of this kind of set, getInitialElement()
+ * will return end(); if the passed-in iterator pointed to the only
+ * member of this set, getInitialElement() and getFinalElement() will
+ * be equal.
+ *
+ * These classes are not intended to be stored anywhere; all they
+ * contain is iterators into the main container, and those might not
+ * persist. Instead you should create these on-the-fly when you want,
+ * for example, to consider a note as part of a chord; and then you
+ * should let them expire when you've finished with them.
+ */
+
+template <class Element, class Container>
+class AbstractSet // abstract base
+{
+public:
+ typedef typename Container::iterator Iterator;
+
+ virtual ~AbstractSet() { }
+
+ /**
+ * getInitialElement() returns end() if there are no elements in
+ * the set. getInitialElement() == getFinalElement() if there is
+ * only one element in the set
+ */
+ Iterator getInitialElement() const { return m_initial; }
+ Iterator getFinalElement() const { return m_final; }
+
+ /// only return note elements; will return end() if there are none
+ Iterator getInitialNote() const { return m_initialNote; }
+ Iterator getFinalNote() const { return m_finalNote; }
+
+ /**
+ * only elements with duration > 0 are candidates for shortest and
+ * longest; these will return end() if there are no such elements
+ */
+ Iterator getLongestElement() const { return m_longest; }
+ Iterator getShortestElement() const { return m_shortest; }
+
+ /// these will return end() if there are no note elements in the set
+ Iterator getHighestNote() const { return m_highest; }
+ Iterator getLowestNote() const { return m_lowest; }
+
+ virtual bool contains(const Iterator &) const = 0;
+
+ /// Return the pointed-to element, in Event form (public to work around gcc-2.95 bug)
+ static Event *getAsEvent(const Iterator &i);
+
+protected:
+ AbstractSet(Container &c, Iterator elementInSet, const Quantizer *);
+ void initialise();
+
+ /// Return true if this element is not definitely beyond bounds of set
+ virtual bool test(const Iterator &i) = 0;
+
+ /// Return true if this element, known to test() true, is a set member
+ virtual bool sample(const Iterator &i, bool goingForwards);
+
+ Container &getContainer() const { return m_container; }
+ const Quantizer &getQuantizer() const { return *m_quantizer; }
+
+ // Data members:
+
+ Container &m_container;
+ Iterator m_initial, m_final, m_initialNote, m_finalNote;
+ Iterator m_shortest, m_longest, m_highest, m_lowest;
+ Iterator m_baseIterator;
+ const Quantizer *m_quantizer;
+};
+
+
+/**
+ * Chord is subclassed from a vector of iterators; this vector
+ * contains iterators pointing at all the notes in the chord, in
+ * ascending order of pitch. You can also track through all the
+ * events in the chord by iterating from getInitialElement() to
+ * getFinalElement(), but this will only get them in the order in
+ * which they appear in the original container.
+ *
+ * However, the notes in a chord might not be contiguous events in the
+ * container, as there could be other zero-duration events such as
+ * controllers (or even conceivably some short rests) between notes in
+ * the same chord, depending on the quantization settings. The Chord
+ * itself only contains iterators pointing at the notes, so if you
+ * want to iterate through all events spanned by the Chord, iterate
+ * from getInitialElement() to getFinalElement() instead.
+ *
+ * This class can tell you various things about the chord it
+ * describes, but not everything. It can't tell you whether the
+ * chord has a stem, for example, because that depends on the
+ * notation style and system in use. See gui/notationsets.h
+ * for a NotationChord class (subclassed from this) that can.
+ */
+
+template <class Element, class Container, bool singleStaff>
+class GenericChord : public AbstractSet<Element, Container>,
+ public std::vector<typename Container::iterator>
+{
+public:
+ typedef typename Container::iterator Iterator;
+
+ /* You only need to provide the clef and key if the notes
+ making up your chord lack HEIGHT_ON_STAFF properties, in
+ which case this constructor will write those properties
+ in to the chord for you */
+ GenericChord(Container &c,
+ Iterator elementInChord,
+ const Quantizer *quantizer,
+ PropertyName stemUpProperty = PropertyName::EmptyPropertyName);
+
+ virtual ~GenericChord();
+
+ virtual int getMarkCountForChord() const;
+ virtual std::vector<Mark> getMarksForChord() const;
+ virtual std::vector<int> getPitches() const;
+ virtual bool contains(const Iterator &) const;
+
+ /**
+ * Return an iterator pointing to the previous note before this
+ * chord, or container's end() if there is no previous note.
+ */
+ virtual Iterator getPreviousNote();
+
+ /**
+ * Return an iterator pointing to the next note after this chord,
+ * or container's end() if there is no next note. Remember this
+ * class can't know about Segment end marker times, so if your
+ * container is a Segment, check the returned note is actually
+ * before the end marker.
+ */
+ virtual Iterator getNextNote();
+
+ /**
+ * It's possible for a chord to surround (in the segment) elements
+ * that are not members of the chord. This function returns an
+ * iterator pointing to the first of those after the iterator that
+ * was passed to the chord's constructor. If there are none, it
+ * returns the container's end().
+ */
+ virtual Iterator getFirstElementNotInChord();
+
+ virtual int getSubOrdering() { return m_subordering; }
+
+protected:
+ virtual bool test(const Iterator&);
+ virtual bool sample(const Iterator&, bool goingForwards);
+
+ class PitchGreater {
+ public:
+ bool operator()(const Iterator &a, const Iterator &b);
+ };
+
+ void copyGroupProperties(Event *e0, Event *e1) const;
+
+ //--------------- Data members ---------------------------------
+
+ PropertyName m_stemUpProperty;
+ timeT m_time;
+ int m_subordering;
+ Iterator m_firstReject;
+};
+
+
+
+///
+/// Implementation only from here on.
+///
+
+// forward declare hack functions -- see Sets.C for an explanation
+
+extern long
+get__Int(Event *e, const PropertyName &name);
+
+extern bool
+get__Bool(Event *e, const PropertyName &name);
+
+extern std::string
+get__String(Event *e, const PropertyName &name);
+
+extern bool
+get__Int(Event *e, const PropertyName &name, long &ref);
+
+extern bool
+get__Bool(Event *e, const PropertyName &name, bool &ref);
+
+extern bool
+get__String(Event *e, const PropertyName &name, std::string &ref);
+
+extern bool
+isPersistent__Bool(Event *e, const PropertyName &name);
+
+extern void
+setMaybe__Int(Event *e, const PropertyName &name, long value);
+
+extern void
+setMaybe__String(Event *e, const PropertyName &name, const std::string &value);
+
+
+
+template <class Element, class Container>
+AbstractSet<Element, Container>::AbstractSet(Container &c,
+ Iterator i, const Quantizer *q):
+ m_container(c),
+ m_initial(c.end()),
+ m_final(c.end()),
+ m_initialNote(c.end()),
+ m_finalNote(c.end()),
+ m_shortest(c.end()),
+ m_longest(c.end()),
+ m_highest(c.end()),
+ m_lowest(c.end()),
+ m_baseIterator(i),
+ m_quantizer(q)
+{
+ // ...
+}
+
+template <class Element, class Container>
+void
+AbstractSet<Element, Container>::initialise()
+{
+ if (m_baseIterator == getContainer().end() || !test(m_baseIterator)) return;
+
+ m_initial = m_baseIterator;
+ m_final = m_baseIterator;
+ sample(m_baseIterator, true);
+
+ if (getAsEvent(m_baseIterator)->isa(Note::EventType)) {
+ m_initialNote = m_baseIterator;
+ m_finalNote = m_baseIterator;
+ }
+
+ Iterator i, j;
+
+ // first scan back to find an element not in the desired set,
+ // sampling everything as far back as the one after it
+
+ for (i = j = m_baseIterator; i != getContainer().begin() && test(--j); i = j){
+ if (sample(j, false)) {
+ m_initial = j;
+ if (getAsEvent(j)->isa(Note::EventType)) {
+ m_initialNote = j;
+ if (m_finalNote == getContainer().end()) {
+ m_finalNote = j;
+ }
+ }
+ }
+ }
+
+ j = m_baseIterator;
+
+ // then scan forwards to find an element not in the desired set,
+ // sampling everything as far forward as the one before it
+
+ for (i = j = m_baseIterator; ++j != getContainer().end() && test(j); i = j) {
+ if (sample(j, true)) {
+ m_final = j;
+ if (getAsEvent(j)->isa(Note::EventType)) {
+ m_finalNote = j;
+ if (m_initialNote == getContainer().end()) {
+ m_initialNote = j;
+ }
+ }
+ }
+ }
+}
+
+template <class Element, class Container>
+bool
+AbstractSet<Element, Container>::sample(const Iterator &i, bool)
+{
+ const Quantizer &q(getQuantizer());
+ Event *e = getAsEvent(i);
+ timeT d(q.getQuantizedDuration(e));
+
+ if (e->isa(Note::EventType) || d > 0) {
+ if (m_longest == getContainer().end() ||
+ d > q.getQuantizedDuration(getAsEvent(m_longest))) {
+// std::cerr << "New longest in set at duration " << d << " and time " << e->getAbsoluteTime() << std::endl;
+ m_longest = i;
+ }
+ if (m_shortest == getContainer().end() ||
+ d < q.getQuantizedDuration(getAsEvent(m_shortest))) {
+// std::cerr << "New shortest in set at duration " << d << " and time " << e->getAbsoluteTime() << std::endl;
+ m_shortest = i;
+ }
+ }
+
+ if (e->isa(Note::EventType)) {
+ long p = get__Int(e, BaseProperties::PITCH);
+
+ if (m_highest == getContainer().end() ||
+ p > get__Int(getAsEvent(m_highest), BaseProperties::PITCH)) {
+// std::cerr << "New highest in set at pitch " << p << " and time " << e->getAbsoluteTime() << std::endl;
+ m_highest = i;
+ }
+ if (m_lowest == getContainer().end() ||
+ p < get__Int(getAsEvent(m_lowest), BaseProperties::PITCH)) {
+// std::cerr << "New lowest in set at pitch " << p << " and time " << e->getAbsoluteTime() << std::endl;
+ m_lowest = i;
+ }
+ }
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+
+template <class Element, class Container, bool singleStaff>
+GenericChord<Element, Container, singleStaff>::GenericChord(Container &c,
+ Iterator i,
+ const Quantizer *q,
+ PropertyName stemUpProperty) :
+ AbstractSet<Element, Container>(c, i, q),
+ m_stemUpProperty(stemUpProperty),
+ m_time(q->getQuantizedAbsoluteTime(getAsEvent(i))),
+ m_subordering(getAsEvent(i)->getSubOrdering()),
+ m_firstReject(c.end())
+{
+ AbstractSet<Element, Container>::initialise();
+
+ if (std::vector<typename Container::iterator>::size() > 1) {
+ std::stable_sort(std::vector<typename Container::iterator>::begin(),
+ std::vector<typename Container::iterator>::end(),
+ PitchGreater());
+ }
+
+/*!!! this should all be removed ultimately
+// std::cerr << "GenericChord::GenericChord: pitches are:" << std::endl;
+ int prevPitch = -999;
+ for (unsigned int i = 0; i < size(); ++i) {
+ try {
+ int pitch = getAsEvent((*this)[i])->get<Int>(BaseProperties::PITCH);
+// std::cerr << i << ": " << pitch << std::endl;
+ if (pitch < prevPitch) {
+ cerr << "ERROR: Pitch less than previous pitch (" << pitch
+ << " < " << prevPitch << ")" << std::endl;
+ throw(1);
+ }
+ } catch (Event::NoData) {
+ std::cerr << i << ": no pitch property" << std::endl;
+ }
+ }
+*/
+}
+
+template <class Element, class Container, bool singleStaff>
+GenericChord<Element, Container, singleStaff>::~GenericChord()
+{
+}
+
+template <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::test(const Iterator &i)
+{
+ Event *e = getAsEvent(i);
+ if (AbstractSet<Element, Container>::
+ getQuantizer().getQuantizedAbsoluteTime(e) != m_time) {
+ return false;
+ }
+ if (e->getSubOrdering() != m_subordering) {
+ return false;
+ }
+
+ // We permit note or rest events etc here, because if a chord is a
+ // little staggered (for performance reasons) then it's not at all
+ // unlikely we could get other events (even rests) in the middle
+ // of it. So long as sample() only permits notes, we should be
+ // okay with this.
+ //
+ // (We're really only refusing things like clef and key events
+ // here, though it's slightly quicker [since most things are
+ // notes] and perhaps a bit safer to do it by testing for
+ // inclusion rather than exclusion.)
+
+ std::string type(e->getType());
+ return (type == Note::EventType ||
+ type == Note::EventRestType ||
+ type == Text::EventType ||
+ type == Indication::EventType ||
+ type == PitchBend::EventType ||
+ type == Controller::EventType ||
+ type == KeyPressure::EventType ||
+ type == ChannelPressure::EventType);
+}
+
+template <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::sample(const Iterator &i,
+ bool goingForwards)
+{
+ Event *e1 = getAsEvent(i);
+ if (!e1->isa(Note::EventType)) {
+ if (goingForwards && m_firstReject == AbstractSet<Element, Container>::getContainer().end()) m_firstReject = i;
+ return false;
+ }
+
+ if (singleStaff) {
+
+ // Two notes that would otherwise be in a chord but are
+ // explicitly in different groups, or have stems pointing in
+ // different directions by design, or have substantially
+ // different x displacements, count as separate chords.
+
+ // Per #930473 ("Inserting notes into beamed chords is
+ // broken"), if one note is in a group and the other isn't,
+ // that's no problem. In fact we should actually modify the
+ // one that isn't so as to suggest that it is.
+
+ if (AbstractSet<Element, Container>::m_baseIterator != AbstractSet<Element, Container>::getContainer().end()) {
+
+ Event *e0 = getAsEvent(AbstractSet<Element, Container>::m_baseIterator);
+
+ if (!(m_stemUpProperty == PropertyName::EmptyPropertyName)) {
+
+ if (e0->has(m_stemUpProperty) &&
+ e1->has(m_stemUpProperty) &&
+ isPersistent__Bool(e0, m_stemUpProperty) &&
+ isPersistent__Bool(e1, m_stemUpProperty) &&
+ get__Bool(e0, m_stemUpProperty) !=
+ get__Bool(e1, m_stemUpProperty)) {
+
+ if (goingForwards && m_firstReject == AbstractSet<Element, Container>::getContainer().end())
+ m_firstReject = i;
+ return false;
+ }
+ }
+
+ long dx0 = 0, dx1 = 0;
+ get__Int(e0, BaseProperties::DISPLACED_X, dx0);
+ get__Int(e1, BaseProperties::DISPLACED_X, dx1);
+ if (abs(dx0 - dx1) >= 700) {
+ if (goingForwards && m_firstReject == AbstractSet<Element, Container>::getContainer().end())
+ m_firstReject = i;
+ return false;
+ }
+
+ if (e0->has(BaseProperties::BEAMED_GROUP_ID)) {
+ if (e1->has(BaseProperties::BEAMED_GROUP_ID)) {
+ if (get__Int(e1, BaseProperties::BEAMED_GROUP_ID) !=
+ get__Int(e0, BaseProperties::BEAMED_GROUP_ID)) {
+ if (goingForwards && m_firstReject == AbstractSet<Element, Container>::getContainer().end())
+ m_firstReject = i;
+ return false;
+ }
+ } else {
+ copyGroupProperties(e0, e1); // #930473
+ }
+ } else {
+ if (e1->has(BaseProperties::BEAMED_GROUP_ID)) {
+ copyGroupProperties(e1, e0); // #930473
+ }
+ }
+ }
+ }
+
+ AbstractSet<Element, Container>::sample(i, goingForwards);
+ push_back(i);
+ return true;
+}
+
+template <class Element, class Container, bool singleStaff>
+void
+GenericChord<Element, Container, singleStaff>::copyGroupProperties(Event *e0,
+ Event *e1) const
+{
+ if (e0->has(BaseProperties::BEAMED_GROUP_TYPE)) {
+ setMaybe__String(e1, BaseProperties::BEAMED_GROUP_TYPE,
+ get__String(e0, BaseProperties::BEAMED_GROUP_TYPE));
+ }
+ if (e0->has(BaseProperties::BEAMED_GROUP_ID)) {
+ setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_ID,
+ get__Int(e0, BaseProperties::BEAMED_GROUP_ID));
+ }
+ if (e0->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE)) {
+ setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_TUPLET_BASE,
+ get__Int(e0, BaseProperties::BEAMED_GROUP_TUPLET_BASE));
+ }
+ if (e0->has(BaseProperties::BEAMED_GROUP_TUPLED_COUNT)) {
+ setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_TUPLED_COUNT,
+ get__Int(e0, BaseProperties::BEAMED_GROUP_TUPLED_COUNT));
+ }
+ if (e0->has(BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT)) {
+ setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT,
+ get__Int(e0, BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT));
+ }
+}
+
+
+template <class Element, class Container, bool singleStaff>
+int
+GenericChord<Element, Container, singleStaff>::getMarkCountForChord() const
+{
+ // need to weed out duplicates
+
+ std::set<Mark> cmarks;
+
+ for (unsigned int i = 0; i < std::vector<typename Container::iterator>::size(); ++i) {
+
+ Event *e = getAsEvent((*this)[i]);
+ std::vector<Mark> marks(Marks::getMarks(*e));
+
+ for (std::vector<Mark>::iterator j = marks.begin(); j != marks.end(); ++j) {
+ cmarks.insert(*j);
+ }
+ }
+
+ return cmarks.size();
+}
+
+
+template <class Element, class Container, bool singleStaff>
+std::vector<Mark>
+GenericChord<Element, Container, singleStaff>::getMarksForChord() const
+{
+ std::vector<Mark> cmarks;
+
+ for (unsigned int i = 0; i < std::vector<typename Container::iterator>::size(); ++i) {
+
+ Event *e = getAsEvent((*this)[i]);
+ std::vector<Mark> marks(Marks::getMarks(*e));
+
+
+ for (std::vector<Mark>::iterator j = marks.begin(); j != marks.end(); ++j) {
+
+ // We permit multiple identical fingering marks per chord,
+ // but not any other sort
+ if (Marks::isFingeringMark(*j) ||
+ std::find(cmarks.begin(), cmarks.end(), *j) == cmarks.end()) {
+ cmarks.push_back(*j);
+ }
+ }
+ }
+
+ return cmarks;
+}
+
+
+template <class Element, class Container, bool singleStaff>
+std::vector<int>
+GenericChord<Element, Container, singleStaff>::getPitches() const
+{
+ std::vector<int> pitches;
+
+ for (typename std::vector<typename Container::iterator>::const_iterator
+ i = std::vector<typename Container::iterator>::begin(); i != std::vector<typename Container::iterator>::end(); ++i) {
+ if (getAsEvent(*i)->has(BaseProperties::PITCH)) {
+ int pitch = get__Int
+ (getAsEvent(*i), BaseProperties::PITCH);
+ if (pitches.size() > 0 && pitches[pitches.size()-1] == pitch)
+ continue;
+ pitches.push_back(pitch);
+ }
+ }
+
+ return pitches;
+}
+
+
+template <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::contains(const Iterator &itr) const
+{
+ for (typename std::vector<typename Container::iterator>::const_iterator
+ i = std::vector<typename Container::iterator>::begin();
+ i != std::vector<typename Container::iterator>::end(); ++i) {
+ if (*i == itr) return true;
+ }
+ return false;
+}
+
+
+template <class Element, class Container, bool singleStaff>
+typename GenericChord<Element, Container, singleStaff>::Iterator
+GenericChord<Element, Container, singleStaff>::getPreviousNote()
+{
+ Iterator i(AbstractSet<Element, Container>::getInitialElement());
+ while (1) {
+ if (i == AbstractSet<Element, Container>::getContainer().begin()) return AbstractSet<Element, Container>::getContainer().end();
+ --i;
+ if (getAsEvent(i)->isa(Note::EventType)) {
+ return i;
+ }
+ }
+}
+
+
+template <class Element, class Container, bool singleStaff>
+typename GenericChord<Element, Container, singleStaff>::Iterator
+GenericChord<Element, Container, singleStaff>::getNextNote()
+{
+ Iterator i(AbstractSet<Element, Container>::getFinalElement());
+ while ( i != AbstractSet<Element, Container>::getContainer().end() &&
+ ++i != AbstractSet<Element, Container>::getContainer().end()) {
+ if (getAsEvent(i)->isa(Note::EventType)) {
+ return i;
+ }
+ }
+ return AbstractSet<Element, Container>::getContainer().end();
+}
+
+
+template <class Element, class Container, bool singleStaff>
+typename GenericChord<Element, Container, singleStaff>::Iterator
+GenericChord<Element, Container, singleStaff>::getFirstElementNotInChord()
+{
+ return m_firstReject;
+}
+
+
+template <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::PitchGreater::operator()(const Iterator &a,
+ const Iterator &b)
+{
+ try {
+ long ap = get__Int(getAsEvent(a), BaseProperties::PITCH);
+ long bp = get__Int(getAsEvent(b), BaseProperties::PITCH);
+ return (ap < bp);
+ } catch (Event::NoData) {
+ std::cerr << "Bad karma: PitchGreater failed to find one or both pitches" << std::endl;
+ return false;
+ }
+}
+
+
+typedef GenericChord<Event, Segment, true> Chord;
+typedef GenericChord<Event, CompositionTimeSliceAdapter, false> GlobalChord;
+
+
+}
+
+
+#endif
+
diff --git a/src/base/SnapGrid.cpp b/src/base/SnapGrid.cpp
new file mode 100644
index 0000000..6d0061e
--- /dev/null
+++ b/src/base/SnapGrid.cpp
@@ -0,0 +1,192 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "SnapGrid.h"
+#include "Composition.h"
+
+namespace Rosegarden {
+
+
+//////////////////////////////////////////////////////////////////////
+// SnapGrid
+//////////////////////////////////////////////////////////////////////
+
+
+const timeT SnapGrid::NoSnap = -1;
+const timeT SnapGrid::SnapToBar = -2;
+const timeT SnapGrid::SnapToBeat = -3;
+const timeT SnapGrid::SnapToUnit = -4;
+
+SnapGrid::SnapGrid(RulerScale *rulerScale, int ysnap) :
+ m_rulerScale(rulerScale),
+ m_snapTime(SnapToBeat),
+ m_ysnap(ysnap)
+{
+ // nothing else
+}
+
+void
+SnapGrid::setSnapTime(timeT snap)
+{
+ assert(snap > 0 ||
+ snap == NoSnap ||
+ snap == SnapToBar ||
+ snap == SnapToBeat ||
+ snap == SnapToUnit);
+ m_snapTime = snap;
+}
+
+timeT
+SnapGrid::getSnapSetting() const
+{
+ return m_snapTime;
+}
+
+timeT
+SnapGrid::getSnapTime(double x) const
+{
+ timeT time = m_rulerScale->getTimeForX(x);
+ return getSnapTime(time);
+}
+
+timeT
+SnapGrid::getSnapTime(timeT time) const
+{
+ if (m_snapTime == NoSnap) return 0;
+
+ Rosegarden::Composition *composition = m_rulerScale->getComposition();
+ int barNo = composition->getBarNumber(time);
+ std::pair<timeT, timeT> barRange = composition->getBarRange(barNo);
+
+ timeT snapTime = barRange.second - barRange.first;
+
+ if (m_snapTime == SnapToBeat) {
+ snapTime = composition->getTimeSignatureAt(time).getBeatDuration();
+ } else if (m_snapTime == SnapToUnit) {
+ snapTime = composition->getTimeSignatureAt(time).getUnitDuration();
+ } else if (m_snapTime != SnapToBar && m_snapTime < snapTime) {
+ snapTime = m_snapTime;
+ }
+
+ return snapTime;
+}
+
+timeT
+SnapGrid::snapX(double x, SnapDirection direction) const
+{
+ return snapTime(m_rulerScale->getTimeForX(x), direction);
+}
+
+timeT
+SnapGrid::snapTime(timeT time, SnapDirection direction) const
+{
+ if (m_snapTime == NoSnap) return time;
+
+ Rosegarden::Composition *composition = m_rulerScale->getComposition();
+ int barNo = composition->getBarNumber(time);
+ std::pair<timeT, timeT> barRange = composition->getBarRange(barNo);
+
+ timeT snapTime = barRange.second - barRange.first;
+
+ if (m_snapTime == SnapToBeat) {
+ snapTime = composition->getTimeSignatureAt(time).getBeatDuration();
+ } else if (m_snapTime == SnapToUnit) {
+ snapTime = composition->getTimeSignatureAt(time).getUnitDuration();
+ } else if (m_snapTime != SnapToBar && m_snapTime < snapTime) {
+ snapTime = m_snapTime;
+ }
+
+ timeT offset = (time - barRange.first);
+ timeT rounded = (offset / snapTime) * snapTime;
+
+ timeT left = rounded + barRange.first;
+ timeT right = left + snapTime;
+
+ if (direction == SnapLeft) return left;
+ else if (direction == SnapRight) return right;
+ else if ((offset - rounded) > (rounded + snapTime - offset)) return right;
+ else return left;
+}
+
+int
+SnapGrid::getYBin(int y) const
+{
+ if (m_ysnap == 0) return y;
+
+ int cy = 0;
+
+ std::map<int, int>::const_iterator i = m_ymultiple.begin();
+
+ int nextbin = -1;
+ if (i != m_ymultiple.end()) nextbin = i->first;
+
+ for (int b = 0; ; ++b) {
+
+ if (nextbin == b) {
+
+ cy += i->second * m_ysnap;
+ ++i;
+ if (i == m_ymultiple.end()) nextbin = -1;
+ else nextbin = i->first;
+
+ } else {
+
+ cy += m_ysnap;
+ }
+
+ if (cy > y) {
+ return b;
+ }
+ }
+}
+
+int
+SnapGrid::getYBinCoordinate(int bin) const
+{
+ if (m_ysnap == 0) return bin;
+
+ int y = 0;
+
+ std::map<int, int>::const_iterator i = m_ymultiple.begin();
+
+ int nextbin = -1;
+ if (i != m_ymultiple.end()) nextbin = i->first;
+
+ for (int b = 0; b < bin; ++b) {
+
+ if (nextbin == b) {
+
+ y += i->second * m_ysnap;
+ ++i;
+ if (i == m_ymultiple.end()) nextbin = -1;
+ else nextbin = i->first;
+
+ } else {
+
+ y += m_ysnap;
+ }
+ }
+
+ return y;
+}
+
+
+}
diff --git a/src/base/SnapGrid.h b/src/base/SnapGrid.h
new file mode 100644
index 0000000..e0c9ec5
--- /dev/null
+++ b/src/base/SnapGrid.h
@@ -0,0 +1,183 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SNAP_GRID_H_
+#define _SNAP_GRID_H_
+
+#include "RulerScale.h"
+
+#include <map>
+
+namespace Rosegarden {
+
+/**
+ * SnapGrid is a class that maps x-coordinate onto time, using a
+ * RulerScale to get the mapping but constraining the results to a
+ * discrete set of suitable times.
+ *
+ * (It also snaps y-coordinates, but that bit isn't very interesting.)
+ */
+
+class SnapGrid
+{
+public:
+ /**
+ * Construct a SnapGrid that uses the given RulerScale for
+ * x-coordinate mappings and the given ysnap for y-coords.
+ * If ysnap is zero, y-coords are not snapped at all.
+ */
+ SnapGrid(RulerScale *rulerScale, int ysnap = 0);
+
+ static const timeT NoSnap;
+ static const timeT SnapToBar;
+ static const timeT SnapToBeat;
+ static const timeT SnapToUnit;
+
+ enum SnapDirection { SnapEither, SnapLeft, SnapRight };
+
+ /**
+ * Set the snap size of the grid to the given time.
+ * The snap time must be positive, or else one of the
+ * special constants NoSnap, SnapToBar, SnapToBeat or
+ * SnapToUnit.
+ * The default is SnapToBeat.
+ */
+ void setSnapTime(timeT snap);
+
+ /**
+ * Return the snap size of the grid, at the given x-coordinate.
+ * (The x-coordinate is required in case the built-in snap size is
+ * SnapToBar, SnapToBeat or SnapToUnit, in which case we need to
+ * know the current time signature.) Returns zero for NoSnap.
+ */
+ timeT getSnapTime(double x) const;
+
+ /**
+ * Return the snap setting -- the argument that was passed to
+ * setSnapTime. This differs from getSnapTime, which interprets
+ * the NoSnap, SnapToBar, SnapToBeat and SnapToUnit settings to
+ * return actual timeT values; instead this function returns those
+ * actual constants if set.
+ */
+ timeT getSnapSetting() const;
+
+ /**
+ * Return the snap size of the grid, at the given time. (The time
+ * is required in case the built-in snap size is SnapToBar,
+ * SnapToBeat or SnapToUnit, in which case we need to know the
+ * current time signature.) Returns zero for NoSnap.
+ */
+ timeT getSnapTime(timeT t) const;
+
+ /**
+ * Snap a given x-coordinate to the nearest time on the grid. Of
+ * course this also does x-to-time conversion, so it's useful even
+ * in NoSnap mode. If the snap time is greater than the bar
+ * duration at this point, the bar duration will be used instead.
+ *
+ * If d is SnapLeft or SnapRight, a time to the left or right
+ * respectively of the given coordinate will be returned;
+ * otherwise the nearest time on either side will be returned.
+ */
+ timeT snapX(double x, SnapDirection d = SnapEither) const;
+
+ /**
+ * Snap a given time to the nearest time on the grid. Unlike
+ * snapX, this is not useful in NoSnap mode. If the snap time is
+ * greater than the bar duration at this point, the bar duration
+ * will be used instead.
+ *
+ * If d is SnapLeft or SnapRight, a time to the left or right
+ * respectively of the given coordinate will be returned;
+ * otherwise the nearest time on either side will be returned.
+ */
+ timeT snapTime(timeT t, SnapDirection d = SnapEither) const;
+
+ /**
+ * Snap a given y-coordinate to the nearest lower bin coordinate.
+ */
+ int snapY(int y) const {
+ if (m_ysnap == 0) return y;
+ return getYBinCoordinate(getYBin(y));
+ }
+
+ /**
+ * Return the bin number for the given y-coordinate.
+ */
+ int getYBin(int y) const;
+
+ /**
+ * Return the y-coordinate of the grid line at the start of the
+ * given bin.
+ */
+ int getYBinCoordinate(int bin) const;
+
+ /**
+ * Set the default vertical step. This is used as the height for
+ * bins that have no specific height multiple set, and the base
+ * height for bins that have a multiple. Setting the Y snap here
+ * is equivalent to specifying it in the constructor.
+ */
+ void setYSnap(int ysnap) {
+ m_ysnap = ysnap;
+ }
+
+ /**
+ * Retrieve the default vertical step.
+ */
+ int getYSnap() const {
+ return m_ysnap;
+ }
+
+ /**
+ * Set the height multiple for a specific bin. The bin will be
+ * multiple * ysnap high. The default is 1 for all bins.
+ */
+ void setBinHeightMultiple(int bin, int multiple) {
+ m_ymultiple[bin] = multiple;
+ }
+
+ /**
+ * Retrieve the height multiple for a bin.
+ */
+ int getBinHeightMultiple(int bin) {
+ if (m_ymultiple.find(bin) == m_ymultiple.end()) return 1;
+ return m_ymultiple[bin];
+ }
+
+ RulerScale *getRulerScale() {
+ return m_rulerScale;
+ }
+
+ const RulerScale *getRulerScale() const {
+ return m_rulerScale;
+ }
+
+protected:
+ RulerScale *m_rulerScale; // I don't own this
+ timeT m_snapTime;
+ int m_ysnap;
+ std::map<int, int> m_ymultiple;
+};
+
+}
+
+#endif
diff --git a/src/base/SoftSynthDevice.cpp b/src/base/SoftSynthDevice.cpp
new file mode 100644
index 0000000..9a736b7
--- /dev/null
+++ b/src/base/SoftSynthDevice.cpp
@@ -0,0 +1,174 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "SoftSynthDevice.h"
+#include "Instrument.h"
+#include "MidiTypes.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+
+namespace Rosegarden
+{
+
+ControlList
+SoftSynthDevice::m_controlList;
+
+SoftSynthDevice::SoftSynthDevice() :
+ Device(0, "Default Soft Synth Device", Device::SoftSynth)
+{
+ checkControlList();
+}
+
+SoftSynthDevice::SoftSynthDevice(DeviceId id, const std::string &name) :
+ Device(id, name, Device::SoftSynth)
+{
+ checkControlList();
+}
+
+
+SoftSynthDevice::SoftSynthDevice(const SoftSynthDevice &dev) :
+ Device(dev.getId(), dev.getName(), dev.getType()),
+ Controllable()
+{
+ // Copy the instruments
+ //
+ InstrumentList insList = dev.getAllInstruments();
+ InstrumentList::iterator iIt = insList.begin();
+ for (; iIt != insList.end(); iIt++)
+ m_instruments.push_back(new Instrument(**iIt));
+}
+
+SoftSynthDevice::~SoftSynthDevice()
+{
+}
+
+void
+SoftSynthDevice::checkControlList()
+{
+ // Much as MidiDevice::generateDefaultControllers
+
+ static std::string controls[][9] = {
+ { "Pan", Rosegarden::Controller::EventType, "<none>", "0", "127", "64", "10", "2", "0" },
+ { "Chorus", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "93", "3", "1" },
+ { "Volume", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "7", "1", "2" },
+ { "Reverb", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "91", "3", "3" },
+ { "Sustain", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "64", "4", "-1" },
+ { "Expression", Rosegarden::Controller::EventType, "<none>", "0", "127", "100", "11", "2", "-1" },
+ { "Modulation", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "1", "4", "-1" },
+ { "PitchBend", Rosegarden::PitchBend::EventType, "<none>", "0", "16383", "8192", "1", "4", "-1" }
+ };
+
+ if (m_controlList.empty()) {
+
+ for (unsigned int i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) {
+
+ Rosegarden::ControlParameter con(controls[i][0],
+ controls[i][1],
+ controls[i][2],
+ atoi(controls[i][3].c_str()),
+ atoi(controls[i][4].c_str()),
+ atoi(controls[i][5].c_str()),
+ Rosegarden::MidiByte(atoi(controls[i][6].c_str())),
+ atoi(controls[i][7].c_str()),
+ atoi(controls[i][8].c_str()));
+ m_controlList.push_back(con);
+ }
+ }
+}
+
+const ControlParameter *
+SoftSynthDevice::getControlParameter(int index) const
+{
+ if (index >= 0 && ((unsigned int)index) < m_controlList.size())
+ return &m_controlList[index];
+ return 0;
+}
+
+const ControlParameter *
+SoftSynthDevice::getControlParameter(const std::string &type,
+ Rosegarden::MidiByte controllerValue) const
+{
+ ControlList::iterator it = m_controlList.begin();
+
+ for (; it != m_controlList.end(); ++it)
+ {
+ if (it->getType() == type)
+ {
+ // Return matched on type for most events
+ //
+ if (type != Rosegarden::Controller::EventType)
+ return &*it;
+
+ // Also match controller value for Controller events
+ //
+ if (it->getControllerValue() == controllerValue)
+ return &*it;
+ }
+ }
+
+ return 0;
+}
+
+std::string
+SoftSynthDevice::toXmlString()
+{
+ std::stringstream ssiDevice;
+ InstrumentList::iterator iit;
+
+ ssiDevice << " <device id=\"" << m_id
+ << "\" name=\"" << m_name
+ << "\" type=\"softsynth\">" << std::endl;
+
+ for (iit = m_instruments.begin(); iit != m_instruments.end(); ++iit)
+ ssiDevice << (*iit)->toXmlString();
+
+ ssiDevice << " </device>"
+#if (__GNUC__ < 3)
+ << std::endl << std::ends;
+#else
+ << std::endl;
+#endif
+
+ return ssiDevice.str();
+}
+
+
+// Add to instrument list
+//
+void
+SoftSynthDevice::addInstrument(Instrument *instrument)
+{
+ m_instruments.push_back(instrument);
+}
+
+}
+
+
diff --git a/src/base/SoftSynthDevice.h b/src/base/SoftSynthDevice.h
new file mode 100644
index 0000000..381a58b
--- /dev/null
+++ b/src/base/SoftSynthDevice.h
@@ -0,0 +1,70 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _SOFT_SYNTH_DEVICE_H_
+#define _SOFT_SYNTH_DEVICE_H_
+
+#include <string>
+
+#include "Device.h"
+#include "Instrument.h"
+#include "Controllable.h"
+
+namespace Rosegarden
+{
+
+class SoftSynthDevice : public Device, public Controllable
+{
+
+public:
+ SoftSynthDevice();
+ SoftSynthDevice(DeviceId id, const std::string &name);
+ virtual ~SoftSynthDevice();
+
+ // Copy constructor
+ //
+ SoftSynthDevice(const SoftSynthDevice &);
+
+ virtual void addInstrument(Instrument*);
+
+ // Turn into XML string
+ //
+ virtual std::string toXmlString();
+
+ virtual InstrumentList getAllInstruments() const { return m_instruments; }
+ virtual InstrumentList getPresentationInstruments() const
+ { return m_instruments; }
+
+ // implemented from Controllable interface
+ //
+ virtual const ControlList &getControlParameters() const { return m_controlList; }
+ virtual const ControlParameter *getControlParameter(int index) const;
+ virtual const ControlParameter *getControlParameter(const std::string &type,
+ MidiByte controllerNumber) const;
+
+private:
+ static ControlList m_controlList;
+ static void checkControlList();
+};
+
+}
+
+#endif
diff --git a/src/base/Staff.cpp b/src/base/Staff.cpp
new file mode 100644
index 0000000..e34aaca
--- /dev/null
+++ b/src/base/Staff.cpp
@@ -0,0 +1,213 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Staff.h"
+
+namespace Rosegarden
+{
+
+Staff::Staff(Segment &t) :
+ m_segment(t),
+ m_viewElementList(0)
+{
+ // empty
+}
+
+Staff::~Staff()
+{
+ if (m_viewElementList) m_segment.removeObserver(this);
+ notifySourceDeletion();
+ delete m_viewElementList;
+}
+
+ViewElementList *
+Staff::getViewElementList()
+{
+ return getViewElementList(m_segment.begin(), m_segment.end());
+}
+
+ViewElementList *
+Staff::getViewElementList(Segment::iterator from,
+ Segment::iterator to)
+{
+ if (!m_viewElementList) {
+
+ m_viewElementList = new ViewElementList;
+
+ for (Segment::iterator i = from; i != to; ++i) {
+
+ if (!wrapEvent(*i)) continue;
+
+ ViewElement *el = makeViewElement(*i);
+ m_viewElementList->insert(el);
+ }
+
+ m_segment.addObserver(this);
+
+ }
+
+ return m_viewElementList;
+}
+
+bool
+Staff::wrapEvent(Event *e)
+{
+ timeT emt = m_segment.getEndMarkerTime();
+ return
+ (e->getAbsoluteTime() < emt) ||
+ (e->getAbsoluteTime() == emt && e->getDuration() == 0);
+}
+
+ViewElementList::iterator
+Staff::findEvent(Event *e)
+{
+ // Note that we have to create this using the virtual
+ // makeViewElement, because the result of equal_range depends on
+ // the value of the view absolute time for the element, which
+ // depends on the particular subclass of ViewElement in use.
+
+ //!!! (This is also why this method has to be here and not in
+ // ViewElementList -- ViewElementList has no equivalent of
+ // makeViewElement. Possibly things like NotationElementList
+ // should be subclasses of ViewElementList that implement
+ // makeViewElement instead of having makeViewElement in Staff, but
+ // that's for another day.)
+
+ ViewElement *dummy = makeViewElement(e);
+
+ std::pair<ViewElementList::iterator,
+ ViewElementList::iterator>
+ r = m_viewElementList->equal_range(dummy);
+
+ delete dummy;
+
+ for (ViewElementList::iterator i = r.first; i != r.second; ++i) {
+ if ((*i)->event() == e) {
+ return i;
+ }
+ }
+
+ return m_viewElementList->end();
+}
+
+void
+Staff::eventAdded(const Segment *t, Event *e)
+{
+ assert(t == &m_segment);
+ (void)t; // avoid warnings
+
+ if (wrapEvent(e)) {
+ ViewElement *el = makeViewElement(e);
+ m_viewElementList->insert(el);
+ notifyAdd(el);
+ }
+}
+
+void
+Staff::eventRemoved(const Segment *t, Event *e)
+{
+ assert(t == &m_segment);
+ (void)t; // avoid warnings
+
+ // If we have it, lose it
+
+ ViewElementList::iterator i = findEvent(e);
+ if (i != m_viewElementList->end()) {
+ notifyRemove(*i);
+ m_viewElementList->erase(i);
+ return;
+ }
+
+// std::cerr << "Event at " << e->getAbsoluteTime() << ", notation time " << e->getNotationAbsoluteTime() << ", type " << e->getType()
+// << " not found in Staff" << std::endl;
+}
+
+void
+Staff::endMarkerTimeChanged(const Segment *segment, bool shorten)
+{
+ Segment *s = const_cast<Segment *>(segment);
+
+ assert(s == &m_segment);
+
+ if (shorten) {
+
+ m_viewElementList->erase
+ (m_viewElementList->findTime(s->getEndMarkerTime()),
+ m_viewElementList->end());
+
+ } else {
+
+ timeT myLastEltTime = s->getStartTime();
+ if (m_viewElementList->end() != m_viewElementList->begin()) {
+ ViewElementList::iterator i = m_viewElementList->end();
+ myLastEltTime = (*--i)->event()->getAbsoluteTime();
+ }
+
+ for (Segment::iterator j = s->findTime(myLastEltTime);
+ s->isBeforeEndMarker(j); ++j) {
+
+ ViewElementList::iterator newi = findEvent(*j);
+ if (newi == m_viewElementList->end()) {
+ m_viewElementList->insert(makeViewElement(*j));
+ }
+ }
+ }
+}
+void
+Staff::segmentDeleted(const Segment *s)
+{
+ assert(s == &m_segment);
+ (void)s; // avoid warnings
+ /*
+ std::cerr << "WARNING: Staff notified of segment deletion: this is probably a bug "
+ << "(staff should have been deleted before segment)" << std::endl;
+ */
+}
+
+void
+Staff::notifyAdd(ViewElement *e) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->elementAdded(this, e);
+ }
+}
+
+void
+Staff::notifyRemove(ViewElement *e) const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->elementRemoved(this, e);
+ }
+}
+
+void
+Staff::notifySourceDeletion() const
+{
+ for (ObserverSet::const_iterator i = m_observers.begin();
+ i != m_observers.end(); ++i) {
+ (*i)->staffDeleted(this);
+ }
+}
+
+
+}
diff --git a/src/base/Staff.h b/src/base/Staff.h
new file mode 100644
index 0000000..ad06930
--- /dev/null
+++ b/src/base/Staff.h
@@ -0,0 +1,149 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _STAFF_H_
+#define _STAFF_H_
+
+#include "ViewElement.h"
+#include "Segment.h"
+
+#include <iostream>
+#include <cassert>
+
+namespace Rosegarden
+{
+
+class StaffObserver;
+
+/**
+ * Staff is the base class for classes which represent a Segment as an
+ * on-screen graphic. It manages the relationship between Segment/Event
+ * and specific implementations of ViewElement.
+ *
+ * The template argument T must be a subclass of ViewElement.
+ *
+ * Staff was formerly known as ViewElementsManager.
+ */
+class Staff : public SegmentObserver
+{
+public:
+ virtual ~Staff();
+
+ /**
+ * Create a new ViewElementList wrapping all Events in the
+ * segment, or return the previously created one
+ */
+ ViewElementList *getViewElementList();
+
+ /**
+ * Create a new ViewElementList wrapping Events in the
+ * [from, to[ interval, or return the previously created one
+ * (even if passed new arguments)
+ */
+ ViewElementList *getViewElementList(Segment::iterator from,
+ Segment::iterator to);
+
+ /**
+ * Return the Segment wrapped by this object
+ */
+ Segment &getSegment() { return m_segment; }
+
+ /**
+ * Return the Segment wrapped by this object
+ */
+ const Segment &getSegment() const { return m_segment; }
+
+ /**
+ * Return the location of the given event in this Staff
+ */
+ ViewElementList::iterator findEvent(Event *);
+
+ /**
+ * SegmentObserver method - called after the event has been added to
+ * the segment
+ */
+ virtual void eventAdded(const Segment *, Event *);
+
+ /**
+ * SegmentObserver method - called after the event has been removed
+ * from the segment, and just before it is deleted
+ */
+ virtual void eventRemoved(const Segment *, Event *);
+
+ /**
+ * SegmentObserver method - called after the segment's end marker
+ * time has been changed
+ */
+ virtual void endMarkerTimeChanged(const Segment *, bool shorten);
+
+ /**
+ * SegmentObserver method - called from Segment dtor
+ */
+ virtual void segmentDeleted(const Segment *);
+
+ void addObserver (StaffObserver *obs) { m_observers.push_back(obs); }
+ void removeObserver(StaffObserver *obs) { m_observers.remove(obs); }
+
+protected:
+ Staff(Segment &);
+ virtual ViewElement* makeViewElement(Event*) = 0;
+
+ /**
+ * Return true if the event should be wrapped
+ * Useful for piano roll where we only want to wrap notes
+ * (always true by default)
+ */
+ virtual bool wrapEvent(Event *);
+
+ void notifyAdd(ViewElement *) const;
+ void notifyRemove(ViewElement *) const;
+ void notifySourceDeletion() const;
+
+ //--------------- Data members ---------------------------------
+
+ Segment &m_segment;
+ ViewElementList *m_viewElementList;
+
+ typedef std::list<StaffObserver*> ObserverSet;
+ ObserverSet m_observers;
+
+private: // not provided
+ Staff(const Staff &);
+ Staff &operator=(const Staff &);
+};
+
+class StaffObserver
+{
+public:
+ virtual ~StaffObserver() {}
+ virtual void elementAdded(const Staff *, ViewElement *) = 0;
+ virtual void elementRemoved(const Staff *, ViewElement *) = 0;
+
+ /// called when the observed object is being deleted
+ virtual void staffDeleted(const Staff *) = 0;
+};
+
+
+
+}
+
+#endif
+
diff --git a/src/base/StaffExportTypes.h b/src/base/StaffExportTypes.h
new file mode 100644
index 0000000..0aeeb78
--- /dev/null
+++ b/src/base/StaffExportTypes.h
@@ -0,0 +1,75 @@
+/* -*- 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]>
+
+ This file Coypright 2008 D. Michael McIntyre
+
+ 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.
+*/
+
+#ifndef _STAFF_EXPORT_H_
+#define _STAFF_EXPORT_H_
+
+namespace Rosegarden
+{
+
+/**
+ * StaffTypes are currently only used for LilyPond export, and amount to named
+ * constant indices for the Track Parameters Box. They are used to control the
+ * size of notation exported on a given staff, and boil down to a complicated
+ * way to insert a \tiny or \small in the data stream ahead of the first clef,
+ * etc.
+ */
+
+typedef int StaffType;
+
+namespace StaffTypes
+{
+ const StaffType Normal = 0;
+ const StaffType Small = 1;
+ const StaffType Tiny = 2;
+}
+
+/**
+ * Brackets are currently only used for LilyPond export, and amount to named
+ * constant indices for the Track Parameters Box. They are used to control how
+ * staffs are bracketed together, and it is unfortunately necessary to have a
+ * staggering number of them in order to handle all the possible combinations of
+ * opening and closing brackets while keeping the interface as simple as
+ * possible.
+ */
+
+typedef int Bracket;
+
+namespace Brackets
+{
+ const Bracket None = 0; // ----
+ const Bracket SquareOn = 1; // [
+ const Bracket SquareOff = 2; // ]
+ const Bracket SquareOnOff = 3; // [ ]
+ const Bracket CurlyOn = 4; // {
+ const Bracket CurlyOff = 5; // }
+ const Bracket CurlySquareOn = 6; // {[
+ const Bracket CurlySquareOff = 7; // ]}
+}
+
+}
+
+#endif
diff --git a/src/base/Studio.cpp b/src/base/Studio.cpp
new file mode 100644
index 0000000..89e7cc1
--- /dev/null
+++ b/src/base/Studio.cpp
@@ -0,0 +1,674 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <iostream>
+
+#include "Studio.h"
+#include "MidiDevice.h"
+#include "AudioDevice.h"
+#include "Instrument.h"
+
+#include "Segment.h"
+#include "Track.h"
+#include "Composition.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+using std::cerr;
+using std::endl;
+
+
+namespace Rosegarden
+{
+
+Studio::Studio() :
+ m_midiThruFilter(0),
+ m_midiRecordFilter(0),
+ m_mixerDisplayOptions(0),
+ m_metronomeDevice(0)
+{
+ // We _always_ have a buss with id zero, for the master out
+ m_busses.push_back(new Buss(0));
+
+ // And we always create one audio record in
+ m_recordIns.push_back(new RecordIn());
+}
+
+Studio::~Studio()
+{
+ DeviceListIterator dIt = m_devices.begin();
+
+ for (; dIt != m_devices.end(); ++dIt)
+ delete(*dIt);
+
+ m_devices.clear();
+
+ for (size_t i = 0; i < m_busses.size(); ++i) {
+ delete m_busses[i];
+ }
+
+ for (size_t i = 0; i < m_recordIns.size(); ++i) {
+ delete m_recordIns[i];
+ }
+}
+
+void
+Studio::addDevice(const std::string &name,
+ DeviceId id,
+ Device::DeviceType type)
+{
+ switch(type)
+ {
+ case Device::Midi:
+ m_devices.push_back(new MidiDevice(id, name, MidiDevice::Play));
+ break;
+
+ case Device::Audio:
+ m_devices.push_back(new AudioDevice(id, name));
+ break;
+
+ default:
+ std::cerr << "Studio::addDevice() - unrecognised device"
+ << endl;
+ break;
+ }
+}
+
+void
+Studio::addDevice(Device *device)
+{
+ m_devices.push_back(device);
+}
+
+void
+Studio::removeDevice(DeviceId id)
+{
+ DeviceListIterator it;
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ if ((*it)->getId() == id) {
+ delete *it;
+ m_devices.erase(it);
+ return;
+ }
+ }
+}
+
+InstrumentList
+Studio::getAllInstruments()
+{
+ InstrumentList list, subList;
+
+ DeviceListIterator it;
+
+ // Append lists
+ //
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ // get sub list
+ subList = (*it)->getAllInstruments();
+
+ // concetenate
+ list.insert(list.end(), subList.begin(), subList.end());
+ }
+
+ return list;
+
+}
+
+InstrumentList
+Studio::getPresentationInstruments()
+{
+ InstrumentList list, subList;
+
+ std::vector<Device*>::iterator it;
+ MidiDevice *midiDevice;
+
+ // Append lists
+ //
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice)
+ {
+ // skip read-only devices
+ if (midiDevice->getDirection() == MidiDevice::Record)
+ continue;
+ }
+
+ // get sub list
+ subList = (*it)->getPresentationInstruments();
+
+ // concatenate
+ list.insert(list.end(), subList.begin(), subList.end());
+ }
+
+ return list;
+
+}
+
+Instrument*
+Studio::getInstrumentById(InstrumentId id)
+{
+ std::vector<Device*>::iterator it;
+ InstrumentList list;
+ InstrumentList::iterator iit;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ list = (*it)->getAllInstruments();
+
+ for (iit = list.begin(); iit != list.end(); iit++)
+ if ((*iit)->getId() == id)
+ return (*iit);
+ }
+
+ return 0;
+
+}
+
+// From a user selection (from a "Presentation" list) return
+// the matching Instrument
+//
+Instrument*
+Studio::getInstrumentFromList(int index)
+{
+ std::vector<Device*>::iterator it;
+ InstrumentList list;
+ InstrumentList::iterator iit;
+ int count = 0;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ MidiDevice *midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice)
+ {
+ // skip read-only devices
+ if (midiDevice->getDirection() == MidiDevice::Record)
+ continue;
+ }
+
+ list = (*it)->getPresentationInstruments();
+
+ for (iit = list.begin(); iit != list.end(); iit++)
+ {
+ if (count == index)
+ return (*iit);
+
+ count++;
+ }
+ }
+
+ return 0;
+
+}
+
+Instrument *
+Studio::getInstrumentFor(Segment *segment)
+{
+ if (!segment) return 0;
+ if (!segment->getComposition()) return 0;
+ TrackId tid = segment->getTrack();
+ Track *track = segment->getComposition()->getTrackById(tid);
+ if (!track) return 0;
+ return getInstrumentFor(track);
+}
+
+Instrument *
+Studio::getInstrumentFor(Track *track)
+{
+ if (!track) return 0;
+ InstrumentId iid = track->getInstrument();
+ return getInstrumentById(iid);
+}
+
+BussList
+Studio::getBusses()
+{
+ return m_busses;
+}
+
+Buss *
+Studio::getBussById(BussId id)
+{
+ for (BussList::iterator i = m_busses.begin(); i != m_busses.end(); ++i) {
+ if ((*i)->getId() == id) return *i;
+ }
+ return 0;
+}
+
+void
+Studio::addBuss(Buss *buss)
+{
+ m_busses.push_back(buss);
+}
+
+PluginContainer *
+Studio::getContainerById(InstrumentId id)
+{
+ PluginContainer *pc = getInstrumentById(id);
+ if (pc) return pc;
+ else return getBussById(id);
+}
+
+RecordIn *
+Studio::getRecordIn(int number)
+{
+ if (number >= 0 && number < int(m_recordIns.size())) return m_recordIns[number];
+ else return 0;
+}
+
+// Clear down the devices - the devices will clear down their
+// own Instruments.
+//
+void
+Studio::clear()
+{
+ InstrumentList list;
+ std::vector<Device*>::iterator it;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ delete *it;
+
+ m_devices.erase(m_devices.begin(), m_devices.end());
+}
+
+std::string
+Studio::toXmlString()
+{
+ return toXmlString(std::vector<DeviceId>());
+}
+
+std::string
+Studio::toXmlString(const std::vector<DeviceId> &devices)
+{
+ std::stringstream studio;
+
+ studio << "<studio thrufilter=\"" << m_midiThruFilter
+ << "\" recordfilter=\"" << m_midiRecordFilter
+ << "\" audioinputpairs=\"" << m_recordIns.size()
+ << "\" mixerdisplayoptions=\"" << m_mixerDisplayOptions
+ << "\" metronomedevice=\"" << m_metronomeDevice
+ << "\">" << endl << endl;
+
+ studio << endl;
+
+ InstrumentList list;
+
+ // Get XML version of devices
+ //
+ if (devices.empty()) { // export all devices and busses
+
+ for (DeviceListIterator it = m_devices.begin();
+ it != m_devices.end(); it++) {
+ studio << (*it)->toXmlString() << endl << endl;
+ }
+
+ for (BussList::iterator it = m_busses.begin();
+ it != m_busses.end(); ++it) {
+ studio << (*it)->toXmlString() << endl << endl;
+ }
+
+ } else {
+ for (std::vector<DeviceId>::const_iterator di(devices.begin());
+ di != devices.end(); ++di) {
+ Device *d = getDevice(*di);
+ if (!d) {
+ std::cerr << "WARNING: Unknown device id " << (*di)
+ << " in Studio::toXmlString" << std::endl;
+ } else {
+ studio << d->toXmlString() << endl << endl;
+ }
+ }
+ }
+
+ studio << endl << endl;
+
+#if (__GNUC__ < 3)
+ studio << "</studio>" << endl << std::ends;
+#else
+ studio << "</studio>" << endl;
+#endif
+
+ return studio.str();
+}
+
+// Run through the Devices checking for MidiDevices and
+// returning the first Metronome we come across
+//
+const MidiMetronome*
+Studio::getMetronomeFromDevice(DeviceId id)
+{
+ std::vector<Device*>::iterator it;
+ MidiDevice *midiDevice;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice &&
+ midiDevice->getId() == id &&
+ midiDevice->getMetronome())
+ {
+ return midiDevice->getMetronome();
+ }
+ }
+
+ return 0;
+}
+
+// Scan all MIDI devices for available channels and map
+// them to a current program
+
+Instrument*
+Studio::assignMidiProgramToInstrument(MidiByte program,
+ int msb, int lsb,
+ bool percussion)
+{
+ MidiDevice *midiDevice;
+ std::vector<Device*>::iterator it;
+ Rosegarden::InstrumentList::iterator iit;
+ Rosegarden::InstrumentList instList;
+
+ // Instruments that we may return
+ //
+ Rosegarden::Instrument *newInstrument = 0;
+ Rosegarden::Instrument *firstInstrument = 0;
+
+ bool needBank = (msb >= 0 || lsb >= 0);
+ if (needBank) {
+ if (msb < 0) msb = 0;
+ if (lsb < 0) lsb = 0;
+ }
+
+ // Pass one - search through all MIDI instruments looking for
+ // a match that we can re-use. i.e. if we have a matching
+ // Program Change then we can use this Instrument.
+ //
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice && midiDevice->getDirection() == MidiDevice::Play)
+ {
+ instList = (*it)->getPresentationInstruments();
+
+ for (iit = instList.begin(); iit != instList.end(); iit++)
+ {
+ if (firstInstrument == 0)
+ firstInstrument = *iit;
+
+ // If we find an Instrument sending the right program already.
+ //
+ if ((*iit)->sendsProgramChange() &&
+ (*iit)->getProgramChange() == program &&
+ (!needBank || ((*iit)->sendsBankSelect() &&
+ (*iit)->getMSB() == msb &&
+ (*iit)->getLSB() == lsb &&
+ (*iit)->isPercussion() == percussion)))
+ {
+ return (*iit);
+ }
+ else
+ {
+ // Ignore the program change and use the percussion
+ // flag.
+ //
+ if ((*iit)->isPercussion() && percussion)
+ {
+ return (*iit);
+ }
+
+ // Otherwise store the first Instrument for
+ // possible use later.
+ //
+ if (newInstrument == 0 &&
+ (*iit)->sendsProgramChange() == false &&
+ (*iit)->sendsBankSelect() == false &&
+ (*iit)->isPercussion() == percussion)
+ newInstrument = *iit;
+ }
+ }
+ }
+ }
+
+
+ // Okay, if we've got this far and we have a new Instrument to use
+ // then use it.
+ //
+ if (newInstrument != 0)
+ {
+ newInstrument->setSendProgramChange(true);
+ newInstrument->setProgramChange(program);
+
+ if (needBank) {
+ newInstrument->setSendBankSelect(true);
+ newInstrument->setPercussion(percussion);
+ newInstrument->setMSB(msb);
+ newInstrument->setLSB(lsb);
+ }
+ }
+ else // Otherwise we just reuse the first Instrument we found
+ newInstrument = firstInstrument;
+
+
+ return newInstrument;
+}
+
+// Just make all of these Instruments available for automatic
+// assignment in the assignMidiProgramToInstrument() method
+// by invalidating the ProgramChange flag.
+//
+// This method sounds much more dramatic than it actually is -
+// it could probably do with a rename.
+//
+//
+void
+Studio::unassignAllInstruments()
+{
+ MidiDevice *midiDevice;
+ AudioDevice *audioDevice;
+ std::vector<Device*>::iterator it;
+ Rosegarden::InstrumentList::iterator iit;
+ Rosegarden::InstrumentList instList;
+ int channel = 0;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice)
+ {
+ instList = (*it)->getPresentationInstruments();
+
+ for (iit = instList.begin(); iit != instList.end(); iit++)
+ {
+ // Only for true MIDI Instruments - not System ones
+ //
+ if ((*iit)->getId() >= MidiInstrumentBase)
+ {
+ (*iit)->setSendBankSelect(false);
+ (*iit)->setSendProgramChange(false);
+ (*iit)->setMidiChannel(channel);
+ channel = ( channel + 1 ) % 16;
+
+ (*iit)->setSendPan(false);
+ (*iit)->setSendVolume(false);
+ (*iit)->setPan(MidiMidValue);
+ (*iit)->setVolume(100);
+
+ }
+ }
+ }
+ else
+ {
+ audioDevice = dynamic_cast<AudioDevice*>(*it);
+
+ if (audioDevice)
+ {
+ instList = (*it)->getPresentationInstruments();
+
+ for (iit = instList.begin(); iit != instList.end(); iit++)
+ (*iit)->emptyPlugins();
+ }
+ }
+ }
+}
+
+void
+Studio::clearMidiBanksAndPrograms()
+{
+ MidiDevice *midiDevice;
+ std::vector<Device*>::iterator it;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice)
+ {
+ midiDevice->clearProgramList();
+ midiDevice->clearBankList();
+ }
+ }
+}
+
+void
+Studio::clearBusses()
+{
+ for (size_t i = 0; i < m_busses.size(); ++i) {
+ delete m_busses[i];
+ }
+ m_busses.clear();
+ m_busses.push_back(new Buss(0));
+}
+
+void
+Studio::clearRecordIns()
+{
+ for (size_t i = 0; i < m_recordIns.size(); ++i) {
+ delete m_recordIns[i];
+ }
+ m_recordIns.clear();
+ m_recordIns.push_back(new RecordIn());
+}
+
+Device*
+Studio::getDevice(DeviceId id)
+{
+ //cerr << "Studio[" << this << "]::getDevice(" << id << ")... ";
+
+ std::vector<Device*>::iterator it;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++) {
+
+// if (it != m_devices.begin()) cerr << ", ";
+
+// cerr << (*it)->getId();
+ if ((*it)->getId() == id) {
+ //cerr << ". Found" << endl;
+ return (*it);
+ }
+ }
+
+ //cerr << ". Not found" << endl;
+
+ return 0;
+}
+
+std::string
+Studio::getSegmentName(InstrumentId id)
+{
+ MidiDevice *midiDevice;
+ std::vector<Device*>::iterator it;
+ Rosegarden::InstrumentList::iterator iit;
+ Rosegarden::InstrumentList instList;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*it);
+
+ if (midiDevice)
+ {
+ instList = (*it)->getAllInstruments();
+
+ for (iit = instList.begin(); iit != instList.end(); iit++)
+ {
+ if ((*iit)->getId() == id)
+ {
+ if ((*iit)->sendsProgramChange())
+ {
+ return (*iit)->getProgramName();
+ }
+ else
+ {
+ return midiDevice->getName() + " " + (*iit)->getName();
+ }
+ }
+ }
+ }
+ }
+
+ return std::string("");
+}
+
+InstrumentId
+Studio::getAudioPreviewInstrument()
+{
+ AudioDevice *audioDevice;
+ std::vector<Device*>::iterator it;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ audioDevice = dynamic_cast<AudioDevice*>(*it);
+
+ // Just the first one will do - we can make this more
+ // subtle if we need to later.
+ //
+ if (audioDevice)
+ return audioDevice->getPreviewInstrument();
+ }
+
+ // system instrument - won't accept audio
+ return 0;
+}
+
+bool
+Studio::haveMidiDevices() const
+{
+ Rosegarden::DeviceListConstIterator it = m_devices.begin();
+ for (; it != m_devices.end(); it++)
+ {
+ if ((*it)->getType() == Device::Midi) return true;
+ }
+ return false;
+}
+
+
+}
+
diff --git a/src/base/Studio.h b/src/base/Studio.h
new file mode 100644
index 0000000..7223524
--- /dev/null
+++ b/src/base/Studio.h
@@ -0,0 +1,208 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 <string>
+#include <vector>
+
+#include "XmlExportable.h"
+#include "Instrument.h"
+#include "Device.h"
+#include "MidiProgram.h"
+#include "ControlParameter.h"
+
+// The Studio is where Midi and Audio devices live. We can query
+// them for a list of Instruments, connect them together or to
+// effects units (eventually) and generally do real studio-type
+// stuff to them.
+//
+//
+
+
+#ifndef _STUDIO_H_
+#define _STUDIO_H_
+
+namespace Rosegarden
+{
+
+typedef std::vector<Instrument *> InstrumentList;
+typedef std::vector<Device*> DeviceList;
+typedef std::vector<Buss *> BussList;
+typedef std::vector<RecordIn *> RecordInList;
+typedef std::vector<Device*>::iterator DeviceListIterator;
+typedef std::vector<Device*>::const_iterator DeviceListConstIterator;
+
+class MidiDevice;
+
+class Segment;
+class Track;
+
+
+class Studio : public XmlExportable
+{
+
+public:
+ Studio();
+ ~Studio();
+
+private:
+ Studio(const Studio &);
+ Studio& operator=(const Studio &);
+public:
+
+ void addDevice(const std::string &name,
+ DeviceId id,
+ Device::DeviceType type);
+ void addDevice(Device *device);
+
+ void removeDevice(DeviceId id);
+
+ // Return the combined instrument list from all devices
+ // for all and presentation Instrument (Presentation is
+ // just a subset of All).
+ //
+ InstrumentList getAllInstruments();
+ InstrumentList getPresentationInstruments();
+
+ // Return an Instrument
+ Instrument* getInstrumentById(InstrumentId id);
+ Instrument* getInstrumentFromList(int index);
+
+ // Convenience functions
+ Instrument *getInstrumentFor(Segment *);
+ Instrument *getInstrumentFor(Track *);
+
+ // Return a Buss
+ BussList getBusses();
+ Buss *getBussById(BussId id);
+ void addBuss(Buss *buss);
+
+ // Return an Instrument or a Buss
+ PluginContainer *getContainerById(InstrumentId id);
+
+ RecordInList getRecordIns() { return m_recordIns; }
+ RecordIn *getRecordIn(int number);
+ void addRecordIn(RecordIn *ri) { m_recordIns.push_back(ri); }
+
+ // A clever method to best guess MIDI file program mappings
+ // to available MIDI channels across all MidiDevices.
+ //
+ // Set the percussion flag if it's a percussion channel (mapped
+ // channel) we're after.
+ //
+ Instrument* assignMidiProgramToInstrument(MidiByte program,
+ bool percussion) {
+ return assignMidiProgramToInstrument(program, -1, -1, percussion);
+ }
+
+ // Same again, but with bank select
+ //
+ Instrument* assignMidiProgramToInstrument(MidiByte program,
+ int msb, int lsb,
+ bool percussion);
+
+ // Get a suitable name for a Segment belonging to this instrument.
+ // Takes into account ProgramChanges.
+ //
+ std::string getSegmentName(InstrumentId id);
+
+ // Clear down all the ProgramChange flags in all MIDI Instruments
+ //
+ void unassignAllInstruments();
+
+ // Clear down all MIDI banks and programs on all MidiDevices
+ // prior to reloading. The Instruments and Devices are generated
+ // at the Sequencer - the Banks and Programs are loaded from the
+ // RG4 file.
+ //
+ void clearMidiBanksAndPrograms();
+
+ void clearBusses();
+ void clearRecordIns();
+
+ // Clear down
+ void clear();
+
+ // Get a MIDI metronome from a given device
+ //
+ const MidiMetronome* getMetronomeFromDevice(DeviceId id);
+
+ // Return the device list
+ //
+ DeviceList* getDevices() { return &m_devices; }
+
+ // Const iterators
+ //
+ DeviceListConstIterator begin() const { return m_devices.begin(); }
+ DeviceListConstIterator end() const { return m_devices.end(); }
+
+ // Get a device by ID
+ //
+ Device* getDevice(DeviceId id);
+
+ bool haveMidiDevices() const;
+
+ // Export as XML string
+ //
+ virtual std::string toXmlString();
+
+ // Export a subset of devices as XML string. If devices is empty,
+ // exports all devices just as the above method does.
+ //
+ virtual std::string toXmlString(const std::vector<DeviceId> &devices);
+
+ // Get an audio preview Instrument
+ //
+ InstrumentId getAudioPreviewInstrument();
+
+ // MIDI filtering into and thru Rosegarden
+ //
+ void setMIDIThruFilter(MidiFilter filter) { m_midiThruFilter = filter; }
+ MidiFilter getMIDIThruFilter() const { return m_midiThruFilter; }
+
+ void setMIDIRecordFilter(MidiFilter filter) { m_midiRecordFilter = filter; }
+ MidiFilter getMIDIRecordFilter() const { return m_midiRecordFilter; }
+
+ void setMixerDisplayOptions(unsigned int options) { m_mixerDisplayOptions = options; }
+ unsigned int getMixerDisplayOptions() const { return m_mixerDisplayOptions; }
+
+ DeviceId getMetronomeDevice() const { return m_metronomeDevice; }
+ void setMetronomeDevice(DeviceId device) { m_metronomeDevice = device; }
+
+private:
+
+ DeviceList m_devices;
+
+ BussList m_busses;
+ RecordInList m_recordIns;
+
+ int m_audioInputs; // stereo pairs
+
+ MidiFilter m_midiThruFilter;
+ MidiFilter m_midiRecordFilter;
+
+ unsigned int m_mixerDisplayOptions;
+
+ DeviceId m_metronomeDevice;
+};
+
+}
+
+#endif // _STUDIO_H_
diff --git a/src/base/Track.cpp b/src/base/Track.cpp
new file mode 100644
index 0000000..903e500
--- /dev/null
+++ b/src/base/Track.cpp
@@ -0,0 +1,201 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "Track.h"
+#include <iostream>
+#include <cstdio>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include "Composition.h"
+#include "StaffExportTypes.h"
+
+namespace Rosegarden
+{
+
+Track::Track():
+ m_id(0),
+ m_muted(false),
+ m_position(-1),
+ m_instrument(0),
+ m_owningComposition(0),
+ m_input_device(Device::ALL_DEVICES),
+ m_input_channel(-1),
+ m_armed(false),
+ m_clef(0),
+ m_transpose(0),
+ m_color(0),
+ m_highestPlayable(127),
+ m_lowestPlayable(0),
+ m_staffSize(StaffTypes::Normal),
+ m_staffBracket(Brackets::None)
+{
+}
+
+Track::Track(TrackId id,
+ InstrumentId instrument,
+ int position,
+ const std::string &label,
+ bool muted):
+ m_id(id),
+ m_muted(muted),
+ m_label(label),
+ m_position(position),
+ m_instrument(instrument),
+ m_owningComposition(0),
+ m_input_device(Device::ALL_DEVICES),
+ m_input_channel(-1),
+ m_armed(false),
+ m_clef(0),
+ m_transpose(0),
+ m_color(0),
+ m_highestPlayable(127),
+ m_lowestPlayable(0),
+ m_staffSize(StaffTypes::Normal),
+ m_staffBracket(Brackets::None)
+{
+}
+
+Track::~Track()
+{
+}
+
+void Track::setMuted(bool muted)
+{
+ if (m_muted == muted) return;
+
+ m_muted = muted;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+void Track::setLabel(const std::string &label)
+{
+ if (m_label == label) return;
+
+ m_label = label;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+void Track::setPresetLabel(const std::string &label)
+{
+ if (m_presetLabel == label) return;
+
+ m_presetLabel = label;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+void Track::setInstrument(InstrumentId instrument)
+{
+ if (m_instrument == instrument) return;
+
+ m_instrument = instrument;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+
+void Track::setArmed(bool armed)
+{
+ if (m_armed == armed) return;
+
+ m_armed = armed;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+void Track::setMidiInputDevice(DeviceId id)
+{
+ if (m_input_device == id) return;
+
+ m_input_device = id;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+void Track::setMidiInputChannel(char ic)
+{
+ if (m_input_channel == ic) return;
+
+ m_input_channel = ic;
+
+ if (m_owningComposition)
+ m_owningComposition->notifyTrackChanged(this);
+}
+
+
+// Our virtual method for exporting Xml.
+//
+//
+std::string Track::toXmlString()
+{
+
+ std::stringstream track;
+
+ track << "<track id=\"" << m_id;
+ track << "\" label=\"" << encode(m_label);
+ track << "\" position=\"" << m_position;
+
+ track << "\" muted=";
+
+ if (m_muted)
+ track << "\"true\"";
+ else
+ track << "\"false\"";
+
+ track << " instrument=\"" << m_instrument << "\"";
+
+ track << " defaultLabel=\"" << m_presetLabel << "\"";
+ track << " defaultClef=\"" << m_clef << "\"";
+ track << " defaultTranspose=\"" << m_transpose << "\"";
+ track << " defaultColour=\"" << m_color << "\"";
+ track << " defaultHighestPlayable=\"" << m_highestPlayable << "\"";
+ track << " defaultLowestPlayable=\"" << m_lowestPlayable << "\"";
+
+ track << " staffSize=\"" << m_staffSize << "\"";
+ track << " staffBracket=\"" << m_staffBracket << "\"";
+
+#if (__GNUC__ < 3)
+ track << "/>"<< std::ends;
+#else
+ track << "/>";
+#endif
+
+ return track.str();
+
+}
+
+}
+
+
diff --git a/src/base/Track.h b/src/base/Track.h
new file mode 100644
index 0000000..bcded51
--- /dev/null
+++ b/src/base/Track.h
@@ -0,0 +1,162 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+// Representation of a Track
+//
+//
+
+
+#ifndef _TRACK_H_
+#define _TRACK_H_
+
+#include <string>
+
+#include "XmlExportable.h"
+#include "Instrument.h"
+#include "Device.h"
+
+namespace Rosegarden
+{
+class Composition;
+
+typedef unsigned int TrackId;
+
+/**
+ * A Track represents a line on the SegmentCanvas on the
+ * Rosegarden GUI. A Track is owned by a Composition and
+ * has reference to an Instrument from which the playback
+ * characteristics of the Track can be derived. A Track
+ * has no type itself - the type comes only from the
+ * Instrument relationship.
+ *
+ */
+class Track : public XmlExportable
+{
+ friend class Composition;
+
+public:
+ Track();
+ Track(TrackId id,
+ InstrumentId instrument = 0,
+ int position =0 ,
+ const std::string &label = "",
+ bool muted = false);
+
+ void setId(TrackId id) { m_id = id; }
+
+private:
+ Track(const Track &);
+ Track operator=(const Track &);
+
+public:
+
+ ~Track();
+
+ TrackId getId() const { return m_id; }
+
+ void setMuted(bool muted);
+ bool isMuted() const { return m_muted; }
+
+ void setPosition(int position) { m_position = position; }
+ int getPosition() const { return m_position; }
+
+ void setLabel(const std::string &label);
+ std::string getLabel() const { return m_label; }
+
+ void setPresetLabel(const std::string &label);
+ std::string getPresetLabel() const { return m_presetLabel; }
+
+ void setInstrument(InstrumentId instrument);
+ InstrumentId getInstrument() const { return m_instrument; }
+
+ // Implementation of virtual
+ //
+ virtual std::string toXmlString();
+
+ Composition* getOwningComposition() { return m_owningComposition; }
+
+ void setMidiInputDevice(DeviceId id);
+ DeviceId getMidiInputDevice() const { return m_input_device; }
+
+ void setMidiInputChannel(char ic);
+ char getMidiInputChannel() const { return m_input_channel; }
+
+ int getClef() { return m_clef; }
+ void setClef(int clef) { m_clef = clef; }
+
+ int getTranspose() { return m_transpose; }
+ void setTranspose(int transpose) { m_transpose = transpose; }
+
+ int getColor() { return m_color; }
+ void setColor(int color) { m_color = color; }
+
+ int getHighestPlayable() { return m_highestPlayable; }
+ void setHighestPlayable(int pitch) { m_highestPlayable = pitch; }
+
+ int getLowestPlayable() { return m_lowestPlayable; }
+ void setLowestPlayable(int pitch) { m_lowestPlayable = pitch; }
+
+ // Controls size of exported staff in LilyPond
+ int getStaffSize() { return m_staffSize; }
+ void setStaffSize(int index) { m_staffSize = index; }
+
+ // Staff bracketing in LilyPond
+ int getStaffBracket() { return m_staffBracket; }
+ void setStaffBracket(int index) { m_staffBracket = index; }
+
+ bool isArmed() const { return m_armed; }
+ void setArmed(bool armed);
+
+protected: // For Composition use only
+ void setOwningComposition(Composition* comp) { m_owningComposition = comp; }
+
+private:
+ //--------------- Data members ---------------------------------
+
+ TrackId m_id;
+ bool m_muted;
+ std::string m_label;
+ std::string m_presetLabel;
+ int m_position;
+ InstrumentId m_instrument;
+
+ Composition* m_owningComposition;
+
+ DeviceId m_input_device;
+ char m_input_channel;
+ bool m_armed;
+
+ // default parameters for new segments created belonging to this track
+ int m_clef;
+ int m_transpose;
+ int m_color;
+ int m_highestPlayable;
+ int m_lowestPlayable;
+
+ // staff parameters for LilyPond export
+ int m_staffSize;
+ int m_staffBracket;
+};
+
+}
+
+#endif // _TRACK_H_
+
diff --git a/src/base/TriggerSegment.cpp b/src/base/TriggerSegment.cpp
new file mode 100644
index 0000000..c4c29de
--- /dev/null
+++ b/src/base/TriggerSegment.cpp
@@ -0,0 +1,130 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "TriggerSegment.h"
+
+#include "Segment.h"
+#include "Composition.h"
+#include "BaseProperties.h"
+
+namespace Rosegarden
+{
+
+TriggerSegmentRec::~TriggerSegmentRec()
+{
+ // nothing -- we don't delete the segment here
+}
+
+TriggerSegmentRec::TriggerSegmentRec(TriggerSegmentId id,
+ Segment *segment,
+ int basePitch,
+ int baseVelocity,
+ std::string timeAdjust,
+ bool retune) :
+ m_id(id),
+ m_segment(segment),
+ m_basePitch(basePitch),
+ m_baseVelocity(baseVelocity),
+ m_defaultTimeAdjust(timeAdjust),
+ m_defaultRetune(retune)
+{
+ if (m_defaultTimeAdjust == "") {
+ m_defaultTimeAdjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH;
+ }
+
+ calculateBases();
+ updateReferences();
+}
+
+TriggerSegmentRec::TriggerSegmentRec(const TriggerSegmentRec &rec) :
+ m_id(rec.m_id),
+ m_segment(rec.m_segment),
+ m_basePitch(rec.m_basePitch),
+ m_baseVelocity(rec.m_baseVelocity),
+ m_defaultTimeAdjust(rec.m_defaultTimeAdjust),
+ m_defaultRetune(rec.m_defaultRetune),
+ m_references(rec.m_references)
+{
+ // nothing else
+}
+
+TriggerSegmentRec &
+TriggerSegmentRec::operator=(const TriggerSegmentRec &rec)
+{
+ if (&rec == this) return *this;
+ m_id = rec.m_id;
+ m_segment = rec.m_segment;
+ m_basePitch = rec.m_basePitch;
+ m_baseVelocity = rec.m_baseVelocity;
+ m_references = rec.m_references;
+ return *this;
+}
+
+void
+TriggerSegmentRec::updateReferences()
+{
+ if (!m_segment) return;
+
+ Composition *c = m_segment->getComposition();
+ if (!c) return;
+
+ m_references.clear();
+
+ for (Composition::iterator i = c->begin(); i != c->end(); ++i) {
+ for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if ((*j)->has(BaseProperties::TRIGGER_SEGMENT_ID) &&
+ (*j)->get<Int>(BaseProperties::TRIGGER_SEGMENT_ID) == long(m_id)) {
+ m_references.insert((*i)->getRuntimeId());
+ break; // from inner loop only: go on to next segment
+ }
+ }
+ }
+}
+
+void
+TriggerSegmentRec::calculateBases()
+{
+ if (!m_segment) return;
+
+ for (Segment::iterator i = m_segment->begin();
+ m_segment->isBeforeEndMarker(i); ++i) {
+
+ if (m_basePitch >= 0 && m_baseVelocity >= 0) return;
+
+ if (m_basePitch < 0) {
+ if ((*i)->has(BaseProperties::PITCH)) {
+ m_basePitch = (*i)->get<Int>(BaseProperties::PITCH);
+ }
+ }
+
+ if (m_baseVelocity < 0) {
+ if ((*i)->has(BaseProperties::VELOCITY)) {
+ m_baseVelocity = (*i)->get<Int>(BaseProperties::VELOCITY);
+ }
+ }
+ }
+
+ if (m_basePitch < 0) m_basePitch = 60;
+ if (m_baseVelocity < 0) m_baseVelocity = 100;
+}
+
+}
+
diff --git a/src/base/TriggerSegment.h b/src/base/TriggerSegment.h
new file mode 100644
index 0000000..7095e25
--- /dev/null
+++ b/src/base/TriggerSegment.h
@@ -0,0 +1,100 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _TRIGGER_SEGMENT_H_
+#define _TRIGGER_SEGMENT_H_
+
+#include <set>
+#include <string>
+
+namespace Rosegarden
+{
+
+typedef unsigned int TriggerSegmentId;
+
+class Segment;
+
+class TriggerSegmentRec
+{
+public:
+ typedef std::set<int> SegmentRuntimeIdSet;
+ ~TriggerSegmentRec();
+ TriggerSegmentRec(const TriggerSegmentRec &);
+ TriggerSegmentRec &operator=(const TriggerSegmentRec &);
+ bool operator==(const TriggerSegmentRec &rec) { return rec.m_id == m_id; }
+
+ TriggerSegmentId getId() const { return m_id; }
+
+ Segment *getSegment() { return m_segment; }
+ const Segment *getSegment() const { return m_segment; }
+
+ int getBasePitch() const { return m_basePitch; }
+ void setBasePitch(int basePitch) { m_basePitch = basePitch; }
+
+ int getBaseVelocity() const { return m_baseVelocity; }
+ void setBaseVelocity(int baseVelocity) { m_baseVelocity = baseVelocity; }
+
+ std::string getDefaultTimeAdjust() const { return m_defaultTimeAdjust; }
+ void setDefaultTimeAdjust(std::string a) { m_defaultTimeAdjust = a; }
+
+ bool getDefaultRetune() const { return m_defaultRetune; }
+ void setDefaultRetune(bool r) { m_defaultRetune = r; }
+
+ SegmentRuntimeIdSet &getReferences() { return m_references; }
+ const SegmentRuntimeIdSet &getReferences() const { return m_references; }
+
+ void updateReferences();
+
+protected:
+ friend class Composition;
+ TriggerSegmentRec(TriggerSegmentId id, Segment *segment,
+ int basePitch = -1, int baseVelocity = -1,
+ std::string defaultTimeAdjust = "", bool defaultRetune = true);
+
+ void setReferences(const SegmentRuntimeIdSet &s) { m_references = s; }
+
+ void calculateBases();
+
+ // data members:
+
+ TriggerSegmentId m_id;
+ Segment *m_segment;
+ int m_basePitch;
+ int m_baseVelocity;
+ std::string m_defaultTimeAdjust;
+ bool m_defaultRetune;
+ SegmentRuntimeIdSet m_references;
+};
+
+struct TriggerSegmentCmp
+{
+ bool operator()(const TriggerSegmentRec &r1, const TriggerSegmentRec &r2) const {
+ return r1.getId() < r2.getId();
+ }
+ bool operator()(const TriggerSegmentRec *r1, const TriggerSegmentRec *r2) const {
+ return r1->getId() < r2->getId();
+ }
+};
+
+
+}
+
+#endif
diff --git a/src/base/ViewElement.cpp b/src/base/ViewElement.cpp
new file mode 100644
index 0000000..425bdc1
--- /dev/null
+++ b/src/base/ViewElement.cpp
@@ -0,0 +1,172 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "ViewElement.h"
+#include <iostream>
+#include <cassert>
+
+namespace Rosegarden
+{
+
+extern const int MIN_SUBORDERING;
+
+ViewElement::ViewElement(Event *e) :
+ m_layoutX(0.0),
+ m_layoutY(0.0),
+ m_event(e)
+{
+ // nothing
+}
+
+ViewElement::~ViewElement()
+{
+ // nothing
+}
+
+//////////////////////////////////////////////////////////////////////
+
+bool
+operator<(const ViewElement &a, const ViewElement &b)
+{
+ timeT at = a.getViewAbsoluteTime(), bt = b.getViewAbsoluteTime();
+/*
+ if (at < bt) {
+ if (!(*(a.event()) < *(b.event()))) {
+ std::cerr << " types: a: " << a.event()->getType() << " b: " << b.event()->getType() << std::endl;
+ std::cerr << "performed: a: " << a.event()->getAbsoluteTime() << " b: " << b.event()->getAbsoluteTime() << std::endl;
+ std::cerr << " notated: a: " << a.getViewAbsoluteTime() << " b: " << b.getViewAbsoluteTime() << std::endl;
+// assert(*(a.event()) < *(b.event()));
+ }
+ }
+ else if (at > bt) {
+ if (*(a.event()) < *(b.event())) {
+ std::cerr << " types: a: " << a.event()->getType() << " b: " << b.event()->getType() << std::endl;
+ std::cerr << "performed: a: " << a.event()->getAbsoluteTime() << " b: " << b.event()->getAbsoluteTime() << std::endl;
+ std::cerr << " notated: a: " << a.getViewAbsoluteTime() << " b: " << b.getViewAbsoluteTime() << std::endl;
+// assert(!(*(a.event()) < *(b.event())));
+ }
+ }
+*/
+ if (at == bt) return *(a.event()) < *(b.event());
+ else return (at < bt);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+
+ViewElementList::~ViewElementList()
+{
+ for (iterator i = begin(); i != end(); ++i) {
+ delete (*i);
+ }
+}
+
+void
+ViewElementList::insert(ViewElement* el)
+{
+ set_type::insert(el);
+}
+
+void
+ViewElementList::erase(iterator pos)
+{
+ delete *pos;
+ set_type::erase(pos);
+}
+
+void
+ViewElementList::erase(iterator from, iterator to)
+{
+ for (iterator i = from; i != to; ++i) {
+ delete *i;
+ }
+
+ set_type::erase(from, to);
+}
+
+void
+ViewElementList::eraseSingle(ViewElement *el)
+{
+ iterator elPos = findSingle(el);
+ if (elPos != end()) erase(elPos);
+}
+
+ViewElementList::iterator
+ViewElementList::findPrevious(const std::string &type, iterator i)
+
+{
+ // what to return on failure? I think probably
+ // end(), as begin() could be a success case
+ if (i == begin()) return end();
+ --i;
+ for (;;) {
+ if ((*i)->event()->isa(type)) return i;
+ if (i == begin()) return end();
+ --i;
+ }
+}
+
+ViewElementList::iterator
+ViewElementList::findNext(const std::string &type, iterator i)
+{
+ if (i == end()) return i;
+ for (++i; i != end() && !(*i)->event()->isa(type); ++i);
+ return i;
+}
+
+ViewElementList::iterator
+ViewElementList::findSingle(ViewElement *el)
+{
+ iterator res = end();
+
+ std::pair<iterator, iterator> interval = equal_range(el);
+
+ for (iterator i = interval.first; i != interval.second; ++i) {
+ if (*i == el) {
+ res = i;
+ break;
+ }
+ }
+
+ return res;
+}
+
+ViewElementList::iterator
+ViewElementList::findTime(timeT time)
+{
+ Event dummy("dummy", time, 0, MIN_SUBORDERING);
+ ViewElement dummyT(&dummy);
+ return lower_bound(&dummyT);
+}
+
+ViewElementList::iterator
+ViewElementList::findNearestTime(timeT t)
+{
+ iterator i = findTime(t);
+ if (i == end() || (*i)->getViewAbsoluteTime() > t) {
+ if (i == begin()) return end();
+ else --i;
+ }
+ return i;
+}
+
+}
+
diff --git a/src/base/ViewElement.h b/src/base/ViewElement.h
new file mode 100644
index 0000000..8cc3d09
--- /dev/null
+++ b/src/base/ViewElement.h
@@ -0,0 +1,164 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _VIEWELEMENT_H_
+#define _VIEWELEMENT_H_
+
+
+#include "Event.h"
+
+#include <set>
+#include <list>
+
+namespace Rosegarden
+{
+
+/**
+ * The abstract base for classes which represent an Event as an
+ * on-screen graphic item (a note, a rectangle on a piano roll).
+ */
+
+class ViewElement
+{
+ friend class ViewElementList;
+ friend class Staff;
+
+public:
+ virtual ~ViewElement();
+
+ const Event* event() const { return m_event; }
+ Event* event() { return m_event; }
+
+ virtual timeT getViewAbsoluteTime() const { return event()->getAbsoluteTime(); }
+ virtual timeT getViewDuration() const { return event()->getDuration(); }
+
+ /**
+ * Returns the X coordinate of the element, as computed by the
+ * layout. This is not the coordinate of the associated canvas
+ * item.
+ *
+ * @see getCanvasX()
+ */
+ virtual double getLayoutX() const { return m_layoutX; }
+
+ /**
+ * Returns the Y coordinate of the element, as computed by the
+ * layout. This is not the coordinate of the associated canvas
+ * item.
+ *
+ * @see getCanvasY()
+ */
+ virtual double getLayoutY() const { return m_layoutY; }
+
+ /**
+ * Sets the X coordinate which was computed by the layout engine
+ * @see getLayoutX()
+ */
+ virtual void setLayoutX(double x) { m_layoutX = x; }
+
+ /**
+ * Sets the Y coordinate which was computed by the layout engine
+ * @see getLayoutY()
+ */
+ virtual void setLayoutY(double y) { m_layoutY = y; }
+
+ void dump(std::ostream&) const;
+
+ friend bool operator<(const ViewElement&, const ViewElement&);
+
+protected:
+ ViewElement(Event *);
+
+ double m_layoutX;
+ double m_layoutY;
+
+ Event *m_event;
+};
+
+
+
+class ViewElementComparator
+{
+public:
+ bool operator()(const ViewElement *e1, const ViewElement *e2) const {
+ return *e1 < *e2;
+ }
+};
+
+/**
+ * This class owns the objects its items are pointing at.
+ *
+ * The template argument T must be a subclass of ViewElement.
+ */
+class ViewElementList : public std::multiset<ViewElement *, ViewElementComparator >
+{
+ typedef std::multiset<ViewElement *, ViewElementComparator > set_type;
+public:
+ typedef set_type::iterator iterator;
+
+ ViewElementList() : set_type() { }
+ virtual ~ViewElementList();
+
+ void insert(ViewElement *);
+ void erase(iterator i);
+ void erase(iterator from, iterator to);
+ void eraseSingle(ViewElement *);
+
+ iterator findPrevious(const std::string &type, iterator i);
+ iterator findNext(const std::string &type, iterator i);
+
+ /**
+ * Returns an iterator pointing to that specific element,
+ * end() otherwise
+ */
+ iterator findSingle(ViewElement *);
+
+ const_iterator findSingle(ViewElement *e) const {
+ return const_iterator(((const ViewElementList *)this)->findSingle(e));
+ }
+
+ /**
+ * Returns first iterator pointing at or after the given time,
+ * end() if time is beyond the end of the list
+ */
+ iterator findTime(timeT time);
+
+ const_iterator findTime(timeT time) const {
+ return const_iterator(((const ViewElementList *)this)->findTime(time));
+ }
+
+ /**
+ * Returns iterator pointing to the first element starting at
+ * or before the given absolute time
+ */
+ iterator findNearestTime(timeT time);
+
+ const_iterator findNearestTime(timeT time) const {
+ return const_iterator(((const ViewElementList *)this)->findNearestTime(time));
+ }
+};
+
+}
+
+
+#endif
+
diff --git a/src/base/XmlExportable.cpp b/src/base/XmlExportable.cpp
new file mode 100644
index 0000000..b874340
--- /dev/null
+++ b/src/base/XmlExportable.cpp
@@ -0,0 +1,197 @@
+// -*- c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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 "XmlExportable.h"
+#include <iostream>
+#include <cstdlib>
+#include <cstring>
+
+namespace Rosegarden
+{
+
+static std::string s1;
+static std::string multibyte;
+
+std::string XmlExportable::encode(const std::string &s0)
+{
+ static char *buffer = 0;
+ static size_t bufsiz = 0;
+ size_t buflen = 0;
+
+ static char multibyte[20];
+ size_t mblen = 0;
+
+ size_t len = s0.length();
+
+ if (bufsiz < len * 2 + 10) {
+ bufsiz = len * 2 + 10;
+ buffer = (char *)malloc(bufsiz);
+ }
+
+ // Escape any xml special characters, and also make sure we have
+ // valid utf8 -- otherwise we won't be able to re-read the xml.
+ // Amazing how complicated this gets.
+
+ bool warned = false; // no point in warning forever for long bogus strings
+
+ for (size_t i = 0; i < len; ++i) {
+
+ unsigned char c = s0[i];
+
+ if (((c & 0xc0) == 0xc0) || !(c & 0x80)) {
+
+ // 11xxxxxx or 0xxxxxxx: first byte of a character sequence
+
+ if (mblen > 0) {
+
+ // does multibyte contain a valid sequence?
+ unsigned int length =
+ (!(multibyte[0] & 0x20)) ? 2 :
+ (!(multibyte[0] & 0x10)) ? 3 :
+ (!(multibyte[0] & 0x08)) ? 4 :
+ (!(multibyte[0] & 0x04)) ? 5 : 0;
+
+ if (length == 0 || mblen == length) {
+ if (bufsiz < buflen + mblen + 1) {
+ bufsiz = 2 * buflen + mblen + 1;
+ buffer = (char *)realloc(buffer, bufsiz);
+ }
+ strncpy(buffer + buflen, multibyte, mblen);
+ buflen += mblen;
+ } else {
+ if (!warned) {
+ std::cerr
+ << "WARNING: Invalid utf8 char width in string \""
+ << s0 << "\" at index " << i << " ("
+ << mblen << " octet"
+ << (mblen != 1 ? "s" : "")
+ << ", expected " << length << ")" << std::endl;
+ warned = true;
+ }
+ // and drop the character
+ }
+ }
+
+ mblen = 0;
+
+ if (!(c & 0x80)) { // ascii
+
+ if (bufsiz < buflen + 10) {
+ bufsiz = 2 * buflen + 10;
+ buffer = (char *)realloc(buffer, bufsiz);
+ }
+
+ switch (c) {
+ case '&' : strncpy(buffer + buflen, "&amp;", 5); buflen += 5; break;
+ case '<' : strncpy(buffer + buflen, "&lt;", 4); buflen += 4; break;
+ case '>' : strncpy(buffer + buflen, "&gt;", 4); buflen += 4; break;
+ case '"' : strncpy(buffer + buflen, "&quot;", 6); buflen += 6; break;
+ case '\'' : strncpy(buffer + buflen, "&apos;", 6); buflen += 6; break;
+ case 0x9:
+ case 0xa:
+ case 0xd:
+ // convert these special cases to plain whitespace:
+ buffer[buflen++] = ' ';
+ break;
+ default:
+ if (c >= 32) buffer[buflen++] = c;
+ else {
+ if (!warned) {
+ std::cerr
+ << "WARNING: Invalid utf8 octet in string \""
+ << s0 << "\" at index " << i << " ("
+ << (int)c << " < 32)" << std::endl;
+ }
+ warned = true;
+ }
+ }
+
+ } else {
+
+ // store in multibyte rather than straight to s1, so
+ // that we know we're in the middle of something
+ // (below). At this point we know mblen == 0.
+ multibyte[mblen++] = c;
+ }
+
+ } else {
+
+ // second or subsequent byte
+
+ if (mblen == 0) { // ... without a first byte!
+ if (!warned) {
+ std::cerr
+ << "WARNING: Invalid utf8 octet sequence in string \""
+ << s0 << "\" at index " << i << std::endl;
+ warned = true;
+ }
+ } else {
+
+ if (mblen >= sizeof(multibyte)-1) {
+ if (!warned) {
+ std::cerr
+ << "WARNING: Character too wide in string \""
+ << s0 << "\" at index " << i << " (reached width of "
+ << mblen << ")" << std::endl;
+ }
+ warned = true;
+ mblen = 0;
+ } else {
+ multibyte[mblen++] = c;
+ }
+ }
+ }
+ }
+
+ if (mblen > 0) {
+ // does multibyte contain a valid sequence?
+ unsigned int length =
+ (!(multibyte[0] & 0x20)) ? 2 :
+ (!(multibyte[0] & 0x10)) ? 3 :
+ (!(multibyte[0] & 0x08)) ? 4 :
+ (!(multibyte[0] & 0x04)) ? 5 : 0;
+
+ if (length == 0 || mblen == length) {
+ if (bufsiz < buflen + mblen + 1) {
+ bufsiz = 2 * buflen + mblen + 1;
+ buffer = (char *)realloc(buffer, bufsiz);
+ }
+ strncpy(buffer + buflen, multibyte, mblen);
+ buflen += mblen;
+ } else {
+ if (!warned) {
+ std::cerr
+ << "WARNING: Invalid utf8 char width in string \""
+ << s0 << "\" at index " << len << " ("
+ << mblen << " octet"
+ << (mblen != 1 ? "s" : "")
+ << ", expected " << length << ")" << std::endl;
+ warned = true;
+ }
+ // and drop the character
+ }
+ }
+ buffer[buflen] = '\0';
+
+ return buffer;
+}
+
+}
+
diff --git a/src/base/XmlExportable.h b/src/base/XmlExportable.h
new file mode 100644
index 0000000..e619221
--- /dev/null
+++ b/src/base/XmlExportable.h
@@ -0,0 +1,55 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A 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 right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+#ifndef _XMLEXPORTABLE_H_
+#define _XMLEXPORTABLE_H_
+
+#include <string>
+
+// [rwb]
+//
+// Abstract base class that forces all derived classes
+// to implement the virtual toXmlString object.
+//
+// Yes, this is similar to the XmlStoreableEvent class
+// in gui/ but with hopes to be more general so that any
+// classes in base/ can go ahead and implement it.
+//
+//
+
+namespace Rosegarden
+{
+
+class XmlExportable
+{
+public:
+ XmlExportable() {;}
+ virtual ~XmlExportable() {;}
+
+ virtual std::string toXmlString() = 0;
+
+ static std::string encode(const std::string &);
+};
+
+}
+
+#endif // _XMLEXPORTABLE_H_
+
diff --git a/src/base/test/Makefile b/src/base/test/Makefile
new file mode 100644
index 0000000..b517955
--- /dev/null
+++ b/src/base/test/Makefile
@@ -0,0 +1,57 @@
+
+# debug flags need to be consistent with base build
+#CPPFLAGS = -O2
+CPPFLAGS = -g
+
+LIBBASE = ../../../RGbuild/libRosegardenCommon.a
+
+INCPATH = -I..
+
+SRCS := test.C pitch.C
+
+default: test utf8 colour transpose accidentals
+
+clean:
+ rm -f test test.o pitch pitch.o utf8 utf8.o colour colour.o transpose.o transpose accidentals.o accidentals
+
+%.o: %.cpp
+ $(CXX) $(CPPFLAGS) -c $< $(INCPATH) -o $@
+
+test: test.o
+ $(CXX) $< $(LIBBASE) -o $@
+
+pitch: pitch.o
+ $(CXX) $< $(LIBBASE) -o $@
+
+utf8: utf8.o
+ $(CXX) $< $(LIBBASE) -o $@
+
+colour: colour.o
+ $(CXX) $< $(LIBBASE) -o $@
+
+transpose: transpose.o
+ $(CXX) $< $(LIBBASE) -o $@
+
+accidentals: accidentals.o
+ $(CXX) $< $(LIBBASE) -o $@
+
+
+depend:
+ makedepend $(INCPATH) -- $(CPPFLAGS) -- $(SRCS)
+
+# DO NOT DELETE
+
+test.o: ../Event.h ../PropertyMap.h ../Property.h ../RealTime.h
+test.o: ../PropertyName.h ../Exception.h ../Segment.h ../Track.h
+test.o: ../XmlExportable.h ../Instrument.h ../NotationTypes.h #../StringHash.h
+test.o: ../XmlExportable.h ../Instrument.h ../NotationTypes.h
+test.o: ../RefreshStatus.h ../Composition.h ../FastVector.h
+test.o: ../Configuration.h ../ColourMap.h ../Colour.h
+test.o: ../SegmentNotationHelper.h ../SegmentPerformanceHelper.h
+test.o: ../MidiTypes.h
+pitch.o: ../NotationTypes.h ../Event.h ../PropertyMap.h ../Property.h
+pitch.o: ../RealTime.h ../PropertyName.h ../Exception.h ../Instrument.h
+pitch.o: ../XmlExportable.h #../StringHash.h
+
+transpose.o: ../NotationTypes.h
+accidentals.o: ../NotationTypes.h
diff --git a/src/base/test/accidentals.cpp b/src/base/test/accidentals.cpp
new file mode 100644
index 0000000..53dbfc8
--- /dev/null
+++ b/src/base/test/accidentals.cpp
@@ -0,0 +1,60 @@
+// -*- c-basic-offset: 4 -*-
+
+#include "NotationTypes.h"
+
+using namespace Rosegarden;
+using std::cout;
+
+// Unit test-ish tests for resolving accidentals
+//
+// Returns -1 (or crashes :)) on error, 0 on success
+void assertHasAccidental(Pitch &pitch,
+ const Accidental& accidental, const Key& key)
+{
+ Accidental calculatedAccidental =
+ pitch.getAccidental(key);
+
+ std::cout << "Got " << calculatedAccidental << " for pitch " << pitch.getPerformancePitch() << " in key " << key.getName() << std::endl;
+
+ if (calculatedAccidental != accidental)
+ {
+ std::cout << "Expected " << accidental << std::endl;
+ exit(-1);
+ }
+}
+
+void testBInEMinor()
+{
+ // a B, also in E minor, has no accidental
+ Pitch testPitch(59 % 12);
+ assertHasAccidental(testPitch,
+ Accidentals::NoAccidental, Key("E minor"));
+}
+
+/**
+ *
+ */
+void testFInBMinor()
+{
+ Pitch testPitch(77);
+ assertHasAccidental(testPitch,
+ Accidentals::NoAccidental, Key("B minor"));
+}
+
+void testInvalidSuggestion()
+{
+ // If we specify an invalid suggestion,
+ // getAccidental() should be robust against that.
+ Pitch testPitch = Pitch(59, Accidentals::Sharp);
+ assertHasAccidental(testPitch,
+ Accidentals::NoAccidental, Key("E minor"));
+}
+
+int main(int argc, char **argv)
+{
+ testBInEMinor();
+ testFInBMinor();
+ testInvalidSuggestion();
+ std::cout << "Success" << std::endl;
+ exit(0);
+}
diff --git a/src/base/test/colour.cpp b/src/base/test/colour.cpp
new file mode 100644
index 0000000..3aa7ba2
--- /dev/null
+++ b/src/base/test/colour.cpp
@@ -0,0 +1,222 @@
+// -*- c-basic-offset: 4 -*-
+
+
+/*
+ Rosegarden-4
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2003
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2003
+ Mark Hymers <[email protected]>
+
+ The moral right of the authors to claim authorship of this work
+ has been asserted.
+
+ 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.
+*/
+
+/*
+ If you compile this to a test program
+ g++ -o colour -I../ ../Colour.C ../ColourMap.C colour.C
+
+ you can then run it like this:
+ ./colour > temp.output
+
+ and do a diff to check that it worked:
+ diff -u temp.output colour.output
+
+ If there are any differences, there's a problem
+ (or colour.output hasn't been updated when colour.C has been changed)
+*/
+
+#include "Colour.h"
+#include "ColourMap.h"
+#include <iostream>
+
+
+using namespace Rosegarden;
+using std::cout;
+using std::string;
+
+// Some printing routines
+
+void printRC(Colour const *temp)
+{
+ cout << "red: " << temp->getRed() << " green: " << temp->getGreen() << " blue: " << temp->getBlue() << "\n";
+}
+
+void printSRC(const string *s, const Colour *c)
+{
+ cout << "name: " << *s << " ";
+ printRC(c);
+}
+
+void printSIRC(const unsigned int *i, const string *s, const Colour *c)
+{
+ cout << "index: " << *i << " ";
+ printSRC(s, c);
+}
+
+void printIteratorContents (ColourMap *input)
+{
+ RCMap::const_iterator iter = input->begin();
+ for ( ; !(iter == input->end()) ; ++iter)
+ printSIRC(&(iter->first), &(iter->second.second), &(iter->second.first));
+}
+
+// The main test program
+int main()
+{
+ cout << "TEST: Colour.C\n\n";
+ cout << "Can we create an Colour with the right default values?\n";
+ Colour *red = new Colour;
+ printRC(red);
+
+ cout << "Can we set values; green here is invalid - it should be set to 0 instead\n";
+ red->setRed(210);
+ red->setGreen(276);
+ red->setBlue(100);
+
+ cout << "Testing the copy constructor\n";
+ Colour *blue = new Colour(*red);
+ printRC(blue);
+
+ cout << "Check operator= works\n";
+ Colour green;
+ green = *red;
+ printRC(&green);
+
+ cout << "Check the setColour routine\n";
+ green.setColour(1,2,3);
+ printRC(&green);
+
+ cout << "Check the getColour routine\n";
+ unsigned int r, g, b;
+ green.getColour(r, g, b);
+ printRC(&green);
+
+ cout << "\nTEST: ColourMap.C\n\n";
+ cout << "Can we create a ColourMap with the right default Colour + String\n";
+ ColourMap *map = new ColourMap();
+
+ cout << "Can we get the default colour back out of it?\n";
+ string s1 = map->getNameByIndex(0);
+ green = map->getColourByIndex(0);
+ printSRC(&s1, &green);
+
+ cout << "Can we create a ColourMap with a specified default Colour?\n";
+ ColourMap *map2 = new ColourMap(*red);
+
+ cout << "Can we get the information back out of it?\n";
+ s1 = map2->getNameByIndex(0);
+ green = map2->getColourByIndex(0);
+ printSRC(&s1, &green);
+
+ cout << "Can we add a Colour\n";
+ s1 = "TEST1";
+ green.setColour(100, 101, 102);
+ map2->addItem(green, s1);
+
+ cout << "Can we get the info back out?\n";
+ s1 = "";
+ s1 = map2->getNameByIndex(1);
+ green = map2->getColourByIndex(1);
+ printSRC(&s1, &green);
+
+ cout << "Add a couple more colours\n";
+ s1 = "TEST2";
+ green.setColour(101, 102, 103);
+ map2->addItem(green, s1);
+ s1 = "TEST3";
+ green.setColour(102, 103, 104);
+ map2->addItem(green, s1);
+ s1 = "TEST4";
+ green.setColour(103, 104, 105);
+ map2->addItem(green, s1);
+
+ // From an iterator:
+ // iterator->first ==> Index
+ // iterator->second.first ==> Colour
+ // iterator->second.second ==> string
+ // This rather unwieldy notation is because we store a pair in the map which is made up of a pair
+ // to start with
+ printIteratorContents(map2);
+
+ cout << "Now try deleting the third item\n";
+ map2->deleteItemByIndex(3);
+
+ // Print the map again
+ printIteratorContents(map2);
+
+ cout << "Make sure we get false when we try and modify item number 3\n";
+ s1 = "NO";
+ green.setColour(199,199,199);
+ bool check = map2->modifyColourByIndex(3, green);
+ if (check) cout << "WARNING: Managed to modify colour which doesn't exist\n";
+ check = map2->modifyNameByIndex(3, s1);
+ if (check) cout << "WARNING: Managed to modify name which doesn't exist\n";
+
+ cout << "Check we can modify a colour which *is* there\n";
+ s1 = "YES";
+ green.setColour(233,233,233);
+
+ check = map2->modifyColourByIndex(4, green);
+ if (!check) cout << "WARNING: Couldn't modify colour which does exist\n";
+
+ check = map2->modifyNameByIndex(4, s1);
+ if (!check) cout << "WARNING: Couldn't modify name which does exist\n";
+
+ // Print the map again
+ printIteratorContents(map2);
+
+ cout << "Now try adding another item - it should take the place of the one we removed.\n";
+ s1 = "NEW";
+ green.setColour(211, 212, 213);
+ map2->addItem(green, s1);
+
+ // Print the map again
+ printIteratorContents(map2);
+
+ cout << "Try swapping two items:\n";
+ check = map2->swapItems(3, 4);
+ if (!check) cout << "WARNING: Couldn't swap two items which both exist\n";
+
+ // Print the map again
+ printIteratorContents(map2);
+
+ cout << "\nTEST: Generic Colour routines\n\n";
+
+ cout << "Try getting a combination colour:\n";
+ Colour blah = map2->getColourByIndex(0);
+ Colour blah2 = map2->getColourByIndex(1);
+ cout << "Original colours:\n";
+ printRC(&blah);
+ printRC(&blah2);
+ cout << "Combination colour:\n";
+ blah = getCombinationColour(blah, blah2);
+ printRC(&blah);
+
+ // Test the XML output
+ cout << "\nTEST: XML Output\n\n";
+ cout << "For a single colour:\n";
+ cout << blah.toXmlString();
+
+ cout << "For a colourmap:\n";
+ cout << map2->toXmlString(std::string("segmentmap"));
+
+
+ delete map;
+ delete map2;
+ delete red;
+ delete blue;
+
+ return 0;
+}
diff --git a/src/base/test/colour.output b/src/base/test/colour.output
new file mode 100644
index 0000000..d6dc301
--- /dev/null
+++ b/src/base/test/colour.output
@@ -0,0 +1,76 @@
+TEST: Colour.C
+
+Can we create an Colour with the right default values?
+red: 0 green: 0 blue: 0
+Can we set values; green here is invalid - it should be set to 0 instead
+Testing the copy constructor
+red: 210 green: 0 blue: 100
+Check operator= works
+red: 210 green: 0 blue: 100
+Check the setColour routine
+red: 1 green: 2 blue: 3
+Check the getColour routine
+red: 1 green: 2 blue: 3
+
+TEST: ColourMap.C
+
+Can we create a ColourMap with the right default Colour + String
+Can we get the default colour back out of it?
+name: red: 197 green: 211 blue: 125
+Can we create a ColourMap with a specified default Colour?
+Can we get the information back out of it?
+name: red: 210 green: 0 blue: 100
+Can we add a Colour
+Can we get the info back out?
+name: TEST1 red: 100 green: 101 blue: 102
+Add a couple more colours
+index: 0 name: red: 210 green: 0 blue: 100
+index: 1 name: TEST1 red: 100 green: 101 blue: 102
+index: 2 name: TEST2 red: 101 green: 102 blue: 103
+index: 3 name: TEST3 red: 102 green: 103 blue: 104
+index: 4 name: TEST4 red: 103 green: 104 blue: 105
+Now try deleting the third item
+index: 0 name: red: 210 green: 0 blue: 100
+index: 1 name: TEST1 red: 100 green: 101 blue: 102
+index: 2 name: TEST2 red: 101 green: 102 blue: 103
+index: 4 name: TEST4 red: 103 green: 104 blue: 105
+Make sure we get false when we try and modify item number 3
+Check we can modify a colour which *is* there
+index: 0 name: red: 210 green: 0 blue: 100
+index: 1 name: TEST1 red: 100 green: 101 blue: 102
+index: 2 name: TEST2 red: 101 green: 102 blue: 103
+index: 4 name: YES red: 233 green: 233 blue: 233
+Now try adding another item - it should take the place of the one we removed.
+index: 0 name: red: 210 green: 0 blue: 100
+index: 1 name: TEST1 red: 100 green: 101 blue: 102
+index: 2 name: TEST2 red: 101 green: 102 blue: 103
+index: 3 name: NEW red: 211 green: 212 blue: 213
+index: 4 name: YES red: 233 green: 233 blue: 233
+Try swapping two items:
+index: 0 name: red: 210 green: 0 blue: 100
+index: 1 name: TEST1 red: 100 green: 101 blue: 102
+index: 2 name: TEST2 red: 101 green: 102 blue: 103
+index: 3 name: YES red: 233 green: 233 blue: 233
+index: 4 name: NEW red: 211 green: 212 blue: 213
+
+TEST: Generic Colour routines
+
+Try getting a combination colour:
+Original colours:
+red: 210 green: 0 blue: 100
+red: 100 green: 101 blue: 102
+Combination colour:
+red: 155 green: 50 blue: 101
+
+TEST: XML Output
+
+For a single colour:
+<colour red="155" green="50" blue="101"/>
+For a colourmap:
+ <colourmap name="segmentmap">
+ <colourpair id="0" name="" red="210" green="0" blue="100"/>
+ <colourpair id="1" name="TEST1" red="100" green="101" blue="102"/>
+ <colourpair id="2" name="TEST2" red="101" green="102" blue="103"/>
+ <colourpair id="3" name="YES" red="233" green="233" blue="233"/>
+ <colourpair id="4" name="NEW" red="211" green="212" blue="213"/>
+ </colourmap>
diff --git a/src/base/test/pitch.cpp b/src/base/test/pitch.cpp
new file mode 100644
index 0000000..5d46f9e
--- /dev/null
+++ b/src/base/test/pitch.cpp
@@ -0,0 +1,474 @@
+// -*- c-basic-offset: 4 -*-
+
+#include "NotationRules.h"
+#include "NotationTypes.h"
+
+using namespace Rosegarden;
+using std::cout;
+using std::endl;
+using std::string;
+
+static const int verbose = 0;
+
+// This is the old NotationDisplayPitch -- this file was written for
+// regression testing when implementing the new Pitch class. It won't
+// compile any more as NotationDisplayPitch needs to be a friend of
+// Pitch for this implementation to work. Add "friend class
+// NotationDisplayPitch;" to end of Pitch in ../NotationTypes.h to
+// build it
+
+/**
+ * NotationDisplayPitch stores a note's pitch in terms of the position
+ * of the note on the staff and its associated accidental, and
+ * converts these values to and from performance (MIDI) pitches.
+ *
+ * Rationale: When we insert a note, we need to query the height of the
+ * staff line next to which it's being inserted, then translate this
+ * back to raw pitch according to the clef in force at the x-coordinate
+ * at which the note is inserted. For display, we translate from raw
+ * pitch using both the clef and the key in force.
+ *
+ * Whether an accidental should be displayed or not depends on the
+ * current key, on whether we've already shown the same accidental for
+ * that pitch in the same bar, on whether the note event explicitly
+ * requests an accidental... All we calculate here is whether the
+ * pitch "should" have an accidental, not whether it really will
+ * (e.g. if the accidental has already appeared).
+ *
+ * (See also docs/discussion/units.txt for explanation of pitch units.)
+ */
+
+class NotationDisplayPitch
+{
+public:
+ /**
+ * Construct a NotationDisplayPitch containing the given staff
+ * height and accidental
+ */
+ NotationDisplayPitch(int heightOnStaff,
+ const Accidental &accidental);
+
+ /**
+ * Construct a NotationDisplayPitch containing the height and
+ * accidental to which the given performance pitch corresponds
+ * in the given clef and key
+ */
+ NotationDisplayPitch(int pitch, const Clef &clef, const Key &key,
+ const Accidental &explicitAccidental =
+ Accidentals::NoAccidental);
+
+ int getHeightOnStaff() const { return m_heightOnStaff; }
+ Accidental getAccidental() const { return m_accidental; }
+
+ /**
+ * Calculate and return the performance (MIDI) pitch
+ * corresponding to the stored height and accidental, in the
+ * given clef and key
+ */
+ int getPerformancePitch(const Clef &clef, const Key &key) const;
+
+ /**
+ * Calculate and return the performance (MIDI) pitch
+ * corresponding to the stored height and accidental,
+ * interpreting them as Rosegarden-2.1-style values (for
+ * backward compatibility use), in the given clef and key
+ */
+ int getPerformancePitchFromRG21Pitch(const Clef &clef,
+ const Key &key) const;
+
+ /**
+ * Return the stored pitch as a string (C4, Bb2, etc...)
+ * according to http://www.harmony-central.com/MIDI/Doc/table2.html
+ *
+ * If inclOctave is false, this will return C, Bb, etc.
+ */
+ std::string getAsString(const Clef &clef, const Key &key,
+ bool inclOctave = true,
+ int octaveBase = -2) const;
+
+ /**
+ * Return the stored pitch as a description of a note in a
+ * scale. Return values are:
+ *
+ * -- placeInScale: a number from 0-6 where 0 is C and 6 is B
+ *
+ * -- accidentals: a number from -2 to 2 where -2 is double flat,
+ * -1 is flat, 0 is nothing, 1 is sharp, 2 is double sharp
+ *
+ * -- octave: MIDI octave in range -2 to 8, where pitch 0 is in
+ * octave -2 and thus middle-C is in octave 3
+ *
+ * This function is guaranteed never to return values out of
+ * the above ranges.
+ */
+ void getInScale(const Clef &clef, const Key &key,
+ int &placeInScale, int &accidentals, int &octave) const;
+
+private:
+ int m_heightOnStaff;
+ Accidental m_accidental;
+
+ static void rawPitchToDisplayPitch(int, const Clef &, const Key &,
+ int &, Accidental &);
+ static void displayPitchToRawPitch(int, Accidental, const Clef &, const Key &,
+ int &, bool ignoreOffset = false);
+};
+//////////////////////////////////////////////////////////////////////
+// NotationDisplayPitch
+//////////////////////////////////////////////////////////////////////
+
+NotationDisplayPitch::NotationDisplayPitch(int heightOnStaff,
+ const Accidental &accidental)
+ : m_heightOnStaff(heightOnStaff),
+ m_accidental(accidental)
+{
+}
+
+NotationDisplayPitch::NotationDisplayPitch(int pitch, const Clef &clef,
+ const Key &key,
+ const Accidental &explicitAccidental) :
+ m_accidental(explicitAccidental)
+{
+ rawPitchToDisplayPitch(pitch, clef, key, m_heightOnStaff, m_accidental);
+}
+
+int
+NotationDisplayPitch::getPerformancePitch(const Clef &clef, const Key &key) const
+{
+ int p = 0;
+ displayPitchToRawPitch(m_heightOnStaff, m_accidental, clef, key, p);
+ return p;
+}
+
+int
+NotationDisplayPitch::getPerformancePitchFromRG21Pitch(const Clef &clef,
+ const Key &) const
+{
+ // Rosegarden 2.1 pitches are a bit weird; see
+ // docs/data_struct/units.txt
+
+ // We pass the accidental and clef, a faked key of C major, and a
+ // flag telling displayPitchToRawPitch to ignore the clef offset
+ // and take only its octave into account
+
+ int p = 0;
+ displayPitchToRawPitch(m_heightOnStaff, m_accidental, clef, Key(), p, true);
+ return p;
+}
+
+
+void
+NotationDisplayPitch::rawPitchToDisplayPitch(int rawpitch,
+ const Clef &clef,
+ const Key &key,
+ int &height,
+ Accidental &accidental)
+{
+ Pitch::rawPitchToDisplayPitch(rawpitch, clef, key, height, accidental);
+}
+
+void
+NotationDisplayPitch::displayPitchToRawPitch(int height,
+ Accidental accidental,
+ const Clef &clef,
+ const Key &key,
+ int &pitch,
+ bool ignoreOffset)
+{
+ Pitch::displayPitchToRawPitch(height, accidental, clef, key, pitch,
+ ignoreOffset);
+}
+string
+NotationDisplayPitch::getAsString(const Clef &clef, const Key &key,
+ bool inclOctave, int octaveBase) const
+{
+ static const string noteNamesSharps[] = {
+ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
+ };
+ static const string noteNamesFlats[] = {
+ "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"
+ };
+
+ int performancePitch = getPerformancePitch(clef, key);
+
+ // highly unlikely, but fatal if it happened:
+ if (performancePitch < 0) performancePitch = 0;
+
+ int pitch = performancePitch % 12;
+ int octave = performancePitch / 12;
+
+ if (!inclOctave)
+ return key.isSharp() ? noteNamesSharps[pitch] : noteNamesFlats[pitch];
+
+ char tmp[1024];
+
+ if (key.isSharp())
+ sprintf(tmp, "%s%d", noteNamesSharps[pitch].c_str(),
+ octave + octaveBase);
+ else
+ sprintf(tmp, "%s%d", noteNamesFlats[pitch].c_str(),
+ octave + octaveBase);
+
+ return string(tmp);
+}
+
+void
+NotationDisplayPitch::getInScale(const Clef &clef, const Key &key,
+ int &placeInScale, int &accidentals, int &octave) const
+{
+ //!!! Maybe we should bring the logic from rawPitchToDisplayPitch down
+ // into this method, and make rawPitchToDisplayPitch wrap this
+
+ static int pitches[2][12] = {
+ { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 },
+ { 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 },
+ };
+ static int accidentalsForPitches[2][12] = {
+ { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 },
+ { 0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1, 0 },
+ };
+
+ int performancePitch = getPerformancePitch(clef, key);
+
+ // highly unlikely, but fatal if it happened:
+ if (performancePitch < 0) performancePitch = 0;
+ if (performancePitch > 127) performancePitch = 127;
+
+ int pitch = performancePitch % 12;
+ octave = performancePitch / 12 - 2;
+
+ if (key.isSharp()) { //!!! need to [optionally?] handle minor keys (similarly in getAsString?)
+ placeInScale = pitches[0][pitch];
+ accidentals = accidentalsForPitches[0][pitch];
+ } else {
+ placeInScale = pitches[1][pitch];
+ accidentals = accidentalsForPitches[1][pitch];
+ }
+}
+
+
+
+int testNote(Accidental &acc, Key &key, int octave, int note)
+{
+ int rv = 0;
+
+ Pitch pitch(note, octave, key, acc);
+
+ static int prevPerformancePitch = -1;
+ static Accidental prevAcc = Accidentals::NoAccidental;
+ static int prevOctave = -2;
+
+ int p = pitch.getPerformancePitch();
+ if (p < prevPerformancePitch && (prevAcc == acc && prevOctave == octave)) {
+ cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "pitch is " << p << ", should be >= " << prevPerformancePitch << endl;
+ rv = 1;
+ }
+
+ int nis = pitch.getNoteInScale(key);
+ if (nis != note) {
+ cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "note in scale is " << nis << " (not " << note << ")" << endl;
+ rv = 1;
+ }
+
+ // can do special checks on C-major etc 'cos it's easy, and stuff like that
+
+ if (key == Key("C major")) {
+ if (acc == Accidentals::NoAccidental) {
+ Pitch comparative(scale_Cmajor[nis], octave);
+ if (comparative.getPerformancePitch() != p) {
+ cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "comparative pitch is " << comparative.getPerformancePitch() << ", should be " << p << endl;
+ rv = 1;
+ }
+ }
+ }
+
+ prevPerformancePitch = p;
+ prevOctave = octave;
+ prevAcc = acc;
+
+ if (!rv && verbose) {
+ cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "pitch " << p << endl;
+ }
+ return rv;
+}
+
+int testNoteName(Accidental &acc, Key &key, int octave, char noteName)
+{
+ int rv = 0;
+
+ Pitch pitch(noteName, octave, key, acc);
+
+ static int prevPerformancePitch = -1;
+ static Accidental prevAcc = Accidentals::NoAccidental;
+ static int prevOctave = -2;
+
+ int p = pitch.getPerformancePitch();
+ if (p < prevPerformancePitch && (prevAcc == acc && prevOctave == octave)) {
+ cout << "testNoteName: " << noteName << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "pitch is " << p << ", should be >= " << prevPerformancePitch << endl;
+ rv = 1;
+ }
+
+ char nn = pitch.getNoteName(key);
+ if (nn != noteName) {
+ cout << "testNoteName: " << noteName << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "note is " << nn << " (not " << noteName << ") (pitch was " << p << ")" << endl;
+ rv = 1;
+ }
+
+ prevPerformancePitch = p;
+ prevOctave = octave;
+ prevAcc = acc;
+
+ if (!rv && verbose) {
+ cout << "testNoteName: " << noteName << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "pitch " << p << endl;
+ }
+ return rv;
+}
+
+int testPitchInOctave(Accidental &acc, Key &key, int octave, int pio)
+{
+ int rv = 0;
+
+ Pitch pitch(pio, octave, acc);
+
+ int p = pitch.getPerformancePitch();
+ if (p != (octave + 2) * 12 + pio) {
+ cout << "testPitchInOctave: " << pio << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "pitch is " << p << ", should be " << ((octave + 2) * 12 + pio) << endl;
+ rv = 1;
+ }
+
+ if (!rv && verbose) {
+ cout << "testNote: " << pio << " " << acc << ", " << key.getName() << ", octave " << octave << ": "
+ << "pitch " << p << endl;
+ }
+ return rv;
+}
+
+int testPitch(Accidental &acc, Key &key, Clef &clef, int pp)
+{
+ int rv = 0;
+
+ Pitch pitch(pp, acc);
+ NotationDisplayPitch ndp(pp, clef, key, acc);
+
+ int h = pitch.getHeightOnStaff(clef, key);
+ int nh = ndp.getHeightOnStaff();
+ if (h != nh) {
+ cout << "testPitch: " << pp << ", " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": "
+ << "height is " << h << " (ndp returns " << nh << ")" << endl;
+ rv = 1;
+ }
+
+ Accidental pa = pitch.getDisplayAccidental(key);
+ Accidental na = ndp.getAccidental();
+ if (pa != na) {
+ cout << "testPitch: " << pp << ", " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": "
+ << "display acc is " << pa << " (ndp returns " << na << ")" << endl;
+ rv = 1;
+ }
+
+ return rv;
+}
+
+int testHeight(Accidental &acc, Key &key, Clef &clef, int height)
+{
+ int rv = 0;
+
+ Pitch pitch(height, clef, key, acc);
+ NotationDisplayPitch ndp(height, acc);
+ NotationDisplayPitch ndp2(pitch.getPerformancePitch(), clef, key, acc);
+
+ int ppp = pitch.getPerformancePitch();
+ int npp = ndp.getPerformancePitch(clef, key);
+
+ if (ppp != npp) {
+ cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": "
+ << "pitch " << ppp << " (ndp returns " << npp << ")" << endl;
+ rv = 1;
+ }
+
+ int h = pitch.getHeightOnStaff(clef, key);
+ if (h != ndp.getHeightOnStaff() || h != height) {
+ cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": "
+ << "height " << h << " (ndp returns " << ndp.getHeightOnStaff() << ")" << endl;
+ rv = 1;
+ }
+
+ // for NoAccidental, the Pitch object will acquire the accidental
+ // from the current key whereas NotationDisplayPitch will not --
+ // hence we skip this test for NoAccidental
+ if (acc != Accidentals::NoAccidental) {
+ Accidental nacc = ndp2.getAccidental();
+ Accidental pacc = pitch.getDisplayAccidental(key);
+ if (nacc != pacc) {
+ cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": "
+ "acc " << pacc << " (ndp returns " << nacc << ")" << endl;
+ rv = 1;
+ }
+ }
+
+ if (!rv && verbose) {
+ cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": "
+ << "pitch " << ppp << endl;
+ }
+ return rv;
+
+}
+
+
+int main(int argc, char **argv)
+{
+ Accidentals::AccidentalList accidentals(Accidentals::getStandardAccidentals());
+ Clef::ClefList clefs(Clef::getClefs());
+
+ Key::KeyList keys;
+ Key::KeyList majorKeys(Key::getKeys(false));
+ Key::KeyList minorKeys(Key::getKeys(true));
+ keys.insert(keys.end(), majorKeys.begin(), majorKeys.end());
+ keys.insert(keys.end(), minorKeys.begin(), minorKeys.end());
+
+ for (int a = 0; a < accidentals.size(); ++a) {
+
+ for (int k = 0; k < keys.size(); ++k) {
+
+ for (int o = -2; o < 9; ++o) {
+ for (int n = 0; n < 7; ++n) {
+ testNote(accidentals[a], keys[k], o, n);
+ }
+ }
+
+ for (int o = -2; o < 9; ++o) {
+ for (int p = 0; p < 12; ++p) {
+ testPitchInOctave(accidentals[a], keys[k], o, p);
+ }
+ }
+
+ for (int o = -2; o < 9; ++o) {
+ for (int p = 0; p < 7; ++p) {
+ testNoteName(accidentals[a], keys[k], o, Pitch::getNoteForIndex(p));
+ }
+ }
+
+ for (int c = 0; c < clefs.size(); ++c) {
+
+ for (int p = 0; p < 128; ++p) {
+ testPitch(accidentals[a], keys[k], clefs[c], p);
+ }
+
+ for (int h = -20; h < 30; ++h) {
+ testHeight(accidentals[a], keys[k], clefs[c], h);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/base/test/seq/Makefile b/src/base/test/seq/Makefile
new file mode 100644
index 0000000..c32946e
--- /dev/null
+++ b/src/base/test/seq/Makefile
@@ -0,0 +1,6 @@
+
+all: complainer generator queue-timer queue-timer-jack
+
+%: %.c
+ cc $< -o $@ -ljack -lasound
+
diff --git a/src/base/test/seq/complainer.c b/src/base/test/seq/complainer.c
new file mode 100644
index 0000000..afe0a7f
--- /dev/null
+++ b/src/base/test/seq/complainer.c
@@ -0,0 +1,74 @@
+
+#include <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <sys/time.h>
+#include <sched.h>
+
+void
+callback(snd_seq_t *handle)
+{
+ snd_seq_event_t *ev = 0;
+
+ do {
+ if (snd_seq_event_input(handle, &ev) > 0) {
+
+ if (ev->type == SND_SEQ_EVENT_NOTEON) {
+
+ struct timeval tv;
+ static long last_usec = 0;
+ int pitch = ev->data.note.note;
+
+ snd_seq_timestamp_t evt = ev->time;
+
+ gettimeofday(&tv, 0);
+ printf("pitch %d at %ld sec %ld usec, off by %ld usec\n",
+ pitch, tv.tv_sec, tv.tv_usec, tv.tv_usec - ((last_usec + 500000) % 1000000));
+
+ last_usec = tv.tv_usec;
+ }
+ }
+
+ } while (snd_seq_event_input_pending(handle, 0) > 0);
+}
+
+int
+main(int argc, char **argv)
+{
+ snd_seq_t *handle;
+ int portid;
+ int npfd;
+ struct pollfd *pfd;
+ struct sched_param param;
+
+ if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+ fprintf(stderr, "failed to open ALSA sequencer interface\n");
+ return 1;
+ }
+
+ snd_seq_set_client_name(handle, "complainer");
+
+ if ((portid = snd_seq_create_simple_port
+ (handle, "complainer",
+ SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, 0)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer port\n");
+ return 1;
+ }
+
+ npfd = snd_seq_poll_descriptors_count(handle, POLLIN);
+ pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
+ snd_seq_poll_descriptors(handle, pfd, npfd, POLLIN);
+
+ param.sched_priority = 99;
+ if (sched_setscheduler(0, SCHED_FIFO, &param)) {
+ perror("failed to set high-priority scheduler");
+ }
+
+ printf("ready\n", npfd);
+
+ while (1) {
+ if (poll(pfd, npfd, 100000) > 0) {
+ callback(handle);
+ }
+ }
+}
+
diff --git a/src/base/test/seq/generator.c b/src/base/test/seq/generator.c
new file mode 100644
index 0000000..9f64d61
--- /dev/null
+++ b/src/base/test/seq/generator.c
@@ -0,0 +1,96 @@
+
+#include <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <sys/time.h>
+
+int
+main(int argc, char **argv)
+{
+ snd_seq_t *handle;
+ int portid;
+ int npfd;
+ struct pollfd *pfd;
+ int queue;
+ int i;
+ int rval;
+ int target;
+ snd_seq_queue_timer_t *timer;
+ snd_timer_id_t *timerid;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: generator <target-client-id>\n");
+ exit(2);
+ }
+ target = atoi(argv[1]);
+
+ if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+ fprintf(stderr, "failed to open ALSA sequencer interface\n");
+ return 1;
+ }
+
+ snd_seq_set_client_name(handle, "generator");
+
+ if ((portid = snd_seq_create_simple_port
+ (handle, "generator",
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer port\n");
+ return 1;
+ }
+
+ if ((queue = snd_seq_alloc_queue(handle)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer queue\n");
+ return 1;
+ }
+/*
+ snd_seq_queue_timer_alloca(&timer);
+ snd_seq_get_queue_timer(handle, queue, timer);
+ snd_timer_id_alloca(&timerid);
+ snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM);
+ snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE);
+ snd_timer_id_set_card(timerid, 0);
+ snd_timer_id_set_device(timerid, 0);
+ snd_timer_id_set_subdevice(timerid, 0);
+ snd_seq_queue_timer_set_id(timer, timerid);
+ snd_seq_set_queue_timer(handle, queue, timer);
+*/
+ snd_seq_start_queue(handle, queue, 0);
+
+ // stuff two minutes worth of events on the queue
+ for (i = 0; i < 240; ++i) {
+ snd_seq_real_time_t rtime;
+ rtime.tv_sec = i / 2;
+ rtime.tv_nsec = (i % 2) * 500000000;
+ snd_seq_event_t ev;
+ snd_seq_ev_clear(&ev);
+ snd_seq_ev_set_source(&ev, portid);
+ snd_seq_ev_set_dest(&ev, target, 0);
+ snd_seq_ev_schedule_real(&ev, queue, 0, &rtime);
+ snd_seq_ev_set_noteon(&ev, 0, 64, 127);
+ if ((rval = snd_seq_event_output(handle, &ev)) < 0) {
+ fprintf(stderr, "failed to write event: %s", snd_strerror(rval));
+ }
+ }
+
+ snd_seq_drain_output(handle);
+
+ for (i = 0; i < 120; ++i) {
+ snd_seq_queue_status_t *status;
+ const snd_seq_real_time_t *rtime;
+ struct timeval tv;
+
+ snd_seq_queue_status_alloca(&status);
+
+ snd_seq_get_queue_status(handle, queue, status);
+ rtime = snd_seq_queue_status_get_real_time(status);
+
+ gettimeofday(&tv, 0);
+
+ fprintf(stderr, " real time: %ld sec, %ld usec\nqueue time: %ld sec, %ld usec (diff to real time %ld sec %ld usec)\n",
+ tv.tv_sec, tv.tv_usec,
+ rtime->tv_sec, rtime->tv_nsec / 1000,
+ tv.tv_sec - rtime->tv_sec, tv.tv_usec - (rtime->tv_nsec / 1000));
+
+ sleep(1);
+ }
+}
+
diff --git a/src/base/test/seq/queue-timer-jack.c b/src/base/test/seq/queue-timer-jack.c
new file mode 100644
index 0000000..2648e94
--- /dev/null
+++ b/src/base/test/seq/queue-timer-jack.c
@@ -0,0 +1,166 @@
+
+#include <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <jack/jack.h>
+#include <sys/time.h>
+
+static jack_nframes_t sample_frames = 0;
+
+void normalize(struct timeval *tv)
+{
+ if (tv->tv_sec == 0) {
+ while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; }
+ while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+ } else if (tv->tv_sec < 0) {
+ while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; }
+ while (tv->tv_usec > 0) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+ } else {
+ while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+ while (tv->tv_usec < 0) { tv->tv_usec += 1000000; --tv->tv_sec; }
+ }
+}
+
+int
+jack_process(jack_nframes_t nframes, void *arg)
+{
+ sample_frames += nframes;
+}
+
+jack_nframes_t
+rt_to_frame(struct timeval tv, jack_nframes_t sample_rate)
+{
+ if (tv.tv_sec < 0) tv.tv_sec = -tv.tv_sec;
+ if (tv.tv_usec < 0) tv.tv_usec = -tv.tv_usec;
+ return
+ tv.tv_sec * sample_rate +
+ ((tv.tv_usec / 1000) * sample_rate) / 1000 +
+ ((tv.tv_usec - 1000 * (tv.tv_usec / 1000)) * sample_rate) / 1000000;
+}
+
+int
+main(int argc, char **argv)
+{
+ snd_seq_t *handle;
+ int portid;
+ int npfd;
+ struct pollfd *pfd;
+ int queue;
+ int i;
+ int rval;
+ struct timeval starttv;
+ int countdown = -1;
+ snd_seq_queue_timer_t *timer;
+ snd_timer_id_t *timerid;
+ jack_client_t *jclient;
+ jack_nframes_t sample_rate;
+
+ if ((jclient = jack_client_new("queue-timer-jack")) == 0) {
+ fprintf(stderr, "failed to connect to JACK server\n");
+ return 1;
+ }
+
+ jack_set_process_callback(jclient, jack_process, 0);
+
+ sample_rate = jack_get_sample_rate(jclient);
+
+ if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+ fprintf(stderr, "failed to open ALSA sequencer interface\n");
+ return 1;
+ }
+
+ snd_seq_set_client_name(handle, "generator");
+
+ if ((portid = snd_seq_create_simple_port
+ (handle, "generator",
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer port\n");
+ return 1;
+ }
+
+ if ((queue = snd_seq_alloc_queue(handle)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer queue\n");
+ return 1;
+ }
+
+ snd_seq_queue_timer_alloca(&timer);
+ snd_seq_get_queue_timer(handle, queue, timer);
+ snd_timer_id_alloca(&timerid);
+
+ /* To test a PCM timer: */
+/*
+ snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM);
+ snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE);
+ snd_timer_id_set_card(timerid, 0);
+ snd_timer_id_set_device(timerid, 0);
+ snd_timer_id_set_subdevice(timerid, 0);
+*/
+
+ /* To test the system timer: */
+ snd_timer_id_set_class(timerid, SND_TIMER_CLASS_GLOBAL);
+ snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE);
+ snd_timer_id_set_device(timerid, SND_TIMER_GLOBAL_SYSTEM);
+
+ snd_seq_queue_timer_set_id(timer, timerid);
+ snd_seq_set_queue_timer(handle, queue, timer);
+
+ if (jack_activate(jclient)) {
+ fprintf (stderr, "cannot activate jack client");
+ exit(1);
+ }
+
+ snd_seq_start_queue(handle, queue, 0);
+ snd_seq_drain_output(handle);
+
+ gettimeofday(&starttv, 0);
+
+ while (countdown != 0) {
+
+ snd_seq_queue_status_t *status;
+ const snd_seq_real_time_t *rtime;
+ struct timeval tv, qtv, jtv, diff, jdiff;
+ jack_nframes_t frames_now;
+
+ snd_seq_queue_status_alloca(&status);
+
+ snd_seq_get_queue_status(handle, queue, status);
+ rtime = snd_seq_queue_status_get_real_time(status);
+
+ gettimeofday(&tv, 0);
+
+ frames_now = sample_frames;
+ fprintf(stderr, " frames: %ld\n", frames_now);
+
+ qtv.tv_sec = rtime->tv_sec;
+ qtv.tv_usec = rtime->tv_nsec / 1000;
+
+ tv.tv_sec -= starttv.tv_sec;
+ tv.tv_usec -= starttv.tv_usec;
+ normalize(&tv);
+
+ jtv.tv_sec = frames_now / sample_rate;
+ frames_now -= jtv.tv_sec * sample_rate;
+ jtv.tv_usec = (int)(((float)frames_now * 1000000) / sample_rate);
+
+ diff.tv_sec = tv.tv_sec - qtv.tv_sec;
+ diff.tv_usec = tv.tv_usec - qtv.tv_usec;
+ normalize(&diff);
+
+ jdiff.tv_sec = jtv.tv_sec - qtv.tv_sec;
+ jdiff.tv_usec = jtv.tv_usec - qtv.tv_usec;
+ normalize(&jdiff);
+
+ fprintf(stderr, " real time: %12ld sec %8ld usec /%12ld frames\nqueue time: %12ld sec %8ld usec /%12ld frames\n jack time: %12ld sec %8ld usec /%12ld frames\n rq diff: %12ld sec %8ld usec /%12ld frames\n jq diff: %12ld sec %8ld usec /%12ld frames\n",
+ tv.tv_sec, tv.tv_usec, rt_to_frame(tv, sample_rate),
+ qtv.tv_sec, qtv.tv_usec, rt_to_frame(qtv, sample_rate),
+ jtv.tv_sec, jtv.tv_usec, rt_to_frame(jtv, sample_rate),
+ diff.tv_sec, diff.tv_usec, rt_to_frame(diff, sample_rate),
+ jdiff.tv_sec, jdiff.tv_usec, rt_to_frame(jdiff, sample_rate));
+
+ fprintf(stderr, "\n");
+ struct timespec ts;
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ nanosleep(&ts, 0);
+ }
+}
+
diff --git a/src/base/test/seq/queue-timer.c b/src/base/test/seq/queue-timer.c
new file mode 100644
index 0000000..2b7bac4
--- /dev/null
+++ b/src/base/test/seq/queue-timer.c
@@ -0,0 +1,123 @@
+
+#include <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <sys/time.h>
+
+void normalize(struct timeval *tv)
+{
+ if (tv->tv_sec == 0) {
+ while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; }
+ while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+ } else if (tv->tv_sec < 0) {
+ while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; }
+ while (tv->tv_usec > 0) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+ } else {
+ while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+ while (tv->tv_usec < 0) { tv->tv_usec += 1000000; --tv->tv_sec; }
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ snd_seq_t *handle;
+ int portid;
+ int npfd;
+ struct pollfd *pfd;
+ int queue;
+ int i;
+ int rval;
+ struct timeval starttv, prevdiff;
+ int countdown = -1;
+ snd_seq_queue_timer_t *timer;
+ snd_timer_id_t *timerid;
+
+ if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+ fprintf(stderr, "failed to open ALSA sequencer interface\n");
+ return 1;
+ }
+
+ snd_seq_set_client_name(handle, "generator");
+
+ if ((portid = snd_seq_create_simple_port
+ (handle, "generator",
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer port\n");
+ return 1;
+ }
+
+ if ((queue = snd_seq_alloc_queue(handle)) < 0) {
+ fprintf(stderr, "failed to create ALSA sequencer queue\n");
+ return 1;
+ }
+/*
+ snd_seq_queue_timer_alloca(&timer);
+ snd_seq_get_queue_timer(handle, queue, timer);
+ snd_timer_id_alloca(&timerid);
+ snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM);
+ snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE);
+ snd_timer_id_set_card(timerid, 0);
+ snd_timer_id_set_device(timerid, 0);
+ snd_timer_id_set_subdevice(timerid, 0);
+ snd_seq_queue_timer_set_id(timer, timerid);
+ snd_seq_set_queue_timer(handle, queue, timer);
+*/
+ snd_seq_start_queue(handle, queue, 0);
+ snd_seq_drain_output(handle);
+
+ gettimeofday(&starttv, 0);
+ prevdiff.tv_sec = 0;
+ prevdiff.tv_usec = 0;
+
+ while (countdown != 0) {
+
+ snd_seq_queue_status_t *status;
+ const snd_seq_real_time_t *rtime;
+ struct timeval tv, diff, diffdiff;
+
+ snd_seq_queue_status_alloca(&status);
+
+ snd_seq_get_queue_status(handle, queue, status);
+ rtime = snd_seq_queue_status_get_real_time(status);
+
+ gettimeofday(&tv, 0);
+
+ tv.tv_sec -= starttv.tv_sec;
+ tv.tv_usec -= starttv.tv_usec;
+ normalize(&tv);
+
+ diff.tv_sec = tv.tv_sec - rtime->tv_sec;
+ diff.tv_usec = tv.tv_usec - rtime->tv_nsec / 1000;
+ normalize(&diff);
+
+ diffdiff.tv_sec = diff.tv_sec - prevdiff.tv_sec;
+ diffdiff.tv_usec = diff.tv_usec - prevdiff.tv_usec;
+ normalize(&diffdiff);
+ prevdiff = diff;
+
+ fprintf(stderr, " real time: %12ld sec %8ld usec\nqueue time: %12ld sec %8ld usec\n diff: %12ld sec %8ld usec\n diffdiff: %12ld sec %8ld usec\n",
+ tv.tv_sec, tv.tv_usec,
+ rtime->tv_sec, rtime->tv_nsec / 1000,
+ diff.tv_sec, diff.tv_usec,
+ diffdiff.tv_sec, diffdiff.tv_usec);
+
+ if (diffdiff.tv_usec > 5000 ||
+ diffdiff.tv_usec < -5000) {
+ fprintf(stderr, "oops! queue slipped\n");
+ if (tv.tv_sec < 5) {
+ fprintf(stderr, "(ignoring in first few seconds)\n");
+ } else {
+ countdown = 2;
+ }
+ } else {
+ if (countdown > 0) --countdown;
+ }
+
+ fprintf(stderr, "\n");
+ struct timespec ts;
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ nanosleep(&ts, 0);
+ }
+}
+
diff --git a/src/base/test/test.cpp b/src/base/test/test.cpp
new file mode 100644
index 0000000..9a9b496
--- /dev/null
+++ b/src/base/test/test.cpp
@@ -0,0 +1,535 @@
+// -*- c-basic-offset: 4 -*-
+// -*- c-file-style: "bsd" -*-
+
+#define NDEBUG
+
+// This does some rather shoddy tests on a small selection of core classes.
+
+#include "Event.h"
+#include "Segment.h"
+#include "Composition.h"
+//#include "Sets.h"
+
+#define TEST_NOTATION_TYPES 1
+#define TEST_SPEED 1
+
+#ifdef TEST_NOTATION_TYPES
+#include "NotationTypes.h"
+#include "SegmentNotationHelper.h"
+#include "SegmentPerformanceHelper.h"
+#endif
+
+#include "MidiTypes.h"
+
+#include <cstdio>
+
+#include <sys/times.h>
+#include <iostream>
+
+using namespace std;
+using namespace Rosegarden;
+
+static const PropertyName DURATION_PROPERTY = "duration";
+static const PropertyName SOME_INT_PROPERTY = "someIntProp";
+static const PropertyName SOME_BOOL_PROPERTY = "someBoolProp";
+static const PropertyName SOME_STRING_PROPERTY = "someStringProp";
+static const PropertyName NONEXISTENT_PROPERTY = "nonexistentprop";
+static const PropertyName ANNOTATION_PROPERTY = "annotation";
+
+#if 0
+// Some attempts at reproducing the func-template-within-template problem
+//
+enum FooType {A, B, C};
+
+class Foo
+{
+public:
+ template<FooType T> void func();
+};
+
+template<class T>
+void Foo::func()
+{
+ // dummy code
+ T j = 0;
+ for(T i = 0; i < 100; ++i) j += i;
+}
+
+//template void Foo::func<int>();
+
+template <class R>
+class FooR
+{
+public:
+ void rfunc();
+};
+
+template<class R>
+void FooR<R>::rfunc()
+{
+ // this won't compile
+ Foo* foo;
+ foo->func<A>();
+}
+
+void templateTest()
+{
+ Foo foo;
+ foo.func<A>();
+
+// FooR<float> foor;
+// foor.rfunc();
+}
+
+
+template <class Element, class Container>
+class GenericSet // abstract base
+{
+public:
+ typedef typename Container::iterator Iterator;
+
+ /// Return true if this element, known to test() true, is a set member
+ virtual bool sample(const Iterator &i);
+};
+
+
+template <class Element, class Container>
+bool
+GenericSet<Element, Container>::sample(const Iterator &i)
+{
+ Event *e;
+ long p = e->get<Int>("blah");
+}
+
+#endif
+
+int main(int argc, char **argv)
+{
+ typedef std::vector<int> intvect;
+
+// intvect foo;
+
+// GenericSet<int, intvect> genset;
+// genset.sample(foo.begin());
+
+ clock_t st, et;
+ struct tms spare;
+
+#ifdef TEST_WIDE_STRING
+ basic_string<wchar_t> widestring(L"This is a test");
+ widestring += L" of wide character strings";
+ for (unsigned int i = 0; i < widestring.length(); ++i) {
+ if (widestring[i] == L'w' ||
+ widestring[i] == L'c') {
+ widestring[i] = toupper(widestring[i]);
+ }
+ }
+ cout << "Testing wide string: string value is \"" << widestring << "\""
+ << endl;
+ cout << "String's length is " << widestring.length() << endl;
+ cout << "and storage space is "
+ << (widestring.length() * sizeof(widestring[0]))
+ << endl;
+ cout << "Characters are: ";
+ for (unsigned int i = 0; i < widestring.length(); ++i) {
+ cout << widestring[i];
+ if (i < widestring.length()-1) cout << " ";
+ else cout << endl;
+ }
+#endif
+
+ cout << "\nTesting Event..." << endl
+ << "sizeof Event : " << sizeof(Event) << endl;
+
+ Event e("note", 0);
+ e.set<Int>(DURATION_PROPERTY, 20);
+ cout << "duration is " << e.get<Int>(DURATION_PROPERTY) << endl;
+
+ e.set<Bool>(SOME_BOOL_PROPERTY, true);
+ e.set<String>(SOME_STRING_PROPERTY, "foobar");
+
+ cout << "sizeof event after some properties set : "
+ << sizeof e << endl;
+
+ try {
+ cout << "duration is " << e.get<String>(DURATION_PROPERTY) << endl;
+ } catch (Event::BadType bt) {
+ cout << "Correctly caught BadType when trying to get<String> of duration" << endl;
+ }
+
+ string s;
+
+ if (!e.get<String>(DURATION_PROPERTY, s)) {
+ cout << "Correctly got error when trying to get<String> of duration" << endl;
+ } else {
+ cerr << "ERROR AT " << __LINE__ << endl;
+ }
+
+ try {
+ cout << "dummy prop is " << e.get<String>(NONEXISTENT_PROPERTY) << endl;
+ } catch (Event::NoData bt) {
+ cout << "Correctly caught NoData when trying to get non existent property" << endl;
+ }
+
+ if (!e.get<String>(NONEXISTENT_PROPERTY, s)) {
+ cout << "Correctly got error when trying to get<String> of non existent property" << endl;
+ } else {
+ cerr << "ERROR AT " << __LINE__ << endl;
+ }
+
+
+ e.setFromString<Int>(DURATION_PROPERTY, "30");
+ cout << "duration is " << e.get<Int>(DURATION_PROPERTY) << endl;
+
+ e.setFromString<String>(ANNOTATION_PROPERTY, "This is my house");
+ cout << "annotation is " << e.get<String>(ANNOTATION_PROPERTY) << endl;
+
+ long durationVal;
+ if (e.get<Int>(DURATION_PROPERTY, durationVal))
+ cout << "duration is " << durationVal << endl;
+ else
+ cerr << "ERROR AT " << __LINE__ << endl;
+
+ if (e.get<String>(ANNOTATION_PROPERTY, s))
+ cout << "annotation is " << s << endl;
+ else
+ cerr << "ERROR AT " << __LINE__ << endl;
+
+ cout << "\nTesting persistence & setMaybe..." << endl;
+
+ e.setMaybe<Int>(SOME_INT_PROPERTY, 1);
+ if (e.get<Int>(SOME_INT_PROPERTY) == 1) {
+ cout << "a. Correct: 1" << endl;
+ } else {
+ cout << "a. ERROR: " << e.get<Int>(SOME_INT_PROPERTY) << endl;
+ }
+
+ e.set<Int>(SOME_INT_PROPERTY, 2, false);
+ e.setMaybe<Int>(SOME_INT_PROPERTY, 3);
+ if (e.get<Int>(SOME_INT_PROPERTY) == 3) {
+ cout << "b. Correct: 3" << endl;
+ } else {
+ cout << "b. ERROR: " << e.get<Int>(SOME_INT_PROPERTY) << endl;
+ }
+
+ e.set<Int>(SOME_INT_PROPERTY, 4);
+ e.setMaybe<Int>(SOME_INT_PROPERTY, 5);
+ if (e.get<Int>(SOME_INT_PROPERTY) == 4) {
+ cout << "c. Correct: 4" << endl;
+ } else {
+ cout << "c. ERROR: " << e.get<Int>(SOME_INT_PROPERTY) << endl;
+ }
+
+ cout << "\nTesting debug dump : " << endl;
+ e.dump(cout);
+ cout << endl << "dump finished" << endl;
+
+#if TEST_SPEED
+ cout << "Testing speed of Event..." << endl;
+ int i;
+ long j;
+
+ char b[20];
+ strcpy(b, "test");
+
+#define NAME_COUNT 500
+
+ PropertyName names[NAME_COUNT];
+ for (i = 0; i < NAME_COUNT; ++i) {
+ sprintf(b+4, "%d", i);
+ names[i] = b;
+ }
+
+ Event e1("note", 0);
+ int gsCount = 200000;
+
+ st = times(&spare);
+ for (i = 0; i < gsCount; ++i) {
+ e1.set<Int>(names[i % NAME_COUNT], i);
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " setInts: " << (et-st)*10 << "ms\n";
+
+ st = times(&spare);
+ j = 0;
+ for (i = 0; i < gsCount; ++i) {
+ if (i%4==0) sprintf(b+4, "%d", i);
+ j += e1.get<Int>(names[i % NAME_COUNT]);
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " getInts: " << (et-st)*10 << "ms (result: " << j << ")\n";
+
+ st = times(&spare);
+ for (i = 0; i < 1000; ++i) {
+ Event e11(e1);
+ (void)e11.get<Int>(names[i % NAME_COUNT]);
+ }
+ et = times(&spare);
+ cout << "Event: 1000 copy ctors of " << e1.getStorageSize() << "-byte element: "
+ << (et-st)*10 << "ms\n";
+
+// gsCount = 100000;
+
+ for (i = 0; i < NAME_COUNT; ++i) {
+ sprintf(b+4, "%ds", i);
+ names[i] = b;
+ }
+
+ st = times(&spare);
+ for (i = 0; i < gsCount; ++i) {
+ e1.set<String>(names[i % NAME_COUNT], b);
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " setStrings: " << (et-st)*10 << "ms\n";
+
+ st = times(&spare);
+ j = 0;
+ for (i = 0; i < gsCount; ++i) {
+ if (i%4==0) sprintf(b+4, "%ds", i);
+ j += e1.get<String>(names[i % NAME_COUNT]).size();
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " getStrings: " << (et-st)*10 << "ms (result: " << j << ")\n";
+
+ st = times(&spare);
+ for (i = 0; i < 1000; ++i) {
+ Event e11(e1);
+ (void)e11.get<String>(names[i % NAME_COUNT]);
+ }
+ et = times(&spare);
+ cout << "Event: 1000 copy ctors of " << e1.getStorageSize() << "-byte element: "
+ << (et-st)*10 << "ms\n";
+
+ st = times(&spare);
+ for (i = 0; i < 1000; ++i) {
+ Event e11(e1);
+ (void)e11.get<String>(names[i % NAME_COUNT]);
+ (void)e11.set<String>(names[i % NAME_COUNT], "blah");
+ }
+ et = times(&spare);
+ cout << "Event: 1000 copy ctors plus set<String> of " << e1.getStorageSize() << "-byte element: "
+ << (et-st)*10 << "ms\n";
+
+// gsCount = 1000000;
+
+ st = times(&spare);
+ for (i = 0; i < gsCount; ++i) {
+ Event e21("dummy", i, 0, MIN_SUBORDERING);
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " event ctors alone: "
+ << (et-st)*10 << "ms\n";
+
+ st = times(&spare);
+ for (i = 0; i < gsCount; ++i) {
+ std::string s0("dummy");
+ std::string s1 = s0;
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " string ctors+assignents: "
+ << (et-st)*10 << "ms\n";
+
+ st = times(&spare);
+ for (i = 0; i < gsCount; ++i) {
+ Event e21("dummy", i, 0, MIN_SUBORDERING);
+ (void)e21.getAbsoluteTime();
+ (void)e21.getDuration();
+ (void)e21.getSubOrdering();
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " event ctors plus getAbsTime/Duration/SubOrdering: "
+ << (et-st)*10 << "ms\n";
+
+ st = times(&spare);
+ for (i = 0; i < gsCount; ++i) {
+ Event e21("dummy", i, 0, MIN_SUBORDERING);
+ (void)e21.getAbsoluteTime();
+ (void)e21.getDuration();
+ (void)e21.getSubOrdering();
+ e21.set<Int>(names[0], 40);
+ (void)e21.get<Int>(names[0]);
+ }
+ et = times(&spare);
+ cout << "Event: " << gsCount << " event ctors plus one get/set and getAbsTime/Duration/SubOrdering: "
+ << (et-st)*10 << "ms\n";
+
+
+#else
+ cout << "Skipping test speed of Event\n";
+#endif // TEST_SPEED
+
+#ifdef NOT_DEFINED
+ cout << "Testing segment shrinking\n";
+
+ Segment segment(5, 0);
+ unsigned int nbBars = segment.getNbBars();
+
+ cout << "Segment nbBars : " << nbBars << endl;
+ if (nbBars != 5) {
+ cerr << "%%%ERROR : segment nbBars should be 5\n";
+ }
+
+ Segment::iterator iter = segment.end();
+ --iter;
+ cout << "Last segment el. time : " << (*iter)->getAbsoluteTime() << endl;
+
+ cout << "Shrinking segment to 3 bars : \n";
+ segment.setNbBars(3);
+ nbBars = segment.getNbBars();
+
+ cout << "Segment new nbBars : " << nbBars << endl;
+ if (nbBars != 3) {
+ cerr << "%%%ERROR : segment new nbBars should be 3\n";
+ }
+#endif // NOT_DEFINED
+
+#ifdef TEST_NOTATION_TYPES
+ cout << "Testing duration-list stuff\n";
+
+ cout << "2/4..." << endl;
+ TimeSignature ts(2,4);
+ DurationList dlist;
+ ts.getDurationListForInterval
+ (dlist, 1209,
+ ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration());
+ int acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+
+
+ cout << "4/4 96/96..." << endl;
+ ts = TimeSignature(4,4);
+ dlist = DurationList();
+ ts.getDurationListForInterval(dlist, 96, 96);
+ acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+
+
+ cout << "6/8..." << endl;
+ ts = TimeSignature(6,8);
+ dlist = DurationList();
+ ts.getDurationListForInterval
+ (dlist, 1209,
+ ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration());
+ acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+ cout << "3/4..." << endl;
+ ts = TimeSignature(3,4);
+ dlist = DurationList();
+ ts.getDurationListForInterval
+ (dlist, 1209,
+ ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration());
+ acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+ cout << "4/4..." << endl;
+ ts = TimeSignature(4,4);
+ dlist = DurationList();
+ ts.getDurationListForInterval
+ (dlist, 1209,
+ ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration());
+ acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+ cout << "3/8..." << endl;
+ ts = TimeSignature(3,8);
+ dlist = DurationList();
+ ts.getDurationListForInterval
+ (dlist, 1209,
+ ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration());
+ acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+ cout << "4/4 wacky placement..." << endl;
+ ts = TimeSignature(4,4);
+ dlist = DurationList();
+ ts.getDurationListForInterval(dlist, 160, 1280);
+ acc = 0;
+ for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) {
+ cout << "duration: " << *i << endl;
+ acc += *i;
+ }
+ cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl;
+
+ cout << "Testing Segment::splitIntoTie() - splitting 384 -> 2*192\n";
+
+ Composition c;
+ Segment *ht = new Segment();
+ c.addSegment(ht);
+ Segment &t(*ht);
+ SegmentNotationHelper nh(t);
+ SegmentPerformanceHelper ph(t);
+
+ Event *ev = new Event("note", 0, 384);
+ ev->set<Int>("pitch", 60);
+ t.insert(ev);
+
+ Segment::iterator sb(t.begin());
+ nh.splitIntoTie(sb, 384/2);
+
+ for(Segment::iterator i = t.begin(); i != t.end(); ++i) {
+ cout << "Event at " << (*i)->getAbsoluteTime()
+ << " - duration : " << (*i)->getDuration()
+ << endl;
+ }
+
+ Segment::iterator half2 = t.begin(); ++half2;
+
+ cout << "Splitting 192 -> (48 + 144) : \n";
+
+ sb = t.begin();
+ nh.splitIntoTie(sb, 48);
+
+ for(Segment::iterator i = t.begin(); i != t.end(); ++i) {
+ cout << "Event at " << (*i)->getAbsoluteTime()
+ << " - duration : " << (*i)->getDuration()
+ << endl;
+ }
+
+ cout << "Splitting 192 -> (144 + 48) : \n";
+
+ nh.splitIntoTie(half2, 144);
+
+
+ for(Segment::iterator i = t.begin(); i != t.end(); ++i) {
+ cout << "Event at " << (*i)->getAbsoluteTime()
+ << " - duration : " << (*i)->getDuration()
+ << " - performance duration : " <<
+ ph.getSoundingDuration(i) << endl;
+
+ cout << endl;
+ (*i)->dump(cout);
+ cout << endl;
+ }
+
+ nh.autoBeam(t.begin(), t.end(), "beamed");
+
+#endif // TEST_NOTATION_TYPES
+};
+
diff --git a/src/base/test/thread.cpp b/src/base/test/thread.cpp
new file mode 100644
index 0000000..ab327ff
--- /dev/null
+++ b/src/base/test/thread.cpp
@@ -0,0 +1,126 @@
+// -*- c-basic-offset: 4 -*-
+// -*- c-file-style: "bsd" -*-
+
+// This does some rather shoddy tests on a small selection of core classes.
+
+#include "Lock.h"
+#include "Composition.h"
+#include "Segment.h"
+#include "Event.h"
+
+#include <cstdio>
+#include <sys/times.h>
+#include <iostream>
+
+#include <pthread.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace Rosegarden;
+
+static void*
+writer_thread1(void *arg)
+{
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ cout << "write_thread1 - init" << endl;
+
+ Rosegarden::Composition *comp =
+ static_cast<Rosegarden::Composition*>(arg);
+
+ Rosegarden::Composition::segmentcontainer segs = comp->getSegments();
+ Rosegarden::Composition::segmentcontainer::iterator it = segs.begin();
+ Rosegarden::Segment *segment = *it;
+
+ Rosegarden::timeT insertTime = 50000;
+ while (true)
+ {
+ usleep(90000);
+ cout << "LENGTH = " << comp->getNbBars() << endl;
+ segment->insert(new Event(Note::EventType, insertTime));
+ insertTime += 96;
+ }
+}
+
+static void*
+write_thread2(void *arg)
+{
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ cout << "write_thread2 - init" << endl;
+
+ Rosegarden::Composition *comp =
+ static_cast<Rosegarden::Composition*>(arg);
+
+ Rosegarden::Composition::segmentcontainer segs = comp->getSegments();
+ Rosegarden::Composition::segmentcontainer::iterator it = segs.begin();
+ Rosegarden::Segment *segment = *it;
+
+ Rosegarden::timeT insertTime = 0;
+ while (true)
+ {
+ usleep(50);
+ cout << "LENGTH = " << comp->getNbBars() << endl;
+ segment->insert(new Event(Note::EventType, insertTime));
+ insertTime += 96;
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ clock_t st, et;
+ struct tms spare;
+
+ cout << "Threading test" << endl;
+
+ pthread_t thread1;
+ pthread_t thread2;
+ Rosegarden::Composition comp;
+ Rosegarden::Segment segment;
+ comp.addSegment(&segment);
+
+ if (pthread_create(&thread1, 0, writer_thread1, &comp))
+ {
+ cerr << "Couldn't start thread 1" << endl;
+ exit(1);
+ }
+ pthread_detach(thread1);
+
+ if (pthread_create(&thread2, 0, write_thread2, &comp))
+ {
+ cerr << "Couldn't start thread 2" << endl;
+ exit(1);
+ }
+ pthread_detach(thread2);
+
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+ static Lock lock;
+
+ if (lock.getWriteLock(1))
+ {
+ cout << "got write lock" << endl;
+ }
+
+ if (lock.getWriteLock(0))
+ {
+ cout << "got second write lock" << endl;
+ }
+ else
+ {
+ cout << "couldn't get second write lock" << endl;
+ }
+
+ Rosegarden::timeT insertTime = 0;
+ while(true)
+ {
+ usleep(50000);
+
+ cout << "Inserting Event at time " << insertTime << endl;
+ segment.insert(new Event(Note::EventType, insertTime));
+ insertTime += 96;
+ }
+
+};
+
+
diff --git a/src/base/test/transpose.cpp b/src/base/test/transpose.cpp
new file mode 100644
index 0000000..b1254f5
--- /dev/null
+++ b/src/base/test/transpose.cpp
@@ -0,0 +1,83 @@
+// -*- c-basic-offset: 4 -*-
+
+#include "NotationTypes.h"
+
+using namespace Rosegarden;
+using std::cout;
+
+// Unit test-ish tests for transposition.
+//
+// Returns -1 (or crashes :)) on error, 0 on success
+
+/**
+ * should be in Pitch eventually
+ */
+void testAisDisplayAccidentalInCmaj()
+{
+ Pitch ais(70, Accidentals::Sharp);
+ Key cmaj ("C major");
+ Accidental accidental = ais.getDisplayAccidental(cmaj);
+ if (accidental != Accidentals::Sharp)
+ {
+ std::cout << "Accidental for A# in Cmaj was " << accidental << " instead of expected Sharp" << std::endl;
+ exit(-1);
+ }
+}
+
+/**
+ * transpose an A# up by a major second, should
+ * yield a B# (as C would be a minor triad)
+ */
+void testAisToBis()
+{
+ std::cout << "Testing transposing A# to B#... ";
+ Pitch ais(70, Accidentals::Sharp);
+ Key cmaj ("C major");
+
+ Pitch result = ais.transpose(cmaj, 2, 1);
+
+ Accidental resultAccidental = result.getAccidental(cmaj);
+ int resultPitch = result.getPerformancePitch();
+ if (resultAccidental != Accidentals::Sharp || resultPitch != 72)
+ {
+ std::cout << "Transposing A# up by a major second didn't yield B#, but " << result.getNoteName(cmaj) << resultAccidental << std::endl;
+ exit(-1);
+ }
+ std::cout << "Success" << std::endl;
+}
+
+/**
+ * Transpose G to D in the key of D major.
+ */
+void testGToD()
+{
+ std::cout << "Testing transposing G to D... ";
+ Pitch g(67, Accidentals::Natural);
+ Key* dmaj = new Key("D major");
+
+ Pitch result = g.transpose(*dmaj, 7, 4);
+
+ Accidental resultAccidental = result.getAccidental(*dmaj);
+ int resultPitch = result.getPerformancePitch();
+ if (resultAccidental != Accidentals::NoAccidental || resultPitch != 74)
+ {
+ std::cout << "Transposing G up by a fifth didn't yield D, but " << result.getNoteName(*dmaj) << resultAccidental << std::endl;
+ exit(-1);
+ }
+ std::cout << "Success" << std::endl;
+}
+
+void testKeyTransposition()
+{
+
+}
+
+int main(int argc, char **argv)
+{
+ testAisDisplayAccidentalInCmaj();
+ testAisToBis();
+ testGToD();
+ testKeyTransposition();
+
+ return 0;
+}
diff --git a/src/base/test/utf8.cpp b/src/base/test/utf8.cpp
new file mode 100644
index 0000000..7104cc0
--- /dev/null
+++ b/src/base/test/utf8.cpp
@@ -0,0 +1,96 @@
+// -*- c-basic-offset: 4 -*-
+
+#include "XmlExportable.h"
+#include <iostream>
+#include <string>
+
+using namespace Rosegarden;
+using std::cout;
+using std::cerr;
+using std::endl;
+using std::string;
+
+
+string binary(unsigned char c)
+{
+ string s;
+ for (int i = 0; i < 8; ++i) {
+ s = ((c & 0x1) ? '1' : '0') + s;
+ c >>= 1;
+ }
+ return s;
+}
+
+
+int main(int argc, char **argv)
+{
+ string valid[] = {
+ "ニュース",
+ "주요 뉴스",
+ "Nyheter",
+ "天气",
+ "Notícias",
+ };
+
+ string escapable[] = {
+ "ニュ&ース",
+ "주요 <뉴스>",
+ "\"Nyheter\"",
+ "\'Notícias\'",
+ };
+
+ string invalid[] = {
+ "����ース",
+ "주� � 뉴스",
+ "Nyhe\004ter",
+ "�天气",
+ "Not�cias",
+ };
+
+ cout << "Testing valid strings -- should be no errors here" << endl;
+
+ for (int i = 0; i < sizeof(valid)/sizeof(valid[0]); ++i) {
+ string encoded = XmlExportable::encode(valid[i]);
+ if (encoded != valid[i]) {
+ cerr << "Encoding failed:" << endl;
+ for (int j = 0; j < valid[i].length(); ++j) {
+ cerr << (char)valid[i][j] << " ("
+ << binary(valid[i][j]) << ")" << endl;
+ }
+ exit(1);
+ }
+ }
+
+ cout << "Testing escapable strings -- should be no errors here" << endl;
+
+ for (int i = 0; i < sizeof(escapable)/sizeof(escapable[0]); ++i) {
+ string encoded = XmlExportable::encode(escapable[i]);
+ if (encoded == escapable[i]) {
+ cerr << "Escaping failed:" << endl;
+ for (int j = 0; j < escapable[i].length(); ++j) {
+ cerr << (char)escapable[i][j] << " ("
+ << binary(escapable[i][j]) << ")" << endl;
+ }
+ exit(1);
+ }
+ }
+
+ cout << "Testing invalid strings -- should be "
+ << (sizeof(invalid)/sizeof(invalid[0]))
+ << " errors here (but no fatal ones)" << endl;
+
+ for (int i = 0; i < sizeof(invalid)/sizeof(invalid[0]); ++i) {
+ string encoded = XmlExportable::encode(invalid[i]);
+ if (encoded == invalid[i]) {
+ cerr << "Encoding succeeded but should have failed:" << endl;
+ for (int j = 0; j < invalid[i].length(); ++j) {
+ cerr << (char)invalid[i][j] << " ("
+ << binary(invalid[i][j]) << ")" << endl;
+ }
+ exit(1);
+ }
+ }
+
+ exit(0);
+}
+