Split the different MIDI format implementations into a separate 'Source' class.

This is necessary to write a universal, device independent wave dumper for MIDIs.
With each format inheriting from the main player class it is not possible to create a generic dumper player.
This commit is contained in:
Christoph Oelckers 2018-02-23 12:40:43 +01:00
parent 8734511e80
commit 9aa1199902
9 changed files with 552 additions and 610 deletions

View file

@ -315,23 +315,27 @@ MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate)
static MIDIStreamer *CreateMIDIStreamer(FileReader &reader, EMidiDevice devtype, EMIDIType miditype, const char *args) static MIDIStreamer *CreateMIDIStreamer(FileReader &reader, EMidiDevice devtype, EMIDIType miditype, const char *args)
{ {
MIDISource *source = nullptr;
switch (miditype) switch (miditype)
{ {
case MIDI_MUS: case MIDI_MUS:
return new MUSSong2(reader, devtype, args); source = new MUSSong2(reader);
case MIDI_MIDI: case MIDI_MIDI:
return new MIDISong2(reader, devtype, args); source = new MIDISong2(reader);
case MIDI_HMI: case MIDI_HMI:
return new HMISong(reader, devtype, args); source = new HMISong(reader);
case MIDI_XMI: case MIDI_XMI:
return new XMISong(reader, devtype, args); source = new XMISong(reader);
default: default:
return NULL; return NULL;
} }
auto streamer = new MIDIStreamer(devtype, args);
streamer->SetMIDISource(source);
} }
//========================================================================== //==========================================================================

View file

