Skip to content

Commit fc84214

Browse files
committed
Fix GitHub Issue #13: Implement MIDI file export
- Exports tempo track, note on/off, velocity
1 parent 4eec527 commit fc84214

23 files changed

+967
-9
lines changed

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Release date:
55

66
New features:
77

8+
* Fix GitHub Issue #13: Implement MIDI file export
9+
- Exports tempo track, note on/off, velocity
10+
811
* Add percentage interpolation to velocity dialog
912

1013
Bug fixes:

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ set(HEADER_FILES
6868
domain/track.hpp
6969
infra/audio/audio_recorder.hpp
7070
infra/audio/implementation/librtaudio/audio_recorder_rt_audio.hpp
71+
infra/midi/export/midi_exporter.hpp
7172
infra/midi/implementation/librtmidi/midi_in_rt_midi.hpp
7273
infra/midi/implementation/librtmidi/midi_out_rt_midi.hpp
7374
infra/midi/midi.hpp
@@ -144,6 +145,7 @@ set(SOURCE_FILES
144145
domain/song.cpp
145146
domain/track.cpp
146147
infra/audio/audio_recorder.cpp
148+
infra/midi/export/midi_exporter.cpp
147149
infra/audio/implementation/librtaudio/audio_recorder_rt_audio.cpp
148150
infra/midi/implementation/librtmidi/midi_in_rt_midi.cpp
149151
infra/midi/implementation/librtmidi/midi_out_rt_midi.cpp

src/application/application.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "../contrib/Argengine/src/argengine.hpp"
1919
#include "../contrib/SimpleLogger/src/simple_logger.hpp"
20+
#include "../infra/midi/export/midi_exporter.hpp"
2021
#include "../infra/video/video_generator.hpp"
2122
#include "common/utils.hpp"
2223
#include "domain/midi_note_data.hpp"
@@ -67,6 +68,7 @@ Application::Application(int & argc, char ** argv)
6768
, m_selectionService { std::make_unique<SelectionService>() }
6869
, m_editorService { std::make_unique<EditorService>(m_selectionService) }
6970
, m_eventSelectionModel { std::make_unique<EventSelectionModel>() }
71+
, m_midiExporter { std::make_unique<MidiExporter>(m_automationService) }
7072
, m_midiService { std::make_unique<MidiService>() }
7173
, m_mixerService { std::make_unique<MixerService>() }
7274
, m_playerService { std::make_unique<PlayerService>(m_midiService, m_mixerService, m_automationService, m_settingsService) }
@@ -258,6 +260,21 @@ void Application::connectApplicationService()
258260
connect(m_applicationService.get(), &ApplicationService::liveNoteOnRequested, m_midiService.get(), &MidiService::playNote);
259261
connect(m_applicationService.get(), &ApplicationService::liveNoteOffRequested, m_midiService.get(), &MidiService::stopNote);
260262
connect(m_applicationService.get(), &ApplicationService::allNotesOffRequested, this, &Application::stopAllNotes);
263+
264+
connect(m_applicationService.get(), &ApplicationService::midiExportRequested, this, &Application::exportToMidi);
265+
}
266+
267+
void Application::exportToMidi(QString fileName)
268+
{
269+
try {
270+
m_midiExporter->exportTo(fileName.toStdString(), m_editorService->song());
271+
const auto message = QString { "Exported the project to '%1' " }.arg(fileName);
272+
m_applicationService->statusTextRequested(message);
273+
} catch (std::exception & e) {
274+
const auto message = QString { "Failed to export as MIDI: %1 " }.arg(e.what());
275+
juzzlin::L(TAG).error() << message.toStdString();
276+
m_applicationService->statusTextRequested(message);
277+
}
261278
}
262279

263280
void Application::connectAutomationService()

src/application/application.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class EditorService;
4040
class EventSelectionModel;
4141
class Instrument;
4242
class MidiCcAutomationsModel;
43+
class MidiExporter;
4344
class MidiService;
4445
class MidiSettingsModel;
4546
class MixerService;
@@ -103,6 +104,8 @@ class Application : public QObject
103104
void requestInstruments(QStringList midiPorts);
104105
void stopAllNotes() const;
105106

107+
void exportToMidi(QString fileName);
108+
106109
std::unique_ptr<UiLogger> m_uiLogger;
107110

108111
std::unique_ptr<QGuiApplication> m_application;
@@ -119,6 +122,7 @@ class Application : public QObject
119122

120123
std::shared_ptr<EventSelectionModel> m_eventSelectionModel;
121124

125+
std::shared_ptr<MidiExporter> m_midiExporter;
122126
std::shared_ptr<MidiService> m_midiService;
123127
std::shared_ptr<MixerService> m_mixerService;
124128
std::shared_ptr<PlayerService> m_playerService;

src/application/service/application_service.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ QString ApplicationService::fileFormatExtension() const
6060
return Constants::fileFormatExtension();
6161
}
6262

