- made the MIDI to WAVE dumper functional again, including the option of choosing the MIDI synth and sound font with which it should be rendered.

This commit is contained in:
Christoph Oelckers 2018-02-24 18:19:27 +01:00
parent 786f1b34be
commit 2202a52840
6 changed files with 177 additions and 94 deletions

View file

@ -713,6 +713,45 @@ ADD_STAT(music)
return "No song playing";
}
//==========================================================================
//
// Common loader for the dumpers.
//
//==========================================================================
extern MusPlayingInfo mus_playing;
static MIDISource *GetMIDISource(const char *fn)
{
FString src = fn;
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("Cannot find MIDI lump %s.\n", src.GetChars());
return nullptr;
}
FWadLump wlump = Wads.OpenLumpNum(lump);
uint32_t id[32 / 4];
if (wlump.Read(id, 32) != 32 || wlump.Seek(-32, SEEK_CUR) != 0)
{
Printf("Unable to read lump %s\n", src.GetChars());
return nullptr;
}
auto type = IdentifyMIDIType(id, 32);
auto source = CreateMIDISource(wlump, type);
if (source == nullptr)
{
Printf("%s is not MIDI-based.\n", src.GetChars());
return nullptr;
}
return source;
}
//==========================================================================
//
// CCMD writeopl
@ -762,29 +801,42 @@ UNSAFE_CCMD (writeopl)
UNSAFE_CCMD (writewave)
{
if (argv.argc() >= 2 && argv.argc() <= 3)
if (argv.argc() >= 3 && argv.argc() <= 7)
{
if (currSong == nullptr)
auto source = GetMIDISource(argv[1]);
if (source == nullptr) return;
EMidiDevice dev = MDEV_DEFAULT;
if (argv.argc() >= 6)
{
Printf ("No song is currently playing.\n");
}
else
{
MusInfo *dumper = currSong->GetWaveDumper(argv[1], argv.argc() == 3 ? atoi(argv[2]) : 0);
if (dumper == nullptr)
{
Printf ("Current song cannot be saved as wave data.\n");
}
if (!stricmp(argv[5], "WildMidi")) dev = MDEV_WILDMIDI;
else if (!stricmp(argv[5], "GUS")) dev = MDEV_GUS;
else if (!stricmp(argv[5], "Timidity") || !stricmp(argv[5], "Timidity++")) dev = MDEV_TIMIDITY;
else if (!stricmp(argv[5], "FluidSynth")) dev = MDEV_FLUIDSYNTH;
else if (!stricmp(argv[5], "OPL")) dev = MDEV_OPL;
else
{
dumper->Play(false, 0); // FIXME: Remember subsong
delete dumper;
Printf("%s: Unknown MIDI device\n", argv[5]);
return;
}
}
// We must stop the currently playing music to avoid interference between two synths.
auto savedsong = mus_playing;
S_StopMusic(true);
if (snd_mididevice >= 0) dev = MDEV_FLUIDSYNTH; // The Windows system synth cannot dump a wave.
auto streamer = new MIDIStreamer(dev, argv.argc() < 6 ? nullptr : argv[6]);
streamer->SetMIDISource(source);
streamer->DumpWave(argv[2], argv.argc() <4? 0: (int)strtol(argv[3], nullptr, 10), argv.argc() <5 ? 0 : (int)strtol(argv[4], nullptr, 10));
delete streamer;
S_ChangeMusic(savedsong.name, savedsong.baseorder, savedsong.loop, true);
}
else
{
Printf ("Usage: writewave <filename> [sample rate]");
Printf ("Usage: writewave <midi> <filename> [subsong] [sample rate] [synth] [soundfont]\n"
" - use '*' as song name to dump the currently playing song\n"
" - use 0 for subsong and sample rate to play the default\n");
}
}
@ -796,44 +848,16 @@ UNSAFE_CCMD (writewave)
// like older versions did.
//
//==========================================================================
extern MusPlayingInfo mus_playing;
UNSAFE_CCMD (writemidi)
UNSAFE_CCMD(writemidi)
{
if (argv.argc() != 3)
{
Printf("Usage: writemidi <midisong> <filename> - use '*' as song name to dump the currently playing song");
return;
}
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("Cannot find MIDI lump %s.\n", src.GetChars());
return;
}
FWadLump wlump = Wads.OpenLumpNum(lump);
uint32_t id[32/4];
if(wlump.Read(id, 32) != 32 || wlump.Seek(-32, SEEK_CUR) != 0)
{
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());
Printf("Usage: writemidi <midisong> <filename> - use '*' as song name to dump the currently playing song\n");
return;
}
auto source = GetMIDISource(argv[1]);
if (source == nullptr) return;
TArray<uint8_t> midi;
bool success;

