code:
/*
 * MidiExport.cpp - support for Exporting MIDI files
 *
 * Copyright (c) 2015 Mohamed Abdel Maksoud <mohamed at amaksoud.com>
 * Copyright (c) 2017 Hyunjin Song <tteu.ingog/at/gmail.com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDomDocument>
#include <QDir>
#include <QApplication>
#include <QMessageBox>
#include <QProgressDialog>

#include "MidiExport.h"

#include "lmms_math.h"
#include "TrackContainer.h"
#include "BBTrack.h"
#include "DataFile.h"
#include "InstrumentTrack.h"
#include "LocaleHelper.h"

#include "plugin_export.h"

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT midiexport_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"MIDI Export",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"Filter for exporting MIDI-files from LMMS" ),
	"Mohamed Abdel Maksoud <mohamed at amaksoud.com> and "
	"Hyunjin Song <tteu.ingog/at/gmail.com>",
	0x0100,
	Plugin::ExportFilter,
	NULL,
	NULL,
	NULL
} ;

}

MidiExport::MidiExport() : ExportFilter( &midiexport_plugin_descriptor)
{
}

MidiExport::~MidiExport()
{
}

bool MidiExport::tryExport(const TrackContainer::TrackList &tracks,
			const TrackContainer::TrackList &tracks_BB,
			int tempo, int masterPitch, const QString &filename)
{
	QFile f(filename);
	f.open(QIODevice::WriteOnly);
	QDataStream midiout(&f);

	InstrumentTrack* instTrack;
	BBTrack* bbTrack;
	QDomElement element;

	int nTracks = 0;
	uint8_t buffer[BUFFER_SIZE];
	uint32_t size;

	for (const Track* track : tracks) if (track->type() == Track::InstrumentTrack) nTracks++;
	for (const Track* track : tracks_BB) if (track->type() == Track::InstrumentTrack) nTracks++;

	// midi header
	MidiFile::MIDIHeader header(nTracks);
	size = header.writeToBuffer(buffer);
	midiout.writeRawData((char *)buffer, size);

	std::vector<std::vector<std::pair<int,int>>> plists;

	// midi tracks
	for (Track* track : tracks)
	{
		DataFile dataFile(DataFile::SongProject);
		MTrack mtrack;

		if (track->type() == Track::InstrumentTrack)
		{

			mtrack.addName(track->name().toStdString(), 0);
			//mtrack.addProgramChange(0, 0);
			mtrack.addTempo(tempo, 0);

			instTrack = dynamic_cast<InstrumentTrack *>(track);
			element = instTrack->saveState(dataFile, dataFile.content());

			int base_pitch = 0;
			double base_volume = 1.0;
			int base_time = 0;

			MidiNoteVector pat;

			for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling())
			{

				if (n.nodeName() == "instrumenttrack")
				{
					QDomElement it = n.toElement();
					// transpose +12 semitones, workaround for #1857
					base_pitch = (69 - it.attribute("basenote", "57").toInt());
					if (it.attribute("usemasterpitch", "1").toInt())
					{
						base_pitch += masterPitch;
					}
					base_volume = LocaleHelper::toDouble(it.attribute("volume", "100"))/100.0;
				}

				if (n.nodeName() == "pattern")
				{
					base_time = n.toElement().attribute("pos", "0").toInt();
					writePattern(pat, n, base_pitch, base_volume, base_time);
				}

			}
			ProcessBBNotes(pat, INT_MAX);
			writePatternToTrack(mtrack, pat);
			size = mtrack.writeToBuffer(buffer);
			midiout.writeRawData((char *)buffer, size);
		}

		if (track->type() == Track::BBTrack)
		{
			bbTrack = dynamic_cast<BBTrack *>(track);
			element = bbTrack->saveState(dataFile, dataFile.content());

			std::vector<std::pair<int,int>> plist;
			for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling())
			{

				if (n.nodeName() == "bbtco")
				{
					QDomElement it = n.toElement();
					int pos = it.attribute("pos", "0").toInt();
					int len = it.attribute("len", "0").toInt();
					plist.push_back(std::pair<int,int>(pos, pos+len));
				}
			}
			std::sort(plist.begin(), plist.end());
			plists.push_back(plist);

		}
	} // for each track

	// midi tracks in BB tracks
	for (Track* track : tracks_BB)
	{
		DataFile dataFile(DataFile::SongProject);
		MTrack mtrack;

		auto itr = plists.begin();
		std::vector<std::pair<int,int>> st;

		if (track->type() != Track::InstrumentTrack) continue;

		mtrack.addName(track->name().toStdString(), 0);
		//mtrack.addProgramChange(0, 0);
		mtrack.addTempo(tempo, 0);

		instTrack = dynamic_cast<InstrumentTrack *>(track);
		element = instTrack->saveState(dataFile, dataFile.content());

		int base_pitch = 0;
		double base_volume = 1.0;

		for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling())
		{
			if (n.nodeName() == "instrumenttrack")
			{
				QDomElement it = n.toElement();
				// transpose +12 semitones, workaround for #1857
				base_pitch = (69 - it.attribute("basenote", "57").toInt());
				if (it.attribute("usemasterpitch", "1").toInt())
				{
					base_pitch += masterPitch;
				}
				base_volume = LocaleHelper::toDouble(it.attribute("volume", "100")) / 100.0;
			}

			if (n.nodeName() == "pattern")
			{
				std::vector<std::pair<int,int>> &plist = *itr;

				MidiNoteVector nv, pat;
				writePattern(pat, n, base_pitch, base_volume, 0);

				// workaround for nested BBTCOs
				int pos = 0;
				int len = n.toElement().attribute("steps", "1").toInt() * 12;
				for (auto it = plist.begin(); it != plist.end(); ++it)
				{
					while (!st.empty() && st.back().second <= it->first)
					{
						writeBBPattern(pat, nv, len, st.back().first, pos, st.back().second);
						pos = st.back().second;
						st.pop_back();
					}

					if (!st.empty() && st.back().second <= it->second)
					{
						writeBBPattern(pat, nv, len, st.back().first, pos, it->first);
						pos = it->first;
						while (!st.empty() && st.back().second <= it->second)
						{
							st.pop_back();
						}
					}

					st.push_back(*it);
					pos = it->first;
				}

				while (!st.empty())
				{
					writeBBPattern(pat, nv, len, st.back().first, pos, st.back().second);
					pos = st.back().second;
					st.pop_back();
				}

				ProcessBBNotes(nv, pos);
				writePatternToTrack(mtrack, nv);
				++itr;
			}
		}
		size = mtrack.writeToBuffer(buffer);
		midiout.writeRawData((char *)buffer, size);
	}

	return true;

}

void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n,
				int base_pitch, double base_volume, int base_time)
{
	// TODO interpret steps="12" muted="0" type="1" name="Piano1"  len="2592"
	for (QDomNode nn = n.firstChild(); !nn.isNull(); nn = nn.nextSibling())
	{
		QDomElement note = nn.toElement();
		if (note.attribute("len", "0") == "0") continue;
		// TODO interpret pan="0" fxch="0" pitchrange="1"
		MidiNote mnote;
		mnote.pitch = qMax(0, qMin(127, note.attribute("key", "0").toInt() + base_pitch));
		 // Map from LMMS volume to MIDI velocity
		mnote.volume = qMin(qRound(base_volume * LocaleHelper::toDouble(note.attribute("vol", "100")) * (127.0 / 200.0)), 127);
		mnote.time = base_time + note.attribute("pos", "0").toInt();
		mnote.duration = note.attribute("len", "0").toInt();
		pat.push_back(mnote);
	}
}

void MidiExport::writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv)
{
	for (auto it = nv.begin(); it != nv.end(); ++it)
	{
		mtrack.addNote(it->pitch, it->volume, it->time / 48.0, it->duration / 48.0);
	}
}

void MidiExport::writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst,
				int len, int base, int start, int end)
{
	if (start >= end) { return; }
	start -= base;
	end -= base;
	std::sort(src.begin(), src.end());
	for (auto it = src.begin(); it != src.end(); ++it)
	{
		for (int time = it->time  + ceil((start - it->time) / len)
				* len; time < end; time += len)
		{
			MidiNote note;
			note.duration = it->duration;
			note.pitch = it->pitch;
			note.time = base + time;
			note.volume = it->volume;
			dst.push_back(note);
		}
	}
}

void MidiExport::ProcessBBNotes(MidiNoteVector &nv, int cutPos)
{
	std::sort(nv.begin(), nv.end());
	int cur = INT_MAX, next = INT_MAX;
	for (auto it = nv.rbegin(); it != nv.rend(); ++it)
	{
		if (it->time < cur)
		{
			next = cur;
			cur = it->time;
		}
		if (it->duration < 0)
		{
			it->duration = qMin(qMin(-it->duration, next - cur), cutPos - it->time);
		}
	}
}

void MidiExport::error()
{
	//qDebug() << "MidiExport error: " << m_error ;
}

extern "C"
{

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *, void * _data )
{
	return new MidiExport();
}

}


code:
/*
 * MidiExport.h - support for Exporting MIDI-files
 *
 * Copyright (c) 2015 Mohamed Abdel Maksoud <mohamed at amaksoud.com>
 * Copyright (c) 2017 Hyunjin Song <tteu.ingog/at/gmail.com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef _MIDI_EXPORT_H
#define _MIDI_EXPORT_H

#include <QString>

#include "ExportFilter.h"
#include "MidiFile.hpp"

const int BUFFER_SIZE = 50*1024;
typedef MidiFile::MIDITrack<BUFFER_SIZE> MTrack;

struct MidiNote
{
	int time;
	uint8_t pitch;
	int duration;
	uint8_t volume;

	inline bool operator<(const MidiNote &b) const
	{
		return this->time < b.time;
	}
} ;

typedef std::vector<MidiNote> MidiNoteVector;
typedef std::vector<MidiNote>::iterator MidiNoteIterator;

class MidiExport: public ExportFilter
{
// 	Q_OBJECT
public:
	MidiExport();
	~MidiExport();

	virtual PluginView *instantiateView(QWidget *)
	{
		return nullptr;
	}

	virtual bool tryExport(const TrackContainer::TrackList &tracks,
				const TrackContainer::TrackList &tracks_BB,
				int tempo, int masterPitch, const QString &filename);

private:
	void writePattern(MidiNoteVector &pat, QDomNode n,
				int base_pitch, double base_volume, int base_time);
	void writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv);
	void writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst,
				int len, int base, int start, int end);
	void ProcessBBNotes(MidiNoteVector &nv, int cutPos);

	void error();

} ;

#endif

repo_name: CYBERDEViL/LMMS

license: mit


code:
#ifndef MIDIFILE_HPP
#define MIDIFILE_HPP

/**
 * Name:        MidiFile.hpp
 * Purpose:     C++ re-write of the python module MidiFile.py
 * Author:      Mohamed Abdel Maksoud <mohamed at amaksoud.com>
 *-----------------------------------------------------------------------------
 * Name:        MidiFile.py
 * Purpose:     MIDI file manipulation utilities
 *
 * Author:      Mark Conway Wirt <emergentmusics) at (gmail . com>
 *
 * Created:     2008/04/17
 * Copyright:   (c) 2009 Mark Conway Wirt
 * License:     Please see License.txt for the terms under which this
 *              software is distributed.
 *-----------------------------------------------------------------------------
 */

