MuseScore/mscore/importmidi.cpp

1211 lines
46 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"
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"
#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-04-15 10:38:16 +02:00
#include "preferences.h"
2013-05-26 01:19:09 +02:00
#include "importmidi_meter.h"
#include "importmidi_chord.h"
#include "importmidi_quant.h"
2013-06-11 23:14:05 +02:00
#include "importmidi_tupletdata.h"
2013-06-27 16:14:39 +02:00
#include "libmscore/tuplet.h"
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;
2012-05-26 14:49:10 +02:00
2013-04-15 10:38:16 +02:00
//---------------------------------------------------------
// MTrack
//---------------------------------------------------------
class MTrack {
public:
int minPitch = 127, maxPitch = 0, medPitch = 0, program = 0;
Staff* staff = 0;
MidiTrack* mtrack = 0;
QString name;
bool hasKey = false;
2013-05-28 17:49:45 +02:00
std::multimap<int, MidiChord> chords;
2013-07-02 23:37:07 +02:00
std::multimap<int, TupletData> tuplets; // <tupletOnTime, TupletData>
2013-04-15 10:38:16 +02:00
void convertTrack(int lastTick);
2013-06-27 16:14:39 +02:00
void processPendingNotes(QList<MidiChord>& midiChords, int voice, int startChordTick, int nextChordTick);
2013-04-15 10:38:16 +02:00
void processMeta(int tick, const MidiEvent& mm);
2013-06-27 16:14:39 +02:00
void fillGapWithRests(Score *score, int voice, int startChordTick, int restLen, int track);
std::vector<TupletData> findTupletsForDuration(int voice, int barTick,
int durationOnTime, int durationLen);
2013-07-03 23:54:54 +02:00
QList<std::pair<Fraction, TDuration> > toDurationList(const Measure *measure, int voice, int startTick, int len,
2013-06-27 16:14:39 +02:00
Meter::DurationType durationType);
int voicesInBar(int tickInBar);
2013-07-03 23:54:54 +02:00
void addElementToTuplet(int voice, int onTime, int len, DurationElement *el);
2013-06-22 23:39:48 +02:00
};
2013-05-24 13:47:16 +02:00
2012-05-26 14:49:10 +02:00
2013-06-02 01:06:41 +02:00
// remove overlapping notes with the same pitch
void removeOverlappingNotes(QList<MTrack> &tracks)
{
for (auto &track: tracks) {
auto &chords = track.chords;
2013-06-11 23:14:05 +02:00
for (auto it = chords.begin(); it != chords.end(); ++it) {
auto &firstChord = it->second;
2013-06-02 01:06:41 +02:00
for (auto &note1: firstChord.notes) {
2013-06-11 23:14:05 +02:00
auto ii = it;
2013-06-02 01:06:41 +02:00
++ii;
bool overlapFound = false;
for (; ii != chords.end(); ++ii) {
auto &secondChord = ii->second;
for (auto &note2: secondChord.notes) {
if (note2.pitch != note1.pitch)
continue;
if (note2.onTime >= (note1.onTime + note1.len))
continue;
qDebug("Midi import: overlapping events: %d+%d %d+%d",
note1.onTime, note1.len, note2.onTime, note2.len);
note1.len = note2.onTime - note1.onTime;
overlapFound = true;
break;
}
if (overlapFound)
break;
}
if (note1.len <= 0) {
qDebug("Midi import: duration <= 0: drop note at %d", note1.onTime);
continue;
}
}
} // for note1
}
}
// based on quickthresh algorithm
// here are default values for audio, in milliseconds
// for midi there will be another values, in ticks
// all notes received in the left inlet within this time period are collected into a chord
// threshTime = 40 ms
// if there are any incoming values within this amount of time
// at the end of the base thresh time,
// the threshold is extended to allow more notes to be added to the chord
// fudgeTime = 10 ms
// this is an extension value of the base thresh time, which is used if notes arrive
// in the object's inlet in the "fudge" time zone
// threshExtTime = 20 ms
void collectChords(QList<MTrack> &tracks, int minNoteDuration)
{
for (auto &track: tracks) {
auto &chords = track.chords;
if (chords.empty())
continue;
Drumset* drumset = track.mtrack->drumTrack() ? smDrumset : 0;
int threshTime = minNoteDuration / 2;
int fudgeTime = threshTime / 4;
int threshExtTime = threshTime / 2;
int startTime = -1; // invalid
int curThreshTime = -1;
bool useDrumset = false;
2013-06-11 23:14:05 +02:00
// if intersection of note durations is less than min(minNoteDuration, threshTime)
// then this is not a chord
int tol = -1; // invalid
2013-06-02 01:06:41 +02:00
int beg = -1;
int end = -1;
2013-06-11 23:14:05 +02:00
// chords here consist of a single note
// because notes are not united into chords yet
for (auto it = chords.begin(); it != chords.end(); ) {
const auto &note = it->second.notes[0];
2013-06-02 01:06:41 +02:00
if (note.onTime <= startTime + curThreshTime) {
if (!useDrumset || (drumset->isValid(note.pitch)
2013-06-11 23:14:05 +02:00
&& drumset->voice(note.pitch) == it->second.voice)) {
2013-06-02 01:06:41 +02:00
beg = std::max(beg, note.onTime);
end = std::min(end, note.onTime + note.len);
tol = std::min(tol, note.len);
if (end - beg >= tol) {
2013-06-11 23:14:05 +02:00
// add current note to the previous chord
auto prev = it;
2013-06-02 01:06:41 +02:00
--prev;
prev->second.notes.push_back(note);
if (note.onTime >= startTime + curThreshTime - fudgeTime)
curThreshTime += threshExtTime;
2013-06-11 23:14:05 +02:00
it = chords.erase(it);
2013-06-02 01:06:41 +02:00
continue;
}
}
}
else {
useDrumset = false;
if (drumset) {
int pitch = note.pitch;
if (drumset->isValid(pitch)) {
useDrumset = true;
2013-06-11 23:14:05 +02:00
it->second.voice = drumset->voice(pitch);
2013-06-02 01:06:41 +02:00
}
}
startTime = note.onTime;
beg = note.onTime;
end = note.onTime + note.len;
tol = threshTime;
if (curThreshTime != threshTime)
curThreshTime = threshTime;
}
2013-06-11 23:14:05 +02:00
++it;
2013-06-02 01:06:41 +02:00
}
}
}
void sortNotesByPitch(std::multimap<int, MidiChord> &chords)
{
struct {
bool operator()(const MidiNote &note1, const MidiNote &note2)
{
return note1.pitch < note2.pitch;
}
} pitchSort;
for (auto &chordEvent: chords) {
2013-06-11 23:14:05 +02:00
// in each chord sort notes by pitches
2013-06-02 01:06:41 +02:00
auto &notes = chordEvent.second.notes;
qSort(notes.begin(), notes.end(), pitchSort);
}
}
void sortNotesByLength(std::multimap<int, MidiChord> &chords)
{
struct {
bool operator()(const MidiNote &note1, const MidiNote &note2)
{
return note1.len < note2.len;
}
} lenSort;
for (auto &chordEvent: chords) {
2013-06-11 23:14:05 +02:00
// in each chord sort notes by pitches
2013-06-02 01:06:41 +02:00
auto &notes = chordEvent.second.notes;
qSort(notes.begin(), notes.end(), lenSort);
}
}
// find notes of each chord that have different durations
// and separate them into different chords
// so all chords will have notes with equal lengths
void splitUnequalChords(QList<MTrack> &tracks)
{
for (auto &track: tracks) {
std::vector<std::pair<int, MidiChord>> newChordEvents;
auto &chords = track.chords;
sortNotesByLength(chords);
for (auto &chordEvent: chords) {
auto &chord = chordEvent.second;
auto &notes = chord.notes;
int len;
2013-06-11 23:14:05 +02:00
for (auto it = notes.begin(); it != notes.end(); ) {
if (it == notes.begin())
len = it->len;
2013-06-02 01:06:41 +02:00
else {
2013-06-11 23:14:05 +02:00
int newLen = it->len;
2013-06-02 01:06:41 +02:00
if (newLen != len) {
auto newChord = chord;
newChord.notes.clear();
newChord.duration = len;
2013-06-11 23:14:05 +02:00
for (int j = it - notes.begin(); j > 0; --j)
2013-06-02 01:06:41 +02:00
newChord.notes.push_back(notes[j - 1]);
newChordEvents.push_back({chord.onTime, newChord});
2013-06-11 23:14:05 +02:00
it = notes.erase(notes.begin(), it);
2013-06-02 01:06:41 +02:00
continue;
}
}
2013-06-11 23:14:05 +02:00
++it;
2013-06-02 01:06:41 +02:00
}
chord.duration = notes.first().len;
}
for (const auto &event: newChordEvents)
chords.insert(event);
}
}
2013-05-24 13:47:16 +02:00
void quantizeAllTracks(QList<MTrack>& tracks, TimeSigMap* sigmap, int lastTick)
2013-05-24 13:47:16 +02:00
{
auto &opers = preferences.midiImportOperations;
if (tracks.size() == 1 && opers.trackOperations(0).quantize.humanPerformance) {
opers.setCurrentTrack(0);
Quantize::applyAdaptiveQuant(tracks[0].chords, sigmap, lastTick);
Quantize::applyGridQuant(tracks[0].chords, sigmap, lastTick);
2013-05-17 20:11:36 +02:00
}
2013-06-02 01:06:41 +02:00
else {
for (int i = 0; i < tracks.size(); ++i) {
2013-06-11 23:14:05 +02:00
// pass current track index through MidiImportOperations
// for further usage
opers.setCurrentTrack(i);
// Quantize::applyGridQuant(tracks[i].chords, sigmap, lastTick);
Quantize::quantizeChordsAndFindTuplets(tracks[i].tuplets, tracks[i].chords,
sigmap, lastTick);
2013-06-02 01:06:41 +02:00
}
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-04-15 10:38:16 +02:00
int staffIdx = staff->idx();
Score* cs = staff->score();
2012-05-26 14:49:10 +02:00
switch (mm.metaType()) {
case META_TEXT:
2013-04-15 10:38:16 +02:00
case META_LYRIC: {
QString s((char*)data);
cs->addLyrics(tick, staffIdx, s);
}
2012-05-26 14:49:10 +02:00
break;
case META_TRACK_NAME:
2013-04-15 10:38:16 +02:00
name = (const char*)data;
2012-05-26 14:49:10 +02:00
break;
case META_TEMPO:
{
unsigned tempo = data[2] + (data[1] << 8) + (data[0] <<16);
double t = 1000000.0 / double(tempo);
cs->setTempo(tick, t);
// TODO: create TempoText
}
break;
case META_KEY_SIGNATURE:
2013-04-28 21:29:12 +02:00
{
int key = ((const char*)data)[0];
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:
2013-04-15 10:38:16 +02:00
qDebug("midi: meta timesig: %d, division %d", tick, MScore::division);
2012-05-26 14:49:10 +02:00
cs->sigmap()->add(tick, Fraction(data[0], 1 << data[1]));
break;
default:
if (MScore::debugMode)
qDebug("unknown meta type 0x%02x", mm.metaType());
break;
}
}
2013-06-11 23:14:05 +02:00
// find tuplets over which duration lies
std::vector<TupletData>
MTrack::findTupletsForDuration(int voice, int barTick, int durationOnTime, int durationLen)
{
std::vector<TupletData> tupletsData;
if (tuplets.empty())
return tupletsData;
auto tupletIt = tuplets.lower_bound(durationOnTime + durationLen);
--tupletIt;
while (durationOnTime + durationLen > tupletIt->first
&& durationOnTime < tupletIt->first + tupletIt->second.len) {
if (tupletIt->second.voice == voice) {
// if tuplet and duration intersect each other
auto tupletData = tupletIt->second;
// convert tuplet onTime to local bar ticks
tupletData.onTime -= barTick;
tupletsData.push_back(tupletData);
}
2013-07-03 20:13:55 +02:00
if (tupletIt == tuplets.begin())
break;
2013-06-11 23:14:05 +02:00
--tupletIt;
}
2013-07-03 20:13:55 +02:00
2013-06-11 23:14:05 +02:00
struct {
bool operator()(const TupletData &d1, const TupletData &d2)
{
return (d1.len > d2.len);
}
} comparator;
// sort by tuplet length in desc order
sort(tupletsData.begin(), tupletsData.end(), comparator);
return tupletsData;
}
2013-07-03 23:54:54 +02:00
QList<std::pair<Fraction, TDuration> >
MTrack::toDurationList(const Measure *measure,
int voice,
int startTick,
int len,
Meter::DurationType durationType)
2013-05-17 20:11:36 +02:00
{
bool useDots = preferences.midiImportOperations.currentTrackOperations().useDots;
2013-06-27 16:14:39 +02:00
// find tuplets over which duration is go
std::vector<TupletData> tupletData = findTupletsForDuration(voice, measure->tick(),
startTick, len);
int startTickInBar = startTick - measure->tick();
int endTickInBar = startTickInBar + len;
2013-07-03 23:54:54 +02:00
return Meter::toDurationList(startTickInBar, endTickInBar,
measure->timesig(), tupletData,
durationType, useDots);
2013-06-27 16:14:39 +02:00
}
int splitDurationOnBarBoundary(int len, int onTime, const Measure* measure)
{
if ((onTime + len) > measure->tick() + measure->ticks())
len = measure->tick() + measure->ticks() - onTime;
return len;
}
// fill the gap between successive chords with rests
void MTrack::fillGapWithRests(Score* score, int voice, int startChordTick,
int restLen, int track)
{
2013-05-17 20:11:36 +02:00
while (restLen > 0) {
int len = restLen;
2013-06-27 16:14:39 +02:00
Measure* measure = score->tick2measure(startChordTick);
if (startChordTick >= measure->tick() + measure->ticks()) {
qDebug("tick2measure: %d end of score?", startChordTick);
startChordTick += restLen;
2013-05-17 20:11:36 +02:00
restLen = 0;
break;
}
2013-06-27 16:14:39 +02:00
len = splitDurationOnBarBoundary(len, startChordTick, measure);
2013-05-17 20:11:36 +02:00
if (len >= measure->ticks()) {
2013-06-27 16:14:39 +02:00
// rest to the whole measure
2013-05-17 20:11:36 +02:00
len = 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);
Segment* s = measure->getSegment(rest, startChordTick);
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 {
2013-07-03 23:54:54 +02:00
auto dl = toDurationList(measure, voice, startChordTick, len,
Meter::DurationType::REST);
2013-05-17 20:11:36 +02:00
if (dl.isEmpty()) {
qDebug("cannot create duration list for len %d", len);
restLen = 0; // fake
break;
}
2013-07-03 23:54:54 +02:00
for (const auto &durationPair: dl) {
const auto &duration = durationPair.second;
2013-06-27 16:14:39 +02:00
Rest* rest = new Rest(score, duration);
rest->setDuration(duration.fraction());
2013-05-17 20:11:36 +02:00
rest->setTrack(track);
2013-06-27 16:14:39 +02:00
Segment* s = measure->getSegment(Segment::SegChordRest, startChordTick);
2013-05-17 20:11:36 +02:00
s->add(rest);
2013-07-03 23:54:54 +02:00
addElementToTuplet(voice, startChordTick, len, rest);
2013-06-27 16:14:39 +02:00
restLen -= duration.ticks();
startChordTick += duration.ticks();
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,
int len,
2013-06-27 16:14:39 +02:00
Chord *chord,
int tick,
const Drumset *drumset,
bool useDrumset)
{
int actualTicks = chord->actualTicks();
for (int i = 0; i < midiNotes.size(); ++i) {
const MidiNote& mn = midiNotes[i];
Note* note = new Note(score);
// 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;
int ron = (mn.onTime - tick) * 1000 / actualTicks;
int rlen = len * 1000 / actualTicks;
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
chord->setStemDirection(drumset->stemDirection(mn.pitch));
}
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-06-27 16:14:39 +02:00
int findMinDuration(const QList<MidiChord> &midiChords, int len)
{
for (const auto &chord: midiChords) {
if ((chord.duration < len) && (chord.duration != 0))
len = chord.duration;
}
return len;
}
void setTies(Chord *chord, Score *score, QList<MidiNote> &midiNotes)
{
for (int i = 0; i < midiNotes.size(); ++i) {
const MidiNote &midiNote = midiNotes[i];
Note *note = chord->findNote(midiNote.pitch);
midiNotes[i].tie = new Tie(score);
midiNotes[i].tie->setStartNote(note);
note->setTieFor(midiNotes[i].tie);
}
}
2013-07-03 23:54:54 +02:00
void MTrack::addElementToTuplet(int voice, int onTime, int len, DurationElement *el)
{
auto it = tuplets.lower_bound(onTime);
if (it != tuplets.begin())
--it;
for ( ; it != tuplets.end(); ++it) {
auto &tupletData = it->second;
if (tupletData.voice != voice)
continue;
if (onTime >= tupletData.onTime
&& onTime + len <= tupletData.onTime + tupletData.len) {
// add chord/rest to the tuplet
tupletData.elements.push_back(el);
break;
}
}
}
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
void MTrack::processPendingNotes(QList<MidiChord> &midiChords, int voice,
int startChordTick, int nextChordTick)
2012-05-26 14:49:10 +02:00
{
2013-04-15 10:38:16 +02:00
Score* score = staff->score();
int track = staff->idx() * VOICES + voice;
Drumset* drumset = staff->part()->instr()->drumset();
bool useDrumset = staff->part()->instr()->useDrumset();
2013-06-27 16:14:39 +02:00
// all midiChords here have the same onTime value
while (!midiChords.isEmpty()) {
int tick = midiChords[0].onTime;
int len = nextChordTick - tick;
2013-04-15 10:38:16 +02:00
if (len <= 0)
break;
2013-06-27 16:14:39 +02:00
len = findMinDuration(midiChords, len);
Measure* measure = score->tick2measure(tick);
2013-06-27 16:14:39 +02:00
len = splitDurationOnBarBoundary(len, tick, measure);
2013-05-26 01:19:09 +02:00
2013-07-03 23:54:54 +02:00
auto dl = toDurationList(measure, voice, tick, len, Meter::DurationType::NOTE);
2013-04-15 10:38:16 +02:00
if (dl.isEmpty())
break;
2013-07-03 23:54:54 +02:00
TDuration d = dl[0].second;
const Fraction &tupletRatio = dl[0].first;
len = d.ticks() * tupletRatio.denominator() / tupletRatio.numerator();
2013-04-15 10:38:16 +02:00
Chord* chord = new Chord(score);
chord->setTrack(track);
chord->setDurationType(d);
chord->setDuration(d.fraction());
Segment* s = measure->getSegment(chord, tick);
s->add(chord);
chord->setUserPlayEvents(true);
2013-06-27 16:14:39 +02:00
for (int k = 0; k < midiChords.size(); ++k) {
MidiChord& midiChord = midiChords[k];
setMusicNotesFromMidi(score, midiChord.notes, len, chord, tick,
2013-06-27 16:14:39 +02:00
drumset, useDrumset);
2013-07-03 23:54:54 +02:00
addElementToTuplet(voice, midiChord.onTime, len, chord);
2013-06-27 16:14:39 +02:00
if (midiChord.duration <= len) {
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);
2012-05-26 14:49:10 +02:00
2013-06-27 16:14:39 +02:00
midiChord.onTime = midiChord.onTime + len;
midiChord.duration = midiChord.duration - len;
2012-05-26 14:49:10 +02:00
2013-06-30 23:22:48 +02:00
for (auto &midiNote: midiChord.notes) {
midiNote.onTime = midiChord.onTime;
midiNote.len = midiChord.duration;
2013-06-27 16:14:39 +02:00
}
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-06-27 16:14:39 +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-04-15 10:38:16 +02:00
void MTrack::convertTrack(int lastTick)
{
2013-04-15 10:38:16 +02:00
Score* score = staff->score();
2013-06-27 16:14:39 +02:00
int key = 0; // TODO-LIB findKey(mtrack, score->sigmap());
2013-04-15 10:38:16 +02:00
int track = staff->idx() * VOICES;
2013-07-03 23:54:54 +02:00
int voices = VOICES;
2013-04-15 10:38:16 +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
int startChordTick = 0;
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-06-27 16:14:39 +02:00
int 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
2013-06-11 23:14:05 +02:00
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
}
2013-07-03 23:54:54 +02:00
for (const auto &tupletEvent: tuplets) {
const auto &tupletData = tupletEvent.second;
Tuplet* tuplet = new Tuplet(score);
auto ratioIt = tupletRatios.find(tupletData.tupletNumber);
Fraction tupletRatio = (ratioIt != tupletRatios.end())
? ratioIt->second : Fraction(2, 2);
tuplet->setRatio(tupletRatio);
2013-07-03 23:54:54 +02:00
tuplet->setDuration(tupletData.len);
TDuration baseLen(Fraction::fromTicks(tupletData.len / tupletRatio.denominator()));
tuplet->setBaseLen(baseLen);
2013-07-03 23:54:54 +02:00
tuplet->setTrack(track);
tuplet->setTick(tupletData.onTime);
Measure* measure = score->tick2measure(tupletData.onTime);
tuplet->setParent(measure);
2013-07-03 23:54:54 +02:00
for (DurationElement *el: tupletData.elements) {
tuplet->add(el);
el->setTuplet(tuplet);
}
2012-05-26 14:49:10 +02:00
}
2013-04-15 10:38:16 +02:00
KeyList* km = staff->keymap();
if (!hasKey && !mtrack->drumTrack()) {
KeySigEvent ks;
ks.setAccidentalType(key);
(*km)[0] = ks;
}
2013-06-11 23:14:05 +02:00
for (auto it = km->begin(); it != km->end(); ++it) {
int tick = it->first;
KeySigEvent key = it->second;
2013-04-15 10:38:16 +02:00
KeySig* ks = new KeySig(score);
ks->setTrack(track);
ks->setGenerated(false);
ks->setKeySigEvent(key);
ks->setMag(staff->mag());
Measure* m = score->tick2measure(tick);
Segment* seg = m->getSegment(ks, tick);
seg->add(ks);
}
#if 0 // TODO
ClefList* cl = staff->clefList();
for (ciClefEvent i = cl->begin(); i != cl->end(); ++i) {
int tick = i.key();
Clef* clef = new Clef(score);
clef->setClefType(i.value());
clef->setTrack(track);
clef->setGenerated(false);
clef->setMag(staff->mag());
Measure* m = score->tick2measure(tick);
Segment* seg = m->getSegment(clef, tick);
seg->add(clef);
}
#endif
}
#if 0
2012-05-26 14:49:10 +02:00
//---------------------------------------------------
// remove empty measures at beginning
//---------------------------------------------------
int startBar, endBar, beat, tick;
score->sigmap()->tickValues(lastTick, &endBar, &beat, &tick);
if (beat || tick)
++endBar;
for (startBar = 0; startBar < endBar; ++startBar) {
int tick1 = score->sigmap()->bar2tick(startBar, 0);
int tick2 = score->sigmap()->bar2tick(startBar + 1, 0);
int events = 0;
foreach (MidiTrack* midiTrack, *tracks) {
if (midiTrack->staffIdx() == -1)
continue;
foreach(const Event ev, midiTrack->events()) {
int t = ev.ontime();
if (t >= tick2)
break;
if (t < tick1)
continue;
if (ev.type() == ME_NOTE) {
++events;
break;
}
}
}
if (events)
break;
}
tick = score->sigmap()->bar2tick(startBar, 0);
if (tick)
qDebug("remove empty measures %d ticks, startBar %d", tick, startBar);
mf->move(-tick);
2013-04-15 10:38:16 +02:00
#endif
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();
int z = data[0];
int nn = data[1];
int n = 1;
for (int i = 0; i < nn; ++i)
n *= 2;
return Fraction(z, n);
}
2013-06-02 01:06:41 +02:00
void insertNewLeftHandTrack(QList<MTrack> &tracks,
2013-06-27 16:14:39 +02:00
int &trackIndex,
const std::multimap<int, MidiChord> &leftHandChords)
2013-06-02 01:06:41 +02:00
{
auto leftHandTrack = tracks[trackIndex];
leftHandTrack.chords = leftHandChords;
tracks.insert(trackIndex + 1, leftHandTrack);
2013-07-03 23:54:54 +02:00
// synchronize operations length and tracks list length
2013-06-02 01:06:41 +02:00
preferences.midiImportOperations.duplicateTrackOperations(trackIndex);
++trackIndex;
}
void addNewLeftHandChord(std::multimap<int, MidiChord> &leftHandChords,
const QList<MidiNote> &leftHandNotes,
const std::multimap<int, MidiChord>::iterator &i)
{
MidiChord leftHandChord = i->second;
leftHandChord.notes = leftHandNotes;
leftHandChords.insert({i->first, leftHandChord});
}
void splitIntoLRHands_FixedPitch(QList<MTrack> &tracks, int &trackIndex)
2013-05-17 20:11:36 +02:00
{
auto &srcTrack = tracks[trackIndex];
2013-06-02 01:06:41 +02:00
auto trackOpers = preferences.midiImportOperations.trackOperations(trackIndex);
int splitPitch = 12 * (int)trackOpers.LHRH.splitPitchOctave
+ (int)trackOpers.LHRH.splitPitchNote;
std::multimap<int, MidiChord> leftHandChords;
for (auto i = srcTrack.chords.begin(); i != srcTrack.chords.end(); ++i) {
auto &notes = i->second.notes;
QList<MidiNote> leftHandNotes;
for (auto j = notes.begin(); j != notes.end(); ) {
auto &note = *j;
if (note.pitch < splitPitch) {
leftHandNotes.push_back(note);
j = notes.erase(j);
2013-05-17 20:11:36 +02:00
continue;
}
2013-06-02 01:06:41 +02:00
++j;
2013-05-17 20:11:36 +02:00
}
2013-06-02 01:06:41 +02:00
if (!leftHandNotes.empty())
addNewLeftHandChord(leftHandChords, leftHandNotes, i);
2013-05-17 20:11:36 +02:00
}
2013-06-02 01:06:41 +02:00
if (!leftHandChords.empty())
insertNewLeftHandTrack(tracks, trackIndex, leftHandChords);
2013-05-17 20:11:36 +02:00
}
void splitIntoLRHands_HandWidth(QList<MTrack> &tracks, int &trackIndex)
{
auto &srcTrack = tracks[trackIndex];
2013-06-02 01:06:41 +02:00
sortNotesByPitch(srcTrack.chords);
2013-05-17 20:11:36 +02:00
const int OCTAVE = 12;
2013-06-02 01:06:41 +02:00
std::multimap<int, MidiChord> leftHandChords;
2013-06-11 23:14:05 +02:00
// chords after MIDI import are sorted by onTime values
2013-05-17 20:11:36 +02:00
for (auto i = srcTrack.chords.begin(); i != srcTrack.chords.end(); ++i) {
2013-06-02 01:06:41 +02:00
auto &notes = i->second.notes;
QList<MidiNote> leftHandNotes;
int minPitch = notes.front().pitch;
int maxPitch = notes.back().pitch;
if (maxPitch - minPitch > OCTAVE) {
2013-06-11 23:14:05 +02:00
// need both hands
// assign all chords in range [minPitch .. minPitch + OCTAVE] to left hand
// and assign all other chords to right hand
2013-06-02 01:06:41 +02:00
for (auto j = notes.begin(); j != notes.end(); ) {
auto &note = *j;
if (note.pitch <= minPitch + OCTAVE) {
leftHandNotes.push_back(note);
j = notes.erase(j);
continue;
2013-05-17 20:11:36 +02:00
}
2013-06-02 01:06:41 +02:00
++j;
2013-05-17 20:11:36 +02:00
// maybe todo later: if range of right-hand chords > OCTAVE => assign all bottom right-hand
// chords to another, third track
}
2013-06-02 01:06:41 +02:00
}
2013-06-11 23:14:05 +02:00
else { // check - use two hands or one hand will be enough (right or left?)
// assign top chord for right hand, all the rest - to left hand
2013-06-02 01:06:41 +02:00
while (notes.size() > 1) {
leftHandNotes.push_back(notes.front());
notes.erase(notes.begin());
2013-05-17 20:11:36 +02:00
}
}
2013-06-02 01:06:41 +02:00
if (!leftHandNotes.empty())
addNewLeftHandChord(leftHandChords, leftHandNotes, i);
2013-05-17 20:11:36 +02:00
}
2013-06-02 01:06:41 +02:00
if (!leftHandChords.empty())
insertNewLeftHandTrack(tracks, trackIndex, leftHandChords);
2013-05-17 20:11:36 +02:00
}
2013-05-17 20:11:36 +02:00
void splitIntoLeftRightHands(QList<MTrack> &tracks)
2013-04-22 01:13:14 +02:00
{
2013-04-28 21:29:12 +02:00
for (int i = 0; i < tracks.size(); ++i) {
2013-05-17 20:11:36 +02:00
const auto &operations = preferences.midiImportOperations.trackOperations(i);
if (!operations.LHRH.doIt)
2013-04-28 21:29:12 +02:00
continue;
2013-05-17 20:11:36 +02:00
switch (operations.LHRH.method) {
2013-05-30 16:04:02 +02:00
case MidiOperation::LHRHMethod::HAND_WIDTH:
2013-05-17 20:11:36 +02:00
splitIntoLRHands_HandWidth(tracks, i);
break;
2013-05-30 16:04:02 +02:00
case MidiOperation::LHRHMethod::FIXED_PITCH:
2013-05-17 20:11:36 +02:00
splitIntoLRHands_FixedPitch(tracks, i);
break;
2013-04-22 01:13:14 +02:00
}
}
2013-04-22 01:13:14 +02:00
}
2013-05-14 16:43:21 +02:00
void createMTrackList(int& lastTick, TimeSigMap* sigmap, QList<MTrack>& tracks, MidiFile* mf)
2013-04-15 10:38:16 +02:00
{
sigmap->clear();
sigmap->add(0, Fraction(4, 4)); // default time signature
2013-04-28 21:29:12 +02:00
int trackIndex = -1;
2013-04-15 10:38:16 +02:00
foreach(MidiTrack* t, mf->tracks()) {
t->mergeNoteOnOff();
MTrack track;
track.mtrack = t;
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-04-15 10:38:16 +02:00
for (auto i : t->events()) {
const MidiEvent& e = i.second;
2013-06-11 23:14:05 +02:00
// change division to MScore::division
2013-04-15 10:38:16 +02:00
int tick = (i.first * MScore::division + mf->division()/2) / mf->division();
2013-06-11 23:14:05 +02:00
// remove time signature events
2013-04-15 10:38:16 +02:00
if ((e.type() == ME_META) && (e.metaType() == META_TIME_SIGNATURE))
sigmap->add(tick, metaTimeSignature(e));
else if (e.type() == ME_NOTE) {
++events;
int pitch = e.pitch();
2013-04-16 10:26:59 +02:00
int len = (e.len() * MScore::division + mf->division()/2) / mf->division();
2013-04-15 10:38:16 +02:00
track.maxPitch = qMax(pitch, track.maxPitch);
track.minPitch = qMin(pitch, track.minPitch);
track.medPitch += pitch;
2013-04-16 10:26:59 +02:00
lastTick = qMax(lastTick, tick + len);
2013-04-15 10:38:16 +02:00
MidiNote n;
n.pitch = pitch;
n.velo = e.velo();
n.onTime = tick;
2013-04-16 10:26:59 +02:00
n.len = len;
2013-04-15 10:38:16 +02:00
MidiChord c;
c.onTime = tick;
2013-04-16 10:26:59 +02:00
c.duration = len;
2013-04-15 10:38:16 +02:00
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-04-15 10:38:16 +02:00
lastTick = qMax(lastTick, tick);
}
if (events != 0) {
auto trackOperations
= preferences.midiImportOperations.trackOperations(++trackIndex);
if (trackOperations.doImport) {
2013-04-28 21:29:12 +02:00
track.medPitch /= events;
tracks.push_back(track);
}
else
preferences.midiImportOperations.eraseTrackOperations(trackIndex--);
2013-04-15 10:38:16 +02:00
}
}
2013-04-28 21:29:12 +02:00
}
2012-05-26 14:49:10 +02:00
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-04-28 21:29:12 +02:00
void createInstruments(Score* score, QList<MTrack>& tracks)
{
2013-04-15 10:38:16 +02:00
int ntracks = tracks.size();
for (int idx = 0; idx < ntracks; ++idx) {
MTrack& track = tracks[idx];
Part* part = new Part(score);
Staff* s = new Staff(score, part, 0);
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-04-15 10:38:16 +02:00
s->setInitialClef(CLEF_PERC);
part->instr()->setDrumset(smDrumset);
}
else {
2013-05-14 16:43:21 +02:00
if ((idx < (ntracks-1))
&& (tracks.at(idx+1).mtrack->outChannel() == track.mtrack->outChannel())
2013-04-28 21:29:12 +02:00
&& (track.program == 0)) {
2013-06-11 23:14:05 +02:00
// assume that the current track and the next track
// form a piano part
2013-04-15 10:38:16 +02:00
Staff* ss = new Staff(score, part, 1);
part->insertStaff(ss);
score->staves().push_back(ss);
s->setInitialClef(CLEF_G);
s->setBracket(0, BRACKET_BRACE);
2013-04-15 10:38:16 +02:00
s->setBracketSpan(0, 2);
ss->setInitialClef(CLEF_F);
++idx;
tracks[idx].staff = ss;
}
else {
2013-06-11 23:14:05 +02:00
// other track type
2013-04-15 10:38:16 +02:00
ClefType ct = track.medPitch < 58 ? CLEF_F : CLEF_G;
s->setInitialClef(ct);
}
}
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
2013-04-28 21:29:12 +02:00
void createMeasures(int& lastTick, Score* score)
{
2013-04-15 10:38:16 +02:00
int bars, beat, tick;
score->sigmap()->tickValues(lastTick, &bars, &beat, &tick);
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
2012-05-26 14:49:10 +02:00
for (int i = 0; i < bars; ++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);
2013-04-28 21:29:12 +02:00
score->add(measure);
2012-05-26 14:49:10 +02:00
}
score->fixTicks();
2013-04-15 10:38:16 +02:00
lastTick = score->lastMeasure()->endTick();
2013-04-28 21:29:12 +02:00
}
2013-04-15 10:38:16 +02:00
2013-05-14 16:43:21 +02:00
QString instrumentName(int type, int program)
{
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(MidiFile* mf, MTrack& mt)
{
if (mt.staff->isTop()) {
Part* part = mt.staff->part();
if (mt.name.isEmpty()) {
QString name = instrumentName(mf->midiType(), mt.program);
if (!name.isEmpty())
part->setLongName(name);
}
else
part->setLongName(mt.name);
part->setPartName(part->longName().toPlainText());
part->setMidiChannel(mt.mtrack->outChannel());
part->setMidiProgram(mt.program & 0x7f); // only GM
}
}
void createTimeSignatures(Score* score)
{
for (auto is = score->sigmap()->begin(); is != score->sigmap()->end(); ++is) {
const SigEvent& se = is->second;
int tick = is->first;
Measure* m = score->tick2measure(tick);
if (!m)
continue;
for (int staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
TimeSig* ts = new TimeSig(score);
ts->setSig(se.timesig());
ts->setTrack(staffIdx * VOICES);
Segment* seg = m->getSegment(ts, tick);
seg->add(ts);
}
}
}
void createNotes(int lastTick, QList<MTrack>& tracks, MidiFile* mf)
{
2013-04-15 10:38:16 +02:00
for (int i = 0; i < tracks.size(); ++i) {
MTrack& mt = tracks[i];
2013-05-14 16:43:21 +02:00
for (auto ie : mt.mtrack->events()) {
2013-04-15 10:38:16 +02:00
const MidiEvent& e = ie.second;
2012-05-26 14:49:10 +02:00
if ((e.type() == ME_META) && (e.metaType() != META_LYRIC))
2013-04-15 10:38:16 +02:00
mt.processMeta(ie.first, e);
2012-05-26 14:49:10 +02:00
}
2013-05-14 16:43:21 +02:00
setTrackInfo(mf, mt);
2013-06-11 23:14:05 +02:00
// pass current track index to the convertTrack function
// through MidiImportOperations
preferences.midiImportOperations.setCurrentTrack(i);
2013-04-15 10:38:16 +02:00
mt.convertTrack(lastTick);
2012-05-26 14:49:10 +02:00
2013-05-14 16:43:21 +02:00
for (auto ie : mt.mtrack->events()) {
2013-04-15 10:38:16 +02:00
const MidiEvent& e = ie.second;
2012-05-26 14:49:10 +02:00
if ((e.type() == ME_META) && (e.metaType() == META_LYRIC))
2013-04-15 10:38:16 +02:00
mt.processMeta(ie.first, e);
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-05-14 16:43:21 +02:00
QList<TrackMeta> getTracksMeta(QList<MTrack>& tracks, MidiFile* mf)
2013-04-28 21:29:12 +02:00
{
2013-05-11 01:35:40 +02:00
QList<TrackMeta> tracksMeta;
2013-04-28 21:29:12 +02:00
for (int i = 0; i < tracks.size(); ++i) {
MTrack& mt = tracks[i];
MidiTrack* track = mt.mtrack;
for (auto ie : track->events()) {
const MidiEvent& e = ie.second;
2013-05-11 01:35:40 +02:00
if ((e.type() == ME_META) && (e.metaType() == META_TRACK_NAME))
mt.name = (const char*)e.edata();
2013-04-28 21:29:12 +02:00
}
2013-05-11 01:35:40 +02:00
MidiType midiType = mf->midiType();
if (midiType == MT_UNKNOWN)
midiType = MT_GM;
2013-05-14 16:43:21 +02:00
QString name = instrumentName(midiType, mt.program);
tracksMeta.push_back({mt.name, name});
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
}
2013-06-02 01:06:41 +02:00
void convertMidi(Score* score, MidiFile* mf)
{
QList<MTrack> tracks;
int lastTick = 0;
auto sigmap = score->sigmap();
mf->separateChannel();
createMTrackList(lastTick, sigmap, tracks, mf);
2013-06-11 23:14:05 +02:00
collectChords(tracks, MScore::division / 32); // tol = 1/128 note
2013-06-02 01:06:41 +02:00
quantizeAllTracks(tracks, sigmap, lastTick);
removeOverlappingNotes(tracks);
splitIntoLeftRightHands(tracks);
splitUnequalChords(tracks);
createInstruments(score, tracks);
createMeasures(lastTick, score);
createNotes(lastTick, tracks, mf);
createTimeSignatures(score);
score->connectTies();
}
2013-05-11 01:35:40 +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>();
2013-04-28 21:29:12 +02:00
QFile fp(fileName);
if (!fp.open(QIODevice::ReadOnly))
2013-05-11 01:35:40 +02:00
return QList<TrackMeta>();
2013-04-28 21:29:12 +02:00
MidiFile mf;
try {
mf.read(&fp);
}
catch(...) {
fp.close();
2013-05-11 01:35:40 +02:00
return QList<TrackMeta>();
2013-04-28 21:29:12 +02:00
}
fp.close();
Score mockScore;
QList<MTrack> tracks;
int lastTick = 0;
mf.separateChannel();
2013-05-14 16:43:21 +02:00
createMTrackList(lastTick, mockScore.sigmap(), tracks, &mf);
return getTracksMeta(tracks, &mf);
2013-04-28 21:29:12 +02:00
}
2012-05-26 14:49:10 +02:00
//---------------------------------------------------------
// importMidi
// return true on success
//---------------------------------------------------------
2012-10-07 19:53:58 +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;
2012-05-26 14:49:10 +02:00
QFile fp(name);
2013-04-15 10:38:16 +02:00
if (!fp.open(QIODevice::ReadOnly)) {
qDebug("importMidi: file open error <%s>", qPrintable(name));
return Score::FILE_OPEN_ERROR;
}
2012-05-26 14:49:10 +02:00
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();
2013-04-15 10:38:16 +02:00
qDebug("importMidi: bad file format");
2012-10-07 19:53:58 +02:00
return Score::FILE_BAD_FORMAT;
2012-05-26 14:49:10 +02:00
}
fp.close();
convertMidi(score, &mf);
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