mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-01-31 04:50:48 +00:00
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:
parent
8734511e80
commit
9aa1199902
9 changed files with 552 additions and 610 deletions
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
|
@ -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
241
src/sound/midisource.cpp
Normal 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
222
src/sound/midisources.h
Normal 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 */
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue