From adebd644f27a56394db91109c122eca53c051503 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 23 Feb 2018 14:26:29 +0100 Subject: [PATCH] Rewroter 'writemidi' CCMD to work independently of the currently playing song's data. The first benefit of separating the MIDI data sources from the playback classes. :) --- src/s_sound.cpp | 12 ++ src/sound/i_music.cpp | 45 ++++-- src/sound/i_musicinterns.h | 33 ++-- src/sound/midisource.cpp | 162 +++++++++++++++++++ src/sound/midisources.h | 6 + src/sound/musicformats/music_hmi_midiout.cpp | 3 - src/sound/musicformats/music_midistream.cpp | 159 ------------------ src/sound/musicformats/music_smf_midiout.cpp | 3 - src/sound/musicformats/music_xmi_midiout.cpp | 3 - 9 files changed, 228 insertions(+), 198 deletions(-) diff --git a/src/s_sound.cpp b/src/s_sound.cpp index d3f587b94..f9ee262c1 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -3154,3 +3154,15 @@ CCMD(listsoundchannels) } Printf("%d sounds playing\n", count); } + +CCMD(currentmusic) +{ + if (mus_playing.name.IsNotEmpty()) + { + Printf("Currently playing music '%s'\n", mus_playing.name.GetChars()); + } + else + { + Printf("Currently no music playing\n"); + } +} diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 012a8518a..fe307f614 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -787,38 +787,57 @@ UNSAFE_CCMD (writewave) // // CCMD writemidi // -// If the currently playing song is a MIDI variant, write it to disk. -// If successful, the current song will restart, since MIDI file generation -// involves a simulated playthrough of the song. +// Writes a given MIDI song to disk. This does not affect playback anymore, +// like older versions did. // //========================================================================== +extern MusPlayingInfo mus_playing; + UNSAFE_CCMD (writemidi) { - if (argv.argc() != 2) + if (argv.argc() != 3) { - Printf("Usage: writemidi "); + Printf("Usage: writemidi - use '*' as song name to dump the currently playing song"); return; } - if (currSong == NULL) + FString src = argv[1]; + if (src.Compare("*") == 0) src= mus_playing.name; + + auto lump = Wads.CheckNumForName(src, ns_music); + if (lump < 0) lump = Wads.CheckNumForFullName(src); + if (lump < 0) { - Printf("No song is currently playing.\n"); + Printf("Cannot find MIDI lump %s.\n", src.GetChars()); return; } - if (!currSong->IsMIDI()) + + FWadLump wlump = Wads.OpenLumpNum(lump); + uint32_t id[32/4]; + + if(wlump.Read(id, 32) != 32 || wlump.Seek(-32, SEEK_CUR) != 0) { - Printf("Current song is not MIDI-based.\n"); + Printf("Unable to read lump %s\n", src.GetChars()); + return; + } + + auto type = IdentifyMIDIType(id, 32); + auto source = CreateMIDISource(wlump, type); + + if (source == nullptr) + { + Printf("%s is not MIDI-based.\n", src.GetChars()); return; } TArray midi; bool success; - static_cast(currSong)->CreateSMF(midi, 1); - auto f = FileWriter::Open(argv[1]); + source->CreateSMF(midi, 1); + auto f = FileWriter::Open(argv[2]); if (f == NULL) { - Printf("Could not open %s.\n", argv[1]); + Printf("Could not open %s.\n", argv[2]); return; } success = (f->Write(&midi[0], midi.Size()) == (size_t)midi.Size()); @@ -826,6 +845,6 @@ UNSAFE_CCMD (writemidi) if (!success) { - Printf("Could not write to music file.\n"); + Printf("Could not write to music file %s.\n", argv[2]); } } diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index d040af0e6..b5ed34f1c 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -317,23 +317,22 @@ public: MIDIStreamer(EMidiDevice type, const char *args); ~MIDIStreamer(); - void MusicVolumeChanged(); - void TimidityVolumeChanged(); - void Play(bool looping, int subsong); - void Pause(); - void Resume(); - void Stop(); - bool IsPlaying(); - bool IsMIDI() const; - bool IsValid() const; - bool SetSubsong(int subsong); - void Update(); - FString GetStats(); - void FluidSettingInt(const char *setting, int value); - void FluidSettingNum(const char *setting, double value); - void FluidSettingStr(const char *setting, const char *value); - void WildMidiSetOption(int opt, int set); - void CreateSMF(TArray &file, int looplimit=0); + void MusicVolumeChanged() override; + void TimidityVolumeChanged() override; + void Play(bool looping, int subsong) override; + void Pause() override; + void Resume() override; + void Stop() override; + bool IsPlaying() override; + bool IsMIDI() const override; + bool IsValid() const override; + bool SetSubsong(int subsong) override; + void Update() override; + FString GetStats() override; + void FluidSettingInt(const char *setting, int value) override; + void FluidSettingNum(const char *setting, double value) override; + void FluidSettingStr(const char *setting, const char *value) override; + void WildMidiSetOption(int opt, int set) override; int ServiceEvent(); void SetMIDISource(MIDISource *_source); diff --git a/src/sound/midisource.cpp b/src/sound/midisource.cpp index 87622cca3..6c639709a 100644 --- a/src/sound/midisource.cpp +++ b/src/sound/midisource.cpp @@ -38,6 +38,10 @@ #include "midisources.h" +char MIDI_EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; +char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + //========================================================================== // // MIDISource :: SetTempo @@ -239,3 +243,161 @@ bool MIDISource::SetMIDISubsong(int subsong) { return subsong == 0; } + +//========================================================================== +// +// WriteVarLen +// +//========================================================================== + +static void WriteVarLen (TArray &file, uint32_t value) +{ + uint32_t buffer = value & 0x7F; + + while ( (value >>= 7) ) + { + buffer <<= 8; + buffer |= (value & 0x7F) | 0x80; + } + + for (;;) + { + file.Push(uint8_t(buffer)); + if (buffer & 0x80) + { + buffer >>= 8; + } + else + { + break; + } + } +} + +//========================================================================== +// +// MIDIStreamer :: CreateSMF +// +// Simulates playback to create a Standard MIDI File. +// +//========================================================================== + +void MIDISource::CreateSMF(TArray &file, int looplimit) +{ + const int EXPORT_LOOP_LIMIT = 30; // Maximum number of times to loop when exporting a MIDI file. + // (for songs with loop controller events) + + static const uint8_t StaticMIDIhead[] = + { + 'M','T','h','d', 0, 0, 0, 6, + 0, 0, // format 0: only one track + 0, 1, // yes, there is really only one track + 0, 0, // divisions (filled in) + 'M','T','r','k', 0, 0, 0, 0, + // The first event sets the tempo (filled in) + 0, 255, 81, 3, 0, 0, 0 + }; + + uint32_t Events[2][MAX_MIDI_EVENTS*3]; + uint32_t delay = 0; + uint8_t running_status = 255; + + // Always create songs aimed at GM devices. + CheckCaps(MIDIDEV_MIDIPORT); + LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit; + DoRestart(); + StartPlayback(false, LoopLimit); + + file.Reserve(sizeof(StaticMIDIhead)); + memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead)); + file[12] = Division >> 8; + file[13] = Division & 0xFF; + file[26] = InitialTempo >> 16; + file[27] = InitialTempo >> 8; + file[28] = InitialTempo; + + while (!CheckDone()) + { + uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_MIDI_EVENTS*3], 1000000*600); + for (uint32_t *event = Events[0]; event < event_end; ) + { + delay += event[0]; + if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO) + { + WriteVarLen(file, delay); + delay = 0; + uint32_t tempo = MEVENT_EVENTPARM(event[2]); + file.Push(MIDI_META); + file.Push(MIDI_META_TEMPO); + file.Push(3); + file.Push(uint8_t(tempo >> 16)); + file.Push(uint8_t(tempo >> 8)); + file.Push(uint8_t(tempo)); + running_status = 255; + } + else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG) + { + WriteVarLen(file, delay); + delay = 0; + uint32_t len = MEVENT_EVENTPARM(event[2]); + uint8_t *bytes = (uint8_t *)&event[3]; + if (bytes[0] == MIDI_SYSEX) + { + len--; + file.Push(MIDI_SYSEX); + WriteVarLen(file, len); + memcpy(&file[file.Reserve(len)], bytes + 1, len); + } + else + { + file.Push(MIDI_SYSEXEND); + WriteVarLen(file, len); + memcpy(&file[file.Reserve(len)], bytes, len); + } + running_status = 255; + } + else if (MEVENT_EVENTTYPE(event[2]) == 0) + { + WriteVarLen(file, delay); + delay = 0; + uint8_t status = uint8_t(event[2]); + if (status != running_status) + { + running_status = status; + file.Push(status); + } + file.Push(uint8_t((event[2] >> 8) & 0x7F)); + if (MIDI_EventLengths[(status >> 4) & 7] == 2) + { + file.Push(uint8_t((event[2] >> 16) & 0x7F)); + } + } + // Advance to next event + if (event[2] < 0x80000000) + { // short message + event += 3; + } + else + { // long message + event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2); + } + } + } + + // End track + WriteVarLen(file, delay); + file.Push(MIDI_META); + file.Push(MIDI_META_EOT); + file.Push(0); + + // Fill in track length + uint32_t len = file.Size() - 22; + file[18] = uint8_t(len >> 24); + file[19] = uint8_t(len >> 16); + file[20] = uint8_t(len >> 8); + file[21] = uint8_t(len & 255); + + LoopLimit = 0; +} + + diff --git a/src/sound/midisources.h b/src/sound/midisources.h index bfbf250fc..7e7381eae 100644 --- a/src/sound/midisources.h +++ b/src/sound/midisources.h @@ -11,6 +11,9 @@ #include #include +extern char MIDI_EventLengths[7]; +extern char MIDI_CommonLengths[15]; + // base class for the different MIDI sources -------------------------------------- class MIDISource @@ -63,6 +66,9 @@ public: { TempoCallback = cb; } + + void CreateSMF(TArray &file, int looplimit); + }; // MUS file played with a MIDI stream --------------------------------------- diff --git a/src/sound/musicformats/music_hmi_midiout.cpp b/src/sound/musicformats/music_hmi_midiout.cpp index c9ff1ca4e..d961c29c1 100644 --- a/src/sound/musicformats/music_hmi_midiout.cpp +++ b/src/sound/musicformats/music_hmi_midiout.cpp @@ -112,9 +112,6 @@ struct HMISong::TrackInfo // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern char MIDI_EventLengths[7]; -extern char MIDI_CommonLengths[15]; - // PRIVATE DATA DEFINITIONS ------------------------------------------------ // PUBLIC DATA DEFINITIONS ------------------------------------------------- diff --git a/src/sound/musicformats/music_midistream.cpp b/src/sound/musicformats/music_midistream.cpp index 917f07def..d5ecb0cce 100644 --- a/src/sound/musicformats/music_midistream.cpp +++ b/src/sound/musicformats/music_midistream.cpp @@ -45,8 +45,6 @@ #define MAX_TIME (1000000/10) // Send out 1/10 of a sec of events at a time. -#define EXPORT_LOOP_LIMIT 30 // Maximum number of times to loop when exporting a MIDI file. - // (for songs with loop controller events) // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -65,21 +63,8 @@ EXTERN_CVAR(Int, snd_mididevice) extern unsigned mididevice; #endif -extern char MIDI_EventLengths[7]; - // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static const uint8_t StaticMIDIhead[] = -{ - 'M','T','h','d', 0, 0, 0, 6, - 0, 0, // format 0: only one track - 0, 1, // yes, there is really only one track - 0, 0, // divisions (filled in) - 'M','T','r','k', 0, 0, 0, 0, - // The first event sets the tempo (filled in) - 0, 255, 81, 3, 0, 0, 0 -}; - // PUBLIC DATA DEFINITIONS ------------------------------------------------- // CODE -------------------------------------------------------------------- @@ -852,150 +837,6 @@ void MIDIStreamer::SetMIDISource(MIDISource *_source) source->setTempoCallback([=](int tempo) { return MIDI->SetTempo(tempo); } ); } -//========================================================================== -// -// MIDIStreamer :: CreateSMF -// -// Simulates playback to create a Standard MIDI File. -// -//========================================================================== - -void MIDIStreamer::CreateSMF(TArray &file, int looplimit) -{ - uint32_t delay = 0; - uint8_t running_status = 255; - - // Always create songs aimed at GM devices. - if (source == nullptr) return; - source->CheckCaps(MIDIDEV_MIDIPORT); - LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit; - source->DoRestart(); - source->StartPlayback(false, LoopLimit); - auto InitialTempo = source->getInitialTempo(); - auto Division = source->getDivision(); - - file.Reserve(sizeof(StaticMIDIhead)); - memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead)); - file[12] = Division >> 8; - file[13] = Division & 0xFF; - file[26] = InitialTempo >> 16; - file[27] = InitialTempo >> 8; - file[28] = InitialTempo; - - while (!source->CheckDone()) - { - uint32_t *event_end = source->MakeEvents(Events[0], &Events[0][MAX_MIDI_EVENTS*3], 1000000*600); - for (uint32_t *event = Events[0]; event < event_end; ) - { - delay += event[0]; - if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO) - { - WriteVarLen(file, delay); - delay = 0; - uint32_t tempo = MEVENT_EVENTPARM(event[2]); - file.Push(MIDI_META); - file.Push(MIDI_META_TEMPO); - file.Push(3); - file.Push(uint8_t(tempo >> 16)); - file.Push(uint8_t(tempo >> 8)); - file.Push(uint8_t(tempo)); - running_status = 255; - } - else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG) - { - WriteVarLen(file, delay); - delay = 0; - uint32_t len = MEVENT_EVENTPARM(event[2]); - uint8_t *bytes = (uint8_t *)&event[3]; - if (bytes[0] == MIDI_SYSEX) - { - len--; - file.Push(MIDI_SYSEX); - WriteVarLen(file, len); - memcpy(&file[file.Reserve(len)], bytes + 1, len); - } - else - { - file.Push(MIDI_SYSEXEND); - WriteVarLen(file, len); - memcpy(&file[file.Reserve(len)], bytes, len); - } - running_status = 255; - } - else if (MEVENT_EVENTTYPE(event[2]) == 0) - { - WriteVarLen(file, delay); - delay = 0; - uint8_t status = uint8_t(event[2]); - if (status != running_status) - { - running_status = status; - file.Push(status); - } - file.Push(uint8_t((event[2] >> 8) & 0x7F)); - if (MIDI_EventLengths[(status >> 4) & 7] == 2) - { - file.Push(uint8_t((event[2] >> 16) & 0x7F)); - } - } - // Advance to next event - if (event[2] < 0x80000000) - { // short message - event += 3; - } - else - { // long message - event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2); - } - } - } - - // End track - WriteVarLen(file, delay); - file.Push(MIDI_META); - file.Push(MIDI_META_EOT); - file.Push(0); - - // Fill in track length - uint32_t len = file.Size() - 22; - file[18] = uint8_t(len >> 24); - file[19] = uint8_t(len >> 16); - file[20] = uint8_t(len >> 8); - file[21] = uint8_t(len & 255); - - LoopLimit = 0; -} - -//========================================================================== -// -// WriteVarLen -// -//========================================================================== - -static void WriteVarLen (TArray &file, uint32_t value) -{ - uint32_t buffer = value & 0x7F; - - while ( (value >>= 7) ) - { - buffer <<= 8; - buffer |= (value & 0x7F) | 0x80; - } - - for (;;) - { - file.Push(uint8_t(buffer)); - if (buffer & 0x80) - { - buffer >>= 8; - } - else - { - break; - } - } -} - //========================================================================== // diff --git a/src/sound/musicformats/music_smf_midiout.cpp b/src/sound/musicformats/music_smf_midiout.cpp index fd32cd8d8..6385bf997 100644 --- a/src/sound/musicformats/music_smf_midiout.cpp +++ b/src/sound/musicformats/music_smf_midiout.cpp @@ -89,9 +89,6 @@ struct MIDISong2::TrackInfo // PUBLIC DATA DEFINITIONS ------------------------------------------------- -char MIDI_EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; -char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - // CODE -------------------------------------------------------------------- //========================================================================== diff --git a/src/sound/musicformats/music_xmi_midiout.cpp b/src/sound/musicformats/music_xmi_midiout.cpp index 78d59a7ac..b59308176 100644 --- a/src/sound/musicformats/music_xmi_midiout.cpp +++ b/src/sound/musicformats/music_xmi_midiout.cpp @@ -91,9 +91,6 @@ struct XMISong::TrackInfo // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern char MIDI_EventLengths[7]; -extern char MIDI_CommonLengths[15]; - // PRIVATE DATA DEFINITIONS ------------------------------------------------ // PUBLIC DATA DEFINITIONS -------------------------------------------------