mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-01-18 15:11:46 +00:00
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:
parent
16f17deb0f
commit
adebd644f2
9 changed files with 228 additions and 198 deletions
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <filename>");
|
||||
Printf("Usage: writemidi <midisong> <filename> - 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<uint8_t> midi;
|
||||
bool success;
|
||||
|
||||
static_cast<MIDIStreamer *>(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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<uint8_t> &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);
|
||||
|
||||
|
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
#include <stdint.h>
|
||||
#include <functional>
|
||||
|
||||
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<uint8_t> &file, int looplimit);
|
||||
|
||||
};
|
||||
|
||||
// MUS file played with a MIDI stream ---------------------------------------
|
||||
|
|
|
@ -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 -------------------------------------------------
|
||||
|
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
|
|
@ -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 --------------------------------------------------------------------
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
@ -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 -------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue