diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 9c760cc25..b276cfcfe 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -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 [sample rate]"); + Printf ("Usage: writewave [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 - 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 - use '*' as song name to dump the currently playing song\n"); return; } + auto source = GetMIDISource(argv[1]); + if (source == nullptr) return; TArray midi; bool success; diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 544bfd188..501e61974 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -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; diff --git a/src/sound/mididevices/music_timidity_mididevice.cpp b/src/sound/mididevices/music_timidity_mididevice.cpp index 474c0efa9..ba2a3b915 100644 --- a/src/sound/mididevices/music_timidity_mididevice.cpp +++ b/src/sound/mididevices/music_timidity_mididevice.cpp @@ -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() { } diff --git a/src/sound/mididevices/music_timiditypp_mididevice.cpp b/src/sound/mididevices/music_timiditypp_mididevice.cpp index 11cc38912..a87c185be 100644 --- a/src/sound/mididevices/music_timiditypp_mididevice.cpp +++ b/src/sound/mididevices/music_timiditypp_mididevice.cpp @@ -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 }; diff --git a/src/sound/musicformats/music_midistream.cpp b/src/sound/musicformats/music_midistream.cpp index 3c4c99454..e3f58bc84 100644 --- a/src/sound/musicformats/music_midistream.cpp +++ b/src/sound/musicformats/music_midistream.cpp @@ -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 diff --git a/src/sound/timiditypp/playmidi.cpp b/src/sound/timiditypp/playmidi.cpp index 0a1af32fb..de2e22b96 100644 --- a/src/sound/timiditypp/playmidi.cpp +++ b/src/sound/timiditypp/playmidi.cpp @@ -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;