//============================================================================= // MuseScore // Music Composition & Notation // $Id: bb.cpp 5427 2012-03-07 12:41:34Z wschweer $ // // Copyright (C) 2008-2011 Werner Schweer // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 // as published by the Free Software Foundation and appearing in // the file LICENCE.GPL //============================================================================= #include "bb.h" #include "musescore.h" #include "libmscore/score.h" #include "libmscore/part.h" #include "libmscore/staff.h" #include "libmscore/text.h" #include "libmscore/box.h" #include "libmscore/slur.h" #include "libmscore/note.h" #include "libmscore/chord.h" #include "libmscore/rest.h" #include "libmscore/drumset.h" #include "libmscore/utils.h" #include "libmscore/harmony.h" #include "libmscore/layoutbreak.h" #include "libmscore/key.h" #include "libmscore/pitchspelling.h" #include "libmscore/measure.h" #include "libmscore/segment.h" #include "libmscore/keysig.h" //--------------------------------------------------------- // BBTrack //--------------------------------------------------------- BBTrack::BBTrack(BBFile* f) { bb = f; _outChannel = -1; _drumTrack = false; } BBTrack::~BBTrack() { } //--------------------------------------------------------- // MNote // special Midi Note //--------------------------------------------------------- struct MNote { Event mc; QList ties; MNote(const Event& _mc) : mc(_mc) { for (int i = 0; i < mc.notes().size(); ++i) ties.append(0); } }; //--------------------------------------------------------- // BBFile //--------------------------------------------------------- BBFile::BBFile() { for (int i = 0; i < MAX_BARS; ++i) _barType[i] = 0; bbDivision = 120; } //--------------------------------------------------------- // BBFile //--------------------------------------------------------- BBFile::~BBFile() { } //--------------------------------------------------------- // read // return false on error //--------------------------------------------------------- bool BBFile::read(const QString& name) { _siglist.clear(); _siglist.add(0, Fraction(4, 4)); // debug _path = name; QFile f(name); if (!f.open(QIODevice::ReadOnly)) { return false; } ba = f.readAll(); f.close(); a = (const unsigned char*) ba.data(); size = ba.size(); //--------------------------------------------------- // read version //--------------------------------------------------- int idx = 0; _version = a[idx++]; switch(_version) { case 0x43 ... 0x49: break; default: qDebug("BB: unknown file version %02x\n", _version); return false; } qDebug("read <%s> version 0x%02x\n", qPrintable(name), _version); //--------------------------------------------------- // read title //--------------------------------------------------- int len = a[idx++]; _title = new char[len+1]; for (int i = 0; i < len; ++i) _title[i] = a[idx++]; _title[len] = 0; //--------------------------------------------------- // read style(timesig), key and bpm //--------------------------------------------------- ++idx; ++idx; _style = a[idx++] - 1; if (_style < 0 || _style >= int(sizeof(styles)/sizeof(*styles))) { qDebug("Import bb: unknown style %d\n", _style + 1); return false; } _key = a[idx++]; // map D# G# A# to Eb Ab Db // major C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B, C#, D#, F#, G#, A# // minor C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B, C#, D#, F#, G#, A# static int kt[] = { 0, 0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5, 7, -3, 6, -4, -2, -3, 4, -1, -6, 1, -4, 3, -2, 5, 0, -5, 2, 4, 6, 3, 5, 7 }; if (_key >= int (sizeof(kt)/sizeof(*kt))) { qDebug("bad key %d\n", _key); return false; } _key = kt[_key]; _bpm = a[idx] + (a[idx+1] << 8); idx += 2; qDebug("Title <%s>\n", _title); qDebug("style %d\n", _style); qDebug("key %d\n", _key); qDebug("sig %d/%d\n", timesigZ(), timesigN()); qDebug("bpm %d\n", _bpm); //--------------------------------------------------- // read bar types //--------------------------------------------------- int bar = a[idx++]; // starting bar number while (bar < 255) { int val = a[idx++]; if (val == 0) bar += a[idx++]; else { qDebug("bar type: bar %d val %d\n", bar, val); _barType[bar++] = val; } } //--------------------------------------------------- // read chord extensions //--------------------------------------------------- int beat; for (beat = 0; beat < MAX_BARS * 4;) { int val = a[idx++]; if (val == 0) beat += a[idx++]; else { BBChord c; c.extension = val; c.beat = beat + timesigZ() * 4 / timesigN(); ++beat; _chords.append(c); } } //--------------------------------------------------- // read chord root //--------------------------------------------------- int roots = 0; int maxbeat = 0; for (beat = 0; beat < MAX_BARS * 4;) { int val = a[idx++]; if (val == 0) beat += a[idx++]; else { int root = val % 18; int bass = (root - 1 + val / 18) % 12 + 1; if (root == bass) bass = 0; int ibeat = beat + timesigZ() * 4 / timesigN(); if (ibeat != _chords[roots].beat) { qDebug("import bb: inconsistent chord type and root beat\n"); return false; } _chords[roots].root = root; _chords[roots].bass = bass; if (maxbeat < beat) maxbeat = beat; ++roots; ++beat; } } _measures = ((maxbeat + timesigZ() - 1) / timesigZ()) + 1; if (roots != _chords.size()) { qDebug("import bb: roots %d != extensions %d\n", roots, _chords.size()); return false; } qDebug("Measures %d\n", _measures); #if 0 qDebug("================chords=======================\n"); foreach(BBChord c, _chords) { qDebug("chord beat %3d bass %d root %d extension %d\n", c.beat, c.bass, c.root, c.extension); } qDebug("================chords=======================\n"); #endif if (a[idx] == 1) { //?? qDebug("Skip 0x%02x at 0x%04x\n", a[idx], idx); ++idx; } _startChorus = a[idx++]; _endChorus = a[idx++]; _repeats = a[idx++]; qDebug("start chorus %d end chorus %d repeats %d, pos now 0x%x\n", _startChorus, _endChorus, _repeats, idx); if (_startChorus >= _endChorus) { _startChorus = 0; _endChorus = 0; _repeats = 1; } //--------------------------------------------------- // read style file //--------------------------------------------------- bool found = false; for (int i = idx; i < size; ++i) { if (a[i] == 0x42) { if (a[i+1] < 16) { for (int k = i+2; k < (i+18); ++k) { if (a[k] == '.' && a[k+1] == 'S' && a[k+2] == 'T' && a[k+3] == 'Y') { found = true; break; } } } if (found) { idx = i + 1; break; } } } if (!found) { qDebug("import bb: style file not found\n"); return false; } qDebug("read styleName at 0x%x\n", idx); len = a[idx++]; _styleName = new char[len+1]; for (int i = 0; i < len; ++i) _styleName[i] = a[idx++]; _styleName[len] = 0; qDebug("style name <%s>\n", _styleName); // read midi events int eventStart = a[size-4] + a[size-3] * 256; int eventCount = a[size-2] + a[size-1] * 256; int endTick = _measures * bbDivision * 4 * timesigZ() / timesigN(); if (eventCount == 0) { qDebug("no melody\n"); return true; } else { idx = eventStart; qDebug("melody found at 0x%x\n", idx); int i = 0; int lastLen = 0; for (i = 0; i < eventCount; ++i, idx+=12) { int type = a[idx + 4] & 0xf0; if (type == 0x90) { int channel = a[idx + 7]; BBTrack* track = 0; foreach (BBTrack* t, _tracks) { if (t->outChannel() == channel) { track = t; break; } } if (track == 0) { track = new BBTrack(this); track->setOutChannel(channel); _tracks.append(track); } int tick = a[idx] + (a[idx+1]<<8) + (a[idx+2]<<16) + (a[idx+3]<<24); tick -= 4 * bbDivision; if (tick >= endTick) { qDebug("event tick %d > %d\n", tick, endTick); continue; } Event note(ME_NOTE); note.setOntime((tick * MScore::division) / bbDivision); note.setPitch(a[idx + 5]); note.setVelo(a[idx + 6]); note.setChannel(channel); int len = a[idx+8] + (a[idx+9]<<8) + (a[idx+10]<<16) + (a[idx+11]<<24); if (len == 0) { if (lastLen == 0) { qDebug("note event of len 0 at idx %04x\n", idx); continue; } len = lastLen; } lastLen = len; note.setDuration((len * MScore::division) / bbDivision); track->append(note); } else if (type == 0xb0) { // ignore controller } else if (type == 0) break; else { qDebug("unknown event type 0x%02x at x%04x\n", a[idx + 4], idx); break; } } qDebug("Events found x%02x (%d)\n", i, i); } return true; } //--------------------------------------------------------- // importBB // return true on success //--------------------------------------------------------- Score::FileError importBB(Score* score, const QString& name) { BBFile bb; if (!bb.read(name)) { qDebug("cannot open file <%s>\n", qPrintable(name)); return Score::FILE_OPEN_ERROR; } *(score->sigmap()) = bb.siglist(); QList* tracks = bb.tracks(); int ntracks = tracks->size(); if (ntracks == 0) // no events? ntracks = 1; for (int i = 0; i < ntracks; ++i) { Part* part = new Part(score); Staff* s = new Staff(score, part, 0); part->insertStaff(s); score->staves().append(s); score->appendPart(part); } //--------------------------------------------------- // create measures //--------------------------------------------------- for (int i = 0; i < bb.measures(); ++i) { Measure* measure = new Measure(score); int tick = score->sigmap()->bar2tick(i, 0); measure->setTick(tick); Fraction ts = score->sigmap()->timesig(tick).timesig(); measure->setTimesig(ts); measure->setLen(ts); score->add(measure); } //--------------------------------------------------- // create notes //--------------------------------------------------- foreach (BBTrack* track, *tracks) track->cleanup(); if (tracks->isEmpty()) { for (MeasureBase* mb = score->first(); mb; mb = mb->next()) { if (mb->type() != Element::MEASURE) continue; Measure* measure = (Measure*)mb; Rest* rest = new Rest(score, TDuration(TDuration::V_MEASURE)); rest->setDuration(measure->len()); rest->setTrack(0); Segment* s = measure->getSegment(rest, measure->tick()); s->add(rest); } } else { int staffIdx = 0; foreach (BBTrack* track, *tracks) bb.convertTrack(score, track, staffIdx++); } for (MeasureBase* mb = score->first(); mb; mb = mb->next()) { if (mb->type() != Element::MEASURE) continue; Measure* measure = (Measure*)mb; Segment* s = measure->findSegment(Segment::SegChordRest, measure->tick()); if (s == 0) { Rest* rest = new Rest(score, TDuration(TDuration::V_MEASURE)); rest->setDuration(measure->len()); rest->setTrack(0); Segment* s = measure->getSegment(rest, measure->tick()); s->add(rest); } } score->spell(); //--------------------------------------------------- // create title //--------------------------------------------------- Text* text = new Text(score); // text->setSubtype(TEXT_TITLE); text->setTextStyleType(TEXT_STYLE_TITLE); text->setText(bb.title()); MeasureBase* measure = score->first(); if (measure->type() != Element::VBOX) { measure = new VBox(score); measure->setTick(0); measure->setNext(score->first()); score->add(measure); } measure->add(text); //--------------------------------------------------- // create chord names //--------------------------------------------------- static const int table[] = { 14, 9, 16, 11, 18, 13, 8, 15, 10, 17, 12, 19 }; foreach(const BBChord& c, bb.chords()) { int tick = c.beat * MScore::division; // qDebug("CHORD %d %d\n", c.beat, tick); Measure* m = score->tick2measure(tick); if (m == 0) { qDebug("import BB: measure for tick %d not found\n", tick); continue; } Segment* s = m->getSegment(Segment::SegChordRest, tick); Harmony* h = new Harmony(score); h->setTrack(0); h->setRootTpc(table[c.root-1]); if (c.bass > 0) h->setBaseTpc(table[c.bass-1]); else h->setBaseTpc(INVALID_TPC); h->setId(c.extension); h->render(); s->add(h); } //--------------------------------------------------- // insert layout breaks // add chorus repeat //--------------------------------------------------- int startChorus = bb.startChorus() - 1; int endChorus = bb.endChorus() - 1; int n = 0; for (MeasureBase* mb = score->first(); mb; mb = mb->next()) { if (mb->type() != Element::MEASURE) continue; Measure* measure = (Measure*)mb; if (n && (n % 4) == 0) { LayoutBreak* lb = new LayoutBreak(score); lb->setLayoutBreakType(LAYOUT_BREAK_LINE); measure->add(lb); } if (startChorus == n) measure->setRepeatFlags(RepeatStart); else if (endChorus == n) { measure->setRepeatFlags(RepeatEnd); measure->setRepeatCount(bb.repeats()); } ++n; } foreach(Staff* staff, score->staves()) { int tick = 0; KeySigEvent kse; kse.setAccidentalType(bb.key()); (*staff->keymap())[tick] = kse; KeySig* keysig = new KeySig(score); keysig->setTrack((score->staffIdx(staff->part()) + staff->rstaff()) * VOICES); keysig->setKeySigEvent(kse); Measure* mks = score->tick2measure(tick); Segment* sks = mks->getSegment(keysig, tick); sks->add(keysig); } return Score::FILE_NO_ERROR; } //--------------------------------------------------------- // processPendingNotes //--------------------------------------------------------- int BBFile::processPendingNotes(Score* score, QList* notes, int len, int track) { Staff* cstaff = score->staff(track/VOICES); Drumset* drumset = cstaff->part()->instr()->drumset(); bool useDrumset = cstaff->part()->instr()->useDrumset(); int tick = notes->at(0)->mc.ontime(); // // look for len of shortest note // foreach (const MNote* n, *notes) { if (n->mc.duration() < len) len = n->mc.duration(); } // // split notes on measure boundary // Measure* measure = score->tick2measure(tick); if (measure == 0 || (tick >= (measure->tick() + measure->ticks()))) { qDebug("no measure found for tick %d\n", tick); notes->clear(); return len; } if ((tick + len) > measure->tick() + measure->ticks()) len = measure->tick() + measure->ticks() - tick; Chord* chord = new Chord(score); chord->setTrack(track); TDuration d; d.setVal(len); chord->setDurationType(d); Segment* s = measure->getSegment(chord, tick); s->add(chord); foreach (MNote* n, *notes) { QList& nl = n->mc.notes(); for (int i = 0; i < nl.size(); ++i) { const Event& mn = nl[i]; Note* note = new Note(score); note->setPitch(mn.pitch(), mn.tpc()); note->setTrack(track); chord->add(note); if (useDrumset) { if (!drumset->isValid(mn.pitch())) { qDebug("unmapped drum note 0x%02x %d\n", mn.pitch(), mn.pitch()); } else { chord->setStemDirection(drumset->stemDirection(mn.pitch())); } } if (n->ties[i]) { n->ties[i]->setEndNote(note); n->ties[i]->setTrack(note->track()); note->setTieBack(n->ties[i]); } } if (n->mc.duration() <= len) { notes->removeAt(notes->indexOf(n)); continue; } for (int i = 0; i < nl.size(); ++i) { const Event& mn = nl[i]; Note* note = chord->findNote(mn.pitch()); n->ties[i] = new Tie(score); n->ties[i]->setStartNote(note); note->setTieFor(n->ties[i]); } n->mc.setOntime(n->mc.ontime() + len); n->mc.setDuration(n->mc.duration() - len); } return len; } //--------------------------------------------------------- // collectNotes //--------------------------------------------------------- static ciEvent collectNotes(int tick, int voice, ciEvent i, const EventList* el, QList* notes) { for (;i != el->end(); ++i) { const Event& e = *i; if (e.type() != ME_CHORD) continue; if (e.voice() != voice) continue; if (e.ontime() > tick) break; if (e.ontime() < tick) continue; MNote* n = new MNote(e); notes->append(n); } return i; } //--------------------------------------------------------- // convertTrack //--------------------------------------------------------- void BBFile::convertTrack(Score* score, BBTrack* track, int staffIdx) { track->findChords(); int voices = track->separateVoices(2); const EventList el = track->events(); for (int voice = 0; voice < voices; ++voice) { int track = staffIdx * VOICES + voice; QList notes; int ctick = 0; ciEvent i = collectNotes(ctick, voice, el.begin(), &el, ¬es); for (; i != el.end();) { const Event& e = *i; if (e.type() != ME_CHORD || e.voice() != voice) { ++i; continue; } // // process pending notes // int restLen = e.ontime() - ctick; // qDebug("ctick %d rest %d ontick %d size %d\n", ctick, restLen, e.ontime(), notes.size()); if (restLen <= 0) { qDebug("bad restlen ontime %d - ctick %d\n", e.ontime(), ctick); abort(); } while (!notes.isEmpty()) { int len = processPendingNotes(score, ¬es, restLen, track); if (len == 0) { qDebug("processPendingNotes returns zero, restlen %d, track %d\n", restLen, track); ctick += restLen; restLen = 0; break; } ctick += len; restLen -= len; } // qDebug(" 1.ctick %d rest %d\n", ctick, restLen); // // check for gap and fill with rest // if (voice == 0) { while (restLen > 0) { int len = restLen; Measure* measure = score->tick2measure(ctick); if (measure == 0 || (ctick >= (measure->tick() + measure->ticks()))) { // at end? ctick += len; restLen -= len; break; } // split rest on measure boundary if ((ctick + len) > measure->tick() + measure->ticks()) { len = measure->tick() + measure->ticks() - ctick; if (len <= 0) { qDebug("bad len %d\n", len); break; } } TDuration d; d.setVal(len); Rest* rest = new Rest(score, d); rest->setDuration(d.fraction()); rest->setTrack(staffIdx * VOICES); Segment* s = measure->getSegment(rest, ctick); s->add(rest); // qDebug(" add rest %d\n", len); ctick += len; restLen -= len; } } else ctick += restLen; // qDebug(" 2.ctick %d rest %d\n", ctick, restLen); // // collect all notes at ctick // i = collectNotes(ctick, voice, i, &el, ¬es); } // // process pending notes // while (!notes.isEmpty()) processPendingNotes(score, ¬es, 0x7fffffff, track); } } //--------------------------------------------------------- // quantize // process one segment (measure) //--------------------------------------------------------- void BBTrack::quantize(int startTick, int endTick, EventList* dst) { int mintick = MScore::division * 64; iEvent i = _events.begin(); for (; i != _events.end(); ++i) { if (i->ontime() >= startTick) break; } iEvent si = i; for (; i != _events.end(); ++i) { const Event& e = *i; if (e.ontime() >= endTick) break; if (e.type() == ME_NOTE && (e.duration() < mintick)) mintick = e.duration(); } if (mintick <= MScore::division / 16) // minimum duration is 1/64 mintick = MScore::division / 16; else if (mintick <= MScore::division / 8) mintick = MScore::division / 8; else if (mintick <= MScore::division / 4) mintick = MScore::division / 4; else if (mintick <= MScore::division / 2) mintick = MScore::division / 2; else if (mintick <= MScore::division) mintick = MScore::division; else if (mintick <= MScore::division * 2) mintick = MScore::division * 2; else if (mintick <= MScore::division * 4) mintick = MScore::division * 4; else if (mintick <= MScore::division * 8) mintick = MScore::division * 8; int raster; if (mintick > MScore::division) raster = MScore::division; else raster = mintick; // // quantize onset // for (iEvent i = si; i != _events.end(); ++i) { Event e = *i; if (e.ontime() >= endTick) break; if (e.type() == ME_NOTE) { // prefer moving note to the right int tick = ((e.ontime() + raster/2) / raster) * raster; int diff = tick - e.ontime(); int len = e.duration() - diff; e.setOntime(tick); e.setDuration(len); } dst->insert(e); } // // quantize duration // for (iEvent i = dst->begin(); i != dst->end(); ++i) { Event& e = *i; if (e.type() != ME_NOTE) continue; int tick = e.ontime(); int len = e.duration(); int ntick = tick + len; int nntick = -1; for (iEvent ii = (i+1); ii != dst->end(); ++ii) { if (ii->type() == ME_NOTE) { const Event& ee = *ii; if (ee.ontime() == tick) continue; nntick = ee.ontime(); break; } } if (nntick == -1) len = quantizeLen(len, raster); else { int diff = nntick - ntick; if (diff > 0) { // insert rest? if (diff <= raster) len = nntick - tick; else len = quantizeLen(len, raster); } else { if (diff > -raster) len = nntick - tick; else len = quantizeLen(len, raster); } } e.setDuration(len); } } //--------------------------------------------------------- // cleanup // - quantize // - remove overlaps //--------------------------------------------------------- void BBTrack::cleanup() { EventList dl; // // quantize // int lastTick = 0; foreach (const Event& e, _events) { if (e.type() != ME_NOTE) continue; int offtime = e.offtime(); if (offtime > lastTick) lastTick = offtime; } int startTick = 0; for (int i = 1;; ++i) { int endTick = bb->siglist().bar2tick(i, 0); quantize(startTick, endTick, &dl); if (endTick > lastTick) break; startTick = endTick; } // // // _events.clear(); for(iEvent i = dl.begin(); i != dl.end(); ++i) { Event& e = *i; if (e.type() == ME_NOTE) { iEvent ii = i; ++ii; for (; ii != dl.end(); ++ii) { const Event& ee = *ii; if (ee.type() != ME_NOTE || ee.pitch() != e.pitch()) continue; if (ee.ontime() >= e.ontime() + e.duration()) break; e.setDuration(ee.ontime() - e.ontime()); break; } if (e.duration() <= 0) continue; } _events.insert(e); } } //--------------------------------------------------------- // findChords //--------------------------------------------------------- void BBTrack::findChords() { EventList dl; int n = _events.size(); Drumset* drumset; if (_drumTrack) drumset = smDrumset; else drumset = 0; int jitter = 3; // tick tolerance for note on/off for (int i = 0; i < n; ++i) { Event e = _events[i]; if (e.type() == ME_INVALID) continue; if (e.type() != ME_NOTE) { dl.append(e); continue; } Event note(e); int ontime = note.ontime(); int offtime = note.offtime(); Event chord(ME_CHORD); chord.setOntime(ontime); chord.setDuration(note.duration()); chord.notes().append(note); int voice = 0; chord.setVoice(voice); dl.append(chord); _events[i].setType(ME_INVALID); bool useDrumset = false; if (drumset) { int pitch = note.pitch(); if (drumset->isValid(pitch)) { useDrumset = true; voice = drumset->voice(pitch); chord.setVoice(voice); } } for (int k = i + 1; k < n; ++k) { if (_events[k].type() != ME_NOTE) continue; Event nn = _events[k]; if (nn.ontime() - jitter > ontime) break; if (qAbs(nn.ontime() - ontime) > jitter || qAbs(nn.offtime() - offtime) > jitter) continue; int pitch = nn.pitch(); if (useDrumset) { if (drumset->isValid(pitch) && drumset->voice(pitch) == voice) { chord.notes().append(nn); _events[k].setType(ME_INVALID); } } else { chord.notes().append(nn); _events[k].setType(ME_INVALID); } } } _events = dl; } //--------------------------------------------------------- // separateVoices //--------------------------------------------------------- int BBTrack::separateVoices(int /*maxVoices*/) { return 1; }