563 lines
18 KiB
C++
563 lines
18 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Linux Music Score Editor
|
|
// $Id: importbww.cpp 5427 2012-03-07 12:41:34Z wschweer $
|
|
//
|
|
// Copyright (C) 2010 Werner Schweer and others
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
//=============================================================================
|
|
|
|
// TODO LVI 2011-10-30: determine how to report import errors.
|
|
// Currently all output (both debug and error reports) are done using qDebug.
|
|
|
|
#include "bww2mxml/lexer.h"
|
|
#include "bww2mxml/writer.h"
|
|
#include "bww2mxml/parser.h"
|
|
|
|
#include "libmscore/barline.h"
|
|
#include "libmscore/box.h"
|
|
#include "libmscore/chord.h"
|
|
#include "libmscore/clef.h"
|
|
#include "libmscore/keysig.h"
|
|
#include "libmscore/layoutbreak.h"
|
|
#include "libmscore/measure.h"
|
|
#include "libmscore/note.h"
|
|
#include "libmscore/part.h"
|
|
#include "libmscore/pitchspelling.h"
|
|
#include "libmscore/score.h"
|
|
#include "libmscore/slur.h"
|
|
#include "libmscore/tie.h"
|
|
#include "libmscore/staff.h"
|
|
#include "libmscore/tempotext.h"
|
|
#include "libmscore/timesig.h"
|
|
#include "libmscore/tuplet.h"
|
|
#include "libmscore/volta.h"
|
|
#include "libmscore/segment.h"
|
|
#include "musescore.h"
|
|
#include "musicxml.h"
|
|
|
|
//---------------------------------------------------------
|
|
// addText
|
|
// copied from importxml.cpp
|
|
// TODO: remove duplicate code
|
|
//---------------------------------------------------------
|
|
|
|
static void addText(Ms::VBox*& vbx, Ms::Score* s, QString strTxt, Ms::SubStyleId stl)
|
|
{
|
|
if (!strTxt.isEmpty()) {
|
|
Ms::Text* text = new Ms::Text(stl, s);
|
|
text->setPlainText(strTxt);
|
|
if (vbx == 0)
|
|
vbx = new Ms::VBox(s);
|
|
vbx->add(text);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// xmlSetPitch
|
|
// copied and adapted from importxml.cpp
|
|
// TODO: remove duplicate code
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert MusicXML \a step / \a alter / \a octave to midi pitch,
|
|
set pitch and tpc.
|
|
*/
|
|
|
|
static void xmlSetPitch(Ms::Note* n, char step, int alter, int octave)
|
|
{
|
|
int istep = step - 'A';
|
|
// a b c d e f g
|
|
static int table[7] = { 9, 11, 0, 2, 4, 5, 7 };
|
|
if (istep < 0 || istep > 6) {
|
|
qDebug("xmlSetPitch: illegal pitch %d, <%c>", istep, step);
|
|
return;
|
|
}
|
|
int pitch = table[istep] + alter + (octave+1) * 12;
|
|
|
|
if (pitch < 0)
|
|
pitch = 0;
|
|
if (pitch > 127)
|
|
pitch = 127;
|
|
|
|
n->setPitch(pitch);
|
|
|
|
// a b c d e f g
|
|
static int table1[7] = { 5, 6, 0, 1, 2, 3, 4 };
|
|
int tpc = step2tpc(table1[istep], Ms::AccidentalVal(alter));
|
|
n->setTpc(tpc);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTempo
|
|
// copied and adapted from importxml.cpp
|
|
// TODO: remove duplicate code
|
|
//---------------------------------------------------------
|
|
|
|
static void setTempo(Ms::Score* score, int tempo)
|
|
{
|
|
Ms::TempoText* tt = new Ms::TempoText(score);
|
|
tt->setTempo(double(tempo)/60.0);
|
|
tt->setTrack(0);
|
|
QString tempoText = Ms::TempoText::duration2tempoTextString(Ms::TDuration::DurationType::V_QUARTER);
|
|
tempoText += QString(" = %1").arg(tempo);
|
|
tt->setPlainText(tempoText);
|
|
Ms::Measure* measure = score->firstMeasure();
|
|
Ms::Segment* segment = measure->getSegment(Ms::SegmentType::ChordRest, 0);
|
|
segment->add(tt);
|
|
}
|
|
|
|
namespace Bww {
|
|
|
|
/**
|
|
The writer that imports into MuseScore.
|
|
*/
|
|
|
|
class MsScWriter : public Writer
|
|
{
|
|
public:
|
|
MsScWriter();
|
|
void beginMeasure(const Bww::MeasureBeginFlags mbf);
|
|
void endMeasure(const Bww::MeasureEndFlags mef);
|
|
void header(const QString title, const QString type,
|
|
const QString composer, const QString footer,
|
|
const unsigned int temp);
|
|
void note(const QString pitch, const QVector<Bww::BeamType> beamList,
|
|
const QString type, const int dots,
|
|
bool tieStart = false, bool tieStop = false,
|
|
StartStop triplet = ST_NONE,
|
|
bool grace = false);
|
|
void setScore(Ms::Score* s) { score = s; }
|
|
void tsig(const int beats, const int beat);
|
|
void trailer();
|
|
private:
|
|
void doTriplet(Ms::Chord* cr, StartStop triplet = ST_NONE);
|
|
static const int WHOLE_DUR = 64; ///< Whole note duration
|
|
struct StepAlterOct { ///< MusicXML step/alter/oct values
|
|
QChar s;
|
|
int a;
|
|
int o;
|
|
StepAlterOct(QChar step = QChar('C'), int alter = 0, int oct = 1)
|
|
: s(step), a(alter), o(oct) {};
|
|
};
|
|
Ms::Score* score; ///< The score
|
|
int beats; ///< Number of beats
|
|
int beat; ///< Beat type
|
|
QMap<QString, StepAlterOct> stepAlterOctMap; ///< Map bww pitch to step/alter/oct
|
|
QMap<QString, QString> typeMap; ///< Map bww note types to MusicXML
|
|
unsigned int measureNumber; ///< Current measure number
|
|
unsigned int tick; ///< Current tick
|
|
Ms::Measure* currentMeasure; ///< Current measure
|
|
Ms::Tuplet* tuplet; ///< Current tuplet
|
|
Ms::Volta* lastVolta; ///< Current volta
|
|
unsigned int tempo; ///< Tempo (0 = not specified)
|
|
unsigned int ending; ///< Current ending
|
|
QList<Ms::Chord*> currentGraceNotes;
|
|
};
|
|
|
|
/**
|
|
MsScWriter constructor.
|
|
*/
|
|
|
|
MsScWriter::MsScWriter()
|
|
: score(0),
|
|
beats(4),
|
|
beat(4),
|
|
measureNumber(0),
|
|
tick(0),
|
|
currentMeasure(0),
|
|
tuplet(0),
|
|
lastVolta(0),
|
|
tempo(0)
|
|
{
|
|
qDebug() << "MsScWriter::MsScWriter()";
|
|
|
|
stepAlterOctMap["LG"] = StepAlterOct('G', 0, 4);
|
|
stepAlterOctMap["LA"] = StepAlterOct('A', 0, 4);
|
|
stepAlterOctMap["B"] = StepAlterOct('B', 0, 4);
|
|
stepAlterOctMap["C"] = StepAlterOct('C', 1, 5);
|
|
stepAlterOctMap["D"] = StepAlterOct('D', 0, 5);
|
|
stepAlterOctMap["E"] = StepAlterOct('E', 0, 5);
|
|
stepAlterOctMap["F"] = StepAlterOct('F', 1, 5);
|
|
stepAlterOctMap["HG"] = StepAlterOct('G', 0, 5);
|
|
stepAlterOctMap["HA"] = StepAlterOct('A', 0, 5);
|
|
|
|
typeMap["1"] = "whole";
|
|
typeMap["2"] = "half";
|
|
typeMap["4"] = "quarter";
|
|
typeMap["8"] = "eighth";
|
|
typeMap["16"] = "16th";
|
|
typeMap["32"] = "32nd";
|
|
}
|
|
|
|
/**
|
|
Begin a new measure.
|
|
*/
|
|
|
|
void MsScWriter::beginMeasure(const Bww::MeasureBeginFlags mbf)
|
|
{
|
|
qDebug() << "MsScWriter::beginMeasure()";
|
|
++measureNumber;
|
|
|
|
// create a new measure
|
|
currentMeasure = new Ms::Measure(score);
|
|
currentMeasure->setTick(tick);
|
|
currentMeasure->setTimesig(Ms::Fraction(beats, beat));
|
|
currentMeasure->setNo(measureNumber);
|
|
score->measures()->add(currentMeasure);
|
|
|
|
if (mbf.repeatBegin)
|
|
currentMeasure->setRepeatStart(true);
|
|
|
|
if (mbf.irregular)
|
|
currentMeasure->setIrregular(true);
|
|
|
|
if (mbf.endingFirst || mbf.endingSecond) {
|
|
Ms::Volta* volta = new Ms::Volta(score);
|
|
volta->setTrack(0);
|
|
volta->endings().clear();
|
|
if (mbf.endingFirst) {
|
|
volta->setText("1");
|
|
volta->endings().append(1);
|
|
ending = 1;
|
|
}
|
|
else {
|
|
volta->setText("2");
|
|
volta->endings().append(2);
|
|
ending = 2;
|
|
}
|
|
volta->setTick(currentMeasure->tick());
|
|
score->addElement(volta);
|
|
lastVolta = volta;
|
|
}
|
|
|
|
// set clef, key and time signature in the first measure
|
|
if (measureNumber == 1) {
|
|
// clef
|
|
Ms::Clef* clef = new Ms::Clef(score);
|
|
clef->setClefType(Ms::ClefType::G);
|
|
clef->setTrack(0);
|
|
Ms::Segment* s = currentMeasure->getSegment(Ms::SegmentType::Clef, tick);
|
|
s->add(clef);
|
|
// keysig
|
|
Ms::KeySigEvent key;
|
|
key.setKey(Ms::Key::D);
|
|
Ms::KeySig* keysig = new Ms::KeySig(score);
|
|
keysig->setKeySigEvent(key);
|
|
keysig->setTrack(0);
|
|
s = currentMeasure->getSegment(Ms::SegmentType::KeySig, tick);
|
|
s->add(keysig);
|
|
// timesig
|
|
Ms::TimeSig* timesig = new Ms::TimeSig(score);
|
|
timesig->setSig(Ms::Fraction(beats, beat));
|
|
timesig->setTrack(0);
|
|
s = currentMeasure->getSegment(Ms::SegmentType::TimeSig, tick);
|
|
s->add(timesig);
|
|
qDebug("tempo %d", tempo);
|
|
}
|
|
}
|
|
|
|
/**
|
|
End the current measure.
|
|
*/
|
|
|
|
void MsScWriter::endMeasure(const Bww::MeasureEndFlags mef)
|
|
{
|
|
qDebug() << "MsScWriter::endMeasure()";
|
|
if (mef.repeatEnd)
|
|
currentMeasure->setRepeatEnd(true);
|
|
|
|
if (mef.endingEnd) {
|
|
if (lastVolta) {
|
|
qDebug("adding volta");
|
|
if (ending == 1)
|
|
lastVolta->setVoltaType(Ms::Volta::Type::CLOSED);
|
|
else
|
|
lastVolta->setVoltaType(Ms::Volta::Type::OPEN);
|
|
lastVolta->setTick2(tick);
|
|
lastVolta = 0;
|
|
}
|
|
else {
|
|
qDebug("lastVolta == 0 on stop");
|
|
}
|
|
}
|
|
|
|
if (mef.lastOfSystem) {
|
|
Ms::LayoutBreak* lb = new Ms::LayoutBreak(score);
|
|
lb->setTrack(0);
|
|
lb->setLayoutBreakType(Ms::LayoutBreak::Type::LINE);
|
|
currentMeasure->add(lb);
|
|
}
|
|
|
|
if (mef.lastOfPart && !mef.repeatEnd) {
|
|
//TODO currentMeasure->setEndBarLineType(Ms::BarLineType::END, false, true);
|
|
}
|
|
else if (mef.doubleBarLine) {
|
|
//TODO currentMeasure->setEndBarLineType(Ms::BarLineType::DOUBLE, false, true);
|
|
}
|
|
// BarLine* barLine = new BarLine(score);
|
|
// bool visible = true;
|
|
// barLine->setSubtype(BarLineType::NORMAL);
|
|
// barLine->setTrack(0);
|
|
// currentMeasure->setEndBarLineType(barLine->subtype(), false, visible);
|
|
}
|
|
|
|
/**
|
|
Write a single note.
|
|
*/
|
|
|
|
void MsScWriter::note(const QString pitch, const QVector<Bww::BeamType> beamList,
|
|
const QString type, const int dots,
|
|
bool tieStart, bool /*TODO tieStop */,
|
|
StartStop triplet,
|
|
bool grace)
|
|
{
|
|
qDebug() << "MsScWriter::note()"
|
|
<< "type:" << type
|
|
<< "dots:" << dots
|
|
<< "grace" << grace
|
|
;
|
|
|
|
if (!stepAlterOctMap.contains(pitch)
|
|
|| !typeMap.contains(type)) {
|
|
// TODO: error message
|
|
return;
|
|
}
|
|
StepAlterOct sao = stepAlterOctMap.value(pitch);
|
|
|
|
int ticks = 4 * Ms::MScore::division / type.toInt();
|
|
if (dots) ticks = 3 * ticks / 2;
|
|
qDebug() << "ticks:" << ticks;
|
|
Ms::TDuration durationType(Ms::TDuration::DurationType::V_INVALID);
|
|
durationType.setVal(ticks);
|
|
qDebug() << "duration:" << durationType.name();
|
|
if (triplet != ST_NONE) ticks = 2 * ticks / 3;
|
|
|
|
Ms::Beam::Mode bm = (beamList.at(0) == Bww::BM_BEGIN) ? Ms::Beam::Mode::BEGIN : Ms::Beam::Mode::AUTO;
|
|
Ms::Direction sd = Ms::Direction::AUTO;
|
|
|
|
// create chord
|
|
Ms::Chord* cr = new Ms::Chord(score);
|
|
//ws cr->setTick(tick);
|
|
cr->setBeamMode(bm);
|
|
cr->setTrack(0);
|
|
if (grace) {
|
|
cr->setNoteType(Ms::NoteType::GRACE32);
|
|
cr->setDurationType(Ms::TDuration::DurationType::V_32ND);
|
|
sd = Ms::Direction::UP;
|
|
}
|
|
else {
|
|
if (durationType.type() == Ms::TDuration::DurationType::V_INVALID)
|
|
durationType.setType(Ms::TDuration::DurationType::V_QUARTER);
|
|
cr->setDurationType(durationType);
|
|
sd = Ms::Direction::DOWN;
|
|
}
|
|
cr->setDuration(durationType.fraction());
|
|
cr->setDots(dots);
|
|
cr->setStemDirection(sd);
|
|
// add note to chord
|
|
Ms::Note* note = new Ms::Note(score);
|
|
note->setTrack(0);
|
|
xmlSetPitch(note, sao.s.toLatin1(), sao.a, sao.o);
|
|
if (tieStart) {
|
|
Ms::Tie* tie = new Ms::Tie(score);
|
|
note->setTieFor(tie);
|
|
tie->setStartNote(note);
|
|
tie->setTrack(0);
|
|
}
|
|
cr->add(note);
|
|
// add chord to measure
|
|
if (!grace) {
|
|
Ms::Segment* s = currentMeasure->getSegment(Ms::SegmentType::ChordRest, tick);
|
|
s->add(cr);
|
|
if (!currentGraceNotes.isEmpty()) {
|
|
for (int i = currentGraceNotes.size() - 1; i >=0; i--)
|
|
cr->add(currentGraceNotes.at(i));
|
|
currentGraceNotes.clear();
|
|
}
|
|
doTriplet(cr, triplet);
|
|
int tickBefore = tick;
|
|
tick += ticks;
|
|
Ms::Fraction nl(Ms::Fraction::fromTicks(tick - currentMeasure->tick()));
|
|
currentMeasure->setLen(nl);
|
|
qDebug() << "MsScWriter::note()"
|
|
<< "tickBefore:" << tickBefore
|
|
<< "tick:" << tick
|
|
<< "nl:" << nl.print()
|
|
;
|
|
}
|
|
else {
|
|
currentGraceNotes.append(cr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Write the header.
|
|
*/
|
|
|
|
void MsScWriter::header(const QString title, const QString type,
|
|
const QString composer, const QString footer,
|
|
const unsigned int temp)
|
|
{
|
|
qDebug() << "MsScWriter::header()"
|
|
<< "title:" << title
|
|
<< "type:" << type
|
|
<< "composer:" << composer
|
|
<< "footer:" << footer
|
|
<< "temp:" << temp
|
|
;
|
|
|
|
// save tempo for later use
|
|
tempo = temp;
|
|
|
|
if (!title.isEmpty()) score->setMetaTag("workTitle", title);
|
|
// TODO re-enable following statement
|
|
// currently disabled because it breaks the bww iotest
|
|
// if (!type.isEmpty()) score->setMetaTag("workNumber", type);
|
|
if (!composer.isEmpty()) score->setMetaTag("composer", composer);
|
|
if (!footer.isEmpty()) score->setMetaTag("copyright", footer);
|
|
|
|
// score->setWorkTitle(title);
|
|
Ms::VBox* vbox = 0;
|
|
addText(vbox, score, title, Ms::SubStyleId::TITLE);
|
|
addText(vbox, score, type, Ms::SubStyleId::SUBTITLE);
|
|
addText(vbox, score, composer, Ms::SubStyleId::COMPOSER);
|
|
// addText(vbox, score, strPoet, Ms::SubStyleId::POET);
|
|
// addText(vbox, score, strTranslator, Ms::SubStyleId::TRANSLATOR);
|
|
if (vbox) {
|
|
vbox->setTick(0);
|
|
score->measures()->add(vbox);
|
|
}
|
|
if (!footer.isEmpty())
|
|
score->style().set(Ms::Sid::oddFooterC, footer);
|
|
|
|
Ms::Part* part = score->staff(0)->part();
|
|
part->setPlainLongName(instrumentName());
|
|
part->setPartName(instrumentName());
|
|
part->instrument()->setTrackName(instrumentName());
|
|
part->setMidiProgram(midiProgram() - 1);
|
|
}
|
|
|
|
/**
|
|
Store beats and beat type for later use.
|
|
*/
|
|
|
|
void MsScWriter::tsig(const int bts, const int bt)
|
|
{
|
|
qDebug() << "MsScWriter::tsig()"
|
|
<< "beats:" << bts
|
|
<< "beat:" << bt
|
|
;
|
|
|
|
beats = bts;
|
|
beat = bt;
|
|
}
|
|
|
|
/**
|
|
Write the trailer.
|
|
*/
|
|
|
|
void MsScWriter::trailer()
|
|
{
|
|
qDebug() << "MsScWriter::trailer()"
|
|
;
|
|
|
|
if (tempo) setTempo(score, tempo);
|
|
}
|
|
|
|
/**
|
|
Handle the triplet.
|
|
*/
|
|
|
|
void MsScWriter::doTriplet(Ms::Chord* cr, StartStop triplet)
|
|
{
|
|
qDebug() << "MsScWriter::doTriplet(" << triplet << ")"
|
|
;
|
|
|
|
if (triplet == ST_START) {
|
|
tuplet = new Ms::Tuplet(score);
|
|
tuplet->setTrack(0);
|
|
tuplet->setRatio(Ms::Fraction(3, 2));
|
|
tuplet->setTick(tick);
|
|
currentMeasure->add(tuplet);
|
|
}
|
|
else if (triplet == ST_STOP) {
|
|
if (tuplet) {
|
|
cr->setTuplet(tuplet);
|
|
tuplet->add(cr);
|
|
tuplet = 0;
|
|
}
|
|
else
|
|
qDebug("BWW::import: triplet stop without triplet start");
|
|
}
|
|
else if (triplet == ST_CONTINUE) {
|
|
if (!tuplet)
|
|
qDebug("BWW::import: triplet continue without triplet start");
|
|
}
|
|
else if (triplet == ST_NONE) {
|
|
if (tuplet)
|
|
qDebug("BWW::import: triplet none inside triplet");
|
|
}
|
|
else
|
|
qDebug("unknown triplet type %d", triplet);
|
|
if (tuplet) {
|
|
cr->setTuplet(tuplet);
|
|
tuplet->add(cr);
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace Bww
|
|
|
|
namespace Ms {
|
|
|
|
//---------------------------------------------------------
|
|
// importBww
|
|
//---------------------------------------------------------
|
|
|
|
Score::FileError importBww(MasterScore* score, const QString& path)
|
|
{
|
|
qDebug("Score::importBww(%s)", qPrintable(path));
|
|
|
|
QFile fp(path);
|
|
if(!fp.exists())
|
|
return Score::FileError::FILE_NOT_FOUND;
|
|
if (!fp.open(QIODevice::ReadOnly))
|
|
return Score::FileError::FILE_OPEN_ERROR;
|
|
|
|
QString id("importBww");
|
|
Part* part = new Part(score);
|
|
part->setId(id);
|
|
score->appendPart(part);
|
|
Staff* staff = new Staff(score);
|
|
staff->setPart(part);
|
|
part->staves()->push_back(staff);
|
|
score->staves().push_back(staff);
|
|
|
|
Bww::Lexer lex(&fp);
|
|
Bww::MsScWriter wrt;
|
|
wrt.setScore(score);
|
|
score->style().set(Sid::measureSpacing, 1.0);
|
|
Bww::Parser p(lex, wrt);
|
|
p.parse();
|
|
|
|
score->setSaved(false);
|
|
score->setCreated(true);
|
|
score->connectTies();
|
|
qDebug("Score::importBww() done");
|
|
return Score::FileError::FILE_NO_ERROR; // OK
|
|
}
|
|
|
|
} // namespace Ms
|
|
|