View file

@ -87,8 +87,6 @@ public:
#ifdef _WIN32
MIDIDevice *CreateWinMIDIDevice(int mididevice);
#elif defined __APPLE__
MIDIDevice *CreateAudioToolboxMIDIDevice();
#endif
MIDIDevice *CreateTimidityPPMIDIDevice(const char *args);
void TimidityPP_Shutdown();
@ -97,6 +95,7 @@ void TimidityPP_Shutdown();
class SoftSynthMIDIDevice : public MIDIDevice
{
friend class MIDIWaveWriter;
public:
SoftSynthMIDIDevice();
~SoftSynthMIDIDevice();
@ -132,6 +131,11 @@ protected:
int OpenStream(int chunks, int flags, MidiCallback, void *userdata);
static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata);
virtual bool ServiceStream (void *buff, int numbytes);
virtual void SetSampleRate(int rate)
{
if (rate > 11025) { SampleRate = rate; }
}
int GetSampleRate() const { return SampleRate; }
virtual void HandleEvent(int status, int parm1, int parm2) = 0;
virtual void HandleLongEvent(const uint8_t *data, int len) = 0;
@ -148,6 +152,7 @@ public:
void Close();
int GetTechnology() const;
FString GetStats();
virtual void SetSampleRate(int rate) { } // cannot be changed.
protected:
void CalcTickRate();
@ -184,6 +189,7 @@ public:
void PrecacheInstruments(const uint16_t *instruments, int count);
FString GetStats();
int GetDeviceType() const override { return MDEV_GUS; }
virtual void SetSampleRate(int rate) { if (rate >= 11025 && rate < 65535) SampleRate = rate; }
protected:
Timidity::Renderer *Renderer;
@ -193,18 +199,35 @@ protected:
void ComputeOutput(float *buffer, int len);
};
// Internal TiMidity disk writing version of a MIDI device ------------------
// Internal disk writing version of a MIDI device ------------------
class TimidityWaveWriterMIDIDevice : public TimidityMIDIDevice
class MIDIWaveWriter : public SoftSynthMIDIDevice
{
public:
TimidityWaveWriterMIDIDevice(const char *filename, int rate);
~TimidityWaveWriterMIDIDevice();
MIDIWaveWriter(const char *filename, MIDIDevice *devtouse, int rate);
~MIDIWaveWriter();
int Resume();
int Open(MidiCallback cb, void *userdata)
{
return playDevice->Open(cb, userdata);
}
void Stop();
void HandleEvent(int status, int parm1, int parm2) { playDevice->HandleEvent(status, parm1, parm2); }
void HandleLongEvent(const uint8_t *data, int len) { playDevice->HandleLongEvent(data, len); }
void ComputeOutput(float *buffer, int len) { playDevice->ComputeOutput(buffer, len); }
int StreamOutSync(MidiHeader *data) { return playDevice->StreamOutSync(data); }
int StreamOut(MidiHeader *data) { return playDevice->StreamOut(data); }
int GetDeviceType() const override { return playDevice->GetDeviceType(); }
bool ServiceStream (void *buff, int numbytes) { return playDevice->ServiceStream(buff, numbytes); }
int GetTechnology() const { return playDevice->GetTechnology(); }
int SetTempo(int tempo) { return playDevice->SetTempo(tempo); }
int SetTimeDiv(int timediv) { return playDevice->SetTimeDiv(timediv); }
bool IsOpen() const { return playDevice->IsOpen(); }
void CalcTickRate() { playDevice->CalcTickRate(); }
protected:
FileWriter *File;
SoftSynthMIDIDevice *playDevice;
};
// WildMidi implementation of a MIDI device ---------------------------------
@ -219,6 +242,7 @@ public:
void PrecacheInstruments(const uint16_t *instruments, int count);
FString GetStats();
int GetDeviceType() const override { return MDEV_WILDMIDI; }
virtual void SetSampleRate(int rate) { if (rate >= 11025 && SampleRate < rate) SampleRate = rate; }
protected:
WildMidi_Renderer *Renderer;
@ -253,6 +277,7 @@ public:
void FluidSettingNum(const char *setting, double value);
void FluidSettingStr(const char *setting, const char *value);
int GetDeviceType() const override { return MDEV_FLUIDSYNTH; }
virtual void SetSampleRate(int rate) { if (rate >= 22050 && rate <= 96000) SampleRate = rate; }
protected:
void HandleEvent(int status, int parm1, int parm2);
@ -336,11 +361,9 @@ public:
? MusInfo::GetDeviceType()
: MIDI->GetDeviceType();
}
// Must be redone later when the rest is properly rebuilt
//MusInfo *GetOPLDumper(const char *filename);
//MusInfo *GetWaveDumper(const char *filename, int rate);
bool DumpWave(const char *filename, int subsong, int samplerate);
protected:
MIDIStreamer(const char *dumpname, EMidiDevice type);
@ -382,7 +405,6 @@ protected:
EMidiDevice DeviceType;
bool CallbackIsThreaded;
int LoopLimit;
FString DumpFilename;
FString Args;
MIDISource *source;