#include <string.h>
#include <stdint.h>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
#include <assert.h>

using std::string;
using std::vector;
using std::set;

namespace MidiFile
{

const int TICKSPERBEAT = 128;

int writeVarLength(uint32_t val, uint8_t *buffer)
{
	/*
	Accept an input, and write a MIDI-compatible variable length stream

	The MIDI format is a little strange, and makes use of so-called variable
	length quantities. These quantities are a stream of bytes. If the most
	significant bit is 1, then more bytes follow. If it is zero, then the
	byte in question is the last in the stream
	*/
	int size = 0;
	uint8_t result, little_endian[4];
	result = val & 0x7F;
	little_endian[size++] = result;
	val = val >> 7;
	while (val > 0)
	{
		result = val & 0x7F;
		result = result | 0x80;
		little_endian[size++] = result;
		val = val >> 7;
	}
	for (int i=0; i<size; i++)
	{
		buffer[i] = little_endian[size-i-1];
	}

	return size;
}

int writeBigEndian4(uint32_t val, uint8_t *buf)
{
	buf[0] = val >> 24;
	buf[1] = val >> 16 & 0xff;
	buf[2] = val >> 8 & 0xff;
	buf[3] = val & 0xff;
	return 4;
}

int writeBigEndian2(uint16_t val, uint8_t *buf)
{
	buf[0] = val >> 8 & 0xff;
	buf[1] = val & 0xff;
	return 2;
}

class MIDIHeader
{
	// Class to encapsulate the MIDI header structure.
	uint16_t numTracks;
	uint16_t ticksPerBeat;