63+
QString ApplicationService::midiFileExtension() const
64+
{
65+
return Constants::midiFileExtension();
66+
}
67+
6368
QString ApplicationService::webSiteUrl() const
6469
{
6570
return Constants::webSiteUrl();
@@ -132,6 +137,22 @@ void ApplicationService::requestSaveProjectAsTemplate()
132137
m_stateMachine->calculateState(StateMachine::Action::SaveProjectAsTemplateRequested);
133138
}
134139

140+
void ApplicationService::requestMidiExportDialog()
141+
{
142+
// No need to route through state machine as the current data can always be exported
143+
juzzlin::L(TAG).info() << "MIDI export requested";
144+
emit midiExportDialogRequested();
145+
}
146+
147+
void ApplicationService::exportAsMidi(QUrl url)
148+
{
149+
auto fileName = url.toLocalFile();
150+
if (!fileName.endsWith(Constants::midiFileExtension())) {
151+
fileName += Constants::midiFileExtension();
152+
}
153+
emit midiExportRequested(fileName);
154+
}
155+
135156
void ApplicationService::requestLiveNoteOn(quint8 key, quint8 octave, quint8 velocity)
136157
{
137158
if (const auto instrument = m_editorService->instrument(m_editorService->position().track); instrument) {

src/application/service/application_service.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class ApplicationService : public QObject
4545
Q_INVOKABLE QString applicationVersion() const;
4646
Q_INVOKABLE QString copyright() const;
4747
Q_INVOKABLE QString fileFormatExtension() const;
48+
Q_INVOKABLE QString midiFileExtension() const;
4849
Q_INVOKABLE QString license() const;
4950
Q_INVOKABLE QString webSiteUrl() const;
5051

@@ -83,6 +84,9 @@ class ApplicationService : public QObject
8384
Q_INVOKABLE void saveProjectAs(QUrl url);
8485
Q_INVOKABLE void saveProjectAsTemplate(QUrl url);
8586

87+
Q_INVOKABLE void requestMidiExportDialog();
88+
Q_INVOKABLE void exportAsMidi(QUrl url);
89+
8690
Q_INVOKABLE bool editMode() const;
8791
Q_INVOKABLE void setEditMode(bool editMode);
8892

@@ -128,6 +132,8 @@ class ApplicationService : public QObject
128132

129133
void saveAsDialogRequested();
130134
void saveAsTemplateDialogRequested();
135+
void midiExportDialogRequested();
136+
void midiExportRequested(QString fileName);
131137

132138
void alertDialogRequested(QString message);
133139
void statusTextRequested(QString message);

src/common/constants.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ QString fileFormatExtension()
4747
return ".nahd";
4848
}
4949

50+
QString midiFileExtension()
51+
{
52+
return ".mid";
53+
}
54+
5055
QString qSettingsCompanyName()
5156
{
5257
return applicationName();

src/common/constants.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ QString license();
2929

3030
QString fileFormatVersion();
3131
QString fileFormatExtension();
32+
QString midiFileExtension();
3233

3334
QString qSettingsCompanyName();
3435
QString qSettingSoftwareName();

src/domain/column.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ void Column::setNoteDataAtPosition(const NoteData & noteData, const Position & p
114114
juzzlin::L(TAG).debug() << "Set note data at position: " << noteData.toString() << " @ " << position.toString();
115115
auto newNoteData = noteData;
116116
newNoteData.setColumn(index());
117+
newNoteData.setTrack(position.track); // Set the track from the position
117118
m_lines.at(static_cast<size_t>(position.line))->setNoteData(newNoteData);
118119
}
119120

src/domain/line.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ void Line::setNoteData(const NoteData & noteData)
6060
*m_noteData = noteData;
6161
}
6262

63-
Line::NoteDataS Line::noteData() const
64-
{
65-
return m_noteData;
66-
}
63+
6764

6865
Line::LineEventOpt Line::lineEvent() const
6966
{
@@ -120,4 +117,9 @@ Line::LineU Line::deserializeFromXml(QXmlStreamReader & reader, size_t trackInde
120117
return line;
121118
}
122119

120+
Line::NoteDataS Line::noteData() const
121+
{
122+
return m_noteData;
123+
}
124+
123125
} // namespace noteahead

0 commit comments

Comments
 (0)