@ -7,10 +7,12 @@
#include "s_sound.h" #include "s_sound.h"
#include "files.h" #include "files.h"
#include "wildmidi/wildmidi_lib.h" #include "wildmidi/wildmidi_lib.h"
#include "midisources.h"
void I_InitMusicWin32 (); void I_InitMusicWin32 ();
extern float relative_volume; extern float relative_volume;
class MIDISource;
EXTERN_CVAR (Float, timidity_mastervolume) EXTERN_CVAR (Float, timidity_mastervolume)
@ -304,6 +306,11 @@ protected:
// Base class for streaming MUS and MIDI files ------------------------------ // Base class for streaming MUS and MIDI files ------------------------------
enum
{
MAX_MIDI_EVENTS = 128
};
class MIDIStreamer : public MusInfo class MIDIStreamer : public MusInfo
{ {
public: public:
@ -328,12 +335,19 @@ public:
void WildMidiSetOption(int opt, int set); void WildMidiSetOption(int opt, int set);
void CreateSMF(TArray<uint8_t> &file, int looplimit=0); void CreateSMF(TArray<uint8_t> &file, int looplimit=0);
int ServiceEvent(); int ServiceEvent();
void SetMIDISource(MIDISource *_source);
int GetDeviceType() const override int GetDeviceType() const override
{ {
return nullptr == MIDI return nullptr == MIDI
? MusInfo::GetDeviceType() ? MusInfo::GetDeviceType()
: MIDI->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: protected:
MIDIStreamer(const char *dumpname, EMidiDevice type); MIDIStreamer(const char *dumpname, EMidiDevice type);
@ -343,28 +357,18 @@ protected:
int FillStopBuffer(int buffer_num); int FillStopBuffer(int buffer_num);
uint32_t *WriteStopNotes(uint32_t *events); uint32_t *WriteStopNotes(uint32_t *events);
int VolumeControllerChange(int channel, int volume); int VolumeControllerChange(int channel, int volume);
int ClampLoopCount(int loopcount);
void SetTempo(int new_tempo); void SetTempo(int new_tempo);
void Precache();
void StartPlayback();
//void SetMidiSynth(MIDIDevice *synth);
static EMidiDevice SelectMIDIDevice(EMidiDevice devtype); static EMidiDevice SelectMIDIDevice(EMidiDevice devtype);
MIDIDevice *CreateMIDIDevice(EMidiDevice devtype); MIDIDevice *CreateMIDIDevice(EMidiDevice devtype);
static void Callback(void *userdata); 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 enum
{ {
SONG_MORE, SONG_MORE,
@ -373,7 +377,7 @@ protected:
}; };
MIDIDevice *MIDI; MIDIDevice *MIDI;
uint32_t Events[2][MAX_EVENTS*3]; uint32_t Events[2][MAX_MIDI_EVENTS*3];
MidiHeader Buffer[2]; MidiHeader Buffer[2];
int BufferNum; int BufferNum;
int EndQueued; int EndQueued;
@ -381,181 +385,14 @@ protected:
bool Restarting; bool Restarting;
bool InitialPlayback; bool InitialPlayback;
uint32_t NewVolume; uint32_t NewVolume;
int Division;
int Tempo;
int InitialTempo;
uint8_t ChannelVolumes[16];
uint32_t Volume; uint32_t Volume;
EMidiDevice DeviceType; EMidiDevice DeviceType;
bool CallbackIsThreaded; bool CallbackIsThreaded;
int LoopLimit; int LoopLimit;
FString DumpFilename; FString DumpFilename;
FString Args; 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<AutoNoteOff>
{
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 -------------------- // Anything supported by the sound system out of the box --------------------

241
src/sound/midisource.cpp Normal file
View file

@ -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<uint16_t> 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<uint16_t> 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;
}

222
src/sound/midisources.h Normal file
View file

@ -0,0 +1,222 @@
//
// midisources.h
// GZDoom
//
// Created by Christoph Oelckers on 23.02.18.
//
#ifndef midisources_h
#define midisources_h
#include <stdint.h>
#include <functional>
// base class for the different MIDI sources --------------------------------------
class MIDISource
{
int Volume = 0xffff;
int LoopLimit = 0;
std::function<bool(int)> 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<uint16_t> 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<bool(int)> 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<uint16_t> 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<AutoNoteOff>
{
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 */

View file

@ -39,6 +39,7 @@
#include "doomdef.h" #include "doomdef.h"
#include "m_swap.h" #include "m_swap.h"
#include "files.h" #include "files.h"
#include "midisources.h"
// MACROS ------------------------------------------------------------------ // MACROS ------------------------------------------------------------------
@ -128,8 +129,7 @@ extern char MIDI_CommonLengths[15];
// //
//========================================================================== //==========================================================================
HMISong::HMISong (FileReader &reader, EMidiDevice type, const char *args) HMISong::HMISong (FileReader &reader)
: MIDIStreamer(type, args), MusHeader(0), Tracks(0)
{ {
int len = reader.GetLength(); int len = reader.GetLength();
if (len < 0x100) 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 // 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. // use the quarter value and multiply it by four than to trust the full value.
Division = GetShort(MusHeader + HMI_DIVISION_OFFSET) << 2; Division = GetShort(MusHeader + HMI_DIVISION_OFFSET) << 2;
InitialTempo = 4000000; Tempo = InitialTempo = 4000000;
Tracks = new TrackInfo[NumTracks + 1]; Tracks = new TrackInfo[NumTracks + 1];
int track_dir = GetInt(MusHeader + HMI_TRACK_DIR_PTR_OFFSET); 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). // The division is the number of pulses per quarter note (PPQN).
Division = GetInt(MusHeader + HMP_DIVISION_OFFSET); Division = GetInt(MusHeader + HMP_DIVISION_OFFSET);
InitialTempo = 1000000; Tempo = InitialTempo = 1000000;
Tracks = new TrackInfo[NumTracks + 1]; 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) if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{ {
len = ReadVarLen(track); 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. { // This message will never fit. Throw it away.
track->TrackP += len; track->TrackP += len;
} }
@ -1013,52 +1013,3 @@ HMISong::TrackInfo *HMISong::FindNextDue ()
return track; 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;
}
}

View file

@ -92,7 +92,7 @@ static const uint8_t StaticMIDIhead[] =
MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args) 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)); memset(Buffer, 0, sizeof(Buffer));
} }
@ -105,7 +105,7 @@ MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type) 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)); memset(Buffer, 0, sizeof(Buffer));
} }
@ -146,21 +146,9 @@ bool MIDIStreamer::IsMIDI() const
bool MIDIStreamer::IsValid() 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; VolumeChanged = false;
Restarting = true; Restarting = true;
InitialPlayback = true; InitialPlayback = true;
if (source == nullptr) return; // We have nothing to play so abort.
assert(MIDI == NULL); 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); devtype = SelectMIDIDevice(DeviceType);
if (DumpFilename.IsNotEmpty()) if (DumpFilename.IsNotEmpty())
{ {
if (devtype == MDEV_OPL) if (devtype == MDEV_OPL)
@ -316,8 +310,8 @@ void MIDIStreamer::Play(bool looping, int subsong)
return; return;
} }
SetMIDISubsong(subsong); source->SetMIDISubsong(subsong);
CheckCaps(MIDI->GetTechnology()); source->CheckCaps(MIDI->GetTechnology());
if (MIDI->Preprocess(this, looping)) if (MIDI->Preprocess(this, looping))
{ {
@ -347,12 +341,13 @@ void MIDIStreamer::Play(bool looping, int subsong)
void MIDIStreamer::StartPlayback() void MIDIStreamer::StartPlayback()
{ {
Precache(); auto data = source->PrecacheData();
LoopLimit = 0; MIDI->PrecacheInstruments(&data[0], data.Size());
source->StartPlayback(m_Looping);
// Set time division and tempo. // Set time division and tempo.
if (0 != MIDI->SetTimeDiv(Division) || if (0 != MIDI->SetTimeDiv(source->getDivision()) ||
0 != MIDI->SetTempo(Tempo = InitialTempo)) 0 != MIDI->SetTempo(source->getInitialTempo()))
{ {
Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n"); Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
MIDI->Close(); MIDI->Close();
@ -368,7 +363,7 @@ void MIDIStreamer::StartPlayback()
BufferNum = 0; BufferNum = 0;
do do
{ {
int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME); int res = FillBuffer(BufferNum, MAX_MIDI_EVENTS, MAX_TIME);
if (res == SONG_MORE) if (res == SONG_MORE)
{ {
if (0 != MIDI->StreamOutSync(&Buffer[BufferNum])) if (0 != MIDI->StreamOutSync(&Buffer[BufferNum]))
@ -503,6 +498,7 @@ void MIDIStreamer::MusicVolumeChanged()
{ {
Volume = 0xFFFF; Volume = 0xFFFF;
} }
source->setVolume(Volume);
if (m_Status == STATE_Playing) if (m_Status == STATE_Playing)
{ {
OutputVolume(Volume); 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 // MIDIStreamer :: Callback Static
@ -680,7 +658,7 @@ fill:
} }
else else
{ {
res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME); res = FillBuffer(BufferNum, MAX_MIDI_EVENTS, MAX_TIME);
} }
switch (res & 3) switch (res & 3)
{ {
@ -729,7 +707,7 @@ fill:
int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time) int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
{ {
if (!Restarting && CheckDone()) if (!Restarting && source->CheckDone())
{ {
return SONG_DONE; 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[3] = MAKE_ID(0xf0,0x7f,0x7f,0x04); // dwParms[0]
events[4] = MAKE_ID(0x01,0x7f,0x7f,0xf7); // dwParms[1] events[4] = MAKE_ID(0x01,0x7f,0x7f,0xf7); // dwParms[1]
events += 5; events += 5;
DoInitialSetup(); source->DoInitialSetup();
} }
// If the volume has changed, stick those events at the start of this buffer. // 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; VolumeChanged = false;
for (i = 0; i < 16; ++i) 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[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID events[1] = 0; // dwStreamID
events[2] = MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16); 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 // Be more responsive when unpausing by only playing each buffer
// for a third of the maximum time. // for a third of the maximum time.
events[0] = MAX<uint32_t>(1, (max_time / 3) * Division / Tempo); events[0] = MAX<uint32_t>(1, (max_time / 3) * source->getDivision() / source->getTempo());
events[1] = 0; events[1] = 0;
events[2] = MEVENT_NOP << 24; events[2] = MEVENT_NOP << 24;
events += 3; 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. // Reset the tempo to the inital value.
events[0] = 0; // dwDeltaTime events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID events[1] = 0; // dwStreamID
events[2] = (MEVENT_TEMPO << 24) | InitialTempo; // dwEvent events[2] = (MEVENT_TEMPO << 24) | source->getInitialTempo(); // dwEvent
events += 3; events += 3;
// Stop all notes in case any were left hanging. // Stop all notes in case any were left hanging.
events = WriteStopNotes(events); 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)); memset(&Buffer[buffer_num], 0, sizeof(MidiHeader));
Buffer[buffer_num].lpData = (uint8_t *)Events[buffer_num]; 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, }; source = _source;
uint8_t found_banks[256] = { 0, }; source->setTempoCallback([=](int tempo) { return MIDI->SetTempo(tempo); } );
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<uint16_t> 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());
} }
//========================================================================== //==========================================================================
@ -979,10 +866,13 @@ void MIDIStreamer::CreateSMF(TArray<uint8_t> &file, int looplimit)
uint8_t running_status = 255; uint8_t running_status = 255;
// Always create songs aimed at GM devices. // 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; LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit;
DoRestart(); source->DoRestart();
Tempo = InitialTempo; source->StartPlayback(false, LoopLimit);
auto InitialTempo = source->getInitialTempo();
auto Division = source->getDivision();
file.Reserve(sizeof(StaticMIDIhead)); file.Reserve(sizeof(StaticMIDIhead));
memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead)); memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead));
@ -992,9 +882,9 @@ void MIDIStreamer::CreateSMF(TArray<uint8_t> &file, int looplimit)
file[27] = InitialTempo >> 8; file[27] = InitialTempo >> 8;
file[28] = InitialTempo; 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; ) for (uint32_t *event = Events[0]; event < event_end; )
{ {
delay += event[0]; delay += event[0];
@ -1106,57 +996,6 @@ static void WriteVarLen (TArray<uint8_t> &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) bool MIDIStreamer::SetSubsong(int subsong)
{ {
if (SetMIDISubsong(subsong)) if (source->SetMIDISubsong(subsong))
{ {
Stop(); Stop();
Play(m_Looping, subsong); Play(m_Looping, subsong);
@ -1192,18 +1031,6 @@ bool MIDIStreamer::SetSubsong(int subsong)
return false; return false;
} }
//==========================================================================
//
// MIDIStreamer :: SetMIDISubsong
//
// Selects which subsong to play. This is private.
//
//==========================================================================
bool MIDIStreamer::SetMIDISubsong(int subsong)
{
return subsong == 0;
}
//========================================================================== //==========================================================================
// //

View file

@ -93,8 +93,8 @@ static const uint8_t CtrlTranslate[15] =
// //
//========================================================================== //==========================================================================
MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type, const char *args) MUSSong2::MUSSong2 (FileReader &reader)
: MIDIStreamer(type, args), MusHeader(0), MusBuffer(0) : MusHeader(0), MusBuffer(0)
{ {
uint8_t front[32]; uint8_t front[32];
int start; int start;
@ -136,7 +136,7 @@ MUSSong2::MUSSong2 (FileReader &reader, EMidiDevice type, const char *args)
MusBuffer = (uint8_t *)MusHeader + LittleShort(MusHeader->SongStart); MusBuffer = (uint8_t *)MusHeader + LittleShort(MusHeader->SongStart);
MaxMusP = MIN<int>(LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart)); MaxMusP = MIN<int>(LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart));
Division = 140; Division = 140;
InitialTempo = 1000000; Tempo = InitialTempo = 1000000;
} }
//========================================================================== //==========================================================================
@ -202,7 +202,7 @@ bool MUSSong2::CheckDone()
// //
//========================================================================== //==========================================================================
void MUSSong2::Precache() TArray<uint16_t> MUSSong2::PrecacheData()
{ {
TArray<uint16_t> work(LittleShort(MusHeader->NumInstruments)); TArray<uint16_t> work(LittleShort(MusHeader->NumInstruments));
const uint8_t *used = (uint8_t *)MusHeader + sizeof(MUSHeader) / sizeof(uint8_t); const uint8_t *used = (uint8_t *)MusHeader + sizeof(MUSHeader) / sizeof(uint8_t);
@ -241,7 +241,7 @@ void MUSSong2::Precache()
work.Push(val); work.Push(val);
} }
} }
MIDI->PrecacheInstruments(&work[0], work.Size()); return work;
} }
//========================================================================== //==========================================================================
@ -369,46 +369,6 @@ end:
return events; 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 // MUSHeaderSearch