	public:

	MIDIHeader(uint16_t nTracks, uint16_t ticksPB=TICKSPERBEAT): numTracks(nTracks), ticksPerBeat(ticksPB) {}

	inline int writeToBuffer(uint8_t *buffer, int start=0) const
	{
		// chunk ID
		buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'h'; buffer[start++] = 'd';
		// chunk size (6 bytes always)
		buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0x06;
		// format: 1 (multitrack)
		buffer[start++] = 0; buffer[start++] = 0x01;

		start += writeBigEndian2(numTracks, buffer+start);

		start += writeBigEndian2(ticksPerBeat, buffer+start);

		return start;
	}

};

struct Event
{
	uint32_t time;
	uint32_t tempo;
	string trackName;
	enum {NOTE_ON, NOTE_OFF, TEMPO, PROG_CHANGE, TRACK_NAME} type;
	// TODO make a union to save up space
	uint8_t pitch;
	uint8_t programNumber;
	uint8_t duration;
	uint8_t volume;
	uint8_t channel;

	Event() {time=tempo=pitch=programNumber=duration=volume=channel=0; trackName="";}

	inline int writeToBuffer(uint8_t *buffer) const
	{
		uint8_t code, fourbytes[4];
		int size=0;
		switch (type)
		{
			case NOTE_ON:
				code = 0x9 << 4 | channel;
				size += writeVarLength(time, buffer+size);
				buffer[size++] = code;
				buffer[size++] = pitch;
				buffer[size++] = volume;
				break;
			case NOTE_OFF:
				code = 0x8 << 4 | channel;
				size += writeVarLength(time, buffer+size);
				buffer[size++] = code;
				buffer[size++] = pitch;
				buffer[size++] = volume;
				break;
			case TEMPO:
				code = 0xFF;
				size += writeVarLength(time, buffer+size);
				buffer[size++] = code;
				buffer[size++] = 0x51;
				buffer[size++] = 0x03;
				writeBigEndian4(int(60000000.0 / tempo), fourbytes);

				//printf("tempo of %x translates to ", tempo);
				/*
				for (int i=0; i<3; i++) printf("%02x ", fourbytes[i+1]);
				printf("\n");
				*/
				buffer[size++] = fourbytes[1];
				buffer[size++] = fourbytes[2];
				buffer[size++] = fourbytes[3];
				break;
			case PROG_CHANGE:
				code = 0xC << 4 | channel;
				size += writeVarLength(time, buffer+size);
				buffer[size++] = code;
				buffer[size++] = programNumber;
				break;
			case TRACK_NAME:
				size += writeVarLength(time, buffer+size);
				buffer[size++] = 0xFF;
				buffer[size++] = 0x03;
				size += writeVarLength(trackName.size(), buffer+size);
				trackName.copy((char *)(&buffer[size]), trackName.size());
				size += trackName.size();
//				 buffer[size++] = '\0';
//				 buffer[size++] = '\0';

				break;
		}
		return size;
	} // writeEventsToBuffer

