mirror of https://github.com/ZDoom/gzdoom.git
- Added the writewave command to write the internal TiMidity's output to a
wave file. - Changed the default channel velocity for MUS files from 64 to 100 to better match apparent MIDI practice. (Would like to know what this is supposed to be.) - Changed the mus2midi channel assignments to match the internal player's. - Fixed: apply_envelope_to_amp() should clamp the mix levels to 0. SVN r926 (trunk)
This commit is contained in:
parent
f6866df0e6
commit
effe9427fd
|
@ -1,3 +1,12 @@
|
||||||
|
April 19, 2008
|
||||||
|
- Added the writewave command to write the internal TiMidity's output to a
|
||||||
|
wave file.
|
||||||
|
- Changed the default channel velocity for MUS files from 64 to 100 to
|
||||||
|
better match apparent MIDI practice. (Would like to know what this is
|
||||||
|
supposed to be.)
|
||||||
|
- Changed the mus2midi channel assignments to match the internal player's.
|
||||||
|
- Fixed: apply_envelope_to_amp() should clamp the mix levels to 0.
|
||||||
|
|
||||||
April 18, 2008
|
April 18, 2008
|
||||||
- Made the maximum number of TiMidity voices configurable through the
|
- Made the maximum number of TiMidity voices configurable through the
|
||||||
timidity_voices cvar.
|
timidity_voices cvar.
|
||||||
|
|
|
@ -33,10 +33,7 @@
|
||||||
** MUS files are essentially format 0 MIDI files with some
|
** MUS files are essentially format 0 MIDI files with some
|
||||||
** space-saving modifications. Conversion is quite straight-forward.
|
** space-saving modifications. Conversion is quite straight-forward.
|
||||||
** If you were to hook a main() into this that calls ProduceMIDI,
|
** If you were to hook a main() into this that calls ProduceMIDI,
|
||||||
** you could create a self-contained MUS->MIDI converter. However, if
|
** you could create a self-contained MUS->MIDI converter.
|
||||||
** you want to do that, you would be better off using qmus2mid, since
|
|
||||||
** it creates multitrack files that usually maintain running status
|
|
||||||
** better than single track files and are thus smaller.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,9 +49,7 @@ static const BYTE StaticMIDIhead[] =
|
||||||
0, 70, // 70 divisions
|
0, 70, // 70 divisions
|
||||||
'M','T','r','k', 0, 0, 0, 0,
|
'M','T','r','k', 0, 0, 0, 0,
|
||||||
// The first event sets the tempo to 500,000 microsec/quarter note
|
// The first event sets the tempo to 500,000 microsec/quarter note
|
||||||
0, 255, 81, 3, 0x07, 0xa1, 0x20,
|
0, 255, 81, 3, 0x07, 0xa1, 0x20
|
||||||
// Set the percussion channel to full volume
|
|
||||||
0, 0xB9, 7, 127
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const BYTE MUSMagic[4] = { 'M','U','S',0x1a };
|
static const BYTE MUSMagic[4] = { 'M','U','S',0x1a };
|
||||||
|
@ -122,9 +117,8 @@ bool ProduceMIDI (const BYTE *musBuf, TArray<BYTE> &outFile)
|
||||||
int deltaTime;
|
int deltaTime;
|
||||||
const MUSHeader *musHead = (const MUSHeader *)musBuf;
|
const MUSHeader *musHead = (const MUSHeader *)musBuf;
|
||||||
BYTE status;
|
BYTE status;
|
||||||
|
BYTE chanUsed[16];
|
||||||
BYTE lastVel[16];
|
BYTE lastVel[16];
|
||||||
SBYTE chanMap[16];
|
|
||||||
int chanCount;
|
|
||||||
long trackLen;
|
long trackLen;
|
||||||
|
|
||||||
// Do some validation of the MUS file
|
// Do some validation of the MUS file
|
||||||
|
@ -143,10 +137,8 @@ bool ProduceMIDI (const BYTE *musBuf, TArray<BYTE> &outFile)
|
||||||
maxmus_p = LittleShort(musHead->SongLen);
|
maxmus_p = LittleShort(musHead->SongLen);
|
||||||
mus_p = 0;
|
mus_p = 0;
|
||||||
|
|
||||||
memset (lastVel, 64, 16);
|
memset (lastVel, 100, 16);
|
||||||
memset (chanMap, -1, 15);
|
memset (chanUsed, 0, 16);
|
||||||
chanMap[15] = 9;
|
|
||||||
chanCount = 0;
|
|
||||||
event = 0;
|
event = 0;
|
||||||
deltaTime = 0;
|
deltaTime = 0;
|
||||||
status = 0;
|
status = 0;
|
||||||
|
@ -163,21 +155,27 @@ bool ProduceMIDI (const BYTE *musBuf, TArray<BYTE> &outFile)
|
||||||
t = musBuf[mus_p++];
|
t = musBuf[mus_p++];
|
||||||
}
|
}
|
||||||
channel = event & 15;
|
channel = event & 15;
|
||||||
|
if (channel == 15)
|
||||||
|
{
|
||||||
|
channel = 9;
|
||||||
|
}
|
||||||
|
else if (channel >= 9)
|
||||||
|
{
|
||||||
|
channel++;
|
||||||
|
}
|
||||||
|
|
||||||
if (chanMap[channel] < 0)
|
if (!chanUsed[channel])
|
||||||
{
|
{
|
||||||
// This is the first time this channel has been used,
|
// This is the first time this channel has been used,
|
||||||
// so sets its volume to 127.
|
// so sets its volume to 127.
|
||||||
|
chanUsed[channel] = 1;
|
||||||
outFile.Push(0);
|
outFile.Push(0);
|
||||||
outFile.Push(0xB0 | chanCount);
|
outFile.Push(0xB0 | channel);
|
||||||
outFile.Push(7);
|
outFile.Push(7);
|
||||||
outFile.Push(127);
|
outFile.Push(127);
|
||||||
chanMap[channel] = chanCount++;
|
|
||||||
if (chanCount == 9)
|
|
||||||
++chanCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
midStatus = channel = chanMap[channel];
|
midStatus = channel;
|
||||||
midArgs = 0; // Most events have two args (0 means 2, 1 means 1)
|
midArgs = 0; // Most events have two args (0 means 2, 1 means 1)
|
||||||
|
|
||||||
switch (event & 0x70)
|
switch (event & 0x70)
|
||||||
|
|
|
@ -72,7 +72,6 @@ extern HWND Window;
|
||||||
|
|
||||||
#define SPECTRUM_SIZE 256
|
#define SPECTRUM_SIZE 256
|
||||||
|
|
||||||
|
|
||||||
// TYPES -------------------------------------------------------------------
|
// TYPES -------------------------------------------------------------------
|
||||||
|
|
||||||
struct FEnumList
|
struct FEnumList
|
||||||
|
|
|
@ -151,6 +151,11 @@ MusInfo *MusInfo::GetOPLDumper(const char *filename)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void I_InitMusic (void)
|
void I_InitMusic (void)
|
||||||
{
|
{
|
||||||
static bool setatterm = false;
|
static bool setatterm = false;
|
||||||
|
@ -629,3 +634,41 @@ CCMD (writeopl)
|
||||||
Printf ("Usage: writeopl <filename>");
|
Printf ("Usage: writeopl <filename>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CCMD writewave
|
||||||
|
//
|
||||||
|
// If the current song can be represented as a waveform, dump it to
|
||||||
|
// the specified file on disk. The sample rate parameter is merely a
|
||||||
|
// suggestion, and the dumper is free to ignore it.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CCMD (writewave)
|
||||||
|
{
|
||||||
|
if (argv.argc() >= 2 && argv.argc() <= 3)
|
||||||
|
{
|
||||||
|
if (currSong == NULL)
|
||||||
|
{
|
||||||
|
Printf ("No song is currently playing.\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MusInfo *dumper = currSong->GetWaveDumper(argv[1], argv.argc() == 3 ? atoi(argv[2]) : 0);
|
||||||
|
if (dumper == NULL)
|
||||||
|
{
|
||||||
|
Printf ("Current song cannot be saved as wave data.\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dumper->Play(false);
|
||||||
|
delete dumper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf ("Usage: writewave <filename> [sample rate]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ public:
|
||||||
virtual void Update();
|
virtual void Update();
|
||||||
virtual FString GetStats();
|
virtual FString GetStats();
|
||||||
virtual MusInfo *GetOPLDumper(const char *filename);
|
virtual MusInfo *GetOPLDumper(const char *filename);
|
||||||
|
virtual MusInfo *GetWaveDumper(const char *filename, int rate);
|
||||||
|
|
||||||
enum EState
|
enum EState
|
||||||
{
|
{
|
||||||
|
@ -225,6 +226,7 @@ class TimidityMIDIDevice : public MIDIDevice
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TimidityMIDIDevice();
|
TimidityMIDIDevice();
|
||||||
|
TimidityMIDIDevice(int rate);
|
||||||
~TimidityMIDIDevice();
|
~TimidityMIDIDevice();
|
||||||
|
|
||||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||||
|
@ -268,6 +270,20 @@ protected:
|
||||||
DWORD Position;
|
DWORD Position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Internal TiMidity disk writing version of a MIDI device ------------------
|
||||||
|
|
||||||
|
class TimidityWaveWriterMIDIDevice : public TimidityMIDIDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TimidityWaveWriterMIDIDevice(const char *filename, int rate);
|
||||||
|
~TimidityWaveWriterMIDIDevice();
|
||||||
|
int Resume();
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FILE *File;
|
||||||
|
};
|
||||||
|
|
||||||
// Base class for streaming MUS and MIDI files ------------------------------
|
// Base class for streaming MUS and MIDI files ------------------------------
|
||||||
|
|
||||||
// MIDI device selection.
|
// MIDI device selection.
|
||||||
|
@ -297,7 +313,7 @@ public:
|
||||||
FString GetStats();
|
FString GetStats();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MIDIStreamer(const char *dumpname);
|
MIDIStreamer(const char *dumpname, EMIDIDevice type);
|
||||||
|
|
||||||
void OutputVolume (DWORD volume);
|
void OutputVolume (DWORD volume);
|
||||||
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
|
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
|
||||||
|
@ -363,9 +379,10 @@ public:
|
||||||
~MUSSong2();
|
~MUSSong2();
|
||||||
|
|
||||||
MusInfo *GetOPLDumper(const char *filename);
|
MusInfo *GetOPLDumper(const char *filename);
|
||||||
|
MusInfo *GetWaveDumper(const char *filename, int rate);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MUSSong2(const MUSSong2 *original, const char *filename); //OPL dump constructor
|
MUSSong2(const MUSSong2 *original, const char *filename, EMIDIDevice type); // file dump constructor
|
||||||
|
|
||||||
void DoInitialSetup();
|
void DoInitialSetup();
|
||||||
void DoRestart();
|
void DoRestart();
|
||||||
|
@ -388,9 +405,10 @@ public:
|
||||||
~MIDISong2();
|
~MIDISong2();
|
||||||
|
|
||||||
MusInfo *GetOPLDumper(const char *filename);
|
MusInfo *GetOPLDumper(const char *filename);
|
||||||
|
MusInfo *GetWaveDumper(const char *filename, int rate);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MIDISong2(const MIDISong2 *original, const char *filename); // OPL dump constructor
|
MIDISong2(const MIDISong2 *original, const char *filename, EMIDIDevice type); // file dump constructor
|
||||||
|
|
||||||
void CheckCaps();
|
void CheckCaps();
|
||||||
void DoInitialSetup();
|
void DoInitialSetup();
|
||||||
|
|
|
@ -891,17 +891,28 @@ void MIDISong2::Precache()
|
||||||
|
|
||||||
MusInfo *MIDISong2::GetOPLDumper(const char *filename)
|
MusInfo *MIDISong2::GetOPLDumper(const char *filename)
|
||||||
{
|
{
|
||||||
return new MIDISong2(this, filename);
|
return new MIDISong2(this, filename, MIDI_OPL);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
// MIDISong2 OPL Dumping Constructor
|
// MIDISong2 :: GetWaveDumper
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
MIDISong2::MIDISong2(const MIDISong2 *original, const char *filename)
|
MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate)
|
||||||
: MIDIStreamer(filename)
|
{
|
||||||
|
return new MIDISong2(this, filename, MIDI_Timidity);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MIDISong2 File Dumping Constructor
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
MIDISong2::MIDISong2(const MIDISong2 *original, const char *filename, EMIDIDevice type)
|
||||||
|
: MIDIStreamer(filename, type)
|
||||||
{
|
{
|
||||||
SongLen = original->SongLen;
|
SongLen = original->SongLen;
|
||||||
MusHeader = new BYTE[original->SongLen];
|
MusHeader = new BYTE[original->SongLen];
|
||||||
|
|
|
@ -97,12 +97,12 @@ MIDIStreamer::MIDIStreamer(EMIDIDevice type)
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
MIDIStreamer::MIDIStreamer(const char *dumpname)
|
MIDIStreamer::MIDIStreamer(const char *dumpname, EMIDIDevice type)
|
||||||
:
|
:
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
|
PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
|
||||||
#endif
|
#endif
|
||||||
MIDI(0), Division(0), InitialTempo(500000), DeviceType(MIDI_OPL), DumpFilename(dumpname)
|
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
BufferDoneEvent = NULL;
|
BufferDoneEvent = NULL;
|
||||||
|
@ -196,7 +196,14 @@ void MIDIStreamer::Play(bool looping)
|
||||||
assert(MIDI == NULL);
|
assert(MIDI == NULL);
|
||||||
if (DumpFilename.IsNotEmpty())
|
if (DumpFilename.IsNotEmpty())
|
||||||
{
|
{
|
||||||
MIDI = new OPLDumperMIDIDevice(DumpFilename);
|
if (DeviceType == MIDI_OPL)
|
||||||
|
{
|
||||||
|
MIDI = new OPLDumperMIDIDevice(DumpFilename);
|
||||||
|
}
|
||||||
|
else if (DeviceType == MIDI_Timidity)
|
||||||
|
{
|
||||||
|
MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else switch(DeviceType)
|
else switch(DeviceType)
|
||||||
{
|
{
|
||||||
|
|
|
@ -150,7 +150,7 @@ void MUSSong2::DoInitialSetup()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 16; ++i)
|
for (int i = 0; i < 16; ++i)
|
||||||
{
|
{
|
||||||
LastVelocity[i] = 64;
|
LastVelocity[i] = 100;
|
||||||
ChannelVolumes[i] = 127;
|
ChannelVolumes[i] = 127;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,7 +340,18 @@ end:
|
||||||
|
|
||||||
MusInfo *MUSSong2::GetOPLDumper(const char *filename)
|
MusInfo *MUSSong2::GetOPLDumper(const char *filename)
|
||||||
{
|
{
|
||||||
return new MUSSong2(this, filename);
|
return new MUSSong2(this, filename, MIDI_OPL);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// MUSSong2 :: GetWaveDumper
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
MusInfo *MUSSong2::GetWaveDumper(const char *filename, int rate)
|
||||||
|
{
|
||||||
|
return new MUSSong2(this, filename, MIDI_Timidity);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
@ -349,8 +360,8 @@ MusInfo *MUSSong2::GetOPLDumper(const char *filename)
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
MUSSong2::MUSSong2(const MUSSong2 *original, const char *filename)
|
MUSSong2::MUSSong2(const MUSSong2 *original, const char *filename, EMIDIDevice type)
|
||||||
: MIDIStreamer(filename)
|
: MIDIStreamer(filename, type)
|
||||||
{
|
{
|
||||||
int songstart = LittleShort(original->MusHeader->SongStart);
|
int songstart = LittleShort(original->MusHeader->SongStart);
|
||||||
MaxMusP = original->MaxMusP;
|
MaxMusP = original->MaxMusP;
|
||||||
|
|
|
@ -44,6 +44,27 @@
|
||||||
|
|
||||||
// MACROS ------------------------------------------------------------------
|
// MACROS ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// TYPES -------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct FmtChunk
|
||||||
|
{
|
||||||
|
DWORD ChunkID;
|
||||||
|
DWORD ChunkLen;
|
||||||
|
WORD FormatTag;
|
||||||
|
WORD Channels;
|
||||||
|
DWORD SamplesPerSec;
|
||||||
|
DWORD AvgBytesPerSec;
|
||||||
|
WORD BlockAlign;
|
||||||
|
WORD BitsPerSample;
|
||||||
|
WORD ExtensionSize;
|
||||||
|
WORD ValidBitsPerSample;
|
||||||
|
DWORD ChannelMask;
|
||||||
|
DWORD SubFormatA;
|
||||||
|
WORD SubFormatB;
|
||||||
|
WORD SubFormatC;
|
||||||
|
BYTE SubFormatD[8];
|
||||||
|
};
|
||||||
|
|
||||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||||
|
|
||||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||||
|
@ -77,6 +98,26 @@ TimidityMIDIDevice::TimidityMIDIDevice()
|
||||||
Renderer = new Timidity::Renderer(GSnd->GetOutputRate());
|
Renderer = new Timidity::Renderer(GSnd->GetOutputRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// TimidityMIDIDevice Constructor with rate parameter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
TimidityMIDIDevice::TimidityMIDIDevice(int rate)
|
||||||
|
{
|
||||||
|
// Need to support multiple instances with different playback rates
|
||||||
|
// before we can use this parameter.
|
||||||
|
rate = (int)GSnd->GetOutputRate();
|
||||||
|
Stream = NULL;
|
||||||
|
Tempo = 0;
|
||||||
|
Division = 0;
|
||||||
|
Events = NULL;
|
||||||
|
Started = false;
|
||||||
|
Renderer = NULL;
|
||||||
|
Renderer = new Timidity::Renderer((float)rate);
|
||||||
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
// TimidityMIDIDevice Destructor
|
// TimidityMIDIDevice Destructor
|
||||||
|
@ -509,7 +550,7 @@ bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes)
|
||||||
{ // end of song
|
{ // end of song
|
||||||
if (numsamples > 0)
|
if (numsamples > 0)
|
||||||
{
|
{
|
||||||
Renderer->ComputeOutput(samples1, samplesleft);
|
Renderer->ComputeOutput(samples1, numsamples);
|
||||||
}
|
}
|
||||||
res = false;
|
res = false;
|
||||||
break;
|
break;
|
||||||
|
@ -521,6 +562,10 @@ bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Events == NULL)
|
||||||
|
{
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
CritSec.Leave();
|
CritSec.Leave();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -596,3 +641,124 @@ FString TimidityMIDIDevice::GetStats()
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// TimidityWaveWriterMIDIDevice Constructor
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
TimidityWaveWriterMIDIDevice::TimidityWaveWriterMIDIDevice(const char *filename, int rate)
|
||||||
|
{
|
||||||
|
File = fopen(filename, "wb");
|
||||||
|
if (File != NULL)
|
||||||
|
{ // Write wave header
|
||||||
|
DWORD work[3];
|
||||||
|
FmtChunk fmt;
|
||||||
|
|
||||||
|
work[0] = MAKE_ID('R','I','F','F');
|
||||||
|
work[1] = 0; // filled in later
|
||||||
|
work[2] = MAKE_ID('W','A','V','E');
|
||||||
|
if (3 != fwrite(work, 4, 3, File)) goto fail;
|
||||||
|
|
||||||
|
fmt.ChunkID = MAKE_ID('f','m','t',' ');
|
||||||
|
fmt.ChunkLen = LittleLong(sizeof(fmt) - 8);
|
||||||
|
fmt.FormatTag = LittleShort(0xFFFE); // WAVE_FORMAT_EXTENSIBLE
|
||||||
|
fmt.Channels = LittleShort(2);
|
||||||
|
fmt.SamplesPerSec = LittleLong((int)Renderer->rate);
|
||||||
|
fmt.AvgBytesPerSec = LittleLong((int)Renderer->rate * 8);
|
||||||
|
fmt.BlockAlign = LittleShort(8);
|
||||||
|
fmt.BitsPerSample = LittleShort(32);
|
||||||
|
fmt.ExtensionSize = LittleShort(2 + 4 + 16);
|
||||||
|
fmt.ValidBitsPerSample = LittleShort(32);
|
||||||
|
fmt.ChannelMask = LittleLong(3);
|
||||||
|
fmt.SubFormatA = LittleLong(0x00000003); // Set subformat to KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
|
||||||
|
fmt.SubFormatB = LittleShort(0x0000);
|
||||||
|
fmt.SubFormatC = LittleShort(0x0010);
|
||||||
|
fmt.SubFormatD[0] = 0x80;
|
||||||
|
fmt.SubFormatD[1] = 0x00;
|
||||||
|
fmt.SubFormatD[2] = 0x00;
|
||||||
|
fmt.SubFormatD[3] = 0xaa;
|
||||||
|
fmt.SubFormatD[4] = 0x00;
|
||||||
|
fmt.SubFormatD[5] = 0x38;
|
||||||
|
fmt.SubFormatD[6] = 0x9b;
|
||||||
|
fmt.SubFormatD[7] = 0x71;
|
||||||
|
if (1 != fwrite(&fmt, sizeof(fmt), 1, File)) goto fail;
|
||||||
|
|
||||||
|
work[0] = MAKE_ID('d','a','t','a');
|
||||||
|
work[1] = 0; // filled in later
|
||||||
|
if (2 != fwrite(work, 4, 2, File)) goto fail;
|
||||||
|
|
||||||
|
return;
|
||||||
|
fail:
|
||||||
|
Printf("Failed to write %s: %s\n", filename, strerror(errno));
|
||||||
|
fclose(File);
|
||||||
|
File = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// TimidityWaveWriterMIDIDevice Destructor
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
TimidityWaveWriterMIDIDevice::~TimidityWaveWriterMIDIDevice()
|
||||||
|
{
|
||||||
|
if (File != NULL)
|
||||||
|
{
|
||||||
|
long pos = ftell(File);
|
||||||
|
DWORD size;
|
||||||
|
|
||||||
|
// data chunk size
|
||||||
|
size = LittleLong(pos - 8);
|
||||||
|
if (0 == fseek(File, 4, SEEK_SET))
|
||||||
|
{
|
||||||
|
if (1 == fwrite(&size, 4, 1, File))
|
||||||
|
{
|
||||||
|
size = LittleLong(pos - 12 - sizeof(FmtChunk) - 8);
|
||||||
|
if (0 == fseek(File, 4 + sizeof(FmtChunk) + 4, SEEK_CUR))
|
||||||
|
{
|
||||||
|
if (1 == fwrite(&size, 4, 1, File))
|
||||||
|
{
|
||||||
|
fclose(File);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Printf("Could not finish writing wave file: %s\n", strerror(errno));
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// TimidityWaveWriterMIDIDevice :: Resume
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int TimidityWaveWriterMIDIDevice::Resume()
|
||||||
|
{
|
||||||
|
float writebuffer[4096];
|
||||||
|
|
||||||
|
while (ServiceStream(writebuffer, sizeof(writebuffer)))
|
||||||
|
{
|
||||||
|
if (fwrite(writebuffer, sizeof(writebuffer), 1, File) != 1)
|
||||||
|
{
|
||||||
|
Printf("Could not write entire wave file: %s\n", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// TimidityWaveWriterMIDIDevice Stop
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void TimidityWaveWriterMIDIDevice::Stop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
|
@ -82,8 +82,8 @@ void apply_envelope_to_amp(Voice *v)
|
||||||
env_vol *= v->envelope_volume / float(1 << 30);
|
env_vol *= v->envelope_volume / float(1 << 30);
|
||||||
}
|
}
|
||||||
// Note: The pan offsets are negative.
|
// Note: The pan offsets are negative.
|
||||||
v->left_mix = (float)calc_gf1_amp(env_vol + v->left_offset) * final_amp;
|
v->left_mix = MAX(0.f, (float)calc_gf1_amp(env_vol + v->left_offset) * final_amp);
|
||||||
v->right_mix = (float)calc_gf1_amp(env_vol + v->right_offset) * final_amp;
|
v->right_mix = MAX(0.f, (float)calc_gf1_amp(env_vol + v->right_offset) * final_amp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int update_envelope(Voice *v)
|
static int update_envelope(Voice *v)
|
||||||
|
@ -440,7 +440,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (v->left_offset == 0) // All the way to the left
|
if (v->right_mix == 0) // All the way to the left
|
||||||
{
|
{
|
||||||
if (v->envelope_increment != 0 || v->tremolo_phase_increment != 0)
|
if (v->envelope_increment != 0 || v->tremolo_phase_increment != 0)
|
||||||
{
|
{
|
||||||
|
@ -451,7 +451,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c)
|
||||||
mix_single_left(sp, buf, v, count);
|
mix_single_left(sp, buf, v, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (v->right_offset == 0) // All the way to the right
|
else if (v->left_mix == 0) // All the way to the right
|
||||||
{
|
{
|
||||||
if (v->envelope_increment != 0 || v->tremolo_phase_increment != 0)
|
if (v->envelope_increment != 0 || v->tremolo_phase_increment != 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -215,6 +215,17 @@ void Renderer::recompute_amp(Voice *v)
|
||||||
|
|
||||||
void Renderer::compute_pan(int panning, float &left_offset, float &right_offset)
|
void Renderer::compute_pan(int panning, float &left_offset, float &right_offset)
|
||||||
{
|
{
|
||||||
|
// Round the left- and right-most positions to their extremes, since
|
||||||
|
// most songs only do coarse panning.
|
||||||
|
if (panning < 128)
|
||||||
|
{
|
||||||
|
panning = 0;
|
||||||
|
}
|
||||||
|
else if (panning > 127*128)
|
||||||
|
{
|
||||||
|
panning = 32767;
|
||||||
|
}
|
||||||
|
|
||||||
if (panning == 0)
|
if (panning == 0)
|
||||||
{
|
{
|
||||||
left_offset = 0;
|
left_offset = 0;
|
||||||
|
@ -355,6 +366,12 @@ void Renderer::kill_note(int i)
|
||||||
/* Only one instance of a note can be playing on a single channel. */
|
/* Only one instance of a note can be playing on a single channel. */
|
||||||
void Renderer::note_on(int chan, int note, int vel)
|
void Renderer::note_on(int chan, int note, int vel)
|
||||||
{
|
{
|
||||||
|
if (vel == 0)
|
||||||
|
{
|
||||||
|
note_off(chan, note, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int i = voices, lowest = -1;
|
int i = voices, lowest = -1;
|
||||||
float lv = 1e10, v;
|
float lv = 1e10, v;
|
||||||
|
|
||||||
|
@ -574,14 +591,7 @@ void Renderer::HandleEvent(int status, int parm1, int parm2)
|
||||||
switch (command)
|
switch (command)
|
||||||
{
|
{
|
||||||
case ME_NOTEON:
|
case ME_NOTEON:
|
||||||
if (parm2 == 0)
|
note_on(chan, parm1, parm2);
|
||||||
{
|
|
||||||
note_off(chan, parm1, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
note_on(chan, parm1, parm2);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ME_NOTEOFF:
|
case ME_NOTEOFF:
|
||||||
|
|
|
@ -66,7 +66,7 @@ config.h
|
||||||
/* For some reason the sample volume is always set to maximum in all
|
/* For some reason the sample volume is always set to maximum in all
|
||||||
patch files. Define this for a crude adjustment that may help
|
patch files. Define this for a crude adjustment that may help
|
||||||
equalize instrument volumes. */
|
equalize instrument volumes. */
|
||||||
#define ADJUST_SAMPLE_VOLUMES
|
//#define ADJUST_SAMPLE_VOLUMES
|
||||||
|
|
||||||
/* The number of samples to use for ramping out a dying note. Affects
|
/* The number of samples to use for ramping out a dying note. Affects
|
||||||
click removal. */
|
click removal. */
|
||||||
|
|
Loading…
Reference in New Issue