diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/base | |
download | rosegarden-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')
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 ¬ationQuantizer(); + + /** + * 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, "&", 5); buflen += 5; break; + case '<' : strncpy(buffer + buflen, "<", 4); buflen += 4; break; + case '>' : strncpy(buffer + buflen, ">", 4); buflen += 4; break; + case '"' : strncpy(buffer + buflen, """, 6); buflen += 6; break; + case '\'' : strncpy(buffer + buflen, "'", 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, ¶m)) { + 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); +} + |