	// events are sorted by their time
	inline bool operator < (const Event& b) const {
		return this->time < b.time ||
			(this->time == b.time && this->type > b.type);
	}
};

template<const int MAX_TRACK_SIZE>
class MIDITrack
{
	// A class that encapsulates a MIDI track
	// Nested class definitions.
	vector<Event> events;

	public:
	uint8_t channel;

	MIDITrack(): channel(0) {}

	inline void addEvent(const Event &e)
	{
		Event E = e;
		events.push_back(E);
	}

	inline void addNote(uint8_t pitch, uint8_t volume, double time, double duration)
	{
		Event event; event.channel = channel;
		event.volume = volume;

		event.type = Event::NOTE_ON; event.pitch = pitch; event.time= (uint32_t) (time * TICKSPERBEAT);
		addEvent(event);

		event.type = Event::NOTE_OFF; event.pitch = pitch; event.time=(uint32_t) ((time+duration) * TICKSPERBEAT);
		addEvent(event);

		//printf("note: %d-%d\n", (uint32_t) time * TICKSPERBEAT, (uint32_t)((time+duration) * TICKSPERBEAT));
	}

	inline void addName(const string &name, uint32_t time)
	{
		Event event; event.channel = channel;
		event.type = Event::TRACK_NAME; event.time=time; event.trackName = name;
		addEvent(event);
	}

	inline void addProgramChange(uint8_t prog, uint32_t time)
	{
		Event event; event.channel = channel;
		event.type = Event::PROG_CHANGE; event.time=time; event.programNumber = prog;
		addEvent(event);
	}

	inline void addTempo(uint8_t tempo, uint32_t time)
	{
		Event event; event.channel = channel;
		event.type = Event::TEMPO; event.time=time; event.tempo = tempo;
		addEvent(event);
	}

	inline int writeMIDIToBuffer(uint8_t *buffer, int start=0) const
	{
		// Write the meta data and note data to the packed MIDI stream.
		// Process the events in the eventList

		start += writeEventsToBuffer(buffer, start);

		// Write MIDI close event.
		buffer[start++] = 0x00;
		buffer[start++] = 0xFF;
		buffer[start++] = 0x2F;
		buffer[start++] = 0x00;

		// return the entire length of the data and write to the header

		return start;
	}

	inline int writeEventsToBuffer(uint8_t *buffer, int start=0) const
	{
		// Write the events in MIDIEvents to the MIDI stream.
		vector<Event> _events = events;
		std::sort(_events.begin(), _events.end());
		vector<Event>::const_iterator it;
		uint32_t time_last = 0, tmp;
		for (it = _events.begin(); it!=_events.end(); ++it)
		{
			Event e = *it;
			if (e.time < time_last){
				printf("error: e.time=%d  time_last=%d\n", e.time, time_last);
				assert(false);
			}
			tmp = e.time;
			e.time -= time_last;
			time_last = tmp;
			start += e.writeToBuffer(buffer+start);
			if (start >= MAX_TRACK_SIZE) {
				break;
			}
		}
		return start;
	}

	inline int writeToBuffer(uint8_t *buffer, int start=0) const
	{
		uint8_t eventsBuffer[MAX_TRACK_SIZE];
		uint32_t events_size = writeMIDIToBuffer(eventsBuffer);
		//printf(">> track %lu events took 0x%x bytes\n", events.size(), events_size);

		// chunk ID
		buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'r'; buffer[start++] = 'k';
		// chunk size
		start += writeBigEndian4(events_size, buffer+start);
		// copy events data
		memmove(buffer+start, eventsBuffer, events_size);
		start += events_size;
		return start;
	}
};

}; // namespace

#endif


code:
INCLUDE(BuildPlugin)

BUILD_PLUGIN(midiimport MidiImport.cpp MidiImport.h
	portsmf/allegro.cpp portsmf/allegro.h portsmf/allegrosmfwr.cpp
	portsmf/allegrord.cpp portsmf/allegrowr.cpp portsmf/allegrosmfrd.cpp
	portsmf/mfmidi.cpp portsmf/mfmidi.h portsmf/strparse.cpp
	portsmf/strparse.h portsmf/algrd_internal.h portsmf/algsmfrd_internal.h
	portsmf/trace.h MOCFILES MidiImport.h)


