gzdoom/src/sound/musicformats/music_midistream.cpp

1377 lines
34 KiB
C++

/*
** music_midistream.cpp
** Implements base class for MIDI and MUS streaming.
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** 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.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include "i_musicinterns.h"
#include "templates.h"
#include "doomdef.h"
#include "m_swap.h"
#include "doomerrors.h"
// MACROS ------------------------------------------------------------------
#define MAX_TIME (1000000/10) // Send out 1/10 of a sec of events at a time.
#define EXPORT_LOOP_LIMIT 30 // Maximum number of times to loop when exporting a MIDI file.
// (for songs with loop controller events)
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static void WriteVarLen (TArray<uint8_t> &file, uint32_t value);
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
EXTERN_CVAR(Float, snd_musicvolume)
EXTERN_CVAR(Int, snd_mididevice)
#ifdef _WIN32
extern unsigned mididevice;
#endif
extern char MIDI_EventLengths[7];
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static const uint8_t StaticMIDIhead[] =
{
'M','T','h','d', 0, 0, 0, 6,
0, 0, // format 0: only one track
0, 1, // yes, there is really only one track
0, 0, // divisions (filled in)
'M','T','r','k', 0, 0, 0, 0,
// The first event sets the tempo (filled in)
0, 255, 81, 3, 0, 0, 0
};
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// MIDIStreamer Constructor
//
//==========================================================================
MIDIStreamer::MIDIStreamer(EMidiDevice type, const char *args)
:
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), Args(args)
{
memset(Buffer, 0, sizeof(Buffer));
}
//==========================================================================
//
// MIDIStreamer OPL Dumping Constructor
//
//==========================================================================
MIDIStreamer::MIDIStreamer(const char *dumpname, EMidiDevice type)
:
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname)
{
memset(Buffer, 0, sizeof(Buffer));
}
//==========================================================================
//
// MIDIStreamer Destructor
//
//==========================================================================
MIDIStreamer::~MIDIStreamer()
{
Stop();
if (MIDI != NULL)
{
delete MIDI;
}
}
//==========================================================================
//
// MIDIStreamer :: IsMIDI
//
// You bet it is!
//
//==========================================================================
bool MIDIStreamer::IsMIDI() const
{
return true;
}
//==========================================================================
//
// MIDIStreamer :: IsValid
//
//==========================================================================
bool MIDIStreamer::IsValid() const
{
return Division != 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)
{
}
//==========================================================================
//
// MIDIStreamer :: SelectMIDIDevice static
//
// Select the MIDI device to play on
//
//==========================================================================
EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
{
/* MIDI are played as:
- OPL:
- if explicitly selected by $mididevice
- when snd_mididevice is -3 and no midi device is set for the song
- Timidity:
- if explicitly selected by $mididevice
- when snd_mididevice is -2 and no midi device is set for the song
- MMAPI (Win32 only):
- if explicitly selected by $mididevice (non-Win32 redirects this to Sound System)
- when snd_mididevice is >= 0 and no midi device is set for the song
- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
*/
// Choose the type of MIDI device we want.
if (device != MDEV_DEFAULT)
{
return device;
}
switch (snd_mididevice)
{
case -1: return MDEV_SNDSYS;
case -2: return MDEV_TIMIDITY;
case -3: return MDEV_OPL;
case -4: return MDEV_GUS;
#ifdef HAVE_FLUIDSYNTH
case -5: return MDEV_FLUIDSYNTH;
#endif
case -6: return MDEV_WILDMIDI;
default:
#ifdef _WIN32
return MDEV_MMAPI;
#else
return MDEV_SNDSYS;
#endif
}
}
//==========================================================================
//
// MIDIStreamer :: CreateMIDIDevice
//
//==========================================================================
MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype)
{
switch (devtype)
{
case MDEV_MMAPI:
#ifdef _WIN32
return CreateWinMIDIDevice(mididevice);
#endif
assert(0);
// Intentional fall-through for non-Windows systems.
case MDEV_GUS:
return new TimidityMIDIDevice(Args);
#ifdef HAVE_FLUIDSYNTH
case MDEV_FLUIDSYNTH:
return new FluidSynthMIDIDevice(Args);
#endif
case MDEV_SNDSYS:
#ifdef HAVE_FLUIDSYNTH
return new FluidSynthMIDIDevice(nullptr);
#endif
// if no FluidSynth, fall through to OPL.
case MDEV_OPL:
try
{
return new OPLMIDIDevice(Args);
}
catch (CRecoverableError &err)
{
// The creation of an OPL MIDI device can abort with an error if no GENMIDI lump can be found.
Printf("Unable to create OPL MIDI device: %s\nFalling back to default playback", err.GetMessage());
#ifdef HAVE_FLUIDSYNTH
return new FluidSynthMIDIDevice(nullptr);
#else
// Someone dared to compile GZDoom without FluidSynth support and then started an IWAD without GENMIDI support. Ugh...
return nullptr;
#endif
}
case MDEV_TIMIDITY:
return CreateTimidityPPMIDIDevice(Args);
case MDEV_WILDMIDI:
return new WildMIDIDevice(Args);
default:
return NULL;
}
}
//==========================================================================
//
// MIDIStreamer :: Play
//
//==========================================================================
void MIDIStreamer::Play(bool looping, int subsong)
{
EMidiDevice devtype;
m_Status = STATE_Stopped;
m_Looping = looping;
EndQueued = 0;
VolumeChanged = false;
Restarting = true;
InitialPlayback = true;
assert(MIDI == NULL);
devtype = SelectMIDIDevice(DeviceType);
if (DumpFilename.IsNotEmpty())
{
if (devtype == MDEV_OPL)
{
MIDI = new OPLDumperMIDIDevice(DumpFilename);
}
else if (devtype == MDEV_GUS)
{
MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0);
}
}
else
{
MIDI = CreateMIDIDevice(devtype);
}
if (MIDI == NULL || 0 != MIDI->Open(Callback, this))
{
Printf(PRINT_BOLD, "Could not open MIDI out device\n");
if (MIDI != NULL)
{
delete MIDI;
MIDI = NULL;
}
return;
}
SetMIDISubsong(subsong);
CheckCaps(MIDI->GetTechnology());
if (MIDI->Preprocess(this, looping))
{
StartPlayback();
if (MIDI == NULL)
{ // The MIDI file had no content and has been automatically closed.
return;
}
}
if (0 != MIDI->Resume())
{
Printf ("Starting MIDI playback failed\n");
Stop();
}
else
{
m_Status = STATE_Playing;
}
}
//==========================================================================
//
// MIDIStreamer :: StartPlayback
//
//==========================================================================
void MIDIStreamer::StartPlayback()
{
Precache();
LoopLimit = 0;
// Set time division and tempo.
if (0 != MIDI->SetTimeDiv(Division) ||
0 != MIDI->SetTempo(Tempo = InitialTempo))
{
Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n");
MIDI->Close();
return;
}
MusicVolumeChanged(); // set volume to current music's properties
OutputVolume(Volume);
MIDI->InitPlayback();
// Fill the initial buffers for the song.
BufferNum = 0;
do
{
int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
if (res == SONG_MORE)
{
if (0 != MIDI->StreamOutSync(&Buffer[BufferNum]))
{
Printf ("Initial midiStreamOut failed\n");
Stop();
return;
}
BufferNum ^= 1;
}
else if (res == SONG_DONE)
{
// Do not play super short songs that can't fill the initial two buffers.
Stop();
return;
}
else
{
Stop();
return;
}
}
while (BufferNum != 0);
}
//==========================================================================
//
// MIDIStreamer :: Pause
//
// "Pauses" the song by setting it to zero volume and filling subsequent
// buffers with NOPs until the song is unpaused. A MIDI device that
// supports real pauses will return true from its Pause() method.
//
//==========================================================================
void MIDIStreamer::Pause()
{
if (m_Status == STATE_Playing)
{
m_Status = STATE_Paused;
if (!MIDI->Pause(true))
{
OutputVolume(0);
}
}
}
//==========================================================================
//
// MIDIStreamer :: Resume
//
// "Unpauses" a song by restoring the volume and letting subsequent
// buffers store real MIDI events again.
//
//==========================================================================
void MIDIStreamer::Resume()
{
if (m_Status == STATE_Paused)
{
if (!MIDI->Pause(false))
{
OutputVolume(Volume);
}
m_Status = STATE_Playing;
}
}
//==========================================================================
//
// MIDIStreamer :: Stop
//
// Stops playback and closes the player thread and MIDI device.
//
//==========================================================================
void MIDIStreamer::Stop()
{
EndQueued = 4;
if (MIDI != NULL && MIDI->IsOpen())
{
MIDI->Stop();
MIDI->UnprepareHeader(&Buffer[0]);
MIDI->UnprepareHeader(&Buffer[1]);
MIDI->Close();
}
if (MIDI != NULL)
{
delete MIDI;
MIDI = NULL;
}
m_Status = STATE_Stopped;
}
//==========================================================================
//
// MIDIStreamer :: IsPlaying
//
//==========================================================================
bool MIDIStreamer::IsPlaying()
{
if (m_Status != STATE_Stopped && (MIDI == NULL || (EndQueued != 0 && EndQueued < 4)))
{
Stop();
}
if (m_Status != STATE_Stopped && !MIDI->IsOpen())
{
Stop();
}
return m_Status != STATE_Stopped;
}
//==========================================================================
//
// MIDIStreamer :: MusicVolumeChanged
//
// WinMM MIDI doesn't go through the sound system, so the normal volume
// changing procedure doesn't work for it.
//
//==========================================================================
void MIDIStreamer::MusicVolumeChanged()
{
if (MIDI != NULL && MIDI->FakeVolume())
{
float realvolume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f);
Volume = clamp<uint32_t>((uint32_t)(realvolume * 65535.f), 0, 65535);
}
else
{
Volume = 0xFFFF;
}
if (m_Status == STATE_Playing)
{
OutputVolume(Volume);
}
}
//==========================================================================
//
// MIDIStreamer :: TimidityVolumeChanged
//
//==========================================================================
void MIDIStreamer::TimidityVolumeChanged()
{
if (MIDI != NULL)
{
MIDI->TimidityVolumeChanged();
}
}
//==========================================================================
//
// MIDIStreamer :: FluidSettingInt
//
//==========================================================================
void MIDIStreamer::FluidSettingInt(const char *setting, int value)
{
if (MIDI != NULL)
{
MIDI->FluidSettingInt(setting, value);
}
}
//==========================================================================
//
// MIDIStreamer :: FluidSettingNum
//
//==========================================================================
void MIDIStreamer::FluidSettingNum(const char *setting, double value)
{
if (MIDI != NULL)
{
MIDI->FluidSettingNum(setting, value);
}
}
//==========================================================================
//
// MIDIDeviceStreamer :: FluidSettingStr
//
//==========================================================================
void MIDIStreamer::FluidSettingStr(const char *setting, const char *value)
{
if (MIDI != NULL)
{
MIDI->FluidSettingStr(setting, value);
}
}
//==========================================================================
//
// MIDIDeviceStreamer :: WildMidiSetOption
//
//==========================================================================
void MIDIStreamer::WildMidiSetOption(int opt, int set)
{
if (MIDI != NULL)
{
MIDI->WildMidiSetOption(opt, set);
}
}
//==========================================================================
//
// MIDIStreamer :: OutputVolume
//
// Signals the buffer filler to send volume change events on all channels.
//
//==========================================================================
void MIDIStreamer::OutputVolume (uint32_t volume)
{
if (MIDI != NULL && MIDI->FakeVolume())
{
NewVolume = volume;
VolumeChanged = true;
}
}
//==========================================================================
//
// 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
//
//==========================================================================
void MIDIStreamer::Callback(void *userdata)
{
MIDIStreamer *self = (MIDIStreamer *)userdata;
if (self->EndQueued >= 4)
{
return;
}
self->ServiceEvent();
}
//==========================================================================
//
// MIDIStreamer :: Update
//
// Called periodically to see if the player thread is still alive. If it
// isn't, stop playback now.
//
//==========================================================================
void MIDIStreamer::Update()
{
if (MIDI != nullptr && !MIDI->Update()) Stop();
}
//==========================================================================
//
// MIDIStreamer :: ServiceEvent
//
// Fills the buffer that just finished playing with new events and appends
// it to the MIDI stream queue. Stops the song if playback is over. Returns
// non-zero if a problem occured and playback should stop.
//
//==========================================================================
int MIDIStreamer::ServiceEvent()
{
int res;
if (EndQueued == 2)
{
return 0;
}
if (0 != (res = MIDI->UnprepareHeader(&Buffer[BufferNum])))
{
return res;
}
fill:
if (EndQueued == 1)
{
res = FillStopBuffer(BufferNum);
if ((res & 3) != SONG_ERROR)
{
EndQueued = 2;
}
}
else
{
res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME);
}
switch (res & 3)
{
case SONG_MORE:
res = MIDI->StreamOut(&Buffer[BufferNum]);
if (res != 0)
{
return res;
}
else
{
BufferNum ^= 1;
}
break;
case SONG_DONE:
if (m_Looping)
{
Restarting = true;
goto fill;
}
EndQueued = 1;
break;
default:
return res >> 2;
}
return 0;
}
//==========================================================================
//
// MIDIStreamer :: FillBuffer
//
// Copies MIDI events from the MIDI file and puts them into a MIDI stream
// buffer. Filling the buffer stops when the song end is encountered, the
// buffer space is used up, or the maximum time for a buffer is hit.
//
// Can return:
// - SONG_MORE if the buffer was prepared with data.
// - SONG_DONE if the song's end was reached.
// The buffer will never have data in this case.
// - SONG_ERROR if there was a problem preparing the buffer.
//
//==========================================================================
int MIDIStreamer::FillBuffer(int buffer_num, int max_events, uint32_t max_time)
{
if (!Restarting && CheckDone())
{
return SONG_DONE;
}
int i;
uint32_t *events = Events[buffer_num], *max_event_p;
uint32_t tot_time = 0;
uint32_t time = 0;
// The final event is for a NOP to hold the delay from the last event.
max_event_p = events + (max_events - 1) * 3;
if (InitialPlayback)
{
InitialPlayback = false;
// Send the full master volume SysEx message.
events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID
events[2] = (MEVENT_LONGMSG << 24) | 8; // dwEvent
events[3] = MAKE_ID(0xf0,0x7f,0x7f,0x04); // dwParms[0]
events[4] = MAKE_ID(0x01,0x7f,0x7f,0xf7); // dwParms[1]
events += 5;
DoInitialSetup();
}
// If the volume has changed, stick those events at the start of this buffer.
if (VolumeChanged && (m_Status != STATE_Paused || NewVolume == 0))
{
VolumeChanged = false;
for (i = 0; i < 16; ++i)
{
uint8_t courseVol = (uint8_t)(((ChannelVolumes[i]+1) * NewVolume) >> 16);
events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID
events[2] = MIDI_CTRLCHANGE | i | (7<<8) | (courseVol<<16);
events += 3;
}
}
// Play nothing while paused.
if (m_Status == STATE_Paused)
{
// 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[1] = 0;
events[2] = MEVENT_NOP << 24;
events += 3;
}
else
{
if (Restarting)
{
Restarting = false;
// Reset the tempo to the inital value.
events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID
events[2] = (MEVENT_TEMPO << 24) | InitialTempo; // dwEvent
events += 3;
// Stop all notes in case any were left hanging.
events = WriteStopNotes(events);
DoRestart();
}
events = MakeEvents(events, max_event_p, max_time);
}
memset(&Buffer[buffer_num], 0, sizeof(MidiHeader));
Buffer[buffer_num].lpData = (uint8_t *)Events[buffer_num];
Buffer[buffer_num].dwBufferLength = uint32_t((uint8_t *)events - Buffer[buffer_num].lpData);
Buffer[buffer_num].dwBytesRecorded = Buffer[buffer_num].dwBufferLength;
if (0 != (i = MIDI->PrepareHeader(&Buffer[buffer_num])))
{
return SONG_ERROR | (i << 2);
}
return SONG_MORE;
}
//==========================================================================
//
// MIDIStreamer :: FillStopBuffer
//
// Fills a MIDI buffer with events to stop all channels.
//
//==========================================================================
int MIDIStreamer::FillStopBuffer(int buffer_num)
{
uint32_t *events = Events[buffer_num];
int i;
events = WriteStopNotes(events);
// wait some tics, just so that this buffer takes some time
events[0] = 500;
events[1] = 0;
events[2] = MEVENT_NOP << 24;
events += 3;
memset(&Buffer[buffer_num], 0, sizeof(MidiHeader));
Buffer[buffer_num].lpData = (uint8_t*)Events[buffer_num];
Buffer[buffer_num].dwBufferLength = uint32_t((uint8_t*)events - Buffer[buffer_num].lpData);
Buffer[buffer_num].dwBytesRecorded = Buffer[buffer_num].dwBufferLength;
if (0 != (i = MIDI->PrepareHeader(&Buffer[buffer_num])))
{
return SONG_ERROR | (i << 2);
}
return SONG_MORE;
}
//==========================================================================
//
// MIDIStreamer :: WriteStopNotes
//
// Generates MIDI events to stop all notes and reset controllers on
// every channel.
//
//==========================================================================
uint32_t *MIDIStreamer::WriteStopNotes(uint32_t *events)
{
for (int i = 0; i < 16; ++i)
{
events[0] = 0; // dwDeltaTime
events[1] = 0; // dwStreamID
events[2] = MIDI_CTRLCHANGE | i | (123 << 8); // All notes off
events[3] = 0;
events[4] = 0;
events[5] = MIDI_CTRLCHANGE | i | (121 << 8); // Reset controllers
events += 6;
}
return 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()
{
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());
}
//==========================================================================
//
// MIDIStreamer :: CreateSMF
//
// Simulates playback to create a Standard MIDI File.
//
//==========================================================================
void MIDIStreamer::CreateSMF(TArray<uint8_t> &file, int looplimit)
{
uint32_t delay = 0;
uint8_t running_status = 255;
// Always create songs aimed at GM devices.
CheckCaps(MIDIDEV_MIDIPORT);
LoopLimit = looplimit <= 0 ? EXPORT_LOOP_LIMIT : looplimit;
DoRestart();
Tempo = InitialTempo;
file.Reserve(sizeof(StaticMIDIhead));
memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead));
file[12] = Division >> 8;
file[13] = Division & 0xFF;
file[26] = InitialTempo >> 16;
file[27] = InitialTempo >> 8;
file[28] = InitialTempo;
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; )
{
delay += event[0];
if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO)
{
WriteVarLen(file, delay);
delay = 0;
uint32_t tempo = MEVENT_EVENTPARM(event[2]);
file.Push(MIDI_META);
file.Push(MIDI_META_TEMPO);
file.Push(3);
file.Push(uint8_t(tempo >> 16));
file.Push(uint8_t(tempo >> 8));
file.Push(uint8_t(tempo));
running_status = 255;
}
else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG)
{
WriteVarLen(file, delay);
delay = 0;
uint32_t len = MEVENT_EVENTPARM(event[2]);
uint8_t *bytes = (uint8_t *)&event[3];
if (bytes[0] == MIDI_SYSEX)
{
len--;
file.Push(MIDI_SYSEX);
WriteVarLen(file, len);
memcpy(&file[file.Reserve(len)], bytes + 1, len);
}
else
{
file.Push(MIDI_SYSEXEND);
WriteVarLen(file, len);
memcpy(&file[file.Reserve(len)], bytes, len);
}
running_status = 255;
}
else if (MEVENT_EVENTTYPE(event[2]) == 0)
{
WriteVarLen(file, delay);
delay = 0;
uint8_t status = uint8_t(event[2]);
if (status != running_status)
{
running_status = status;
file.Push(status);
}
file.Push(uint8_t((event[2] >> 8) & 0x7F));
if (MIDI_EventLengths[(status >> 4) & 7] == 2)
{
file.Push(uint8_t((event[2] >> 16) & 0x7F));
}
}
// Advance to next event
if (event[2] < 0x80000000)
{ // short message
event += 3;
}
else
{ // long message
event += 3 + ((MEVENT_EVENTPARM(event[2]) + 3) >> 2);
}
}
}
// End track
WriteVarLen(file, delay);
file.Push(MIDI_META);
file.Push(MIDI_META_EOT);
file.Push(0);
// Fill in track length
uint32_t len = file.Size() - 22;
file[18] = uint8_t(len >> 24);
file[19] = uint8_t(len >> 16);
file[20] = uint8_t(len >> 8);
file[21] = uint8_t(len & 255);
LoopLimit = 0;
}
//==========================================================================
//
// WriteVarLen
//
//==========================================================================
static void WriteVarLen (TArray<uint8_t> &file, uint32_t value)
{
uint32_t buffer = value & 0x7F;
while ( (value >>= 7) )
{
buffer <<= 8;
buffer |= (value & 0x7F) | 0x80;
}
for (;;)
{
file.Push(uint8_t(buffer));
if (buffer & 0x80)
{
buffer >>= 8;
}
else
{
break;
}
}
}
//==========================================================================
//
// 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;
}
//==========================================================================
//
// MIDIStreamer :: GetStats
//
//==========================================================================
FString MIDIStreamer::GetStats()
{
if (MIDI == NULL)
{
return "No MIDI device in use.";
}
return MIDI->GetStats();
}
//==========================================================================
//
// MIDIStreamer :: SetSubsong
//
// Selects which subsong to play in an already-playing file. This is public.
//
//==========================================================================
bool MIDIStreamer::SetSubsong(int subsong)
{
if (SetMIDISubsong(subsong))
{
Stop();
Play(m_Looping, subsong);
return true;
}
return false;
}
//==========================================================================
//
// MIDIStreamer :: SetMIDISubsong
//
// Selects which subsong to play. This is private.
//
//==========================================================================
bool MIDIStreamer::SetMIDISubsong(int subsong)
{
return subsong == 0;
}
//==========================================================================
//
// MIDIDevice stubs.
//
//==========================================================================
MIDIDevice::MIDIDevice()
{
}
MIDIDevice::~MIDIDevice()
{
}
//==========================================================================
//
// MIDIDevice :: PrecacheInstruments
//
// The MIDIStreamer calls this method between device open and the first
// buffered stream with a list of instruments known to be used by the song.
// If the device can benefit from preloading the instruments, it can do so
// now.
//
// Each entry is packed as follows:
// Bits 0- 6: Instrument number
// Bits 7-13: Bank number
// Bit 14: Select drum set if 1, tone bank if 0
//
//==========================================================================
void MIDIDevice::PrecacheInstruments(const uint16_t *instruments, int count)
{
}
//==========================================================================
//
// MIDIDevice :: Preprocess
//
// Gives the MIDI device a chance to do some processing with the song before
// it starts playing it. Returns true if MIDIStreamer should perform its
// standard playback startup sequence.
//
//==========================================================================
bool MIDIDevice::Preprocess(MIDIStreamer *song, bool looping)
{
return true;
}
//==========================================================================
//
// MIDIDevice :: PrepareHeader
//
// Wrapper for MCI's midiOutPrepareHeader.
//
//==========================================================================
int MIDIDevice::PrepareHeader(MidiHeader *header)
{
return 0;
}
//==========================================================================
//
// MIDIDevice :: UnprepareHeader
//
// Wrapper for MCI's midiOutUnprepareHeader.
//
//==========================================================================
int MIDIDevice::UnprepareHeader(MidiHeader *header)
{
return 0;
}
//==========================================================================
//
// MIDIDevice :: FakeVolume
//
// Since most implementations render as a normal stream, their volume is
// controlled through the GSnd interface, not here.
//
//==========================================================================
bool MIDIDevice::FakeVolume()
{
return false;
}
//==========================================================================
//
//
//
//==========================================================================
void MIDIDevice::InitPlayback()
{
}
//==========================================================================
//
//
//
//==========================================================================
bool MIDIDevice::Update()
{
return true;
}
//==========================================================================
//
// MIDIDevice :: TimidityVolumeChanged
//
//==========================================================================
void MIDIDevice::TimidityVolumeChanged()
{
}
//==========================================================================
//
// MIDIDevice :: FluidSettingInt
//
//==========================================================================
void MIDIDevice::FluidSettingInt(const char *setting, int value)
{
}
//==========================================================================
//
// MIDIDevice :: FluidSettingNum
//
//==========================================================================
void MIDIDevice::FluidSettingNum(const char *setting, double value)
{
}
//==========================================================================
//
// MIDIDevice :: FluidSettingStr
//
//==========================================================================
void MIDIDevice::FluidSettingStr(const char *setting, const char *value)
{
}
//==========================================================================
//
// MIDIDevice :: WildMidiSetOption
//
//==========================================================================
void MIDIDevice::WildMidiSetOption(int opt, int set)
{
}
//==========================================================================
//
// MIDIDevice :: GetStats
//
//==========================================================================
FString MIDIDevice::GetStats()
{
return "This MIDI device does not have any stats.";
}