diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 7733fdbc93..15d96f44ca 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -315,23 +315,27 @@ MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate) static MIDIStreamer *CreateMIDIStreamer(FileReader &reader, EMidiDevice devtype, EMIDIType miditype, const char *args) { + MIDISource *source = nullptr; switch (miditype) { case MIDI_MUS: - return new MUSSong2(reader, devtype, args); + source = new MUSSong2(reader); case MIDI_MIDI: - return new MIDISong2(reader, devtype, args); + source = new MIDISong2(reader); case MIDI_HMI: - return new HMISong(reader, devtype, args); + source = new HMISong(reader); case MIDI_XMI: - return new XMISong(reader, devtype, args); + source = new XMISong(reader); default: return NULL; } + auto streamer = new MIDIStreamer(devtype, args); + streamer->SetMIDISource(source); + } //========================================================================== diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 3af572cc78..d040af0e67 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -7,10 +7,12 @@ #include "s_sound.h" #include "files.h" #include "wildmidi/wildmidi_lib.h" +#include "midisources.h" void I_InitMusicWin32 (); extern float relative_volume; +class MIDISource; EXTERN_CVAR (Float, timidity_mastervolume) @@ -304,6 +306,11 @@ protected: // Base class for streaming MUS and MIDI files ------------------------------ +enum +{ + MAX_MIDI_EVENTS = 128 +}; + class MIDIStreamer : public MusInfo { public: @@ -328,12 +335,19 @@ public: void WildMidiSetOption(int opt, int set); void CreateSMF(TArray &file, int looplimit=0); int ServiceEvent(); + void SetMIDISource(MIDISource *_source); + int GetDeviceType() const override { return nullptr == MIDI ? 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); + protected: MIDIStreamer(const char *dumpname, EMidiDevice type); @@ -343,28 +357,18 @@ protected: int FillStopBuffer(int buffer_num); uint32_t *WriteStopNotes(uint32_t *events); int VolumeControllerChange(int channel, int volume); - int ClampLoopCount(int loopcount); void SetTempo(int new_tempo); + void Precache(); + void StartPlayback(); + + //void SetMidiSynth(MIDIDevice *synth); + + static EMidiDevice SelectMIDIDevice(EMidiDevice devtype); MIDIDevice *CreateMIDIDevice(EMidiDevice devtype); static void Callback(void *userdata); - // Virtuals for subclasses to override - virtual void StartPlayback(); - virtual void CheckCaps(int tech); - virtual void DoInitialSetup() = 0; - virtual void DoRestart() = 0; - virtual bool CheckDone() = 0; - virtual void Precache(); - virtual bool SetMIDISubsong(int subsong); - virtual uint32_t *MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time) = 0; - - enum - { - MAX_EVENTS = 128 - }; - enum { SONG_MORE, @@ -373,7 +377,7 @@ protected: }; MIDIDevice *MIDI; - uint32_t Events[2][MAX_EVENTS*3]; + uint32_t Events[2][MAX_MIDI_EVENTS*3]; MidiHeader Buffer[2]; int BufferNum; int EndQueued; @@ -381,181 +385,14 @@ protected: bool Restarting; bool InitialPlayback; uint32_t NewVolume; - int Division; - int Tempo; - int InitialTempo; - uint8_t ChannelVolumes[16]; uint32_t Volume; EMidiDevice DeviceType; bool CallbackIsThreaded; int LoopLimit; FString DumpFilename; FString Args; -}; + MIDISource *source; -// MUS file played with a MIDI stream --------------------------------------- - -class MUSSong2 : public MIDIStreamer -{ -public: - MUSSong2(FileReader &reader, EMidiDevice type, const char *args); - ~MUSSong2(); - - MusInfo *GetOPLDumper(const char *filename); - MusInfo *GetWaveDumper(const char *filename, int rate); - -protected: - MUSSong2(const MUSSong2 *original, const char *filename, EMidiDevice type); // file dump constructor - - void DoInitialSetup(); - void DoRestart(); - bool CheckDone(); - void Precache(); - uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time); - - MUSHeader *MusHeader; - uint8_t *MusBuffer; - uint8_t LastVelocity[16]; - size_t MusP, MaxMusP; -}; - -// MIDI file played with a MIDI stream -------------------------------------- - -class MIDISong2 : public MIDIStreamer -{ -public: - MIDISong2(FileReader &reader, EMidiDevice type, const char *args); - ~MIDISong2(); - - MusInfo *GetOPLDumper(const char *filename); - MusInfo *GetWaveDumper(const char *filename, int rate); - -protected: - MIDISong2(const MIDISong2 *original, const char *filename, EMidiDevice type); // file dump constructor - - void CheckCaps(int tech); - void DoInitialSetup(); - void DoRestart(); - bool CheckDone(); - uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time); - void AdvanceTracks(uint32_t time); - - struct TrackInfo; - - void ProcessInitialMetaEvents (); - uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom); - TrackInfo *FindNextDue (); - - uint8_t *MusHeader; - int SongLen; - TrackInfo *Tracks; - TrackInfo *TrackDue; - int NumTracks; - int Format; - uint16_t DesignationMask; -}; - -// HMI file played with a MIDI stream --------------------------------------- - -struct AutoNoteOff -{ - uint32_t Delay; - uint8_t Channel, Key; -}; -// Sorry, std::priority_queue, but I want to be able to modify the contents of the heap. -class NoteOffQueue : public TArray -{ -public: - void AddNoteOff(uint32_t delay, uint8_t channel, uint8_t key); - void AdvanceTime(uint32_t time); - bool Pop(AutoNoteOff &item); - -protected: - void Heapify(); - - unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; } - unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; } - unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; } -}; - -class HMISong : public MIDIStreamer -{ -public: - HMISong(FileReader &reader, EMidiDevice type, const char *args); - ~HMISong(); - - MusInfo *GetOPLDumper(const char *filename); - MusInfo *GetWaveDumper(const char *filename, int rate); - -protected: - HMISong(const HMISong *original, const char *filename, EMidiDevice type); // file dump constructor - - void SetupForHMI(int len); - void SetupForHMP(int len); - void CheckCaps(int tech); - - void DoInitialSetup(); - void DoRestart(); - bool CheckDone(); - uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time); - void AdvanceTracks(uint32_t time); - - struct TrackInfo; - - void ProcessInitialMetaEvents (); - uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom); - TrackInfo *FindNextDue (); - - static uint32_t ReadVarLenHMI(TrackInfo *); - static uint32_t ReadVarLenHMP(TrackInfo *); - - uint8_t *MusHeader; - int SongLen; - int NumTracks; - TrackInfo *Tracks; - TrackInfo *TrackDue; - TrackInfo *FakeTrack; - uint32_t (*ReadVarLen)(TrackInfo *); - NoteOffQueue NoteOffs; -}; - -// XMI file played with a MIDI stream --------------------------------------- - -class XMISong : public MIDIStreamer -{ -public: - XMISong(FileReader &reader, EMidiDevice type, const char *args); - ~XMISong(); - - MusInfo *GetOPLDumper(const char *filename); - MusInfo *GetWaveDumper(const char *filename, int rate); - -protected: - struct TrackInfo; - enum EventSource { EVENT_None, EVENT_Real, EVENT_Fake }; - - XMISong(const XMISong *original, const char *filename, EMidiDevice type); // file dump constructor - - int FindXMIDforms(const uint8_t *chunk, int len, TrackInfo *songs) const; - void FoundXMID(const uint8_t *chunk, int len, TrackInfo *song) const; - bool SetMIDISubsong(int subsong); - void DoInitialSetup(); - void DoRestart(); - bool CheckDone(); - uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time); - void AdvanceSong(uint32_t time); - - void ProcessInitialMetaEvents(); - uint32_t *SendCommand (uint32_t *event, EventSource track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom); - EventSource FindNextDue(); - - uint8_t *MusHeader; - int SongLen; // length of the entire file - int NumSongs; - TrackInfo *Songs; - TrackInfo *CurrSong; - NoteOffQueue NoteOffs; - EventSource EventDue; }; // Anything supported by the sound system out of the box -------------------- diff --git a/src/sound/midisource.cpp b/src/sound/midisource.cpp new file mode 100644 index 0000000000..87622cca38 --- /dev/null +++ b/src/sound/midisource.cpp @@ -0,0 +1,241 @@ +/* + ** midisource.cpp + ** Implements base class for the different MIDI formats + ** + **--------------------------------------------------------------------------- + ** Copyright 2008-2016 Randy Heit + ** Copyright 2017-2018 Christoph Oelckers + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + + +#include "i_musicinterns.h" +#include "midisources.h" + + +//========================================================================== +// +// MIDISource :: SetTempo +// +// Sets the tempo from a track's initial meta events. Later tempo changes +// create MEVENT_TEMPO events instead. +// +//========================================================================== + +void MIDISource::SetTempo(int new_tempo) +{ + InitialTempo = new_tempo; + // This intentionally uses a callback to avoid any dependencies on the class that is playing the song. + // This should probably be done differently, but right now that's not yet possible. + if (TempoCallback(new_tempo)) + { + Tempo = new_tempo; + } +} + + +//========================================================================== +// +// MIDISource :: ClampLoopCount +// +// We use the XMIDI interpretation of loop count here, where 1 means it +// plays that section once (in other words, no loop) rather than the EMIDI +// interpretation where 1 means to loop it once. +// +// If LoopLimit is 1, we limit all loops, since this pass over the song is +// used to determine instruments for precaching. +// +// If LoopLimit is higher, we only limit infinite loops, since this song is +// being exported. +// +//========================================================================== + +int MIDISource::ClampLoopCount(int loopcount) +{ + if (LoopLimit == 0) + { + return loopcount; + } + if (LoopLimit == 1) + { + return 1; + } + if (loopcount == 0) + { + return LoopLimit; + } + return loopcount; +} + +//========================================================================== +// +// MIDISource :: VolumeControllerChange +// +// Some devices don't support master volume +// (e.g. the Audigy's software MIDI synth--but not its two hardware ones), +// so assume none of them do and scale channel volumes manually. +// +//========================================================================== + +int MIDISource::VolumeControllerChange(int channel, int volume) +{ + ChannelVolumes[channel] = volume; + // When exporting this MIDI file, + // we should not adjust the volume level. + return Exporting? volume : ((volume + 1) * Volume) >> 16; +} + +//========================================================================== +// +// MIDISource :: Precache +// +// Generates a list of instruments this song uses and passes them to the +// MIDI device for precaching. The default implementation here pretends to +// play the song and watches for program change events on normal channels +// and note on events on channel 10. +// +//========================================================================== + +TArray MIDISource::PrecacheData() +{ + uint32_t Events[2][MAX_MIDI_EVENTS*3]; + uint8_t found_instruments[256] = { 0, }; + uint8_t found_banks[256] = { 0, }; + bool multiple_banks = false; + + LoopLimit = 1; + DoRestart(); + found_banks[0] = true; // Bank 0 is always used. + found_banks[128] = true; + + // Simulate playback to pick out used instruments. + 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; ) + { + if (MEVENT_EVENTTYPE(event[2]) == 0) + { + int command = (event[2] & 0x70); + int channel = (event[2] & 0x0f); + int data1 = (event[2] >> 8) & 0x7f; + int data2 = (event[2] >> 16) & 0x7f; + + if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70)) + { + found_instruments[data1] = true; + } + else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0) + { // On a percussion channel, program change also serves as bank select. + multiple_banks = true; + found_banks[data1 | 128] = true; + } + else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0) + { + found_instruments[data1 | 128] = true; + } + else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0) + { + multiple_banks = true; + if (channel == 9) + { + found_banks[data2 | 128] = true; + } + else + { + found_banks[data2] = true; + } + } + } + // Advance to next event + if (event[2] < 0x80000000) + { // short message + event += 3; + } + else + { // long message + event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2); + } + } + } + DoRestart(); + + // Now pack everything into a contiguous region for the PrecacheInstruments call(). + TArray packed; + + for (int i = 0; i < 256; ++i) + { + if (found_instruments[i]) + { + uint16_t packnum = (i & 127) | ((i & 128) << 7); + if (!multiple_banks) + { + packed.Push(packnum); + } + else + { // In order to avoid having to multiplex tracks in a type 1 file, + // precache every used instrument in every used bank, even if not + // all combinations are actually used. + for (int j = 0; j < 128; ++j) + { + if (found_banks[j + (i & 128)]) + { + packed.Push(packnum | (j << 7)); + } + } + } + } + } + return packed; +} + +//========================================================================== +// +// MIDISource :: CheckCaps +// +// Called immediately after the device is opened in case a source should +// want to alter its behavior depending on which device it got. +// +//========================================================================== + +void MIDISource::CheckCaps(int tech) +{ +} + +//========================================================================== +// +// MIDISource :: SetMIDISubsong +// +// Selects which subsong to play. This is private. +// +//========================================================================== + +bool MIDISource::SetMIDISubsong(int subsong) +{ + return subsong == 0; +} diff --git a/src/sound/midisources.h b/src/sound/midisources.h new file mode 100644 index 0000000000..38cd7dba8e --- /dev/null +++ b/src/sound/midisources.h @@ -0,0 +1,222 @@ +// +// midisources.h +// GZDoom +// +// Created by Christoph Oelckers on 23.02.18. +// + +#ifndef midisources_h +#define midisources_h + +#include +#include + +// base class for the different MIDI sources -------------------------------------- + +class MIDISource +{ + int Volume = 0xffff; + int LoopLimit = 0; + std::function TempoCallback = [](int t) { return false; }; + +protected: + + bool isLooping = false; + int Division = 0; + int Tempo = 500000; + int InitialTempo = 500000; + uint8_t ChannelVolumes[16]; + + int VolumeControllerChange(int channel, int volume); + void SetTempo(int new_tempo); + int ClampLoopCount(int loopcount); + + +public: + bool Exporting = false; + + // Virtuals for subclasses to override + virtual ~MIDISource() {} + virtual void CheckCaps(int tech); + virtual void DoInitialSetup() = 0; + virtual void DoRestart() = 0; + virtual bool CheckDone() = 0; + virtual TArray PrecacheData(); + virtual bool SetMIDISubsong(int subsong); + virtual uint32_t *MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time) = 0; + + void StartPlayback(bool looped = true, int looplimit = 0) + { + Tempo = InitialTempo; + LoopLimit = looplimit; + isLooping = looped; + } + + int getDivision() const { return Division; } + int getInitialTempo() const { return InitialTempo; } + int getTempo() const { return Tempo; } + int getChannelVolume(int ch) const { return ChannelVolumes[ch]; } + void setVolume(int vol) { Volume = vol; } + void setLoopLimit(int lim) { LoopLimit = lim; } + void setTempoCallback(std::function cb) + { + TempoCallback = cb; + } +}; + +// MUS file played with a MIDI stream --------------------------------------- + +class MUSSong2 : public MIDISource +{ +public: + MUSSong2(FileReader &reader); + ~MUSSong2(); + +protected: + void DoInitialSetup() override; + void DoRestart() override; + bool CheckDone() override; + TArray PrecacheData() override; + uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override; + +private: + MUSHeader *MusHeader; + uint8_t *MusBuffer; + uint8_t LastVelocity[16]; + size_t MusP, MaxMusP; +}; + + +// MIDI file played with a MIDI stream -------------------------------------- + +class MIDISong2 : public MIDISource +{ +public: + MIDISong2(FileReader &reader); + ~MIDISong2(); + +protected: + void CheckCaps(int tech) override; + void DoInitialSetup() override; + void DoRestart() override; + bool CheckDone() override; + uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override; + +private: + void AdvanceTracks(uint32_t time); + + struct TrackInfo; + + void ProcessInitialMetaEvents (); + uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom); + TrackInfo *FindNextDue (); + + uint8_t *MusHeader; + int SongLen; + TrackInfo *Tracks; + TrackInfo *TrackDue; + int NumTracks; + int Format; + uint16_t DesignationMask; +}; + +// HMI file played with a MIDI stream --------------------------------------- + +struct AutoNoteOff +{ + uint32_t Delay; + uint8_t Channel, Key; +}; +// Sorry, std::priority_queue, but I want to be able to modify the contents of the heap. +class NoteOffQueue : public TArray +{ +public: + void AddNoteOff(uint32_t delay, uint8_t channel, uint8_t key); + void AdvanceTime(uint32_t time); + bool Pop(AutoNoteOff &item); + +protected: + void Heapify(); + + unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; } + unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; } + unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; } +}; + +class HMISong : public MIDISource +{ +public: + HMISong(FileReader &reader); + ~HMISong(); + +protected: + + void DoInitialSetup() override; + void DoRestart() override; + bool CheckDone() override; + void CheckCaps(int tech) override; + uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override; + +private: + void SetupForHMI(int len); + void SetupForHMP(int len); + void AdvanceTracks(uint32_t time); + + struct TrackInfo; + + void ProcessInitialMetaEvents (); + uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom); + TrackInfo *FindNextDue (); + + static uint32_t ReadVarLenHMI(TrackInfo *); + static uint32_t ReadVarLenHMP(TrackInfo *); + + uint8_t *MusHeader; + int SongLen; + int NumTracks; + TrackInfo *Tracks; + TrackInfo *TrackDue; + TrackInfo *FakeTrack; + uint32_t (*ReadVarLen)(TrackInfo *); + NoteOffQueue NoteOffs; +}; + +// XMI file played with a MIDI stream --------------------------------------- + +class XMISong : public MIDISource +{ +public: + XMISong(FileReader &reader); + ~XMISong(); + +protected: + bool SetMIDISubsong(int subsong) override; + void DoInitialSetup() override; + void DoRestart() override; + bool CheckDone() override; + uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override; + +private: + struct TrackInfo; + enum EventSource { EVENT_None, EVENT_Real, EVENT_Fake }; + + int FindXMIDforms(const uint8_t *chunk, int len, TrackInfo *songs) const; + void FoundXMID(const uint8_t *chunk, int len, TrackInfo *song) const; + void AdvanceSong(uint32_t time); + + void ProcessInitialMetaEvents(); + uint32_t *SendCommand (uint32_t *event, EventSource track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom); + EventSource FindNextDue(); + + uint8_t *MusHeader; + int SongLen; // length of the entire file + int NumSongs; + TrackInfo *Songs; + TrackInfo *CurrSong; + NoteOffQueue NoteOffs; + EventSource EventDue; +}; + + + +#endif /* midisources_h */ diff --git a/src/sound/musicformats/music_hmi_midiout.cpp b/src/sound/musicformats/music_hmi_midiout.cpp index 791c8ec3db..c9ff1ca4e1 100644 --- a/src/sound/musicformats/music_hmi_midiout.cpp +++ b/src/sound/musicformats/music_hmi_midiout.cpp @@ -39,6 +39,7 @@ #include "doomdef.h" #include "m_swap.h" #include "files.h" +#include "midisources.h" // MACROS ------------------------------------------------------------------ @@ -128,8 +129,7 @@ extern char MIDI_CommonLengths[15]; // //========================================================================== -HMISong::HMISong (FileReader &reader, EMidiDevice type, const char *args) -: MIDIStreamer(type, args), MusHeader(0), Tracks(0) +HMISong::HMISong (FileReader &reader) { int len = reader.GetLength(); if (len < 0x100) @@ -195,7 +195,7 @@ void HMISong::SetupForHMI(int len) // notably Quarantines, have identical values for some reason, so it's safer to // use the quarter value and multiply it by four than to trust the full value. Division = GetShort(MusHeader + HMI_DIVISION_OFFSET) << 2; - InitialTempo = 4000000; + Tempo = InitialTempo = 4000000; Tracks = new TrackInfo[NumTracks + 1]; int track_dir = GetInt(MusHeader + HMI_TRACK_DIR_PTR_OFFSET); @@ -296,7 +296,7 @@ void HMISong::SetupForHMP(int len) // The division is the number of pulses per quarter note (PPQN). Division = GetInt(MusHeader + HMP_DIVISION_OFFSET); - InitialTempo = 1000000; + Tempo = InitialTempo = 1000000; Tracks = new TrackInfo[NumTracks + 1]; @@ -651,7 +651,7 @@ uint32_t *HMISong::SendCommand (uint32_t *events, TrackInfo *track, uint32_t del if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = ReadVarLen(track); - if (len >= (MAX_EVENTS-1)*3*4) + if (len >= (MAX_MIDI_EVENTS-1)*3*4) { // This message will never fit. Throw it away. track->TrackP += len; } @@ -1013,52 +1013,3 @@ HMISong::TrackInfo *HMISong::FindNextDue () return track; } - -//========================================================================== -// -// HMISong :: GetOPLDumper -// -//========================================================================== - -MusInfo *HMISong::GetOPLDumper(const char *filename) -{ - return new HMISong(this, filename, MDEV_OPL); -} - -//========================================================================== -// -// HMISong :: GetWaveDumper -// -//========================================================================== - -MusInfo *HMISong::GetWaveDumper(const char *filename, int rate) -{ - return new HMISong(this, filename, MDEV_GUS); -} - -//========================================================================== -// -// HMISong File Dumping Constructor -// -//========================================================================== - -HMISong::HMISong(const HMISong *original, const char *filename, EMidiDevice type) -: MIDIStreamer(filename, type) -{ - SongLen = original->SongLen; - MusHeader = new uint8_t[original->SongLen]; - memcpy(MusHeader, original->MusHeader, original->SongLen); - NumTracks = original->NumTracks; - Division = original->Division; - Tempo = InitialTempo = original->InitialTempo; - Tracks = new TrackInfo[NumTracks]; - for (int i = 0; i < NumTracks; ++i) - { - TrackInfo *newtrack = &Tracks[i]; - const TrackInfo *oldtrack = &original->Tracks[i]; - - newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader); - newtrack->TrackP = 0; - newtrack->MaxTrackP = oldtrack->MaxTrackP; - } -} diff --git a/src/sound/musicformats/music_midistream.cpp b/src/sound/musicformats/music_midistream.cpp index 2a8abdae99..6844c79f41 100644 --- a/src/sound/musicformats/music_midistream.cpp +++ b/src/sound/musicformats/music_midistream.cpp @@ -92,7 +92,7 @@ static const uint8_t StaticMIDIhead[] = MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args) : - MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), Args(args) + MIDI(0), DeviceType(type), Args(args) { memset(Buffer, 0, sizeof(Buffer)); } @@ -105,7 +105,7 @@ MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args) MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type) : - MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname) + MIDI(0), DeviceType(type), DumpFilename(dumpname) { memset(Buffer, 0, sizeof(Buffer)); } @@ -146,21 +146,9 @@ bool MIDIStreamer::IsMIDI() const bool MIDIStreamer::IsValid() const { - return Division != 0; + return source != nullptr && source->getDivision() != 0; } -//========================================================================== -// -// MIDIStreamer :: CheckCaps -// -// Called immediately after the device is opened in case a subclass should -// want to alter its behavior depending on which device it got. -// -//========================================================================== - -void MIDIStreamer::CheckCaps(int tech) -{ -} //========================================================================== // @@ -286,9 +274,15 @@ void MIDIStreamer::Play(bool looping, int subsong) VolumeChanged = false; Restarting = true; InitialPlayback = true; + 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) @@ -316,8 +310,8 @@ void MIDIStreamer::Play(bool looping, int subsong) return; } - SetMIDISubsong(subsong); - CheckCaps(MIDI->GetTechnology()); + source->SetMIDISubsong(subsong); + source->CheckCaps(MIDI->GetTechnology()); if (MIDI->Preprocess(this, looping)) { @@ -347,12 +341,13 @@ void MIDIStreamer::Play(bool looping, int subsong) void MIDIStreamer::StartPlayback() { - Precache(); - LoopLimit = 0; - + auto data = source->PrecacheData(); + MIDI->PrecacheInstruments(&data[0], data.Size()); + source->StartPlayback(m_Looping); + // Set time division and tempo. - if (0 != MIDI->SetTimeDiv(Division) || - 0 != MIDI->SetTempo(Tempo = InitialTempo)) + if (0 != MIDI->SetTimeDiv(source->getDivision()) || + 0 != MIDI->SetTempo(source->getInitialTempo())) { Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n"); MIDI->Close(); @@ -368,7 +363,7 @@ void MIDIStreamer::StartPlayback() BufferNum = 0; do { - int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME); + int res = FillBuffer(BufferNum, MAX_MIDI_EVENTS, MAX_TIME); if (res == SONG_MORE) { if (0 != MIDI->StreamOutSync(&Buffer[BufferNum])) @@ -503,6 +498,7 @@ void MIDIStreamer::MusicVolumeChanged() { Volume = 0xFFFF; } + source->setVolume(Volume); if (m_Status == STATE_Playing) { OutputVolume(Volume); @@ -598,24 +594,6 @@ void MIDIStreamer::OutputVolume (uint32_t volume) } } -//========================================================================== -// -// MIDIStreamer :: VolumeControllerChange -// -// Some devices don't support master volume -// (e.g. the Audigy's software MIDI synth--but not its two hardware ones), -// so assume none of them do and scale channel volumes manually. -// -//========================================================================== - -int MIDIStreamer::VolumeControllerChange(int channel, int volume) -{ - ChannelVolumes[channel] = volume; - // If loops are limited, we can assume we're exporting this MIDI file, - // so we should not adjust the volume level. - return LoopLimit != 0 ? volume : ((volume + 1) * Volume) >> 16; -} - //========================================================================== // // MIDIStreamer :: Callback Static @@ -680,7 +658,7 @@ fill: } else { - res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME); + res = FillBuffer(BufferNum, MAX_MIDI_EVENTS, MAX_TIME); } switch (res & 3) { @@ -729,7 +707,7 @@ fill: int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time) { - if (!Restarting && CheckDone()) + if (!Restarting && source->CheckDone()) { return SONG_DONE; } @@ -752,7 +730,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time) events[3] = MAKE_ID(0xf0,0x7f,0x7f,0x04); // dwParms[0] events[4] = MAKE_ID(0x01,0x7f,0x7f,0xf7); // dwParms[1] events += 5; - DoInitialSetup(); + source->DoInitialSetup(); } // If the volume has changed, stick those events at the start of this buffer. @@ -761,7 +739,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time) VolumeChanged = false; for (i = 0; i < 16; ++i) { - uint8_t courseVol = (uint8_t)(((ChannelVolumes[i]+1) * NewVolume) >> 16); + uint8_t courseVol = (uint8_t)(((source->getChannelVolume(i)+1) * NewVolume) >> 16); events[0] = 0; // dwDeltaTime events[1] = 0; // dwStreamID events[2] = MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16); @@ -774,7 +752,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time) { // Be more responsive when unpausing by only playing each buffer // for a third of the maximum time. - events[0] = MAX(1, (max_time / 3) * Division / Tempo); + events[0] = MAX(1, (max_time / 3) * source->getDivision() / source->getTempo()); events[1] = 0; events[2] = MEVENT_NOP << 24; events += 3; @@ -787,13 +765,13 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time) // Reset the tempo to the inital value. events[0] = 0; // dwDeltaTime events[1] = 0; // dwStreamID - events[2] = (MEVENT_TEMPO << 24) | InitialTempo; // dwEvent + events[2] = (MEVENT_TEMPO << 24) | source->getInitialTempo(); // dwEvent events += 3; // Stop all notes in case any were left hanging. events = WriteStopNotes(events); - DoRestart(); + source->DoRestart(); } - events = MakeEvents(events, max_event_p, max_time); + events = source->MakeEvents(events, max_event_p, max_time); } memset(&Buffer[buffer_num], 0, sizeof(MidiHeader)); Buffer[buffer_num].lpData = (uint8_t *)Events[buffer_num]; @@ -864,105 +842,14 @@ uint32_t *MIDIStreamer::WriteStopNotes(uint32_t *events) //========================================================================== // -// MIDIStreamer :: Precache // -// Generates a list of instruments this song uses and passes them to the -// MIDI device for precaching. The default implementation here pretends to -// play the song and watches for program change events on normal channels -// and note on events on channel 10. // //========================================================================== -void MIDIStreamer::Precache() +void MIDIStreamer::SetMIDISource(MIDISource *_source) { - uint8_t found_instruments[256] = { 0, }; - uint8_t found_banks[256] = { 0, }; - bool multiple_banks = false; - - LoopLimit = 1; - DoRestart(); - found_banks[0] = true; // Bank 0 is always used. - found_banks[128] = true; - - // Simulate playback to pick out used instruments. - while (!CheckDone()) - { - uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600); - for (uint32_t *event = Events[0]; event < event_end; ) - { - if (MEVENT_EVENTTYPE(event[2]) == 0) - { - int command = (event[2] & 0x70); - int channel = (event[2] & 0x0f); - int data1 = (event[2] >> 8) & 0x7f; - int data2 = (event[2] >> 16) & 0x7f; - - if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70)) - { - found_instruments[data1] = true; - } - else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0) - { // On a percussion channel, program change also serves as bank select. - multiple_banks = true; - found_banks[data1 | 128] = true; - } - else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0) - { - found_instruments[data1 | 128] = true; - } - else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0) - { - multiple_banks = true; - if (channel == 9) - { - found_banks[data2 | 128] = true; - } - else - { - found_banks[data2] = true; - } - } - } - // Advance to next event - if (event[2] < 0x80000000) - { // short message - event += 3; - } - else - { // long message - event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2); - } - } - } - DoRestart(); - - // Now pack everything into a contiguous region for the PrecacheInstruments call(). - TArray packed; - - for (int i = 0; i < 256; ++i) - { - if (found_instruments[i]) - { - uint16_t packnum = (i & 127) | ((i & 128) << 7); - if (!multiple_banks) - { - packed.Push(packnum); - } - else - { // In order to avoid having to multiplex tracks in a type 1 file, - // precache every used instrument in every used bank, even if not - // all combinations are actually used. - for (int j = 0; j < 128; ++j) - { - if (found_banks[j + (i & 128)]) - { - packed.Push(packnum | (j << 7)); - } - } - } - } - } - MIDI->PrecacheInstruments(&packed[0], packed.Size()); + source = _source; + source->setTempoCallback([=](int tempo) { return MIDI->SetTempo(tempo); } ); } //========================================================================== @@ -979,10 +866,13 @@ void MIDIStreamer::CreateSMF(TArray &file, int looplimit) uint8_t running_status = 255; // Always create songs aimed at GM devices. - CheckCaps(MIDIDEV_MIDIPORT); + if (source == nullptr) return; + source->CheckCaps(MIDIDEV_MIDIPORT); LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit; - DoRestart(); - Tempo = InitialTempo; + source->DoRestart(); + source->StartPlayback(false, LoopLimit); + auto InitialTempo = source->getInitialTempo(); + auto Division = source->getDivision(); file.Reserve(sizeof(StaticMIDIhead)); memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead)); @@ -992,9 +882,9 @@ void MIDIStreamer::CreateSMF(TArray &file, int looplimit) file[27] = InitialTempo >> 8; file[28] = InitialTempo; - while (!CheckDone()) + while (!source->CheckDone()) { - uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600); + 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]; @@ -1106,57 +996,6 @@ static void WriteVarLen (TArray &file, uint32_t value) } } -//========================================================================== -// -// MIDIStreamer :: SetTempo -// -// Sets the tempo from a track's initial meta events. Later tempo changes -// create MEVENT_TEMPO events instead. -// -//========================================================================== - -void MIDIStreamer::SetTempo(int new_tempo) -{ - InitialTempo = new_tempo; - if (NULL != MIDI && 0 == MIDI->SetTempo(new_tempo)) - { - Tempo = new_tempo; - } -} - - -//========================================================================== -// -// MIDIStreamer :: ClampLoopCount -// -// We use the XMIDI interpretation of loop count here, where 1 means it -// plays that section once (in other words, no loop) rather than the EMIDI -// interpretation where 1 means to loop it once. -// -// If LoopLimit is 1, we limit all loops, since this pass over the song is -// used to determine instruments for precaching. -// -// If LoopLimit is higher, we only limit infinite loops, since this song is -// being exported. -// -//========================================================================== - -int MIDIStreamer::ClampLoopCount(int loopcount) -{ - if (LoopLimit == 0) - { - return loopcount; - } - if (LoopLimit == 1) - { - return 1; - } - if (loopcount == 0) - { - return LoopLimit; - } - return loopcount; -} //========================================================================== // @@ -1183,7 +1022,7 @@ FString MIDIStreamer::GetStats() bool MIDIStreamer::SetSubsong(int subsong) { - if (SetMIDISubsong(subsong)) + if (source->SetMIDISubsong(subsong)) { Stop(); Play(m_Looping, subsong); @@ -1192,18 +1031,6 @@ bool MIDIStreamer::SetSubsong(int subsong) return false; } -//========================================================================== -// -// MIDIStreamer :: SetMIDISubsong -// -// Selects which subsong to play. This is private. -// -//========================================================================== - -bool MIDIStreamer::SetMIDISubsong(int subsong) -{ - return subsong == 0; -} //========================================================================== // diff --git a/src/sound/musicformats/music_mus_midiout.cpp b/src/sound/musicformats/music_mus_midiout.cpp index 8058944a7a..feddfe67c8 100644 --- a/src/sound/musicformats/music_mus_midiout.cpp +++ b/src/sound/musicformats/music_mus_midiout.cpp @@ -93,8 +93,8 @@ static const uint8_t CtrlTranslate[15] = // //========================================================================== -MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type, const char *args) -: MIDIStreamer(type, args), MusHeader(0), MusBuffer(0) +MUSSong2::MUSSong2 (FileReader &reader) +: MusHeader(0), MusBuffer(0) { uint8_t front[32]; int start; @@ -136,7 +136,7 @@ MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type, const char *args) MusBuffer = (uint8_t *)MusHeader + LittleShort(MusHeader->SongStart); MaxMusP = MIN(LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart)); Division = 140; - InitialTempo = 1000000; + Tempo = InitialTempo = 1000000; } //========================================================================== @@ -202,7 +202,7 @@ bool MUSSong2::CheckDone() // //========================================================================== -void MUSSong2::Precache() +TArray MUSSong2::PrecacheData() { TArray work(LittleShort(MusHeader->NumInstruments)); const uint8_t *used = (uint8_t *)MusHeader + sizeof(MUSHeader) / sizeof(uint8_t); @@ -241,7 +241,7 @@ void MUSSong2::Precache() work.Push(val); } } - MIDI->PrecacheInstruments(&work[0], work.Size()); + return work; } //========================================================================== @@ -369,46 +369,6 @@ end: return events; } -//========================================================================== -// -// MUSSong2 :: GetOPLDumper -// -//========================================================================== - -MusInfo *MUSSong2::GetOPLDumper(const char *filename) -{ - return new MUSSong2(this, filename, MDEV_OPL); -} - -//========================================================================== -// -// MUSSong2 :: GetWaveDumper -// -//========================================================================== - -MusInfo *MUSSong2::GetWaveDumper(const char *filename, int rate) -{ - return new MUSSong2(this, filename, MDEV_GUS); -} - -//========================================================================== -// -// MUSSong2 OPL Dumping Constructor -// -//========================================================================== - -MUSSong2::MUSSong2(const MUSSong2 *original, const char *filename, EMidiDevice type) -: MIDIStreamer(filename, type) -{ - int songstart = LittleShort(original->MusHeader->SongStart); - MaxMusP = original->MaxMusP; - MusHeader = (MUSHeader *)new uint8_t[songstart + MaxMusP]; - memcpy(MusHeader, original->MusHeader, songstart + MaxMusP); - MusBuffer = (uint8_t *)MusHeader + songstart; - Division = 140; - InitialTempo = 1000000; -} - //========================================================================== // // MUSHeaderSearch diff --git a/src/sound/musicformats/music_smf_midiout.cpp b/src/sound/musicformats/music_smf_midiout.cpp index 7d2131fc92..fd32cd8d89 100644 --- a/src/sound/musicformats/music_smf_midiout.cpp +++ b/src/sound/musicformats/music_smf_midiout.cpp @@ -102,8 +102,8 @@ char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // //========================================================================== -MIDISong2::MIDISong2 (FileReader &reader, EMidiDevice type, const char *args) -: MIDIStreamer(type, args), MusHeader(0), Tracks(0) +MIDISong2::MIDISong2 (FileReader &reader) +: MusHeader(0), Tracks(0) { int p; int i; @@ -518,7 +518,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d case 117: // EMIDI Loop End if (track->LoopCount >= 0 && data2 == 127) { - if (track->LoopCount == 0 && !m_Looping) + if (track->LoopCount == 0 && !isLooping) { track->Finished = true; } @@ -560,7 +560,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d { if (Tracks[i].LoopCount >= 0) { - if (Tracks[i].LoopCount == 0 && !m_Looping) + if (Tracks[i].LoopCount == 0 && !isLooping) { Tracks[i].Finished = true; } @@ -592,7 +592,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = track->ReadVarLen(); - if (len >= (MAX_EVENTS-1)*3*4) + if (len >= (MAX_MIDI_EVENTS-1)*3*4) { // This message will never fit. Throw it away. track->TrackP += len; } @@ -807,53 +807,3 @@ MIDISong2::TrackInfo *MIDISong2::FindNextDue () } -//========================================================================== -// -// MIDISong2 :: GetOPLDumper -// -//========================================================================== - -MusInfo *MIDISong2::GetOPLDumper(const char *filename) -{ - return new MIDISong2(this, filename, MDEV_OPL); -} - -//========================================================================== -// -// MIDISong2 :: GetWaveDumper -// -//========================================================================== - -MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate) -{ - return new MIDISong2(this, filename, MDEV_GUS); -} - -//========================================================================== -// -// MIDISong2 File Dumping Constructor -// -//========================================================================== - -MIDISong2::MIDISong2(const MIDISong2 *original, const char *filename, EMidiDevice type) -: MIDIStreamer(filename, type) -{ - SongLen = original->SongLen; - MusHeader = new uint8_t[original->SongLen]; - memcpy(MusHeader, original->MusHeader, original->SongLen); - Format = original->Format; - NumTracks = original->NumTracks; - DesignationMask = 0; - Division = original->Division; - Tempo = InitialTempo = original->InitialTempo; - Tracks = new TrackInfo[NumTracks]; - for (int i = 0; i < NumTracks; ++i) - { - TrackInfo *newtrack = &Tracks[i]; - const TrackInfo *oldtrack = &original->Tracks[i]; - - newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader); - newtrack->TrackP = 0; - newtrack->MaxTrackP = oldtrack->MaxTrackP; - } -} diff --git a/src/sound/musicformats/music_xmi_midiout.cpp b/src/sound/musicformats/music_xmi_midiout.cpp index a4e23423bc..78d59a7ac7 100644 --- a/src/sound/musicformats/music_xmi_midiout.cpp +++ b/src/sound/musicformats/music_xmi_midiout.cpp @@ -108,8 +108,8 @@ extern char MIDI_CommonLengths[15]; // //========================================================================== -XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args) -: MIDIStreamer(type, args), MusHeader(0), Songs(0) +XMISong::XMISong (FileReader &reader) +: MusHeader(0), Songs(0) { SongLen = reader.GetLength(); MusHeader = new uint8_t[SongLen]; @@ -131,7 +131,7 @@ XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args) // We can use any combination of Division and Tempo values that work out // to be 120 Hz. Division = 60; - InitialTempo = 500000; + Tempo = InitialTempo = 500000; Songs = new TrackInfo[NumSongs]; memset(Songs, 0, sizeof(*Songs) * NumSongs); @@ -478,7 +478,7 @@ uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t dela int depth = track->ForDepth - 1; if (depth < MAX_FOR_DEPTH) { - if (data2 < 64 || (track->ForLoops[depth].LoopCount == 0 && !m_Looping)) + if (data2 < 64 || (track->ForLoops[depth].LoopCount == 0 && !isLooping)) { // throw away this loop. track->ForLoops[depth].LoopCount = 1; } @@ -522,7 +522,7 @@ uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t dela if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = track->ReadVarLen(); - if (len >= (MAX_EVENTS-1)*3*4) + if (len >= (MAX_MIDI_EVENTS-1)*3*4) { // This message will never fit. Throw it away. track->EventP += len; } @@ -698,53 +698,3 @@ XMISong::EventSource XMISong::FindNextDue() } -//========================================================================== -// -// XMISong :: GetOPLDumper -// -//========================================================================== - -MusInfo *XMISong::GetOPLDumper(const char *filename) -{ - return new XMISong(this, filename, MDEV_OPL); -} - -//========================================================================== -// -// XMISong :: GetWaveDumper -// -//========================================================================== - -MusInfo *XMISong::GetWaveDumper(const char *filename, int rate) -{ - return new XMISong(this, filename, MDEV_GUS); -} - -//========================================================================== -// -// XMISong File Dumping Constructor -// -//========================================================================== - -XMISong::XMISong(const XMISong *original, const char *filename, EMidiDevice type) -: MIDIStreamer(filename, type) -{ - SongLen = original->SongLen; - MusHeader = new uint8_t[original->SongLen]; - memcpy(MusHeader, original->MusHeader, original->SongLen); - NumSongs = original->NumSongs; - Tempo = InitialTempo = original->InitialTempo; - Songs = new TrackInfo[NumSongs]; - for (int i = 0; i < NumSongs; ++i) - { - TrackInfo *newtrack = &Songs[i]; - const TrackInfo *oldtrack = &original->Songs[i]; - - newtrack->EventChunk = MusHeader + (oldtrack->EventChunk - original->MusHeader); - newtrack->EventLen = oldtrack->EventLen; - newtrack->EventP = 0; - - newtrack->TimbreChunk = MusHeader + (oldtrack->TimbreChunk - original->MusHeader); - newtrack->TimbreLen = oldtrack->TimbreLen; - } -}