code:
/*
 * MidiImport.cpp - support for importing MIDI files
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDomDocument>
#include <QDir>
#include <QApplication>
#include <QFile>
#include <QMessageBox>
#include <QProgressDialog>

#include <sstream>
#include <unordered_map>

#include "MidiImport.h"
#include "TrackContainer.h"
#include "InstrumentTrack.h"
#include "AutomationTrack.h"
#include "AutomationPattern.h"
#include "ConfigManager.h"
#include "Pattern.h"
#include "Instrument.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "TimePos.h"
#include "debug.h"
#include "Song.h"

#include "embed.h"
#include "plugin_export.h"

#include "portsmf/allegro.h"

#define makeID(_c0, _c1, _c2, _c3) \
		( 0 | \
		( ( _c0 ) | ( ( _c1 ) << 8 ) | ( ( _c2 ) << 16 ) | ( ( _c3 ) << 24 ) ) )

extern "C"
{

Plugin::Descriptor PLUGIN_EXPORT midiimport_plugin_descriptor =
{
	STRINGIFY( PLUGIN_NAME ),
	"MIDI Import",
	QT_TRANSLATE_NOOP( "PluginBrowser",
				"Filter for importing MIDI-files into LMMS" ),
	"Tobias Doerffel <tobydox/at/users/dot/sf/dot/net>",
	0x0100,
	Plugin::ImportFilter,
	NULL,
	NULL,
	NULL
} ;

}

MidiImport::MidiImport( const QString & _file ) :
	ImportFilter( _file, &midiimport_plugin_descriptor ),
	m_events(),
	m_timingDivision( 0 )
{
}

MidiImport::~MidiImport()
{
}

bool MidiImport::tryImport( TrackContainer* tc )
{
	if( openFile() == false )
	{
		return false;
	}

#ifdef LMMS_HAVE_FLUIDSYNTH
	if( gui != NULL &&
		ConfigManager::inst()->sf2File().isEmpty() )
	{
		QMessageBox::information( gui->mainWindow(),
			tr( "Setup incomplete" ),
			tr( "You have not set up a default soundfont in "
				"the settings dialog (Edit->Settings). "
				"Therefore no sound will be played back after "
				"importing this MIDI file. You should download "
				"a General MIDI soundfont, specify it in "
				"settings dialog and try again." ) );
	}
#else
	if( gui )
	{
		QMessageBox::information( gui->mainWindow(),
			tr( "Setup incomplete" ),
			tr( "You did not compile LMMS with support for "
				"SoundFont2 player, which is used to add default "
				"sound to imported MIDI files. "
				"Therefore no sound will be played back after "
				"importing this MIDI file." ) );
	}
#endif

	switch( readID() )
	{
		case makeID( 'M', 'T', 'h', 'd' ):
			printf( "MidiImport::tryImport(): found MThd\n");
			return readSMF( tc );

		case makeID( 'R', 'I', 'F', 'F' ):
			printf( "MidiImport::tryImport(): found RIFF\n");
			return readRIFF( tc );

		default:
			printf( "MidiImport::tryImport(): not a Standard MIDI "
								"file\n" );
			return false;
	}
}

class smfMidiCC
{

public:
	smfMidiCC() :
		at( NULL ),
		ap( NULL ),
		lastPos( 0 )
	{ }

	AutomationTrack * at;
	AutomationPattern * ap;
	TimePos lastPos;

	smfMidiCC & create( TrackContainer* tc, QString tn )
	{
		if( !at )
		{
			// Keep LMMS responsive, for now the import runs
			// in the main thread. This should probably be
			// removed if that ever changes.
			qApp->processEvents();
			at = dynamic_cast<AutomationTrack *>( Track::create( Track::AutomationTrack, tc ) );
		}
		if( tn != "") {
			at->setName( tn );
		}
		return *this;
	}

	void clear()
	{
		at = NULL;
		ap = NULL;
		lastPos = 0;
	}

	smfMidiCC & putValue( TimePos time, AutomatableModel * objModel, float value )
	{
		if( !ap || time > lastPos + DefaultTicksPerBar )
		{
			TimePos pPos = TimePos( time.getBar(), 0 );
			ap = dynamic_cast<AutomationPattern*>(
				at->createTCO(pPos));
			ap->addObject( objModel );
		}

		lastPos = time;
		time = time - ap->startPosition();
		ap->putValue( time, value, false );
		ap->changeLength( TimePos( time.getBar() + 1, 0 ) );

		return *this;
	}
};

class smfMidiChannel
{

public:
	smfMidiChannel() :
		it( NULL ),
		p( NULL ),
		it_inst( NULL ),
		isSF2( false ),
		hasNotes( false )
	{ }

	InstrumentTrack * it;
	Pattern* p;
	Instrument * it_inst;
	bool isSF2;
	bool hasNotes;
	QString trackName;

	smfMidiChannel * create( TrackContainer* tc, QString tn )
	{
		if( !it ) {
			// Keep LMMS responsive
			qApp->processEvents();
			it = dynamic_cast<InstrumentTrack *>( Track::create( Track::InstrumentTrack, tc ) );

#ifdef LMMS_HAVE_FLUIDSYNTH
			it_inst = it->loadInstrument( "sf2player" );

			if( it_inst )
			{
				isSF2 = true;
				it_inst->loadFile( ConfigManager::inst()->sf2File() );
				it_inst->childModel( "bank" )->setValue( 0 );
				it_inst->childModel( "patch" )->setValue( 0 );
			}
			else
			{
				it_inst = it->loadInstrument( "patman" );
			}
#else
			it_inst = it->loadInstrument( "patman" );
#endif
			trackName = tn;
			if( trackName != "") {
				it->setName( tn );
			}
			// General MIDI default
			it->pitchRangeModel()->setInitValue( 2 );

			// Create a default pattern
			p = dynamic_cast<Pattern*>(it->createTCO(0));
		}
		return this;
	}

	void addNote( Note & n )
	{
		if (!p)
		{
			p = dynamic_cast<Pattern*>(it->createTCO(0));
		}
		p->addNote(n, false);
		hasNotes = true;
	}

	void splitPatterns()
	{
		Pattern * newPattern = nullptr;
		TimePos lastEnd(0);

		p->rearrangeAllNotes();
		for (auto n : p->notes())
		{
			if (!newPattern || n->pos() > lastEnd + DefaultTicksPerBar)
			{
				TimePos pPos = TimePos(n->pos().getBar(), 0);
				newPattern = dynamic_cast<Pattern*>(it->createTCO(pPos));
			}
			lastEnd = n->pos() + n->length();

			Note newNote(*n);
			newNote.setPos(n->pos(newPattern->startPosition()));
			newPattern->addNote(newNote, false);
		}

		delete p;
		p = nullptr;
	}

};

bool MidiImport::readSMF( TrackContainer* tc )
{
	const int MIDI_CC_COUNT = 128 + 1; // 0-127 (128) + pitch bend
	const int preTrackSteps = 2;
	QProgressDialog pd( TrackContainer::tr( "Importing MIDI-file..." ),
	TrackContainer::tr( "Cancel" ), 0, preTrackSteps, gui->mainWindow() );
	pd.setWindowTitle( TrackContainer::tr( "Please wait..." ) );
	pd.setWindowModality(Qt::WindowModal);
	pd.setMinimumDuration( 0 );

	pd.setValue( 0 );

	std::istringstream stream(readAllData().toStdString());
	Alg_seq_ptr seq = new Alg_seq(stream, true);
	seq->convert_to_beats();

	pd.setMaximum( seq->tracks()  + preTrackSteps );
	pd.setValue( 1 );

	// 128 CC + Pitch Bend
	smfMidiCC ccs[MIDI_CC_COUNT];

	// channels can be set out of 256 range
	// using unordered_map should fix most invalid loads and crashes while loading
	std::unordered_map<long, smfMidiChannel> chs;
	// NOTE: unordered_map::operator[] creates a new element if none exists

	MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel();
	AutomationTrack * nt = dynamic_cast<AutomationTrack*>(
		Track::create(Track::AutomationTrack, Engine::getSong()));
	nt->setName(tr("MIDI Time Signature Numerator"));
	AutomationTrack * dt = dynamic_cast<AutomationTrack*>(
		Track::create(Track::AutomationTrack, Engine::getSong()));
	dt->setName(tr("MIDI Time Signature Denominator"));
	AutomationPattern * timeSigNumeratorPat =
		new AutomationPattern(nt);
	timeSigNumeratorPat->setDisplayName(tr("Numerator"));
	timeSigNumeratorPat->addObject(&timeSigMM.numeratorModel());
	AutomationPattern * timeSigDenominatorPat =
		new AutomationPattern(dt);
	timeSigDenominatorPat->setDisplayName(tr("Denominator"));
	timeSigDenominatorPat->addObject(&timeSigMM.denominatorModel());

	// TODO: adjust these to Time.Sig changes
	double beatsPerBar = 4;
	double ticksPerBeat = DefaultTicksPerBar / beatsPerBar;

	// Time-sig changes
	Alg_time_sigs * timeSigs = &seq->time_sig;
	for( int s = 0; s < timeSigs->length(); ++s )
	{
		Alg_time_sig timeSig = (*timeSigs)[s];
		timeSigNumeratorPat->putValue(timeSig.beat * ticksPerBeat, timeSig.num);
		timeSigDenominatorPat->putValue(timeSig.beat * ticksPerBeat, timeSig.den);
	}
	// manually call otherwise the pattern shows being 1 bar
	timeSigNumeratorPat->updateLength();
	timeSigDenominatorPat->updateLength();

	pd.setValue( 2 );

	// Tempo stuff
	AutomationPattern * tap = tc->tempoAutomationPattern();
	if( tap )
	{
		tap->clear();
		Alg_time_map * timeMap = seq->get_time_map();
		Alg_beats & beats = timeMap->beats;
		for( int i = 0; i < beats.len - 1; i++ )
		{
			Alg_beat_ptr b = &(beats[i]);
			double tempo = ( beats[i + 1].beat - b->beat ) /
						   ( beats[i + 1].time - beats[i].time );
			tap->putValue( b->beat * ticksPerBeat, tempo * 60.0 );
		}
		if( timeMap->last_tempo_flag )
		{
			Alg_beat_ptr b = &( beats[beats.len - 1] );
			tap->putValue( b->beat * ticksPerBeat, timeMap->last_tempo * 60.0 );
		}
	}

	// Update the tempo to avoid crash when playing a project imported
	// via the command line
	Engine::updateFramesPerTick();

	// Song events
	for( int e = 0; e < seq->length(); ++e )
	{
		Alg_event_ptr evt = (*seq)[e];

		if( evt->is_update() )
		{
			printf("Unhandled SONG update: %d %f %s\n",
					evt->get_type_code(), evt->time, evt->get_attribute() );
		}
	}

	// Tracks
	for( int t = 0; t < seq->tracks(); ++t )
	{
		QString trackName = QString( tr( "Track" ) + " %1" ).arg( t );
		Alg_track_ptr trk = seq->track( t );
		pd.setValue( t + preTrackSteps );

		for( int c = 0; c < MIDI_CC_COUNT; c++ )
		{
			ccs[c].clear();
		}

		// Now look at events
		for( int e = 0; e < trk->length(); ++e )
		{
			Alg_event_ptr evt = (*trk)[e];

			if( evt->chan == -1 )
			{
				bool handled = false;
                if( evt->is_update() )
				{
					QString attr = evt->get_attribute();
					// seqnames is a track0 identifier (see allegro code)
					if (attr == (t == 0 ? "seqnames" : "tracknames")
						&& evt->get_update_type() == 's')
					{
						trackName = evt->get_string_value();
						handled = true;
					}
				}
                if( !handled ) {
                    // Write debug output
                    printf("MISSING GLOBAL HANDLER\n");
                    printf("     Chn: %d, Type Code: %d, Time: %f", (int) evt->chan,
                           evt->get_type_code(), evt->time );
                    if ( evt->is_update() )
                    {
                        printf( ", Update Type: %s", evt->get_attribute() );
                        if ( evt->get_update_type() == 'a' )
                        {
                            printf( ", Atom: %s", evt->get_atom_value() );
                        }
                    }
                    printf( "\n" );
				}
			}
			else if (evt->is_note())
			{
				smfMidiChannel * ch = chs[evt->chan].create( tc, trackName );
				Alg_note_ptr noteEvt = dynamic_cast<Alg_note_ptr>( evt );
				int ticks = noteEvt->get_duration() * ticksPerBeat;
				Note n( (ticks < 1 ? 1 : ticks ),
						noteEvt->get_start_time() * ticksPerBeat,
						noteEvt->get_identifier() - 12,
						noteEvt->get_loud() * (200.f / 127.f)); // Map from MIDI velocity to LMMS volume
				ch->addNote( n );

			}

			else if( evt->is_update() )
			{
				smfMidiChannel * ch = chs[evt->chan].create( tc, trackName );

				double time = evt->time*ticksPerBeat;
				QString update( evt->get_attribute() );

				if( update == "programi" )
				{
					long prog = evt->get_integer_value();
					if( ch->isSF2 )
					{
						ch->it_inst->childModel( "bank" )->setValue( 0 );
						ch->it_inst->childModel( "patch" )->setValue( prog );
					}
					else {
						const QString num = QString::number( prog );
						const QString filter = QString().fill( '0', 3 - num.length() ) + num + "*.pat";
						const QString dir = "/usr/share/midi/"
								"freepats/Tone_000/";
						const QStringList files = QDir( dir ).
						entryList( QStringList( filter ) );
						if( ch->it_inst && !files.empty() )
						{
							ch->it_inst->loadFile( dir+files.front() );
						}
					}
				}

				else if( update.startsWith( "control" ) || update == "bendr" )
				{
					int ccid = update.mid( 7, update.length()-8 ).toInt();
					if( update == "bendr" )
					{
						ccid = 128;
					}
					if( ccid <= 128 )
					{
						double cc = evt->get_real_value();
						AutomatableModel * objModel = NULL;

						switch( ccid )
						{
							case 0:
								if( ch->isSF2 && ch->it_inst )
								{
									objModel = ch->it_inst->childModel( "bank" );
									printf("BANK SELECT %f %d\n", cc, (int)(cc*127.0));
									cc *= 127.0f;
								}
								break;

							case 7:
								objModel = ch->it->volumeModel();
								cc *= 100.0f;
								break;

							case 10:
								objModel = ch->it->panningModel();
								cc = cc * 200.f - 100.0f;
								break;

							case 128:
								objModel = ch->it->pitchModel();
								cc = cc * 100.0f;
								break;
							default:
								//TODO: something useful for other CCs
								break;
						}

						if( objModel )
						{
							if( time == 0 && objModel )
							{
								objModel->setInitValue( cc );
							}
							else
							{
								if( ccs[ccid].at == NULL ) {
									ccs[ccid].create( tc, trackName + " > " + (
										  objModel != NULL ?
										  objModel->displayName() :
										  QString("CC %1").arg(ccid) ) );
								}
								ccs[ccid].putValue( time, objModel, cc );
							}
						}
					}
				}
				else {
					printf("Unhandled update: %d %d %f %s\n", (int) evt->chan,
							evt->get_type_code(), evt->time, evt->get_attribute() );
				}
			}
		}
	}

	delete seq;

	for( auto& c: chs )
	{
		if (c.second.hasNotes)
		{
			c.second.splitPatterns();
		}
		else if (c.second.it)
		{
			printf(" Should remove empty track\n");
			// must delete trackView first - but where is it?
			//tc->removeTrack( chs[c].it );
			//it->deleteLater();
		}
		// Set channel 10 to drums as per General MIDI's orders
		if (c.first % 16l == 9 /* channel 10 */
			&& c.second.hasNotes && c.second.it_inst && c.second.isSF2)
		{
			c.second.it_inst->childModel("bank")->setValue(128);
			c.second.it_inst->childModel("patch")->setValue(0);
		}
	}

	return true;
}

