@@ -42,13 +42,18 @@ namespace {
4242 constexpr uint8_t MIDI_PORT_EVENT = 0x21 ;
4343 constexpr uint8_t TRACK_NAME_EVENT = 0x03 ;
4444 constexpr uint8_t END_OF_TRACK_EVENT = 0x2f ;
45+ constexpr uint8_t DEVICE_NAME_EVENT = 0x09 ;
4546 constexpr uint8_t SET_TEMPO_EVENT = 0x51 ;
4647
4748 constexpr uint8_t NOTE_ON_STATUS = 0x90 ;
4849 constexpr uint8_t NOTE_OFF_STATUS = 0x80 ;
4950 constexpr uint8_t CONTROL_CHANGE_STATUS = 0xb0 ;
51+ constexpr uint8_t PROGRAM_CHANGE_STATUS = 0xc0 ;
5052 constexpr uint8_t PITCH_BEND_STATUS = 0xe0 ;
5153
54+ constexpr uint8_t BANK_SELECT_MSB = 0x00 ;
55+ constexpr uint8_t BANK_SELECT_LSB = 0x20 ;
56+
5257 void writeBeU32 (std::ostream & out, uint32_t value)
5358 {
5459 out.put (static_cast <uint8_t >(value >> 24 ) & 0xff );
@@ -95,7 +100,7 @@ void MidiExporter::sortEvents(std::vector<EventS> & events)
95100 });
96101}
97102
98- void MidiExporter::exportTo (std::string fileName, SongW songW, size_t startPosition, size_t endPosition) const
103+ void MidiExporter::exportTo (std::string fileName, SongW songW, size_t startPosition, size_t endPosition, MidiExportOptions options ) const
99104{
100105 auto song = songW.lock ();
101106 if (!song) {
@@ -127,8 +132,8 @@ void MidiExporter::exportTo(std::string fileName, SongW songW, size_t startPosit
127132
128133 auto filteredEvents = filterEvents (renderedEvents, m_mixerService);
129134
130- const auto activeTracks = discoverActiveTracks (song, filteredEvents);
131- const auto trackData = buildTrackData (song, filteredEvents, activeTracks);
135+ const auto activeTracks = discoverActiveTracks (song, filteredEvents, options );
136+ const auto trackData = buildTrackData (song, filteredEvents, activeTracks, options );
132137
133138 writeMidiHeader (out, song, activeTracks.trackToInstrument .size ());
134139 writeTempoTrack (out, song);
@@ -169,7 +174,7 @@ void MidiExporter::writeMidiHeader(std::ostream & out, const SongS & song, size_
169174 writeBeU16 (out, ticksPerQuarterNote);
170175}
171176
172- MidiExporter::ActiveTracks MidiExporter::discoverActiveTracks (const SongS & song, const std::vector<EventS> & events) const
177+ MidiExporter::ActiveTracks MidiExporter::discoverActiveTracks (const SongS & song, const std::vector<EventS> & events, MidiExportOptions options ) const
173178{
174179 ActiveTracks activeTracks;
175180 std::set<std::string> portNames;
@@ -185,6 +190,18 @@ MidiExporter::ActiveTracks MidiExporter::discoverActiveTracks(const SongS & song
185190 }
186191 }
187192
193+ // Also add tracks that have Bank or Program settings if enabled
194+ for (size_t i = 0 ; i < song->trackCount (); ++i) {
195+ if (auto instrument = song->instrument (i)) {
196+ const auto & settings = instrument->settings ();
197+ const bool hasBank = options.exportBank && settings.bank .has_value ();
198+ const bool hasPatch = options.exportProgramChange && settings.patch .has_value ();
199+ if (hasBank || hasPatch) {
200+ activeTrackIndices.insert (i);
201+ }
202+ }
203+ }
204+
188205 for (const auto & trackIndex : activeTrackIndices) {
189206 auto instrument = song->instrument (trackIndex);
190207 if (!instrument) {
@@ -208,7 +225,7 @@ MidiExporter::ActiveTracks MidiExporter::discoverActiveTracks(const SongS & song
208225 return activeTracks;
209226}
210227
211- MidiExporter::ByteVector MidiExporter::initializeTrack (const SongS & song, size_t trackIndex, const ActiveTracks & activeTracks) const
228+ MidiExporter::ByteVector MidiExporter::initializeTrack (const SongS & song, size_t trackIndex, uint8_t channel, const ActiveTracks & activeTracks, MidiExportOptions options ) const
212229{
213230 ByteVector data;
214231
@@ -223,6 +240,13 @@ MidiExporter::ByteVector MidiExporter::initializeTrack(const SongS & song, size_
223240 data.push_back (static_cast <char >(portIndex));
224241 juzzlin::L (TAG).info () << " Writing MIDI Port Meta-Event for track " << trackIndex << " , portIndex: " << static_cast <int >(portIndex);
225242
243+ // Add Device Name Meta-Event (Port Name)
244+ data.push_back (static_cast <char >(0x00 )); // Delta time
245+ data.push_back (static_cast <char >(META_EVENT));
246+ data.push_back (static_cast <char >(DEVICE_NAME_EVENT));
247+ writeVlq (data, static_cast <uint32_t >(portName.length ()));
248+ data.insert (data.end (), portName.begin (), portName.end ());
249+
226250 // Add Track Name Meta-Event
227251 data.push_back (static_cast <char >(0x00 )); // Delta time
228252 data.push_back (static_cast <char >(META_EVENT));
@@ -231,6 +255,27 @@ MidiExporter::ByteVector MidiExporter::initializeTrack(const SongS & song, size_
231255 writeVlq (data, static_cast <uint32_t >(trackName.length ()));
232256 data.insert (data.end (), trackName.begin (), trackName.end ());
233257
258+ const auto instrument = activeTracks.trackToInstrument .at (trackIndex);
259+ const auto & settings = instrument->settings ();
260+
261+ if (options.exportBank && settings.bank .has_value ()) {
262+ data.push_back (static_cast <char >(0x00 )); // Delta time
263+ data.push_back (static_cast <char >(CONTROL_CHANGE_STATUS | channel));
264+ data.push_back (static_cast <char >(BANK_SELECT_MSB));
265+ data.push_back (static_cast <char >(settings.bank ->msb ));
266+
267+ data.push_back (static_cast <char >(0x00 )); // Delta time
268+ data.push_back (static_cast <char >(CONTROL_CHANGE_STATUS | channel));
269+ data.push_back (static_cast <char >(BANK_SELECT_LSB));
270+ data.push_back (static_cast <char >(settings.bank ->lsb ));
271+ }
272+
273+ if (options.exportProgramChange && settings.patch .has_value ()) {
274+ data.push_back (static_cast <char >(0x00 )); // Delta time
275+ data.push_back (static_cast <char >(PROGRAM_CHANGE_STATUS | channel));
276+ data.push_back (static_cast <char >(*settings.patch ));
277+ }
278+
234279 return data;
235280}
236281
@@ -266,14 +311,14 @@ void MidiExporter::writePitchBendEvent(ByteVector & dataOut, uint8_t channel, co
266311 dataOut.push_back (static_cast <char >(pitchBendData.msb ()));
267312}
268313
269- std::map<size_t , MidiExporter::ByteVector> MidiExporter::buildTrackData (const SongS & song, const std::vector<EventS> & events, const ActiveTracks & activeTracks) const
314+ std::map<size_t , MidiExporter::ByteVector> MidiExporter::buildTrackData (const SongS & song, const std::vector<EventS> & events, const ActiveTracks & activeTracks, MidiExportOptions options ) const
270315{
271- auto initialState = initializeTracks (song, activeTracks);
316+ auto initialState = initializeTracks (song, activeTracks, options );
272317 auto processedState = processEvents (std::move (initialState), events);
273318 return finalizeTracks (std::move (processedState));
274319}
275320
276- MidiExporter::TrackProcessingState MidiExporter::initializeTracks (const SongS & song, const ActiveTracks & activeTracks) const
321+ MidiExporter::TrackProcessingState MidiExporter::initializeTracks (const SongS & song, const ActiveTracks & activeTracks, MidiExportOptions options ) const
277322{
278323 TrackProcessingState state;
279324 std::map<std::string, uint8_t > portChannelCounters;
@@ -286,9 +331,10 @@ MidiExporter::TrackProcessingState MidiExporter::initializeTracks(const SongS &
286331 if (channel == 9 ) { // Skip drum channel 10
287332 channel++;
288333 }
289- state.trackToChannelMap [trackIndex] = channel++;
334+ const uint8_t trackChannel = channel++;
335+ state.trackToChannelMap [trackIndex] = trackChannel;
290336
291- state.allTracksData [trackIndex] = initializeTrack (song, trackIndex, activeTracks);
337+ state.allTracksData [trackIndex] = initializeTrack (song, trackIndex, trackChannel, activeTracks, options );
292338 }
293339 return state;
294340}
0 commit comments