View file

@ -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) MIDISong2::MIDISong2 (FileReader &reader)
: MIDIStreamer(type, args), MusHeader(0), Tracks(0) : MusHeader(0), Tracks(0)
{ {
int p; int p;
int i; int i;
@ -518,7 +518,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d
case 117: // EMIDI Loop End case 117: // EMIDI Loop End
if (track->LoopCount >= 0 && data2 == 127) if (track->LoopCount >= 0 && data2 == 127)
{ {
if (track->LoopCount == 0 && !m_Looping) if (track->LoopCount == 0 && !isLooping)
{ {
track->Finished = true; 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)
{ {
if (Tracks[i].LoopCount == 0 && !m_Looping) if (Tracks[i].LoopCount == 0 && !isLooping)
{ {
Tracks[i].Finished = true; 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) if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{ {
len = track->ReadVarLen(); 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. { // This message will never fit. Throw it away.
track->TrackP += len; 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;
}
}

View file

@ -108,8 +108,8 @@ extern char MIDI_CommonLengths[15];
// //
//========================================================================== //==========================================================================
XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args) XMISong::XMISong (FileReader &reader)
: MIDIStreamer(type, args), MusHeader(0), Songs(0) : MusHeader(0), Songs(0)
{ {
SongLen = reader.GetLength(); SongLen = reader.GetLength();
MusHeader = new uint8_t[SongLen]; 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 // We can use any combination of Division and Tempo values that work out
// to be 120 Hz. // to be 120 Hz.
Division = 60; Division = 60;
InitialTempo = 500000; Tempo = InitialTempo = 500000;
Songs = new TrackInfo[NumSongs]; Songs = new TrackInfo[NumSongs];
memset(Songs, 0, sizeof(*Songs) * 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; int depth = track->ForDepth - 1;
if (depth < MAX_FOR_DEPTH) 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. { // throw away this loop.
track->ForLoops[depth].LoopCount = 1; 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) if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{ {
len = track->ReadVarLen(); 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. { // This message will never fit. Throw it away.
track->EventP += len; 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;
}
}