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. :)
This commit is contained in:
Christoph Oelckers 2018-02-23 14:26:29 +01:00
parent 16f17deb0f
commit adebd644f2
9 changed files with 228 additions and 198 deletions

View file

@ -3154,3 +3154,15 @@ CCMD(listsoundchannels)
} }
Printf("%d sounds playing\n", count); 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");
}
}

View file

@ -787,38 +787,57 @@ UNSAFE_CCMD (writewave)
// //
// CCMD writemidi // CCMD writemidi
// //
// If the currently playing song is a MIDI variant, write it to disk. // Writes a given MIDI song to disk. This does not affect playback anymore,
// If successful, the current song will restart, since MIDI file generation // like older versions did.
// involves a simulated playthrough of the song.
// //
//========================================================================== //==========================================================================
extern MusPlayingInfo mus_playing;
UNSAFE_CCMD (writemidi) UNSAFE_CCMD (writemidi)
{ {
if (argv.argc() != 2) if (argv.argc() != 3)
{ {
Printf("Usage: writemidi <filename>"); Printf("Usage: writemidi <midisong> <filename> - use '*' as song name to dump the currently playing song");
return; 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; 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; return;
} }
TArray<uint8_t> midi; TArray<uint8_t> midi;
bool success; bool success;
static_cast<MIDIStreamer *>(currSong)->CreateSMF(midi, 1); source->CreateSMF(midi, 1);
auto f = FileWriter::Open(argv[1]); auto f = FileWriter::Open(argv[2]);
if (f == NULL) if (f == NULL)
{ {
Printf("Could not open %s.\n", argv[1]); Printf("Could not open %s.\n", argv[2]);
return; return;
} }
success = (f->Write(&midi[0], midi.Size()) == (size_t)midi.Size()); success = (f->Write(&midi[0], midi.Size()) == (size_t)midi.Size());
@ -826,6 +845,6 @@ UNSAFE_CCMD (writemidi)
if (!success) if (!success)
{ {
Printf("Could not write to music file.\n"); Printf("Could not write to music file %s.\n", argv[2]);
} }
} }

View file

@ -317,23 +317,22 @@ public:
MIDIStreamer(EMidiDevice type, const char *args); MIDIStreamer(EMidiDevice type, const char *args);
~MIDIStreamer(); ~MIDIStreamer();
void MusicVolumeChanged(); void MusicVolumeChanged() override;
void TimidityVolumeChanged(); void TimidityVolumeChanged() override;
void Play(bool looping, int subsong); void Play(bool looping, int subsong) override;
void Pause(); void Pause() override;
void Resume(); void Resume() override;
void Stop(); void Stop() override;
bool IsPlaying(); bool IsPlaying() override;
bool IsMIDI() const; bool IsMIDI() const override;
bool IsValid() const; bool IsValid() const override;
bool SetSubsong(int subsong); bool SetSubsong(int subsong) override;
void Update(); void Update() override;
FString GetStats(); FString GetStats() override;
void FluidSettingInt(const char *setting, int value); void FluidSettingInt(const char *setting, int value) override;
void FluidSettingNum(const char *setting, double value); void FluidSettingNum(const char *setting, double value) override;
void FluidSettingStr(const char *setting, const char *value); void FluidSettingStr(const char *setting, const char *value) override;
void WildMidiSetOption(int opt, int set); void WildMidiSetOption(int opt, int set) override;
void CreateSMF(TArray<uint8_t> &file, int looplimit=0);
int ServiceEvent(); int ServiceEvent();
void SetMIDISource(MIDISource *_source); void SetMIDISource(MIDISource *_source);

View file

@ -38,6 +38,10 @@
#include "midisources.h" #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 // MIDISource :: SetTempo
@ -239,3 +243,161 @@ bool MIDISource::SetMIDISubsong(int subsong)
{ {
return subsong == 0; return subsong == 0;
} }
//==========================================================================
//
// WriteVarLen
//
//==========================================================================
static void WriteVarLen (TArray<uint8_t> &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<uint8_t> &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;
}

View file

@ -11,6 +11,9 @@
#include <stdint.h> #include <stdint.h>
#include <functional> #include <functional>
extern char MIDI_EventLengths[7];
extern char MIDI_CommonLengths[15];
// base class for the different MIDI sources -------------------------------------- // base class for the different MIDI sources --------------------------------------
class MIDISource class MIDISource
@ -63,6 +66,9 @@ public:
{ {
TempoCallback = cb; TempoCallback = cb;
} }
void CreateSMF(TArray<uint8_t> &file, int looplimit);
}; };
// MUS file played with a MIDI stream --------------------------------------- // MUS file played with a MIDI stream ---------------------------------------

View file

@ -112,9 +112,6 @@ struct HMISong::TrackInfo
// EXTERNAL DATA DECLARATIONS ---------------------------------------------- // EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern char MIDI_EventLengths[7];
extern char MIDI_CommonLengths[15];
// PRIVATE DATA DEFINITIONS ------------------------------------------------ // PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS ------------------------------------------------- // PUBLIC DATA DEFINITIONS -------------------------------------------------

View file

@ -45,8 +45,6 @@
#define MAX_TIME (1000000/10) // Send out 1/10 of a sec of events at a time. #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 -------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@ -65,21 +63,8 @@ EXTERN_CVAR(Int, snd_mididevice)
extern unsigned mididevice; extern unsigned mididevice;
#endif #endif
extern char MIDI_EventLengths[7];
// PRIVATE DATA DEFINITIONS ------------------------------------------------ // 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 ------------------------------------------------- // PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE -------------------------------------------------------------------- // CODE --------------------------------------------------------------------
@ -852,150 +837,6 @@ void MIDIStreamer::SetMIDISource(MIDISource *_source)
source->setTempoCallback([=](int tempo) { return MIDI->SetTempo(tempo); } ); source->setTempoCallback([=](int tempo) { return MIDI->SetTempo(tempo); } );
} }
//==========================================================================
//
// MIDIStreamer :: CreateSMF
//
// Simulates playback to create a Standard MIDI File.
//
//==========================================================================
void MIDIStreamer::CreateSMF(TArray<uint8_t> &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<uint8_t> &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;
}
}
}
//========================================================================== //==========================================================================
// //

View file

@ -89,9 +89,6 @@ struct MIDISong2::TrackInfo
// PUBLIC DATA DEFINITIONS ------------------------------------------------- // 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 -------------------------------------------------------------------- // CODE --------------------------------------------------------------------
//========================================================================== //==========================================================================

View file

@ -91,9 +91,6 @@ struct XMISong::TrackInfo
// EXTERNAL DATA DECLARATIONS ---------------------------------------------- // EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern char MIDI_EventLengths[7];
extern char MIDI_CommonLengths[15];
// PRIVATE DATA DEFINITIONS ------------------------------------------------ // PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS ------------------------------------------------- // PUBLIC DATA DEFINITIONS -------------------------------------------------