bool MidiImport::readRIFF( TrackContainer* tc )
{
	// skip file length
	skip( 4 );

	// check file type ("RMID" = RIFF MIDI)
	if( readID() != makeID( 'R', 'M', 'I', 'D' ) )
	{
invalid_format:
			qWarning( "MidiImport::readRIFF(): invalid file format" );
			return false;
	}

	// search for "data" chunk
	while( 1 )
	{
		const int id = readID();
		const int len = read32LE();
		if( file().atEnd() )
		{
data_not_found:
				qWarning( "MidiImport::readRIFF(): data chunk not found" );
				return false;
		}
		if( id == makeID( 'd', 'a', 't', 'a' ) )
		{
				break;
		}
		if( len < 0 )
		{
				goto data_not_found;
		}
		skip( ( len + 1 ) & ~1 );
	}

	// the "data" chunk must contain data in SMF format
	if( readID() != makeID( 'M', 'T', 'h', 'd' ) )
	{
		goto invalid_format;
	}
	return readSMF( tc );
}

void MidiImport::error()
{
	printf( "MidiImport::readTrack(): invalid MIDI data (offset %#x)\n",
						(unsigned int) file().pos() );
}

extern "C"
{

// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *, void * _data )
{
	return new MidiImport( QString::fromUtf8(
									static_cast<const char *>( _data ) ) );
}

}


