On 04/03/11 10:50, Graham Percival wrote: > PATCH 2:
My apologies; that patch does not compile. Please consider this one instead.
PATCH 2: + src/sound/Tunings .h .cpp + data/pitches/tunings === src/base/NotationTypes .h .cpp // no other files modifiedThe change to the existing src/base/NotationTypes just adds stuff like ThreeQuartersFlat.
Cheers, - Graham
Index: src/sound/Tuning.cpp =================================================================== --- src/sound/Tuning.cpp (revision 0) +++ src/sound/Tuning.cpp (revision 0) @@ -0,0 +1,601 @@ +/* -*- 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. + Copyright 2000-2009 the Rosegarden development team. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "Tuning.h" + +#include "base/NotationTypes.h" +#include "gui/general/ResourceFinder.h" + +#include <QtDebug> + +#include <stdlib.h> +#include <qfile.h> +#include <qstring.h> +#include <iostream> +#include <qtextstream.h> +#include <math.h> +#include <string> + +// Set the debug level to: +// +// 1: summary printout of tunings after they've been read (default) +// 2: detail while parsing tunings file (verbose) +#define TUNING_DEBUG 1 + +/* What follows is a good example of why one should use lex+yacc etc + to parse files!! NJB, 2010 */ + +using namespace Rosegarden::Accidentals; + +// Map accidental number to accidental string +const Tuning::AccMap::value_type Tuning::accMapData[] = { + AccMap::value_type(-4, &DoubleFlat), + AccMap::value_type(-3, &ThreeQuarterFlat), + AccMap::value_type(-2, &Flat), + AccMap::value_type(-1, &QuarterFlat), + AccMap::value_type(0, &NoAccidental), + AccMap::value_type(1, &QuarterSharp), + AccMap::value_type(2, &Sharp), + AccMap::value_type(3, &ThreeQuarterSharp), + AccMap::value_type(4, &DoubleSharp) +}; +const unsigned int Tuning::accMapSize = + sizeof(Tuning::accMapData) / sizeof(Tuning::accMapData[0]); +Tuning::AccMap Tuning::accMap(Tuning::accMapData, + Tuning::accMapData + Tuning::accMapSize); + +std::vector<Tuning*> Tuning::m_tunings; + +std::vector<Tuning*> *Tuning::getTunings() { + + // if already have tunings, return them + // TODO: It would be polite to check the mtime on the tunings file + // and to re-read it if it's been changed. For now, you need + // to restart Rosegarden. + if (m_tunings.size()) + return &m_tunings; + + QString tuningsPath = ResourceFinder().getResourcePath("pitches", "tunings"); + # if (TUNING_DEBUG > 1) + qDebug() << "Path to tunings file:" << tuningsPath; + # endif + QFile fin(tuningsPath); + + IntervalList *intervals = new IntervalList; + SpellingList *spellings = new SpellingList; + + if (fin.open(IO_ReadOnly) ) { + bool inTuningDefinition = false; + int lineNumber = 0; + QString line = QString::null; + QTextStream stream( &fin ); + QString tuningName; + + while(!stream.atEnd()) { + line = stream.readLine(); + lineNumber++; + line = line.stripWhiteSpace(); +# if (TUNING_DEBUG > 1) + qDebug()<<lineNumber<<line; +# endif + + // ignore comments or blanks + if (line.isEmpty() || line.at(0) == '#' || line.at(0) == '!') + continue; + + // Tuning declaration + else if (line.startsWith(QString("Tuning"), false)) { + if (inTuningDefinition) { +# if (TUNING_DEBUG > 1) + qDebug() << "End of tuning" << tuningName; +# endif + std::string name = tuningName.ascii(); + Tuning *newTuning = new Tuning(name, intervals, spellings); + m_tunings.push_back(newTuning); +# if (TUNING_DEBUG) + newTuning->printTuning(); +# endif + intervals = new IntervalList; + spellings = new SpellingList; + } + + // Remove tuning keyword and get name from remainder of line + line.remove( 0, 6 ); + line = line.stripWhiteSpace(); + + tuningName = line; + inTuningDefinition = true; + +# if (TUNING_DEBUG > 1) + qDebug() << "\nNew Tuning: " << tuningName; +# endif + } + + // Must be a note line + else { + bool ok; + int commaPos; + + // is cents or ratio + + // get cents + double cents; + commaPos = line.find(QChar(',')); + QString intervalString = line; + intervalString.remove(commaPos, intervalString.length() - commaPos); + intervalString = intervalString.stripWhiteSpace(); + int dotPos = intervalString.find(QChar('.')); + if (dotPos == -1) { // interval is a ratio +# if (TUNING_DEBUG > 1) + qDebug() << "Interval is a ratio"; +# endif + int slashPos = intervalString.find(QChar('/')); + double ratio=1; + if (slashPos == -1) { // interval is integer ratio +# if (TUNING_DEBUG > 1) + qDebug() << "Ratio is an integer"; +# endif + ratio = intervalString.toInt( &ok ); + if (!ok) { + std::cerr << "Syntax Error in tunings file, line " + << lineNumber << std::endl; + continue; + } else { + //convert ratio to cents + QString numeratorString = intervalString; + numeratorString.remove(slashPos, + numeratorString.length() + - slashPos); +# if (TUNING_DEBUG > 1) + qDebug() << "numerator" << numeratorString; +# endif + int numerator = numeratorString.toInt( &ok ); + if (!ok) { + std::cerr << "Syntax Error in tunings file, " + "line " << lineNumber << std::endl; + continue; + } + QString denominatorString = intervalString; + denominatorString.remove( 0, slashPos+1 ); +# if (TUNING_DEBUG > 1) + qDebug() << "d" << denominatorString; +# endif + int denominator = denominatorString.toInt( &ok ); + if (!ok) { + std::cerr << "Syntax Error in tunings file, " + "line " << lineNumber << std::endl; + continue; + } + +# if (TUNING_DEBUG > 1) + qDebug() << "Ratio:" << numeratorString + << "/" << denominatorString; +# endif + + ratio = (double)numerator / (double)denominator; + + + //calculate cents + cents = 1200.0 * log( ratio )/log(2.0); +# if (TUNING_DEBUG > 1) + qDebug() << "cents" << cents; +# endif + } + } + } else { // interval is in cents +# if (TUNING_DEBUG > 1) + qDebug() << "Interval is in cents"; +# endif + cents = intervalString.toDouble(&ok); + if (!ok) { + std::cerr << "Syntax Error in tunings file, line " + << lineNumber << std::endl; + continue; + } +# if (TUNING_DEBUG > 1) + qDebug() << "Cents: " << cents; +# endif + } + + //add intervals to interval list + intervals->push_back(cents); + + //remove interval + line.remove(0, commaPos+1); + + + // get various spellings + while (!line.isEmpty()) { + line = line.stripWhiteSpace(); + commaPos = line.find(QChar(',')); + // Maybe there isn't alternate spelling, or + // maybe there's a naughty trailing comma + if (commaPos == -1 || commaPos == line.length()-1){ +# if (TUNING_DEBUG > 1) + qDebug() << "var:" << line; +# endif + QString note = line; + QString acc = note; + acc.remove(0, 1); + note.remove(1, note.length()-1); +# if (TUNING_DEBUG > 1) + qDebug() << "Acc " << acc << "\nPitch Class " << note; +# endif + if (acc.toInt() != 0) { + const int acc_i = atoi(acc.latin1()); + note.append(accMap[acc_i]->c_str()); + } + //insert into spelling list + spellings->insert(Spelling(note.ascii(), + intervals->size()-1)); + + line = QString::null; +# if (TUNING_DEBUG > 1) + qDebug() << "Translated variation:" << note << "\n"; +# endif + } else { + QString note = line; + note.remove(commaPos, note.length()-commaPos); + QString acc = note; + acc.remove(0, 1); + note.remove(1, note.length()-1); +# if (TUNING_DEBUG > 1) + qDebug() << "Acc" << acc << "\nPitch Class " << note; +# endif + if (acc.toInt() != 0) { + const int acc_i = atoi(acc.latin1()); + note.append(accMap[acc_i]->c_str()); + } + line.remove( 0, commaPos+1 ); + + //insert into spelling list + spellings->insert(Spelling(note.ascii(), + intervals->size()-1)); + +# if (TUNING_DEBUG > 1) + qDebug() << "Translated variation" << note; +# endif + } + } +# if (TUNING_DEBUG > 1) + qDebug("\n"); +# endif + } + + } + fin.close(); + if (inTuningDefinition) { +# if (TUNING_DEBUG > 1) + qDebug() << "End of tuning" << tuningName; +# endif + Tuning *new_tuning = new Tuning(tuningName.ascii(), intervals, spellings); + m_tunings.push_back(new_tuning); +# if (TUNING_DEBUG) + new_tuning->printTuning(); +# endif + } + return &m_tunings; + } + + std::cerr << "Fatal: Cannot find tunings file!" << std::endl; + return NULL; +} + + +Tuning::Tuning(std::string name, IntervalList *intervals, + SpellingList *spellings) : + m_rootPitch(9, 3), + m_refPitch(9, 3), + m_intervals(intervals), + m_spellings(spellings) +{ + m_name = name; + +# if (TUNING_DEBUG > 1) + qDebug() << "Given name:" << name.c_str() << &name; + qDebug() << "Stored name:" << m_name.c_str() << &m_name; +# endif + + m_size = intervals->size(); + + //check interval & spelling list sizes match + for (SpellingListIterator it = spellings->begin(); + it != spellings->end(); + it++) { + if (it->second > m_size) { + std::cerr << "Spelling list does not match " + "number of intervals!" + << std::endl; + spellings->erase( it ); + } + } + + + Rosegarden::Pitch p(9, 3); + + + //default A3 = 440; + setRootPitch(p); + setRefNote(p, 440); +} + +Tuning::Tuning(const Tuning *tuning) : + m_rootPitch(tuning->getRootPitch()), + m_refPitch(tuning->getRefPitch()), + m_intervals(tuning->getIntervalList()), + m_spellings(tuning->getSpellingList()) +{ + m_size = m_intervals->size(); + m_name = tuning->getName(); +# if (TUNING_DEBUG > 1) + qDebug() << "Given name:" << tuning->getName().c_str(); + qDebug() << "Stored name: " << m_name.c_str() << &m_name; +# endif + + //default A3 = 440; + Rosegarden::Pitch p=tuning->getRefPitch(); + Rosegarden::Pitch p2=tuning->getRootPitch(); + + setRootPitch(tuning->getRootPitch()); + setRefNote(p, tuning->getRefFreq()); + + Rosegarden::Key keyofc; // use key of C to obtain unbiased accidental + + std::cout << "Ref note " << p.getNoteName(keyofc) + << p.getDisplayAccidental( keyofc ) + << " " << m_refFreq << std::endl; + + std::cout << "Ref note " << m_refPitch.getNoteName(keyofc) + << m_refPitch.getDisplayAccidental( keyofc ) + << " " << m_refFreq << std::endl; + + std::cout << "Ref freq for C " << m_cRefFreq << std::endl; + + std::cout << "Root note " << p2.getNoteName(keyofc) + << p2.getDisplayAccidental(keyofc) + << std::endl; + std::cout << "Root note " << m_rootPitch.getNoteName(keyofc) + << m_rootPitch.getDisplayAccidental(keyofc) + << std::endl; +} + + + + +void Tuning::setRootPitch(Rosegarden::Pitch pitch){ + + m_rootPitch = pitch; + + std::string spelling = getSpelling( pitch );; + const SpellingListIterator sit = m_spellings->find(spelling); + if (sit == m_spellings->end()){ + std::cerr << "\nFatal: Tuning::setRootPitch root pitch " + "not found in tuning!!" << std::endl; + return; + } +# if (TUNING_DEBUG > 1) + qDebug() << "Root position" << m_rootPosition; +# endif + m_rootPosition = sit->second; +} + + +std::string Tuning::getSpelling(Rosegarden::Pitch &pitch) const { + + + const Rosegarden::Key key; + + QChar qc(pitch.getNoteName(key)); + QString spelling(qc); + + Rosegarden::Accidental acc = pitch.getDisplayAccidental(key); + if (acc != Rosegarden::Accidentals::NoAccidental && + acc != Rosegarden::Accidentals::Natural) { + spelling.append(acc.c_str()); + } + + return spelling.ascii(); +} + + +void Tuning::setRefNote(Rosegarden::Pitch pitch, double freq) { + + m_refPitch = pitch; + m_refFreq = freq; + m_refOctave = pitch.getOctave(); + std::string spelling = getSpelling(pitch); + + // position in chromatic scale + SpellingListIterator it = m_spellings->find(spelling); + if (it == m_spellings->end()) { + std::cerr << "Tuning::setRefNote Spelling " << spelling + << " not found in " << m_name + << " tuning!" << std::endl; + return; + } + int refPosition = it->second; + + // calculate frequency for C in reference octave + // this makes calculation of frequencies easier + + it = m_spellings->find("C"); + if (it == m_spellings->end()){ + std::cerr << "Tuning::setRefNote 'C' not found in " + << m_name << " tuning!\n"; + return; + } + + m_cPosition = it->second; + + // find position of C relative to root note + int cInterval = m_cPosition - m_rootPosition; + if (cInterval < 0) cInterval += m_size; + + // cents above root note for C + double cents = (*m_intervals)[cInterval]; + + // cents above root note for reference note + int refInterval = refPosition - m_rootPosition; + if( refInterval < 0 ) refInterval += m_size; + + double refCents = (*m_intervals)[refInterval]; + + // relative cents from reference note to target note + double relativeCents = cents - refCents; + if (relativeCents > 0) relativeCents -= 1200; + + //frequency ratio between reference and target notes + double ratio = pow( 2, relativeCents/1200 ); + + m_cRefFreq = m_refFreq * ratio; + +# if (TUNING_DEBUG) + qDebug() << "c Position" << m_cPosition + << "\nc interval" << cInterval + << "\nc Cents" << cents + << "\nref position" << refPosition + << "\nref interval" << refInterval + << "\nref Cents" << refCents + << "\nc freq" << m_cRefFreq + << "\nref octave" << m_refOctave; +# endif +} + +/** +* Returns the frequency of the given pitch in the current tuning. +*/ +double Tuning::getFrequency(Rosegarden::Pitch p) const { + + // make note spelling + std::string spelling = getSpelling(p); + + int octave = p.getOctave(); + + // position in chromatic scale + const SpellingListIterator it = m_spellings->find(spelling); + if (it == m_spellings->end()) { + std::cerr << "Tuning::getFreq Spelling '" << spelling + << "' not found in " << m_name << " tuning!\n"; + return 0; + } + int position = it->second; + + // find position relative to root note + int relativePosition = position - m_rootPosition; + if (relativePosition < 0) relativePosition += m_size; + + // cents above root note for target note + double cents = (*m_intervals)[relativePosition]; + + // cents above root note for reference note ( C ) + int refInterval = m_cPosition - m_rootPosition; + if (refInterval < 0) refInterval += m_size; + double refCents = (*m_intervals)[refInterval]; + + // relative cents from reference note to target note + double relativeCents = cents - refCents; + if (relativeCents < 0) relativeCents += 1200; + + //frequency ratio between reference and target notes + double ratio = pow(2, relativeCents/1200); + + /* + B# occurs in the same octave as the C immediatley above them. + In 12ET this is true, but not in all tunings. + Solution: When B# and C are not equivalent spellings, + decrement the octave of every B#. + */ + if (spelling == "Bsharp" && position != m_cPosition) { + octave--; + } + + int octaveDifference = octave - m_refOctave; + + int octaveRatio = pow( 2, octaveDifference ); + + ratio *= octaveRatio; + + double freq = m_cRefFreq * ratio; + +# if (TUNING_DEBUG) + qDebug() << "Spelling " << spelling.c_str() + << "\ntarget position " << position + << "\nroot position " << m_rootPosition + << "\ntarget interval " << relativePosition + << "\ncents above root note " << cents + << "\ninterval for C " << refInterval + << "\ncents from reference note " << refCents + << "\ncents from reference to target" << relativeCents + << "\nrefOctave " << m_refOctave + << "\noctave " << octave + << "\noctave ratio " << octaveRatio + << "\nratio " << ratio + << "\nref freq " << m_refFreq + << "\nfreq " << freq; +# endif + + return freq; +} + +/** +* Prints to std out for debugging +*/ +void Tuning::printTuning() const { + + std::cout << "Print Tuning\n"; + std::cout << "Name: '" << m_name << "'\n"; + + Rosegarden::Key keyofc; // use key of C to obtain unbiased accidental + + std::cout << "Ref note " << m_refPitch.getNoteName(keyofc) + << m_refPitch.getDisplayAccidental( keyofc ) + << " " << m_refFreq << std::endl; + + std::cout << "Ref freq for C " << m_cRefFreq << std::endl; + + std::cout << "Root note " << m_rootPitch.getNoteName(keyofc) + << m_rootPitch.getDisplayAccidental( keyofc ) + << std::endl; + + for (SpellingListIterator sit = m_spellings->begin(); + sit != m_spellings->end(); + sit++) { + + std::cout << "Spelling '" << sit->first + << "'\tinterval " << sit->second + << std::endl; + + } + + for(unsigned int i=0; i < m_intervals->size(); i++) { + std::cout << "Interval '" << i + << "'\tinterval " << m_intervals->at(i) + << std::endl; + } + + std::cout << "Done." << std::endl; + +} + + +Rosegarden::Pitch Tuning::getRootPitch() const { return m_rootPitch; } +Rosegarden::Pitch Tuning::getRefPitch() const { return m_refPitch; } +double Tuning::getRefFreq() const{ return m_refFreq; } +const std::string Tuning::getName() const { return m_name; } +SpellingList *Tuning::getSpellingList() const{ return m_spellings; } +IntervalList *Tuning::getIntervalList() const{ return m_intervals; } + Index: src/sound/Tuning.h =================================================================== --- src/sound/Tuning.h (revision 0) +++ src/sound/Tuning.h (revision 0) @@ -0,0 +1,119 @@ + +#ifndef TUNING_H_ +#define TUNING_H_ + + +#include <vector> +#include <string> +#include <map> + +#include "base/NotationTypes.h" + +//!!! Q: Should this class be using QString and QVector also? +//!!! A: A definite "probably". We use the Qt classes in the +//!!! PitchDetector, but that is RG specific. This code is +//!!! more use in other n-ism projects, and is easier to +//!!! break (more head-scratching, less boiler-plate) so +//!!! for now it's implemented with the std:: classes. - njb + +typedef std::pair< std::string, int > Spelling; +typedef std::map< std::string, int > SpellingList; +typedef SpellingList::iterator SpellingListIterator; +typedef std::vector< double > IntervalList; +typedef std::vector< double >::iterator IntervalListIterator; + +namespace Rosegarden { +namespace Accidentals { + +/** + * \addtogroup Codicil + * \@{ + * \brief Read the Codicil's tunings file and create a tunings database + * + * This is part of the Glasgow Center for Music Technology's + * "Rosegarden Codicil" project. + * http://www.n-ism.org/Projects/microtonalism.php + * + * \author Dougie McGilvray, Nick Bailey + * \date 2010 + */ +class Tuning { + + public: + + /** + * \brief Construct a tuning from its name, and interval and spellings. + * + * \param name Name of the new tuning + * \param intervals List of intervals in cents from the root pitch + * \param spellings List of spellings (enharmonic equivalents) for each pitch + */ + Tuning(std::string name, IntervalList *intervals, + SpellingList *spellings); + Tuning(const Tuning *tuning); + + /** + * \brief Access the vector of tunings known to the system + */ + static std::vector<Tuning*>* getTunings(); + + /** + * \brief Set the frequency associated with the reference pitch + * + * \param pitch The reference pitch + * \param freq Associated frequency (in Hz) + */ + void setRefNote(Rosegarden::Pitch pitch, double freq); + + /** + * \brief Nominate the root pitch for this tuning + * + * \param pitch The root pitch + */ + void setRootPitch(Rosegarden::Pitch pitch); + + /** + * \brief Calculate a frequency + * + * \param pitch The pitch for which a frequency is required + * \return Frequency in Hz. + */ + double getFrequency(Rosegarden::Pitch pitch) const; + + const std::string getName() const; /**< Get the Tuning's name */ + SpellingList *getSpellingList() const; /**< Get the enharmonic spellings */ + IntervalList *getIntervalList() const; /**< Get the intervals in cents*/ + Rosegarden::Pitch getRootPitch() const; /**< Get the root pitch */ + Rosegarden::Pitch getRefPitch() const; /**< Get the reference pitch */ + double getRefFreq() const; /**< Get the reference frequency */ + void printTuning() const; /**< Print the Tuning (debugging) */ + + protected: + + /** Converts pitch to string */ + std::string getSpelling(Rosegarden::Pitch &pitch) const; + std::string m_name; + Rosegarden::Pitch m_rootPitch; + int m_rootPosition; + Rosegarden::Pitch m_refPitch; + int m_refOctave; + int m_cPosition; + double m_refFreq; + double m_cRefFreq; + int m_size; + IntervalList *m_intervals; + SpellingList *m_spellings; + + typedef std::map<const int, const Accidental *> AccMap; + static AccMap accMap; + static const unsigned int accMapSize; + static const AccMap::value_type accMapData[]; + static std::vector<Tuning*> m_tunings; +}; + +} // end of namespace Accidentals +} // end of namespace Rosegarden + +/**\@}*/ + +#endif Index: src/base/NotationTypes.h =================================================================== --- src/base/NotationTypes.h (revision 12260) +++ src/base/NotationTypes.h (working copy) @@ -83,6 +83,10 @@ extern const Accidental Natural; extern const Accidental DoubleSharp; extern const Accidental DoubleFlat; + extern const Accidental QuarterFlat; + extern const Accidental ThreeQuarterFlat; + extern const Accidental QuarterSharp; + extern const Accidental ThreeQuarterSharp; typedef std::vector<Accidental> AccidentalList; Index: src/base/NotationTypes.cpp =================================================================== --- src/base/NotationTypes.cpp (revision 12260) +++ src/base/NotationTypes.cpp (working copy) @@ -56,7 +56,13 @@ const Accidental Natural = "natural"; const Accidental DoubleSharp = "double-sharp"; const Accidental DoubleFlat = "double-flat"; - + + // Additional Accidentals for demi- and sesqui- sharps and flats + const Accidental QuarterFlat = "demiflat"; + const Accidental ThreeQuarterFlat = "sesqiflat"; + const Accidental QuarterSharp = "demisharp"; + const Accidental ThreeQuarterSharp = "sesquisharp"; + AccidentalList getStandardAccidentals() { static Accidental a[] = { Index: data/pitches/tunings =================================================================== --- data/pitches/tunings (revision 0) +++ data/pitches/tunings (revision 0) @@ -0,0 +1,61 @@ + +# Define tunings for the Rosegarden Pitch Tracker here. + +#System +#(name of system) +#Tuning (tuning of system) +# +#(chromatic note number), (note spelling), (spelling variation) +#(chromatic note number), (note spelling), (spelling variation) +# +# etc.. +# +#endSystem + +# Note Spelling +# Rosegarden denotes microtonal accidentals by changing the value of controller 127. +# The values are: +# -4 double flat, -3 triplesemiflat, -2 flat, -1 semiflat +# 4 double sharp, 3 triplesemisharp, 2 sharp, 1 semisharp +# Therefore a B semisharp is B1. +# + + +Tuning 12ET +# cents spelling +0.0, A, B-4, G4 +100.0, A2, B-2, C-4 +200.0, B, A4, C-2 +300.0, C, D-4, B2 +400.0, C2, D-2 +500.0, D, E-4, C4 +600.0, D2, E-2 +700.0, E, F-2, D4 +800.0, F, G-4. E2 +900.0, F2, G-2 +1000.0, G, A-4, F4 +1100.0, G2, A-2 + +Tuning 19 ET +# cents, spelling +0.0, A +63.158, A2, B-4 +126.316, B-2, A4 +189.474, B +252.632, B2, C-2 +315.789, C +378.947, C2, D-4 +442.105, D-2, C4 +505.263, D +568.421, D2, E-4 +631.579, E-2, D4 +694.737, E +757.895, E2, F-2 +821.053, F +884.211, F2, G-4 +947.368, G-2, F4 +1010.526, G +1073.684, G2, A-4 +1136.842, A-2, G4 + +
------------------------------------------------------------------------------ What You Don't Know About Data Connectivity CAN Hurt You This paper provides an overview of data connectivity, details its effect on application quality, and explores various alternative solutions. http://p.sf.net/sfu/progress-d2d
_______________________________________________ Rosegarden-devel mailing list Rosegarden-devel@lists.sourceforge.net - use the link below to unsubscribe https://lists.sourceforge.net/lists/listinfo/rosegarden-devel