MuseScore/mscore/importmidi.cpp

1009 lines
39 KiB
C++
Raw Normal View History

2012-05-26 14:49:10 +02:00
//=============================================================================
// MuseScore
2013-04-15 10:38:16 +02:00
// Music Composition & Notation
2012-05-26 14:49:10 +02:00
//
2013-04-15 10:38:16 +02:00
// Copyright (C) 2002-2013 Werner Schweer
2012-05-26 14:49:10 +02:00
//
// This program is free software; you can redistribute it and/or modify
2013-04-15 10:38:16 +02:00
// 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
2012-05-26 14:49:10 +02:00
//=============================================================================
2013-04-15 10:38:16 +02:00
#include "midi/midifile.h"
#include "midi/midiinstrument.h"
2013-08-20 18:05:49 +02:00
#include "preferences.h"
2012-05-26 14:49:10 +02:00
#include "libmscore/score.h"
#include "libmscore/key.h"
#include "libmscore/clef.h"
#include "libmscore/sig.h"
#include "libmscore/tempo.h"
#include "libmscore/note.h"
#include "libmscore/chord.h"
#include "libmscore/rest.h"
#include "libmscore/segment.h"
#include "libmscore/utils.h"
#include "libmscore/text.h"
#include "libmscore/slur.h"
2013-08-22 12:18:14 +02:00
#include "libmscore/tie.h"
2012-05-26 14:49:10 +02:00
#include "libmscore/staff.h"
#include "libmscore/measure.h"
#include "libmscore/style.h"
#include "libmscore/part.h"
#include "libmscore/timesig.h"
#include "libmscore/barline.h"
#include "libmscore/pedal.h"
#include "libmscore/ottava.h"
#include "libmscore/lyrics.h"
#include "libmscore/bracket.h"
#include "libmscore/drumset.h"
#include "libmscore/box.h"
#include "libmscore/keysig.h"
#include "libmscore/pitchspelling.h"
2013-05-26 01:19:09 +02:00
#include "importmidi_meter.h"
#include "importmidi_chord.h"
#include "importmidi_quant.h"
#include "importmidi_tuplet.h"
2013-06-27 16:14:39 +02:00
#include "libmscore/tuplet.h"
2013-08-01 23:52:09 +02:00
#include "importmidi_swing.h"
#include "importmidi_fraction.h"
2013-08-20 18:05:49 +02:00
#include "importmidi_drum.h"
#include "importmidi_inner.h"
#include "importmidi_clef.h"
#include "importmidi_lrhand.h"
2013-09-07 20:57:23 +02:00
#include "importmidi_lyrics.h"
#include <set>
2013-04-28 21:29:12 +02:00
2013-05-13 18:49:17 +02:00
namespace Ms {
2013-05-03 19:44:23 +02:00
extern Preferences preferences;
2013-07-27 00:01:30 +02:00
extern void updateNoteLines(Segment*, int track);
2012-05-26 14:49:10 +02:00
2013-08-19 12:57:46 +02:00
void cleanUpMidiEvents(std::multimap<int, MTrack> &tracks)
{
auto &opers = preferences.midiImportOperations;
for (auto &track: tracks) {
MTrack &mtrack = track.second;
opers.setCurrentTrack(mtrack.indexOfOperation);
const auto raster = Quantize::fixedQuantRaster();
const bool reduce = opers.currentTrackOperations().quantize.reduceToShorterNotesInBar;
for (auto chordIt = mtrack.chords.begin(); chordIt != mtrack.chords.end(); ) {
MidiChord &ch = chordIt->second;
for (auto noteIt = ch.notes.begin(); noteIt != ch.notes.end(); ) {
if ((noteIt->len < MChord::minAllowedDuration())
2013-08-19 12:57:46 +02:00
|| (!reduce && noteIt->len < raster / 2)) {
noteIt = ch.notes.erase(noteIt);
continue;
}
++noteIt;
}
if (ch.notes.isEmpty()) {
chordIt = mtrack.chords.erase(chordIt);
continue;
}
++chordIt;
}
}
}
2013-07-22 14:03:23 +02:00
void quantizeAllTracks(std::multimap<int, MTrack> &tracks,
TimeSigMap *sigmap,
const ReducedFraction &lastTick)
2013-05-24 13:47:16 +02:00
{
auto &opers = preferences.midiImportOperations;
2013-08-20 13:31:10 +02:00
for (auto &track: tracks) {
MTrack &mtrack = track.second;
// pass current track index through MidiImportOperations
// for further usage
opers.setCurrentTrack(mtrack.indexOfOperation);
opers.adaptForPercussion(mtrack.indexOfOperation, mtrack.mtrack->drumTrack());
mtrack.tuplets = MidiTuplet::findAllTuplets(mtrack.chords, sigmap, lastTick);
Quantize::quantizeChords(mtrack.chords, mtrack.tuplets, sigmap);
2012-05-26 14:49:10 +02:00
}
}
//---------------------------------------------------------
// processMeta
//---------------------------------------------------------
2013-04-15 10:38:16 +02:00
void MTrack::processMeta(int tick, const MidiEvent& mm)
2012-05-26 14:49:10 +02:00
{
2013-04-28 21:29:12 +02:00
if (!staff) {
qDebug("processMeta: no staff");
return;
}
2013-04-08 10:31:17 +02:00
const uchar* data = (uchar*)mm.edata();
2013-08-22 23:26:31 +02:00
Score* cs = staff->score();
2012-05-26 14:49:10 +02:00
switch (mm.metaType()) {
case META_TEXT:
case META_LYRIC:
break; // lyric and text are added in importmidi_lyrics.cpp
2012-05-26 14:49:10 +02:00
case META_TRACK_NAME:
{
std::string text = MidiCharset::fromUchar(data);
2013-08-11 21:54:36 +02:00
if (name.isEmpty())
name = MidiCharset::convertToCharset(text);
}
2012-05-26 14:49:10 +02:00
break;
case META_TEMPO:
{
const unsigned tempo = data[2] + (data[1] << 8) + (data[0] <<16);
const double t = 1000000.0 / double(tempo);
2012-05-26 14:49:10 +02:00
cs->setTempo(tick, t);
// TODO: create TempoText
2012-05-26 14:49:10 +02:00
}
break;
case META_KEY_SIGNATURE:
2013-04-28 21:29:12 +02:00
{
const int key = ((const char*)data)[0];
2013-04-28 21:29:12 +02:00
if (key < -7 || key > 7) {
qDebug("ImportMidi: illegal key %d", key);
break;
2012-05-26 14:49:10 +02:00
}
2013-04-28 21:29:12 +02:00
KeySigEvent ks;
ks.setAccidentalType(key);
(*staff->keymap())[tick] = ks;
hasKey = true;
}
2012-05-26 14:49:10 +02:00
break;
case META_COMPOSER: // mscore extension
case META_POET:
case META_TRANSLATOR:
case META_SUBTITLE:
case META_TITLE:
{
Text* text = new Text(cs);
switch(mm.metaType()) {
case META_COMPOSER:
text->setTextStyleType(TEXT_STYLE_COMPOSER);
break;
case META_TRANSLATOR:
text->setTextStyleType(TEXT_STYLE_TRANSLATOR);
break;
case META_POET:
text->setTextStyleType(TEXT_STYLE_POET);
break;
case META_SUBTITLE:
text->setTextStyleType(TEXT_STYLE_SUBTITLE);
break;
case META_TITLE:
text->setTextStyleType(TEXT_STYLE_TITLE);
break;
}
2013-04-08 10:31:17 +02:00
text->setText((const char*)(mm.edata()));
2012-05-26 14:49:10 +02:00
MeasureBase* measure = cs->first();
2012-09-13 18:01:34 +02:00
if (measure->type() != Element::VBOX) {
2012-05-26 14:49:10 +02:00
measure = new VBox(cs);
measure->setTick(0);
measure->setNext(cs->first());
cs->add(measure);
}
measure->add(text);
}
break;
case META_COPYRIGHT:
2013-04-08 10:31:17 +02:00
cs->setMetaTag("Copyright", QString((const char*)(mm.edata())));
2012-05-26 14:49:10 +02:00
break;
case META_TIME_SIGNATURE:
cs->sigmap()->add(tick, Fraction(data[0], 1 << data[1]));
break;
default:
if (MScore::debugMode)
qDebug("unknown meta type 0x%02x", mm.metaType());
break;
}
}
QList<std::pair<ReducedFraction, TDuration> >
2013-07-03 23:54:54 +02:00
MTrack::toDurationList(const Measure *measure,
int voice,
const ReducedFraction &startTick,
const ReducedFraction &len,
2013-07-03 23:54:54 +02:00
Meter::DurationType durationType)
2013-05-17 20:11:36 +02:00
{
const bool useDots = preferences.midiImportOperations.currentTrackOperations().useDots;
2013-06-27 16:14:39 +02:00
// find tuplets over which duration is go
2013-08-20 14:40:57 +02:00
auto barTick = ReducedFraction::fromTicks(measure->tick());
auto tupletsData = MidiTuplet::findTupletsInBarForDuration(voice, barTick, startTick,
len, tuplets);
struct {
bool operator()(const MidiTuplet::TupletData &d1,
const MidiTuplet::TupletData &d2)
{
return (d1.len > d2.len);
}
} comparator;
// sort by tuplet length in desc order
sort(tupletsData.begin(), tupletsData.end(), comparator);
const ReducedFraction startTickInBar = startTick - barTick;
const ReducedFraction endTickInBar = startTickInBar + len;
2013-07-03 23:54:54 +02:00
return Meter::toDurationList(startTickInBar, endTickInBar,
2013-08-20 14:40:57 +02:00
ReducedFraction(measure->timesig()), tupletsData,
2013-07-03 23:54:54 +02:00
durationType, useDots);
2013-06-27 16:14:39 +02:00
}
ReducedFraction splitDurationOnBarBoundary(const ReducedFraction &len,
const ReducedFraction &onTime,
const Measure* measure)
2013-06-27 16:14:39 +02:00
{
const ReducedFraction barLimit = ReducedFraction::fromTicks(measure->tick() + measure->ticks());
2013-07-08 20:17:13 +02:00
if (onTime + len > barLimit)
return barLimit - onTime;
2013-06-27 16:14:39 +02:00
return len;
}
// fill the gap between successive chords with rests
2013-08-18 12:21:35 +02:00
void MTrack::fillGapWithRests(Score* score,
int voice,
const ReducedFraction &startChordTickFrac,
2013-08-18 12:21:35 +02:00
const ReducedFraction &restLength,
int track)
2013-06-27 16:14:39 +02:00
{
ReducedFraction startChordTick = startChordTickFrac;
ReducedFraction restLen = restLength;
2013-08-18 12:21:35 +02:00
while (restLen > ReducedFraction(0, 1)) {
ReducedFraction len = restLen;
2013-08-22 23:26:31 +02:00
Measure* measure = score->tick2measure(startChordTick.ticks());
if (startChordTick >= ReducedFraction::fromTicks(measure->tick() + measure->ticks())) {
2013-07-08 20:17:13 +02:00
qDebug("tick2measure: %d end of score?", startChordTick.ticks());
2013-06-27 16:14:39 +02:00
startChordTick += restLen;
restLen = ReducedFraction(0, 1);
2013-05-17 20:11:36 +02:00
break;
}
2013-06-27 16:14:39 +02:00
len = splitDurationOnBarBoundary(len, startChordTick, measure);
if (len >= ReducedFraction::fromTicks(measure->ticks())) {
2013-06-27 16:14:39 +02:00
// rest to the whole measure
len = ReducedFraction::fromTicks(measure->ticks());
2013-07-03 23:54:54 +02:00
if (voice == 0) {
TDuration duration(TDuration::V_MEASURE);
Rest* rest = new Rest(score, duration);
rest->setDuration(measure->len());
rest->setTrack(track);
2013-07-08 20:17:13 +02:00
Segment* s = measure->getSegment(rest, startChordTick.ticks());
2013-07-03 23:54:54 +02:00
s->add(rest);
2013-07-02 23:37:07 +02:00
}
2013-05-17 20:11:36 +02:00
restLen -= len;
2013-06-27 16:14:39 +02:00
startChordTick += len;
2013-05-17 20:11:36 +02:00
}
else {
const auto dl = toDurationList(measure, voice, startChordTick, len,
Meter::DurationType::REST);
2013-05-17 20:11:36 +02:00
if (dl.isEmpty()) {
2013-07-08 20:17:13 +02:00
qDebug("cannot create duration list for len %d", len.ticks());
restLen = ReducedFraction(0, 1); // fake
2013-05-17 20:11:36 +02:00
break;
}
2013-07-03 23:54:54 +02:00
for (const auto &durationPair: dl) {
2013-07-08 20:17:13 +02:00
const TDuration &duration = durationPair.second;
const ReducedFraction &tupletRatio = durationPair.first;
len = ReducedFraction(duration.fraction()) / tupletRatio;
2013-08-22 23:26:31 +02:00
Rest* rest = new Rest(score, duration);
2013-06-27 16:14:39 +02:00
rest->setDuration(duration.fraction());
2013-05-17 20:11:36 +02:00
rest->setTrack(track);
2013-08-22 23:26:31 +02:00
Segment* s = measure->getSegment(Segment::SegChordRest,
startChordTick.ticks());
2013-05-17 20:11:36 +02:00
s->add(rest);
2013-08-20 18:05:49 +02:00
MidiTuplet::addElementToTuplet(voice, startChordTick, len, rest, tuplets);
2013-07-05 15:09:23 +02:00
restLen -= len;
startChordTick += len;
2013-05-17 20:11:36 +02:00
}
}
2013-07-02 23:37:07 +02:00
2013-05-17 20:11:36 +02:00
}
}
2013-06-27 16:14:39 +02:00
void setMusicNotesFromMidi(Score *score,
const QList<MidiNote> &midiNotes,
const ReducedFraction &onTime,
const ReducedFraction &len,
2013-06-27 16:14:39 +02:00
Chord *chord,
const ReducedFraction &tick,
2013-06-27 16:14:39 +02:00
const Drumset *drumset,
bool useDrumset)
{
2013-08-18 12:21:35 +02:00
auto actualFraction = ReducedFraction(chord->actualFraction());
2013-06-27 16:14:39 +02:00
for (int i = 0; i < midiNotes.size(); ++i) {
const MidiNote& mn = midiNotes[i];
2013-08-22 23:26:31 +02:00
Note* note = new Note(score);
2013-06-27 16:14:39 +02:00
// TODO - does this need to be key-aware?
note->setPitch(mn.pitch, pitch2tpc(mn.pitch, KEY_C, PREFER_NEAREST));
chord->add(note);
note->setVeloType(MScore::USER_VAL);
note->setVeloOffset(mn.velo);
NoteEventList el;
ReducedFraction f = (onTime - tick) / actualFraction * 1000;
const int ron = f.numerator() / f.denominator();
2013-07-08 20:17:13 +02:00
f = len / actualFraction * 1000;
const int rlen = f.numerator() / f.denominator();
2013-07-08 20:17:13 +02:00
2013-06-27 16:14:39 +02:00
el.append(NoteEvent(0, ron, rlen));
note->setPlayEvents(el);
if (useDrumset) {
if (!drumset->isValid(mn.pitch))
qDebug("unmapped drum note 0x%02x %d", mn.pitch, mn.pitch);
else {
MScore::Direction sd = drumset->stemDirection(mn.pitch);
chord->setStemDirection(sd);
}
2013-06-27 16:14:39 +02:00
}
if (midiNotes[i].tie) {
midiNotes[i].tie->setEndNote(note);
midiNotes[i].tie->setTrack(note->track());
note->setTieBack(midiNotes[i].tie);
}
}
}
2012-05-26 14:49:10 +02:00
2013-08-20 13:31:10 +02:00
void setTies(Chord *chord,
Score *score,
QList<MidiNote> &midiNotes)
2013-06-27 16:14:39 +02:00
{
for (int i = 0; i < midiNotes.size(); ++i) {
const MidiNote &midiNote = midiNotes[i];
2013-08-22 23:26:31 +02:00
Note *note = chord->findNote(midiNote.pitch);
2013-06-27 16:14:39 +02:00
midiNotes[i].tie = new Tie(score);
midiNotes[i].tie->setStartNote(note);
note->setTieFor(midiNotes[i].tie);
}
}
2013-07-03 23:54:54 +02:00
2013-06-27 16:14:39 +02:00
// convert midiChords with the same onTime value to music notation
// and fill the remaining empty duration with rests
2013-07-08 20:17:13 +02:00
void MTrack::processPendingNotes(QList<MidiChord> &midiChords,
int voice,
const ReducedFraction &startChordTickFrac,
const ReducedFraction &nextChordTick)
2012-05-26 14:49:10 +02:00
{
2013-08-22 23:26:31 +02:00
Score* score = staff->score();
const int track = staff->idx() * VOICES + voice;
2013-08-22 23:26:31 +02:00
Drumset* drumset = staff->part()->instr()->drumset();
const bool useDrumset = staff->part()->instr()->useDrumset();
// all midiChords here should have the same onTime value
// and all notes in each midiChord should have the same duration
ReducedFraction startChordTick = startChordTickFrac;
2013-06-27 16:14:39 +02:00
while (!midiChords.isEmpty()) {
const ReducedFraction tick = startChordTick;
ReducedFraction len = nextChordTick - tick;
if (len <= ReducedFraction(0, 1))
2013-04-15 10:38:16 +02:00
break;
len = MChord::findMinDuration(midiChords, len);
2013-08-22 23:26:31 +02:00
Measure* measure = score->tick2measure(tick.ticks());
2013-06-27 16:14:39 +02:00
len = splitDurationOnBarBoundary(len, tick, measure);
2013-05-26 01:19:09 +02:00
const auto dl = toDurationList(measure, voice, tick, len, Meter::DurationType::NOTE);
2013-04-15 10:38:16 +02:00
if (dl.isEmpty())
break;
2013-07-08 20:17:13 +02:00
const TDuration &d = dl[0].second;
const ReducedFraction &tupletRatio = dl[0].first;
len = ReducedFraction(d.fraction()) / tupletRatio;
2013-04-15 10:38:16 +02:00
2013-08-22 23:26:31 +02:00
Chord* chord = new Chord(score);
2013-04-15 10:38:16 +02:00
chord->setTrack(track);
chord->setDurationType(d);
chord->setDuration(d.fraction());
2013-08-22 23:26:31 +02:00
Segment* s = measure->getSegment(chord, tick.ticks());
2013-04-15 10:38:16 +02:00
s->add(chord);
chord->setUserPlayEvents(true);
2013-08-20 18:05:49 +02:00
MidiTuplet::addElementToTuplet(voice, tick, len, chord, tuplets);
2013-04-15 10:38:16 +02:00
2013-06-27 16:14:39 +02:00
for (int k = 0; k < midiChords.size(); ++k) {
MidiChord& midiChord = midiChords[k];
2013-07-08 10:49:05 +02:00
setMusicNotesFromMidi(score, midiChord.notes, startChordTick,
2013-07-08 09:28:43 +02:00
len, chord, tick, drumset, useDrumset);
if (!midiChord.notes.empty() && midiChord.notes.first().len <= len) {
2013-06-27 16:14:39 +02:00
midiChords.removeAt(k);
2013-04-15 10:38:16 +02:00
--k;
continue;
2012-05-26 14:49:10 +02:00
}
2013-06-27 16:14:39 +02:00
setTies(chord, score, midiChord.notes);
2013-07-08 09:28:43 +02:00
for (auto &midiNote: midiChord.notes)
midiNote.len -= len;
2012-05-26 14:49:10 +02:00
}
2013-06-27 16:14:39 +02:00
startChordTick += len;
2013-04-15 10:38:16 +02:00
}
2013-07-08 20:17:13 +02:00
fillGapWithRests(score, voice, startChordTick,
nextChordTick - startChordTick, track);
2013-04-15 10:38:16 +02:00
}
2012-05-26 14:49:10 +02:00
2013-07-27 00:01:30 +02:00
void MTrack::createKeys(int accidentalType)
{
Score* score = staff->score();
const int track = staff->idx() * VOICES;
2013-04-15 10:38:16 +02:00
2013-08-22 23:26:31 +02:00
KeyList* km = staff->keymap();
2013-07-27 00:01:30 +02:00
if (!hasKey && !mtrack->drumTrack()) {
KeySigEvent ks;
ks.setAccidentalType(accidentalType);
(*km)[0] = ks;
}
for (auto it = km->begin(); it != km->end(); ++it) {
const int tick = it->first;
const KeySigEvent &key = it->second;
2013-08-22 23:26:31 +02:00
KeySig* ks = new KeySig(score);
2013-07-27 00:01:30 +02:00
ks->setTrack(track);
ks->setGenerated(false);
ks->setKeySigEvent(key);
ks->setMag(staff->mag());
2013-08-22 23:26:31 +02:00
Measure* m = score->tick2measure(tick);
Segment* seg = m->getSegment(ks, tick);
2013-07-27 00:01:30 +02:00
seg->add(ks);
}
}
void MTrack::convertTrack(const ReducedFraction &lastTick)
2013-07-27 00:01:30 +02:00
{
2013-07-22 14:03:23 +02:00
for (int voice = 0; voice < VOICES; ++voice) {
2013-06-27 16:14:39 +02:00
// startChordTick is onTime value of all simultaneous notes
// chords here are consist of notes with equal durations
// several chords may have the same onTime value
ReducedFraction startChordTick;
2013-06-27 16:14:39 +02:00
QList<MidiChord> midiChords;
2013-04-15 10:38:16 +02:00
2013-06-11 23:14:05 +02:00
for (auto it = chords.begin(); it != chords.end();) {
2013-08-17 15:28:15 +02:00
const auto &nextChordTick = it->first;
const MidiChord& midiChord = it->second;
if (midiChord.voice != voice) {
2013-06-11 23:14:05 +02:00
++it;
2013-04-15 10:38:16 +02:00
continue;
}
2013-06-27 16:14:39 +02:00
processPendingNotes(midiChords, voice, startChordTick, nextChordTick);
// now 'midiChords' list is empty
// so - fill it:
// collect all midiChords on current tick position
startChordTick = nextChordTick; // debug
for ( ; it != chords.end(); ++it) {
const MidiChord& midiChord = it->second;
2013-06-27 16:14:39 +02:00
if (it->first != startChordTick)
2013-04-15 10:38:16 +02:00
break;
if (midiChord.voice != voice)
2013-04-15 10:38:16 +02:00
continue;
midiChords.append(midiChord);
2013-04-15 10:38:16 +02:00
}
2013-06-27 16:14:39 +02:00
if (midiChords.isEmpty())
2013-04-15 10:38:16 +02:00
break;
2012-05-26 14:49:10 +02:00
}
2013-06-27 16:14:39 +02:00
// process last chords at the end of the score
processPendingNotes(midiChords, voice, startChordTick, lastTick);
2013-07-03 23:54:54 +02:00
}
const int key = 0; // TODO-LIB findKey(mtrack, score->sigmap());
2012-05-26 14:49:10 +02:00
MidiTuplet::createTuplets(staff, tuplets);
2013-07-27 00:01:30 +02:00
createKeys(key);
2013-08-01 23:52:09 +02:00
const auto swingType = preferences.midiImportOperations.trackOperations(indexOfOperation).swing;
2013-08-01 23:52:09 +02:00
Swing::detectSwing(staff, swingType);
2013-08-22 23:15:07 +02:00
2013-08-24 20:32:48 +02:00
MidiClef::createClefs(staff, indexOfOperation, mtrack->drumTrack());
2013-04-15 10:38:16 +02:00
}
2013-05-17 20:11:36 +02:00
Fraction metaTimeSignature(const MidiEvent& e)
2013-04-15 10:38:16 +02:00
{
const unsigned char* data = e.edata();
const int z = data[0];
const int nn = data[1];
2013-04-15 10:38:16 +02:00
int n = 1;
for (int i = 0; i < nn; ++i)
n *= 2;
return Fraction(z, n);
}
2013-07-22 14:03:23 +02:00
QList<MTrack> prepareTrackList(const std::multimap<int, MTrack> &tracks)
2013-07-12 00:11:06 +02:00
{
2013-07-22 14:03:23 +02:00
QList<MTrack> trackList;
2013-07-23 13:05:19 +02:00
for (const auto &track: tracks) {
2013-07-22 14:03:23 +02:00
trackList.push_back(track.second);
2013-07-23 13:05:19 +02:00
}
2013-07-22 14:03:23 +02:00
return trackList;
2013-07-12 00:11:06 +02:00
}
std::multimap<int, MTrack> createMTrackList(ReducedFraction &lastTick,
TimeSigMap *sigmap,
const MidiFile *mf)
2013-04-15 10:38:16 +02:00
{
sigmap->clear();
sigmap->add(0, Fraction(4, 4)); // default time signature
std::multimap<int, MTrack> tracks; // <track index, track>
2013-04-28 21:29:12 +02:00
int trackIndex = -1;
for (const auto &t: mf->tracks()) {
2013-04-15 10:38:16 +02:00
MTrack track;
track.mtrack = &t;
track.division = mf->division();
2013-04-28 21:29:12 +02:00
int events = 0;
2013-06-11 23:14:05 +02:00
// - create time signature list from meta events
// - create MidiChord list
// - extract some information from track: program, min/max pitch
2013-09-07 20:57:23 +02:00
for (const auto &i: t.events()) {
2013-04-15 10:38:16 +02:00
const MidiEvent& e = i.second;
const auto tick = toMuseScoreTicks(i.first, track.division);
2013-06-11 23:14:05 +02:00
// remove time signature events
if ((e.type() == ME_META) && (e.metaType() == META_TIME_SIGNATURE)) {
2013-07-08 20:17:13 +02:00
sigmap->add(tick.ticks(), metaTimeSignature(e));
}
2013-04-15 10:38:16 +02:00
else if (e.type() == ME_NOTE) {
++events;
const int pitch = e.pitch();
const auto len = toMuseScoreTicks(e.len(), track.division);
2013-07-08 20:17:13 +02:00
if (tick + len > lastTick)
lastTick = tick + len;
2013-04-15 10:38:16 +02:00
MidiNote n;
n.pitch = pitch;
n.velo = e.velo();
2013-04-16 10:26:59 +02:00
n.len = len;
2013-04-15 10:38:16 +02:00
MidiChord c;
c.notes.push_back(n);
2013-05-11 01:35:40 +02:00
track.chords.insert({tick, c});
2013-04-15 10:38:16 +02:00
}
else if (e.type() == ME_PROGRAM)
2013-05-11 01:35:40 +02:00
track.program = e.dataB();
2013-07-08 20:17:13 +02:00
if (tick > lastTick)
lastTick = tick;
2013-04-15 10:38:16 +02:00
}
if (events != 0) {
2013-07-23 13:05:19 +02:00
++trackIndex;
if (preferences.midiImportOperations.count()) {
auto trackOperations
= preferences.midiImportOperations.trackOperations(trackIndex);
if (trackOperations.doImport) {
track.indexOfOperation = trackIndex;
tracks.insert({trackOperations.reorderedIndex, track});
}
}
else { // if it is an initial track-list query from MIDI import panel
2013-07-22 14:03:23 +02:00
track.indexOfOperation = trackIndex;
2013-07-23 13:05:19 +02:00
tracks.insert({trackIndex, track});
2013-04-28 21:29:12 +02:00
}
2013-04-15 10:38:16 +02:00
}
}
return tracks;
2013-04-28 21:29:12 +02:00
}
2012-05-26 14:49:10 +02:00
2013-07-27 00:01:30 +02:00
Measure* barFromIndex(const Score *score, int barIndex)
{
const int tick = score->sigmap()->bar2tick(barIndex, 0);
2013-07-27 00:01:30 +02:00
return score->tick2measure(tick);
}
bool isGrandStaff(const MTrack &t1, const MTrack &t2)
{
const static std::set<int> grandStaffPrograms = {
// Piano
0, 1, 2, 3, 4, 5, 6, 7
// Chromatic Percussion
, 8, 10, 11, 12, 13, 15
// Organ
, 16, 17, 18, 19, 20, 21, 23
// Strings
, 46
// Ensemble
, 50, 51, 54
// Brass
, 62, 63
// Synth Lead
, 80, 81, 82, 83, 84, 85, 86, 87
// Synth Pad
, 88, 89, 90, 91, 92, 93, 94, 95
// Synth Effects
, 96, 97, 98, 99, 100, 101, 102, 103
};
return t1.mtrack->outChannel() == t2.mtrack->outChannel()
&& grandStaffPrograms.find(t1.program) != grandStaffPrograms.end();
}
bool isSameChannel(const MTrack &t1, const MTrack &t2)
{
return (t1.mtrack->outChannel() == t2.mtrack->outChannel());
}
2013-05-14 16:43:21 +02:00
//---------------------------------------------------------
// createInstruments
// for drum track, if any, set percussion clef
// for piano 2 tracks, if any, set G and F clefs
// for other track types set G or F clef
//---------------------------------------------------------
2013-07-08 20:17:13 +02:00
void createInstruments(Score *score, QList<MTrack> &tracks)
2013-04-28 21:29:12 +02:00
{
const int ntracks = tracks.size();
2013-04-15 10:38:16 +02:00
for (int idx = 0; idx < ntracks; ++idx) {
MTrack& track = tracks[idx];
2013-08-22 23:26:31 +02:00
Part* part = new Part(score);
Staff* s = new Staff(score, part, 0);
2013-04-15 10:38:16 +02:00
part->insertStaff(s);
score->staves().push_back(s);
track.staff = s;
if (track.mtrack->drumTrack()) {
2013-06-11 23:14:05 +02:00
// drum track
2013-09-05 16:37:49 +02:00
s->setClef(0, ClefType::PERC);
2013-04-15 10:38:16 +02:00
part->instr()->setDrumset(smDrumset);
2013-08-07 22:04:33 +02:00
part->instr()->setUseDrumset(true);
2013-04-15 10:38:16 +02:00
}
else {
const int avgPitch = MChord::findAveragePitch(track.chords.begin(),
track.chords.end());
2013-09-05 16:37:49 +02:00
s->setClef(0, MidiClef::clefTypeFromAveragePitch(avgPitch));
if (idx < (tracks.size() - 1) && idx >= 0
&& isGrandStaff(tracks[idx], tracks[idx + 1])) {
2013-06-11 23:14:05 +02:00
// assume that the current track and the next track
// form a piano part
2013-07-27 00:01:30 +02:00
s->setBracket(0, BRACKET_BRACE);
s->setBracketSpan(0, 2);
2013-08-22 23:26:31 +02:00
Staff* ss = new Staff(score, part, 1);
2013-04-15 10:38:16 +02:00
part->insertStaff(ss);
score->staves().push_back(ss);
++idx;
const int avgPitch = MChord::findAveragePitch(tracks[idx].chords.begin(),
tracks[idx].chords.end());
2013-09-05 16:37:49 +02:00
ss->setClef(0, MidiClef::clefTypeFromAveragePitch(avgPitch));
2013-04-15 10:38:16 +02:00
tracks[idx].staff = ss;
}
}
score->appendPart(part);
2012-05-26 14:49:10 +02:00
}
2013-04-28 21:29:12 +02:00
}
2012-05-26 14:49:10 +02:00
void createMeasures(ReducedFraction &lastTick, Score *score)
2013-04-28 21:29:12 +02:00
{
2013-04-15 10:38:16 +02:00
int bars, beat, tick;
2013-07-08 20:17:13 +02:00
score->sigmap()->tickValues(lastTick.ticks(), &bars, &beat, &tick);
2013-04-15 10:38:16 +02:00
if (beat > 0 || tick > 0)
2013-06-11 23:14:05 +02:00
++bars; // convert bar index to number of bars
2013-04-15 10:38:16 +02:00
const bool pickupMeasure = preferences.midiImportOperations.currentTrackOperations().pickupMeasure;
2013-08-11 22:45:48 +02:00
2012-05-26 14:49:10 +02:00
for (int i = 0; i < bars; ++i) {
2013-08-22 23:26:31 +02:00
Measure* measure = new Measure(score);
const int tick = score->sigmap()->bar2tick(i, 0);
2012-05-26 14:49:10 +02:00
measure->setTick(tick);
const Fraction ts = score->sigmap()->timesig(tick).timesig();
2013-07-12 14:20:31 +02:00
Fraction nominalTs = ts;
2013-08-11 22:45:48 +02:00
if (pickupMeasure && i == 0 && bars > 1) {
2013-07-12 14:20:31 +02:00
const int secondBarIndex = 1;
const int secondBarTick = score->sigmap()->bar2tick(secondBarIndex, 0);
2013-07-12 14:20:31 +02:00
Fraction secondTs(score->sigmap()->timesig(secondBarTick).timesig());
2013-08-11 22:45:48 +02:00
if (ts < secondTs) { // the first measure is a pickup measure
2013-07-12 14:20:31 +02:00
nominalTs = secondTs;
measure->setIrregular(true);
}
}
measure->setTimesig(nominalTs);
2012-05-26 14:49:10 +02:00
measure->setLen(ts);
2013-04-28 21:29:12 +02:00
score->add(measure);
2012-05-26 14:49:10 +02:00
}
2013-08-22 23:26:31 +02:00
const Measure *m = score->lastMeasure();
if (m) {
score->fixTicks();
lastTick = ReducedFraction::fromTicks(m->endTick());
}
2013-04-28 21:29:12 +02:00
}
2013-04-15 10:38:16 +02:00
2013-08-11 13:47:17 +02:00
QString instrumentName(int type, int program, bool isDrumTrack)
2013-05-14 16:43:21 +02:00
{
2013-08-11 13:47:17 +02:00
if (isDrumTrack)
return "Percussion";
2013-05-14 16:43:21 +02:00
int hbank = -1, lbank = -1;
if (program == -1)
program = 0;
else {
hbank = (program >> 16);
lbank = (program >> 8) & 0xff;
program = program & 0xff;
}
return MidiInstrument::instrName(type, hbank, lbank, program);
}
void setTrackInfo(MidiType midiType, MTrack &mt)
2013-05-14 16:43:21 +02:00
{
if (mt.staff->isTop()) {
2013-08-22 23:26:31 +02:00
Part *part = mt.staff->part();
2013-05-14 16:43:21 +02:00
if (mt.name.isEmpty()) {
2013-08-11 13:47:17 +02:00
QString name = instrumentName(midiType, mt.program, mt.mtrack->drumTrack());
if (!name.isEmpty()) {
mt.name = name;
2013-05-14 16:43:21 +02:00
part->setLongName(name);
}
2013-05-14 16:43:21 +02:00
}
else
part->setLongName(mt.name);
part->setPartName(part->longName().toPlainText());
part->setMidiChannel(mt.mtrack->outChannel());
2013-08-11 13:42:03 +02:00
int bank = 0;
if (mt.mtrack->drumTrack())
bank = 128;
part->setMidiProgram(mt.program & 0x7f, bank); // only GM
2013-05-14 16:43:21 +02:00
}
}
2013-07-08 20:17:13 +02:00
void createTimeSignatures(Score *score)
2013-05-14 16:43:21 +02:00
{
for (auto is = score->sigmap()->begin(); is != score->sigmap()->end(); ++is) {
const SigEvent& se = is->second;
const int tick = is->first;
2013-07-08 20:17:13 +02:00
Measure* m = score->tick2measure(tick);
2013-05-14 16:43:21 +02:00
if (!m)
continue;
2013-07-12 14:20:31 +02:00
Fraction newTimeSig = se.timesig();
2013-08-11 22:45:48 +02:00
const bool pickupMeasure = preferences.midiImportOperations.currentTrackOperations().pickupMeasure;
2013-08-11 22:45:48 +02:00
if (pickupMeasure && is == score->sigmap()->begin()) {
auto next = std::next(is);
2013-07-12 14:20:31 +02:00
if (next != score->sigmap()->end()) {
Measure* mm = score->tick2measure(next->first);
if (m && mm && m == barFromIndex(score, 0) && mm == barFromIndex(score, 1)
&& m->timesig() == mm->timesig() && newTimeSig != mm->timesig())
{
// it's a pickup measure - change timesig to nominal value
newTimeSig = mm->timesig();
}
}
}
2013-05-14 16:43:21 +02:00
for (int staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
2013-08-22 23:26:31 +02:00
TimeSig* ts = new TimeSig(score);
2013-07-12 14:20:31 +02:00
ts->setSig(newTimeSig);
2013-05-14 16:43:21 +02:00
ts->setTrack(staffIdx * VOICES);
2013-08-22 23:26:31 +02:00
Segment* seg = m->getSegment(ts, tick);
2013-05-14 16:43:21 +02:00
seg->add(ts);
}
2013-07-12 14:20:31 +02:00
if (newTimeSig != se.timesig()) // was a pickup measure - skip next timesig
++is;
2013-05-14 16:43:21 +02:00
}
}
void processMeta(MTrack &mt, bool isLyric)
{
for (const auto &ie : mt.mtrack->events()) {
const MidiEvent &e = ie.second;
const auto tick = toMuseScoreTicks(ie.first, mt.division);
if ((e.type() == ME_META) && ((e.metaType() == META_LYRIC) == isLyric))
mt.processMeta(tick.ticks(), e);
}
}
void createNotes(const ReducedFraction &lastTick, QList<MTrack> &tracks, MidiType midiType)
2013-05-14 16:43:21 +02:00
{
2013-04-15 10:38:16 +02:00
for (int i = 0; i < tracks.size(); ++i) {
2013-07-08 20:17:13 +02:00
MTrack &mt = tracks[i];
processMeta(mt, false);
2013-08-07 15:24:29 +02:00
if (midiType == MT_UNKNOWN)
midiType = MT_GM;
if (i % 2 && isSameChannel(tracks[i - 1], mt)) {
mt.program = tracks[i - 1].program;
}
// if tracks in Grand staff have different names - clear them,
// instrument name will be used instead
if (i % 2 == 0 && i < tracks.size() - 1
&& isGrandStaff(mt, tracks[i + 1])) {
if (mt.name != tracks[i + 1].name) {
mt.name = "";
tracks[i + 1].name = "";
}
}
setTrackInfo(midiType, mt);
2013-06-11 23:14:05 +02:00
// pass current track index to the convertTrack function
// through MidiImportOperations
2013-07-23 13:05:19 +02:00
preferences.midiImportOperations.setCurrentTrack(mt.indexOfOperation);
2013-04-15 10:38:16 +02:00
mt.convertTrack(lastTick);
processMeta(mt, true);
2012-05-26 14:49:10 +02:00
}
2013-05-14 16:43:21 +02:00
}
2012-05-26 14:49:10 +02:00
2013-09-08 22:36:40 +02:00
QList<TrackMeta> getTracksMeta(const QList<MTrack> &tracks,
2013-07-22 14:03:23 +02:00
const MidiFile *mf)
2013-04-28 21:29:12 +02:00
{
2013-05-11 01:35:40 +02:00
QList<TrackMeta> tracksMeta;
2013-09-08 22:36:40 +02:00
for (int i = 0; i < tracks.size(); ++i) {
const MTrack &mt = tracks[i];
std::string trackName;
for (const auto &ie: mt.mtrack->events()) {
2013-07-22 14:03:23 +02:00
const MidiEvent &e = ie.second;
if ((e.type() == ME_META) && (e.metaType() == META_TRACK_NAME)) {
2013-07-22 14:03:23 +02:00
trackName = (const char*)e.edata();
break;
}
2013-04-28 21:29:12 +02:00
}
QString instrName;
if (i % 2 && isSameChannel(tracks[i - 1], tracks[i])){
TrackMeta lastMeta = tracksMeta.back();
instrName = lastMeta.instrumentName;
}
else {
MidiType midiType = mf->midiType();
if (midiType == MT_UNKNOWN)
midiType = MT_GM;
instrName = instrumentName(midiType, mt.program,
mt.mtrack->drumTrack());
}
2013-09-08 22:36:40 +02:00
tracksMeta.push_back({trackName,
instrName,
mt.mtrack->drumTrack(),
mt.initLyricTrackIndex
});
2013-04-28 21:29:12 +02:00
}
2013-05-11 01:35:40 +02:00
return tracksMeta;
2013-04-28 21:29:12 +02:00
}
#ifdef QT_DEBUG
bool doNotesOverlap(const std::multimap<int, MTrack> &tracks)
{
for (const auto &track: tracks) {
const auto &chords = track.second.chords;
for (auto it = chords.begin(); it != chords.end(); ++it) {
const auto &firstChord = it->second;
const auto &firstOnTime = it->first;
for (const auto &note1: firstChord.notes) {
auto ii = std::next(it);
for (; ii != chords.end(); ++ii) {
const auto &secondChord = ii->second;
if (firstChord.voice != secondChord.voice)
continue;
const auto &secondOnTime = ii->first;
for (const auto &note2: secondChord.notes) {
if (note2.pitch != note1.pitch)
continue;
if (secondOnTime >= (firstOnTime + note1.len))
continue;
return true;
}
}
}
2014-01-02 20:12:57 +01:00
}
}
return false;
}
#endif
void convertMidi(Score *score, const MidiFile *mf)
2013-06-02 01:06:41 +02:00
{
ReducedFraction lastTick;
2013-08-22 23:26:31 +02:00
auto *sigmap = score->sigmap();
2013-06-02 01:06:41 +02:00
auto tracks = createMTrackList(lastTick, sigmap, mf);
2013-08-19 12:57:46 +02:00
cleanUpMidiEvents(tracks);
MChord::collectChords(tracks);
MChord::removeOverlappingNotes(tracks);
Q_ASSERT_X(!doNotesOverlap(tracks),
"convertMidi:", "There are overlapping notes of the same voice that is incorrect");
2013-06-02 01:06:41 +02:00
quantizeAllTracks(tracks, sigmap, lastTick);
Q_ASSERT_X(!doNotesOverlap(tracks),
"convertMidi:", "There are overlapping notes of the same voice that is incorrect");
MChord::mergeChordsWithEqualOnTimeAndVoice(tracks);
LRHand::splitIntoLeftRightHands(tracks);
2013-08-20 18:05:49 +02:00
MidiDrum::splitDrumVoices(tracks);
MidiDrum::splitDrumTracks(tracks);
MidiDrum::removeRests(tracks, sigmap);
2013-10-20 01:21:47 +02:00
MChord::splitUnequalChords(tracks);
2013-07-22 14:03:23 +02:00
// no more track insertion/reordering/deletion from now
QList<MTrack> trackList = prepareTrackList(tracks);
createInstruments(score, trackList);
2013-08-20 18:05:49 +02:00
MidiDrum::setStaffBracketForDrums(trackList);
2013-06-02 01:06:41 +02:00
createMeasures(lastTick, score);
createNotes(lastTick, trackList, mf->midiType());
2013-06-02 01:06:41 +02:00
createTimeSignatures(score);
score->connectTies();
2013-10-19 16:01:42 +02:00
MidiLyrics::setLyricsToScore(mf, trackList);
2013-06-02 01:06:41 +02:00
}
void loadMidiData(MidiFile &mf)
{
mf.separateChannel();
MidiType mt = MT_UNKNOWN;
for (auto &track: mf.tracks())
track.mergeNoteOnOffAndFindMidiType(&mt);
mf.setMidiType(mt);
}
2013-07-08 20:17:13 +02:00
QList<TrackMeta> extractMidiTracksMeta(const QString &fileName)
2013-04-28 21:29:12 +02:00
{
if (fileName.isEmpty())
2013-05-11 01:35:40 +02:00
return QList<TrackMeta>();
auto &midiData = preferences.midiImportOperations.midiData();
if (!midiData.midiFile(fileName)) {
QFile fp(fileName);
if (!fp.open(QIODevice::ReadOnly))
return QList<TrackMeta>();
MidiFile mf;
try {
mf.read(&fp);
}
catch (...) {
fp.close();
return QList<TrackMeta>();
2013-04-28 21:29:12 +02:00
}
fp.close();
loadMidiData(mf);
midiData.setMidiFile(fileName, mf);
2013-04-28 21:29:12 +02:00
}
Score mockScore;
ReducedFraction lastTick;
2013-09-08 22:36:40 +02:00
const MidiFile *mf = midiData.midiFile(fileName);
const auto tracks = createMTrackList(lastTick, mockScore.sigmap(), mf);
QList<MTrack> trackList = prepareTrackList(tracks);
MidiLyrics::assignLyricsToTracks(trackList);
2013-09-08 22:36:40 +02:00
return getTracksMeta(trackList, mf);
2013-04-28 21:29:12 +02:00
}
2012-05-26 14:49:10 +02:00
//---------------------------------------------------------
// importMidi
//---------------------------------------------------------
2013-07-08 20:17:13 +02:00
Score::FileError importMidi(Score *score, const QString &name)
2012-05-26 14:49:10 +02:00
{
if (name.isEmpty())
2012-10-07 19:53:58 +02:00
return Score::FILE_NOT_FOUND;
auto &midiData = preferences.midiImportOperations.midiData();
if (!midiData.midiFile(name)) {
QFile fp(name);
if (!fp.open(QIODevice::ReadOnly)) {
qDebug("importMidi: file open error <%s>", qPrintable(name));
return Score::FILE_OPEN_ERROR;
}
MidiFile mf;
try {
mf.read(&fp);
}
catch (QString errorText) {
if (!noGui) {
QMessageBox::warning(0,
QWidget::tr("MuseScore: load midi"),
QWidget::tr("Load failed: ") + errorText,
QString::null, QWidget::tr("Quit"), QString::null, 0, 1);
}
fp.close();
qDebug("importMidi: bad file format");
return Score::FILE_BAD_FORMAT;
2012-05-26 14:49:10 +02:00
}
fp.close();
loadMidiData(mf);
midiData.setMidiFile(name, mf);
2012-05-26 14:49:10 +02:00
}
convertMidi(score, midiData.midiFile(name));
2012-10-07 19:53:58 +02:00
return Score::FILE_NO_ERROR;
2012-05-26 14:49:10 +02:00
}
2013-05-13 18:49:17 +02:00
}
2012-05-26 14:49:10 +02:00