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