code:
/*
 * MidiImport.h - support for importing MIDI-files
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef _MIDI_IMPORT_H
#define _MIDI_IMPORT_H

#include <QString>
#include <QPair>
#include <QVector>

#include "MidiEvent.h"
#include "ImportFilter.h"

class MidiImport : public ImportFilter
{
	Q_OBJECT
public:
	MidiImport( const QString & _file );
	virtual ~MidiImport();

	virtual PluginView * instantiateView( QWidget * )
	{
		return( NULL );
	}

private:
	virtual bool tryImport( TrackContainer* tc );

	bool readSMF( TrackContainer* tc );
	bool readRIFF( TrackContainer* tc );
	bool readTrack( int _track_end, QString & _track_name );

	void error( void );

	inline int readInt( int _bytes )
	{
		int c, value = 0;
		do
		{
			c = readByte();
			if( c == -1 )
			{
				return( -1 );
			}
			value = ( value << 8 ) | c;
		} while( --_bytes );
		return( value );
	}
	inline int read32LE()
	{
		int value = readByte();
		value |= readByte() << 8;
		value |= readByte() << 16;
		value |= readByte() << 24;
		return value;
	}
	inline int readVar()
	{
		int c = readByte();
		int value = c & 0x7f;
		if( c & 0x80 )
		{
			c = readByte();
			value = ( value << 7 ) | ( c & 0x7f );
			if( c & 0x80 )
			{
				c = readByte();
				value = ( value << 7 ) | ( c & 0x7f );
				if( c & 0x80 )
				{
					c = readByte();
					value = ( value << 7 ) | c;
					if( c & 0x80 )
					{
						return -1;
					}
				}
			}
	        }
        	return( !file().atEnd() ? value : -1 );
	}

	inline int readID()
	{
		return read32LE();
	}
	inline void skip( int _bytes )
	{
		while( _bytes > 0 )
		{
			readByte();
			--_bytes;
		}
	}

	typedef QVector<QPair<int, MidiEvent> > EventVector;
	EventVector m_events;
	int m_timingDivision;

} ;

#endif


code:
portsmf README.txt
14 Jun 2008
Roger B. Dannenberg

Portsmf is "Port Standard MIDI File", a cross-platform, C++ library
for reading and writing Standard MIDI Files.

License information: free and open source, see license.txt for details

Features:

- input and output of Standard MIDI Files
- data structures, classes, etc. for representing music data in memory
    o sequence structure consisting of multiple tracks
    o track structure consisting of multiple events
    o events contain note and control data
    o extensible attribute-value property lists
    o tempo track and time signature representation
- input and output of a text-based representation: Allegro files
- extensive editing operations on sequences and tracks
- conversion to/from binary buffers for archiving, undo/redo, etc.

Portsmf is a relatively small number of about 9 files, so there is
currently no support for building/maintaining Portsmf as a separate
library. (Contributions are welcome.) For now, it is suggested that
you simply compile these files along with your application sources.

There is a test program in portsmf_test and makefiles to build it as
an example.

You might want to browse through portsmf_test/allegro_test.cpp
for examples that use and exercise most of the portsmf functions.


code:
/* algread_internal.h -- interface between allegro.cpp and allegrord.cpp */

Alg_error alg_read(std::istream &file, Alg_seq_ptr new_seq,
                   double *offset_ptr = NULL);


code:
/* algsmfrd_internal.h -- interface from allegrosmfrd.cpp to allegro.cpp */

Alg_error alg_smf_read(std::istream &file, Alg_seq_ptr new_seq);