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)
{
MIDISource *source = nullptr;
switch (miditype)
{
case MIDI_MUS:
return new MUSSong2(reader, devtype, args);
source = new MUSSong2(reader);
case MIDI_MIDI:
return new MIDISong2(reader, devtype, args);
source = new MIDISong2(reader);
case MIDI_HMI:
return new HMISong(reader, devtype, args);
source = new HMISong(reader);
case MIDI_XMI:
return new XMISong(reader, devtype, args);
source = new XMISong(reader);
default:
return NULL;
}
auto streamer = new MIDIStreamer(devtype, args);
streamer->SetMIDISource(source);
}
//==========================================================================

View File

@ -7,10 +7,12 @@
#include "s_sound.h"
#include "files.h"
#include "wildmidi/wildmidi_lib.h"
#include "midisources.h"
void I_InitMusicWin32 ();
extern float relative_volume;
class MIDISource;
EXTERN_CVAR (Float, timidity_mastervolume)
@ -304,6 +306,11 @@ protected:
// Base class for streaming MUS and MIDI files ------------------------------
enum
{
MAX_MIDI_EVENTS = 128
};
class MIDIStreamer : public MusInfo
{
public:
@ -328,6 +335,8 @@ public:
void WildMidiSetOption(int opt, int set);
void CreateSMF(TArray<uint8_t> &file, int looplimit=0);
int ServiceEvent();
void SetMIDISource(MIDISource *_source);
int GetDeviceType() const override
{
return nullptr == MIDI
@ -335,6 +344,11 @@ public:
: MIDI->GetDeviceType();
}
// Must be redone later when the rest is properly rebuilt
//MusInfo *GetOPLDumper(const char *filename);
//MusInfo *GetWaveDumper(const char *filename, int rate);
protected:
MIDIStreamer(const char *dumpname, EMidiDevice type);
@ -343,28 +357,18 @@ protected:
int FillStopBuffer(int buffer_num);
uint32_t *WriteStopNotes(uint32_t *events);
int VolumeControllerChange(int channel, int volume);
int ClampLoopCount(int loopcount);
void SetTempo(int new_tempo);
void Precache();
void StartPlayback();
//void SetMidiSynth(MIDIDevice *synth);
static EMidiDevice SelectMIDIDevice(EMidiDevice devtype);
MIDIDevice *CreateMIDIDevice(EMidiDevice devtype);
static void Callback(void *userdata);
// Virtuals for subclasses to override
virtual void StartPlayback();
virtual void CheckCaps(int tech);
virtual void DoInitialSetup() = 0;
virtual void DoRestart() = 0;
virtual bool CheckDone() = 0;
virtual void Precache();
virtual bool SetMIDISubsong(int subsong);
virtual uint32_t *MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time) = 0;
enum
{
MAX_EVENTS = 128
};
enum
{
SONG_MORE,
@ -373,7 +377,7 @@ protected:
};
MIDIDevice *MIDI;
uint32_t Events[2][MAX_EVENTS*3];
uint32_t Events[2][MAX_MIDI_EVENTS*3];
MidiHeader Buffer[2];
int BufferNum;
int EndQueued;
@ -381,181 +385,14 @@ protected:
bool Restarting;
bool InitialPlayback;
uint32_t NewVolume;
int Division;
int Tempo;
int InitialTempo;
uint8_t ChannelVolumes[16];
uint32_t Volume;
EMidiDevice DeviceType;
bool CallbackIsThreaded;
int LoopLimit;
FString DumpFilename;
FString Args;
};
MIDISource *source;
// MUS file played with a MIDI stream ---------------------------------------
class MUSSong2 : public MIDIStreamer
{
public:
MUSSong2(FileReader &reader, EMidiDevice type, const char *args);
~MUSSong2();
MusInfo *GetOPLDumper(const char *filename);
MusInfo *GetWaveDumper(const char *filename, int rate);
protected:
MUSSong2(const MUSSong2 *original, const char *filename, EMidiDevice type); // file dump constructor
void DoInitialSetup();
void DoRestart();
bool CheckDone();
void Precache();
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time);
MUSHeader *MusHeader;
uint8_t *MusBuffer;
uint8_t LastVelocity[16];
size_t MusP, MaxMusP;
};
// MIDI file played with a MIDI stream --------------------------------------
class MIDISong2 : public MIDIStreamer
{
public:
MIDISong2(FileReader &reader, EMidiDevice type, const char *args);
~MIDISong2();
MusInfo *GetOPLDumper(const char *filename);
MusInfo *GetWaveDumper(const char *filename, int rate);
protected:
MIDISong2(const MIDISong2 *original, const char *filename, EMidiDevice type); // file dump constructor
void CheckCaps(int tech);
void DoInitialSetup();
void DoRestart();
bool CheckDone();
uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time);
void AdvanceTracks(uint32_t time);
struct TrackInfo;
void ProcessInitialMetaEvents ();
uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
TrackInfo *FindNextDue ();
uint8_t *MusHeader;
int SongLen;
TrackInfo *Tracks;
TrackInfo *TrackDue;
int NumTracks;
int Format;
uint16_t DesignationMask;
};
// HMI file played with a MIDI stream ---------------------------------------
struct AutoNoteOff
{
uint32_t Delay;
uint8_t Channel, Key;
};
// Sorry, std::priority_queue, but I want to be able to modify the contents of the heap.
class NoteOffQueue : public TArray<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 --------------------

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 "m_swap.h"
#include "files.h"
#include "midisources.h"
// MACROS ------------------------------------------------------------------
@ -128,8 +129,7 @@ extern char MIDI_CommonLengths[15];
//
//==========================================================================
HMISong::HMISong (FileReader &reader, EMidiDevice type, const char *args)
: MIDIStreamer(type, args), MusHeader(0), Tracks(0)
HMISong::HMISong (FileReader &reader)
{
int len = reader.GetLength();
if (len < 0x100)
@ -195,7 +195,7 @@ void HMISong::SetupForHMI(int len)
// notably Quarantines, have identical values for some reason, so it's safer to
// use the quarter value and multiply it by four than to trust the full value.
Division = GetShort(MusHeader + HMI_DIVISION_OFFSET) << 2;
InitialTempo = 4000000;
Tempo = InitialTempo = 4000000;
Tracks = new TrackInfo[NumTracks + 1];
int track_dir = GetInt(MusHeader + HMI_TRACK_DIR_PTR_OFFSET);
@ -296,7 +296,7 @@ void HMISong::SetupForHMP(int len)
// The division is the number of pulses per quarter note (PPQN).
Division = GetInt(MusHeader + HMP_DIVISION_OFFSET);
InitialTempo = 1000000;
Tempo = InitialTempo = 1000000;
Tracks = new TrackInfo[NumTracks + 1];
@ -651,7 +651,7 @@ uint32_t *HMISong::SendCommand (uint32_t *events, TrackInfo *track, uint32_t del
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{
len = ReadVarLen(track);
if (len >= (MAX_EVENTS-1)*3*4)
if (len >= (MAX_MIDI_EVENTS-1)*3*4)
{ // This message will never fit. Throw it away.
track->TrackP += len;
}
@ -1013,52 +1013,3 @@ HMISong::TrackInfo *HMISong::FindNextDue ()
return track;
}
//==========================================================================
//
// HMISong :: GetOPLDumper
//
//==========================================================================
MusInfo *HMISong::GetOPLDumper(const char *filename)
{
return new HMISong(this, filename, MDEV_OPL);
}
//==========================================================================
//
// HMISong :: GetWaveDumper
//
//==========================================================================
MusInfo *HMISong::GetWaveDumper(const char *filename, int rate)
{
return new HMISong(this, filename, MDEV_GUS);
}
//==========================================================================
//
// HMISong File Dumping Constructor
//
//==========================================================================
HMISong::HMISong(const HMISong *original, const char *filename, EMidiDevice type)
: MIDIStreamer(filename, type)
{
SongLen = original->SongLen;
MusHeader = new uint8_t[original->SongLen];
memcpy(MusHeader, original->MusHeader, original->SongLen);
NumTracks = original->NumTracks;
Division = original->Division;
Tempo = InitialTempo = original->InitialTempo;
Tracks = new TrackInfo[NumTracks];
for (int i = 0; i < NumTracks; ++i)
{
TrackInfo *newtrack = &Tracks[i];
const TrackInfo *oldtrack = &original->Tracks[i];
newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader);
newtrack->TrackP = 0;
newtrack->MaxTrackP = oldtrack->MaxTrackP;
}
}

View File

@ -92,7 +92,7 @@ static const uint8_t StaticMIDIhead[] =
MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
:
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), Args(args)
MIDI(0), DeviceType(type), Args(args)
{
memset(Buffer, 0, sizeof(Buffer));
}
@ -105,7 +105,7 @@ MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type)
:
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname)
MIDI(0), DeviceType(type), DumpFilename(dumpname)
{
memset(Buffer, 0, sizeof(Buffer));
}
@ -146,21 +146,9 @@ bool MIDIStreamer::IsMIDI() const
bool MIDIStreamer::IsValid() const
{
return Division != 0;
return source != nullptr && source->getDivision() != 0;
}
//==========================================================================
//
// MIDIStreamer :: CheckCaps
//
// Called immediately after the device is opened in case a subclass should
// want to alter its behavior depending on which device it got.
//
//==========================================================================
void MIDIStreamer::CheckCaps(int tech)
{
}
//==========================================================================
//
@ -286,9 +274,15 @@ void MIDIStreamer::Play(bool looping, int subsong)
VolumeChanged = false;
Restarting = true;
InitialPlayback = true;
if (source == nullptr) return; // We have nothing to play so abort.
assert(MIDI == NULL);
// fixme: The device should be attached by the controlling code to allow more flexibility.
// It should also separate the softsynth device from the playback engines.
// The approach here pretty much prevents the implementation of a generic WAVE writer because no generic dumper device class can be created.
devtype = SelectMIDIDevice(DeviceType);
if (DumpFilename.IsNotEmpty())
{
if (devtype == MDEV_OPL)
@ -316,8 +310,8 @@ void MIDIStreamer::Play(bool looping, int subsong)
return;
}
SetMIDISubsong(subsong);
CheckCaps(MIDI->GetTechnology());
source->SetMIDISubsong(subsong);
source->CheckCaps(MIDI->GetTechnology());
if (MIDI->Preprocess(this, looping))
{
@ -347,12 +341,13 @@ void MIDIStreamer::Play(bool looping, int subsong)
void MIDIStreamer::StartPlayback()
{
Precache();
LoopLimit = 0;
auto data = source->PrecacheData();
MIDI->PrecacheInstruments(&data[0], data.Size());
source->StartPlayback(m_Looping);
// Set time division and tempo.
if (0 != MIDI->SetTimeDiv(Division) ||
0 != MIDI->SetTempo(Tempo = InitialTempo))
if (0 != MIDI->SetTimeDiv(source->getDivision()) ||
0 != MIDI->SetTempo(source->getInitialTempo()))
{
Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
MIDI->Close();
@ -368,7 +363,7 @@ void MIDIStreamer::StartPlayback()
BufferNum = 0;
do
{
int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
int res = FillBuffer(BufferNum, MAX_MIDI_EVENTS, MAX_TIME);
if (res == SONG_MORE)
{
if (0 != MIDI->StreamOutSync(&Buffer[BufferNum]))
@ -503,6 +498,7 @@ void MIDIStreamer::MusicVolumeChanged()
{
Volume = 0xFFFF;
}
source->setVolume(Volume);
if (m_Status == STATE_Playing)
{
OutputVolume(Volume);
@ -598,24 +594,6 @@ void MIDIStreamer::OutputVolume (uint32_t volume)
}
}
//==========================================================================
//
// MIDIStreamer :: VolumeControllerChange
//
// Some devices don't support master volume
// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
// so assume none of them do and scale channel volumes manually.
//
//==========================================================================
int MIDIStreamer::VolumeControllerChange(int channel, int volume)
{
ChannelVolumes[channel] = volume;
// If loops are limited, we can assume we're exporting this MIDI file,
// so we should not adjust the volume level.
return LoopLimit != 0 ? volume : ((volume + 1) * Volume) >> 16;
}
//==========================================================================
//
// MIDIStreamer :: Callback Static
@ -680,7 +658,7 @@ fill:
}
else
{
res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
res = FillBuffer(BufferNum, MAX_MIDI_EVENTS, MAX_TIME);
}
switch (res & 3)
{
@ -729,7 +707,7 @@ fill:
int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
{
if (!Restarting && CheckDone())
if (!Restarting && source->CheckDone())
{
return SONG_DONE;
}
@ -752,7 +730,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
events[3] = MAKE_ID(0xf0,0x7f,0x7f,0x04); // dwParms[0]
events[4] = MAKE_ID(0x01,0x7f,0x7f,0xf7); // dwParms[1]
events += 5;
DoInitialSetup();
source->DoInitialSetup();
}
// If the volume has changed, stick those events at the start of this buffer.
@ -761,7 +739,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
VolumeChanged = false;
for (i = 0; i < 16; ++i)
{
uint8_t courseVol = (uint8_t)(((ChannelVolumes[i]+1) * NewVolume) >> 16);
uint8_t courseVol = (uint8_t)(((source->getChannelVolume(i)+1) * NewVolume) >> 16);
events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID
events[2] = MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16);
@ -774,7 +752,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
{
// Be more responsive when unpausing by only playing each buffer
// for a third of the maximum time.
events[0] = MAX<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[2] = MEVENT_NOP << 24;
events += 3;
@ -787,13 +765,13 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
// Reset the tempo to the inital value.
events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID
events[2] = (MEVENT_TEMPO << 24) | InitialTempo; // dwEvent
events[2] = (MEVENT_TEMPO << 24) | source->getInitialTempo(); // dwEvent
events += 3;
// Stop all notes in case any were left hanging.
events = WriteStopNotes(events);
DoRestart();
source->DoRestart();
}
events = MakeEvents(events, max_event_p, max_time);
events = source->MakeEvents(events, max_event_p, max_time);
}
memset(&Buffer[buffer_num], 0, sizeof(MidiHeader));
Buffer[buffer_num].lpData = (uint8_t *)Events[buffer_num];
@ -864,105 +842,14 @@ uint32_t *MIDIStreamer::WriteStopNotes(uint32_t *events)
//==========================================================================
//
// MIDIStreamer :: Precache
//
// Generates a list of instruments this song uses and passes them to the
// MIDI device for precaching. The default implementation here pretends to
// play the song and watches for program change events on normal channels
// and note on events on channel 10.
//
//==========================================================================
void MIDIStreamer::Precache()
void MIDIStreamer::SetMIDISource(MIDISource *_source)
{
uint8_t found_instruments[256] = { 0, };
uint8_t found_banks[256] = { 0, };
bool multiple_banks = false;
LoopLimit = 1;
DoRestart();
found_banks[0] = true; // Bank 0 is always used.
found_banks[128] = true;
// Simulate playback to pick out used instruments.
while (!CheckDone())
{
uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600);
for (uint32_t *event = Events[0]; event < event_end; )
{
if (MEVENT_EVENTTYPE(event[2]) == 0)
{
int command = (event[2] & 0x70);
int channel = (event[2] & 0x0f);
int data1 = (event[2] >> 8) & 0x7f;
int data2 = (event[2] >> 16) & 0x7f;
if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70))
{
found_instruments[data1] = true;
}
else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0)
{ // On a percussion channel, program change also serves as bank select.
multiple_banks = true;
found_banks[data1 | 128] = true;
}
else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0)
{
found_instruments[data1 | 128] = true;
}
else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0)
{
multiple_banks = true;
if (channel == 9)
{
found_banks[data2 | 128] = true;
}
else
{
found_banks[data2] = true;
}
}
}
// Advance to next event
if (event[2] < 0x80000000)
{ // short message
event += 3;
}
else
{ // long message
event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2);
}
}
}
DoRestart();
// Now pack everything into a contiguous region for the PrecacheInstruments call().
TArray<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());
source = _source;
source->setTempoCallback([=](int tempo) { return MIDI->SetTempo(tempo); } );
}
//==========================================================================
@ -979,10 +866,13 @@ void MIDIStreamer::CreateSMF(TArray<uint8_t> &file, int looplimit)
uint8_t running_status = 255;
// Always create songs aimed at GM devices.
CheckCaps(MIDIDEV_MIDIPORT);
if (source == nullptr) return;
source->CheckCaps(MIDIDEV_MIDIPORT);
LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit;
DoRestart();
Tempo = InitialTempo;
source->DoRestart();
source->StartPlayback(false, LoopLimit);
auto InitialTempo = source->getInitialTempo();
auto Division = source->getDivision();
file.Reserve(sizeof(StaticMIDIhead));
memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead));
@ -992,9 +882,9 @@ void MIDIStreamer::CreateSMF(TArray<uint8_t> &file, int looplimit)
file[27] = InitialTempo >> 8;
file[28] = InitialTempo;
while (!CheckDone())
while (!source->CheckDone())
{
uint32_t *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600);
uint32_t *event_end = source->MakeEvents(Events[0], &Events[0][MAX_MIDI_EVENTS*3], 1000000*600);
for (uint32_t *event = Events[0]; event < event_end; )
{
delay += event[0];
@ -1106,57 +996,6 @@ static void WriteVarLen (TArray<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)
{
if (SetMIDISubsong(subsong))
if (source->SetMIDISubsong(subsong))
{
Stop();
Play(m_Looping, subsong);
@ -1192,18 +1031,6 @@ bool MIDIStreamer::SetSubsong(int subsong)
return false;
}
//==========================================================================
//
// MIDIStreamer :: SetMIDISubsong
//
// Selects which subsong to play. This is private.
//
//==========================================================================
bool MIDIStreamer::SetMIDISubsong(int subsong)
{
return subsong == 0;
}
//==========================================================================
//

View File

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

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)
: MIDIStreamer(type, args), MusHeader(0), Tracks(0)
MIDISong2::MIDISong2 (FileReader &reader)
: MusHeader(0), Tracks(0)
{
int p;
int i;
@ -518,7 +518,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d
case 117: // EMIDI Loop End
if (track->LoopCount >= 0 && data2 == 127)
{
if (track->LoopCount == 0 && !m_Looping)
if (track->LoopCount == 0 && !isLooping)
{
track->Finished = true;
}
@ -560,7 +560,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d
{
if (Tracks[i].LoopCount >= 0)
{
if (Tracks[i].LoopCount == 0 && !m_Looping)
if (Tracks[i].LoopCount == 0 && !isLooping)
{
Tracks[i].Finished = true;
}
@ -592,7 +592,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{
len = track->ReadVarLen();
if (len >= (MAX_EVENTS-1)*3*4)
if (len >= (MAX_MIDI_EVENTS-1)*3*4)
{ // This message will never fit. Throw it away.
track->TrackP += len;
}
@ -807,53 +807,3 @@ MIDISong2::TrackInfo *MIDISong2::FindNextDue ()
}
//==========================================================================
//
// MIDISong2 :: GetOPLDumper
//
//==========================================================================
MusInfo *MIDISong2::GetOPLDumper(const char *filename)
{
return new MIDISong2(this, filename, MDEV_OPL);
}
//==========================================================================
//
// MIDISong2 :: GetWaveDumper
//
//==========================================================================
MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate)
{
return new MIDISong2(this, filename, MDEV_GUS);
}
//==========================================================================
//
// MIDISong2 File Dumping Constructor
//
//==========================================================================
MIDISong2::MIDISong2(const MIDISong2 *original, const char *filename, EMidiDevice type)
: MIDIStreamer(filename, type)
{
SongLen = original->SongLen;
MusHeader = new uint8_t[original->SongLen];
memcpy(MusHeader, original->MusHeader, original->SongLen);
Format = original->Format;
NumTracks = original->NumTracks;
DesignationMask = 0;
Division = original->Division;
Tempo = InitialTempo = original->InitialTempo;
Tracks = new TrackInfo[NumTracks];
for (int i = 0; i < NumTracks; ++i)
{
TrackInfo *newtrack = &Tracks[i];
const TrackInfo *oldtrack = &original->Tracks[i];
newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader);
newtrack->TrackP = 0;
newtrack->MaxTrackP = oldtrack->MaxTrackP;
}
}

View File

@ -108,8 +108,8 @@ extern char MIDI_CommonLengths[15];
//
//==========================================================================
XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args)
: MIDIStreamer(type, args), MusHeader(0), Songs(0)
XMISong::XMISong (FileReader &reader)
: MusHeader(0), Songs(0)
{
SongLen = reader.GetLength();
MusHeader = new uint8_t[SongLen];
@ -131,7 +131,7 @@ XMISong::XMISong (FileReader &reader, EMidiDevice type, const char *args)
// We can use any combination of Division and Tempo values that work out
// to be 120 Hz.
Division = 60;
InitialTempo = 500000;
Tempo = InitialTempo = 500000;
Songs = new TrackInfo[NumSongs];
memset(Songs, 0, sizeof(*Songs) * NumSongs);
@ -478,7 +478,7 @@ uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t dela
int depth = track->ForDepth - 1;
if (depth < MAX_FOR_DEPTH)
{
if (data2 < 64 || (track->ForLoops[depth].LoopCount == 0 && !m_Looping))
if (data2 < 64 || (track->ForLoops[depth].LoopCount == 0 && !isLooping))
{ // throw away this loop.
track->ForLoops[depth].LoopCount = 1;
}
@ -522,7 +522,7 @@ uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t dela
if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
{
len = track->ReadVarLen();
if (len >= (MAX_EVENTS-1)*3*4)
if (len >= (MAX_MIDI_EVENTS-1)*3*4)
{ // This message will never fit. Throw it away.
track->EventP += len;
}
@ -698,53 +698,3 @@ XMISong::EventSource XMISong::FindNextDue()
}
//==========================================================================
//
// XMISong :: GetOPLDumper
//
//==========================================================================
MusInfo *XMISong::GetOPLDumper(const char *filename)
{
return new XMISong(this, filename, MDEV_OPL);
}
//==========================================================================
//
// XMISong :: GetWaveDumper
//
//==========================================================================
MusInfo *XMISong::GetWaveDumper(const char *filename, int rate)
{
return new XMISong(this, filename, MDEV_GUS);
}
//==========================================================================
//
// XMISong File Dumping Constructor
//
//==========================================================================
XMISong::XMISong(const XMISong *original, const char *filename, EMidiDevice type)
: MIDIStreamer(filename, type)
{
SongLen = original->SongLen;
MusHeader = new uint8_t[original->SongLen];
memcpy(MusHeader, original->MusHeader, original->SongLen);
NumSongs = original->NumSongs;
Tempo = InitialTempo = original->InitialTempo;
Songs = new TrackInfo[NumSongs];
for (int i = 0; i < NumSongs; ++i)
{
TrackInfo *newtrack = &Songs[i];
const TrackInfo *oldtrack = &original->Songs[i];
newtrack->EventChunk = MusHeader + (oldtrack->EventChunk - original->MusHeader);
newtrack->EventLen = oldtrack->EventLen;
newtrack->EventP = 0;
newtrack->TimbreChunk = MusHeader + (oldtrack->TimbreChunk - original->MusHeader);
newtrack->TimbreLen = oldtrack->TimbreLen;
}
}