View file

@ -245,10 +245,10 @@ FString TimidityMIDIDevice::GetStats()
//
//==========================================================================
TimidityWaveWriterMIDIDevice::TimidityWaveWriterMIDIDevice(const char *filename, int rate)
:TimidityMIDIDevice(nullptr)
MIDIWaveWriter::MIDIWaveWriter(const char *filename, MIDIDevice *playdevice, int rate)
{
File = FileWriter::Open(filename);
playDevice = (SoftSynthMIDIDevice*) playdevice;
if (File != nullptr)
{ // Write wave header
uint32_t work[3];
@ -259,12 +259,16 @@ TimidityWaveWriterMIDIDevice::TimidityWaveWriterMIDIDevice(const char *filename,
work[2] = MAKE_ID('W','A','V','E');
if (4*3 != File->Write(work, 4 * 3)) goto fail;
playDevice->SetSampleRate(rate);
playDevice->CalcTickRate();
rate = playDevice->GetSampleRate(); // read back what the device made of it.
fmt.ChunkID = MAKE_ID('f','m','t',' ');
fmt.ChunkLen = LittleLong(uint32_t(sizeof(fmt) - 8));
fmt.FormatTag = LittleShort(0xFFFE); // WAVE_FORMAT_EXTENSIBLE
fmt.Channels = LittleShort(2);
fmt.SamplesPerSec = LittleLong((int)Renderer->rate);
fmt.AvgBytesPerSec = LittleLong((int)Renderer->rate * 8);
fmt.SamplesPerSec = LittleLong(rate);
fmt.AvgBytesPerSec = LittleLong(rate * 8);
fmt.BlockAlign = LittleShort(8);
fmt.BitsPerSample = LittleShort(32);
fmt.ExtensionSize = LittleShort(2 + 4 + 16);
@ -301,7 +305,7 @@ fail:
//
//==========================================================================
TimidityWaveWriterMIDIDevice::~TimidityWaveWriterMIDIDevice()
MIDIWaveWriter::~MIDIWaveWriter()
{
if (File != nullptr)
{
@ -317,7 +321,7 @@ TimidityWaveWriterMIDIDevice::~TimidityWaveWriterMIDIDevice()
size = LittleLong(uint32_t(pos - 12 - sizeof(FmtChunk) - 8));
if (0 == File->Seek(4 + sizeof(FmtChunk) + 4, SEEK_CUR))
{
if (1 == File->Write(&size, 4))
if (4 == File->Write(&size, 4))
{
delete File;
return;
@ -336,7 +340,7 @@ TimidityWaveWriterMIDIDevice::~TimidityWaveWriterMIDIDevice()
//
//==========================================================================
int TimidityWaveWriterMIDIDevice::Resume()
int MIDIWaveWriter::Resume()
{
float writebuffer[4096];
@ -357,6 +361,6 @@ int TimidityWaveWriterMIDIDevice::Resume()
//
//==========================================================================
void TimidityWaveWriterMIDIDevice::Stop()
void MIDIWaveWriter::Stop()
{
}

View file

@ -68,6 +68,10 @@ public:
if (instruments != nullptr) delete instruments;
instruments = nullptr;
}
virtual void SetSampleRate(int rate)
{
if (rate >= 4000 && SampleRate < rate) SampleRate = rate;
}
double test[3] = { 0, 0, 0 };

View file

@ -82,19 +82,6 @@ MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
memset(Buffer, 0, sizeof(Buffer));
}
//==========================================================================
//
// MIDIStreamer OPL Dumping Constructor
//
//==========================================================================
MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type)
:
MIDI(0), DeviceType(type), DumpFilename(dumpname)
{
memset(Buffer, 0, sizeof(Buffer));
}
//==========================================================================
//
// MIDIStreamer Destructor
@ -254,28 +241,18 @@ void MIDIStreamer::Play(bool looping, int subsong)
if (source == nullptr) return; // We have nothing to play so abort.
assert(MIDI == NULL);
// fixme: The device should be attached by the controlling code to allow more flexibility.
// It should also separate the softsynth device from the playback engines.
// The approach here pretty much prevents the implementation of a generic WAVE writer because no generic dumper device class can be created.
devtype = SelectMIDIDevice(DeviceType);
/*
if (DumpFilename.IsNotEmpty())
{
if (devtype == MDEV_OPL)
if (DeviceType == MDEV_OPLDUMP)
{
MIDI = new OPLDumperMIDIDevice(DumpFilename);
}
else if (devtype == MDEV_GUS)
{
MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0);
}
}
else
{
MIDI = CreateMIDIDevice(devtype);
}
*/
MIDI = CreateMIDIDevice(devtype);
if (MIDI == NULL || 0 != MIDI->Open(Callback, this))
{
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
@ -366,6 +343,58 @@ void MIDIStreamer::StartPlayback()
while (BufferNum != 0);
}
bool MIDIStreamer::DumpWave(const char *filename, int subsong, int samplerate)
{
EMidiDevice devtype;
m_Status = STATE_Stopped;
m_Looping = false;
EndQueued = 0;
VolumeChanged = false;
Restarting = true;
InitialPlayback = true;
if (source == nullptr) return false; // We have nothing to play so abort.
assert(MIDI == NULL);
devtype = SelectMIDIDevice(DeviceType);
MIDI = CreateMIDIDevice(devtype);
MIDI = new MIDIWaveWriter(filename, MIDI, samplerate);
if (MIDI == NULL || 0 != MIDI->Open(Callback, this))
{
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
if (MIDI != NULL)
{
delete MIDI;
MIDI = NULL;
}
return false;
}
source->SetMIDISubsong(subsong);
source->CheckCaps(MIDI->GetTechnology());
if (MIDI->Preprocess(this, false))
{
StartPlayback();
if (MIDI == NULL)
{ // The MIDI file had no content and has been automatically closed.
return false;
}
}
if (0 != MIDI->Resume())
{
Printf("Starting MIDI playback failed\n");
Stop();
return false;
}
else
{
return true;
}
}
//==========================================================================
//
// MIDIStreamer :: Pause

View file

@ -89,7 +89,7 @@ static bool opt_eq_control = false;
static bool op_nrpn_vibrato = true;
static bool opt_tva_attack = false;
static bool opt_tva_decay = false;
static bool ppt_tva_release = false;
static bool opt_tva_release = false;
static bool opt_insertion_effect = false;
static bool opt_delay_control = false;