mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-01-18 15:42:34 +00:00
- 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
12 changed files with 320 additions and 48 deletions
|
@ -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
|
||||
- Made the maximum number of TiMidity voices configurable through the
|
||||
timidity_voices cvar.
|
||||
|
|
|
@ -33,10 +33,7 @@
|
|||
** MUS files are essentially format 0 MIDI files with some
|
||||
** space-saving modifications. Conversion is quite straight-forward.
|
||||
** If you were to hook a main() into this that calls ProduceMIDI,
|
||||
** you could create a self-contained MUS->MIDI converter. However, if
|
||||
** 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.
|
||||
** you could create a self-contained MUS->MIDI converter.
|
||||
*/
|
||||
|
||||
|
||||
|
@ -52,9 +49,7 @@ static const BYTE StaticMIDIhead[] =
|
|||
0, 70, // 70 divisions
|
||||
'M','T','r','k', 0, 0, 0, 0,
|
||||
// The first event sets the tempo to 500,000 microsec/quarter note
|
||||
0, 255, 81, 3, 0x07, 0xa1, 0x20,
|
||||
// Set the percussion channel to full volume
|
||||
0, 0xB9, 7, 127
|
||||
0, 255, 81, 3, 0x07, 0xa1, 0x20
|
||||
};
|
||||
|
||||
static const BYTE MUSMagic[4] = { 'M','U','S',0x1a };
|
||||
|
@ -122,9 +117,8 @@ bool ProduceMIDI (const BYTE *musBuf, TArray<BYTE> &outFile)
|
|||
int deltaTime;
|
||||
const MUSHeader *musHead = (const MUSHeader *)musBuf;
|
||||
BYTE status;
|
||||
BYTE chanUsed[16];
|
||||
BYTE lastVel[16];
|
||||
SBYTE chanMap[16];
|
||||
int chanCount;
|
||||
long trackLen;
|
||||
|
||||
// Do some validation of the MUS file
|
||||
|
@ -143,10 +137,8 @@ bool ProduceMIDI (const BYTE *musBuf, TArray<BYTE> &outFile)
|
|||
maxmus_p = LittleShort(musHead->SongLen);
|
||||
mus_p = 0;
|
||||
|
||||
memset (lastVel, 64, 16);
|
||||
memset (chanMap, -1, 15);
|
||||
chanMap[15] = 9;
|
||||
chanCount = 0;
|
||||
memset (lastVel, 100, 16);
|
||||
memset (chanUsed, 0, 16);
|
||||
event = 0;
|
||||
deltaTime = 0;
|
||||
status = 0;
|
||||
|
@ -163,21 +155,27 @@ bool ProduceMIDI (const BYTE *musBuf, TArray<BYTE> &outFile)
|
|||
t = musBuf[mus_p++];
|
||||
}
|
||||
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,
|
||||
// so sets its volume to 127.
|
||||
chanUsed[channel] = 1;
|
||||
outFile.Push(0);
|
||||
outFile.Push(0xB0 | chanCount);
|
||||
outFile.Push(0xB0 | channel);
|
||||
outFile.Push(7);
|
||||
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)
|
||||
|
||||
switch (event & 0x70)
|
||||
|
|
|
@ -72,7 +72,6 @@ extern HWND Window;
|
|||
|
||||
#define SPECTRUM_SIZE 256
|
||||
|
||||
|
||||
// TYPES -------------------------------------------------------------------
|
||||
|
||||
struct FEnumList
|
||||
|
|
|
@ -151,6 +151,11 @@ MusInfo *MusInfo::GetOPLDumper(const char *filename)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void I_InitMusic (void)
|
||||
{
|
||||
static bool setatterm = false;
|
||||
|
@ -629,3 +634,41 @@ CCMD (writeopl)
|
|||
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 FString GetStats();
|
||||
virtual MusInfo *GetOPLDumper(const char *filename);
|
||||
virtual MusInfo *GetWaveDumper(const char *filename, int rate);
|
||||
|
||||
enum EState
|
||||
{
|
||||
|
@ -225,6 +226,7 @@ class TimidityMIDIDevice : public MIDIDevice
|
|||
{
|
||||
public:
|
||||
TimidityMIDIDevice();
|
||||
TimidityMIDIDevice(int rate);
|
||||
~TimidityMIDIDevice();
|
||||
|
||||
int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
|
||||
|
@ -268,6 +270,20 @@ protected:
|
|||
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 ------------------------------
|
||||
|
||||
// MIDI device selection.
|
||||
|
@ -297,7 +313,7 @@ public:
|
|||
FString GetStats();
|
||||
|
||||
protected:
|
||||
MIDIStreamer(const char *dumpname);
|
||||
MIDIStreamer(const char *dumpname, EMIDIDevice type);
|
||||
|
||||
void OutputVolume (DWORD volume);
|
||||
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
|
||||
|
@ -363,9 +379,10 @@ public:
|
|||
~MUSSong2();
|
||||
|
||||
MusInfo *GetOPLDumper(const char *filename);
|
||||
MusInfo *GetWaveDumper(const char *filename, int rate);
|
||||
|
||||
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 DoRestart();
|
||||
|
@ -388,9 +405,10 @@ public:
|
|||
~MIDISong2();
|
||||
|
||||
MusInfo *GetOPLDumper(const char *filename);
|
||||
MusInfo *GetWaveDumper(const char *filename, int rate);
|
||||
|
||||
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 DoInitialSetup();
|
||||
|
|
|
@ -891,17 +891,28 @@ void MIDISong2::Precache()
|
|||
|
||||
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)
|
||||
: MIDIStreamer(filename)
|
||||
MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate)
|
||||
{
|
||||
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;
|
||||
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
|
||||
PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
|
||||
#endif
|
||||
MIDI(0), Division(0), InitialTempo(500000), DeviceType(MIDI_OPL), DumpFilename(dumpname)
|
||||
MIDI(0), Division(0), InitialTempo(500000), DeviceType(type), DumpFilename(dumpname)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
BufferDoneEvent = NULL;
|
||||
|
@ -196,7 +196,14 @@ void MIDIStreamer::Play(bool looping)
|
|||
assert(MIDI == NULL);
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -150,7 +150,7 @@ void MUSSong2::DoInitialSetup()
|
|||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
LastVelocity[i] = 64;
|
||||
LastVelocity[i] = 100;
|
||||
ChannelVolumes[i] = 127;
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +340,18 @@ end:
|
|||
|
||||
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)
|
||||
: MIDIStreamer(filename)
|
||||
MUSSong2::MUSSong2(const MUSSong2 *original, const char *filename, EMIDIDevice type)
|
||||
: MIDIStreamer(filename, type)
|
||||
{
|
||||
int songstart = LittleShort(original->MusHeader->SongStart);
|
||||
MaxMusP = original->MaxMusP;
|
||||
|
|
|
@ -44,6 +44,27 @@
|
|||
|
||||
// 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 --------------------------------------------
|
||||
|
||||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||||
|
@ -77,6 +98,26 @@ TimidityMIDIDevice::TimidityMIDIDevice()
|
|||
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
|
||||
|
@ -509,7 +550,7 @@ bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes)
|
|||
{ // end of song
|
||||
if (numsamples > 0)
|
||||
{
|
||||
Renderer->ComputeOutput(samples1, samplesleft);
|
||||
Renderer->ComputeOutput(samples1, numsamples);
|
||||
}
|
||||
res = false;
|
||||
break;
|
||||
|
@ -521,6 +562,10 @@ bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes)
|
|||
}
|
||||
}
|
||||
}
|
||||
if (Events == NULL)
|
||||
{
|
||||
res = false;
|
||||
}
|
||||
CritSec.Leave();
|
||||
return res;
|
||||
}
|
||||
|
@ -596,3 +641,124 @@ FString TimidityMIDIDevice::GetStats()
|
|||
}
|
||||
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);
|
||||
}
|
||||
// Note: The pan offsets are negative.
|
||||
v->left_mix = (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->left_mix = MAX(0.f, (float)calc_gf1_amp(env_vol + v->left_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)
|
||||
|
@ -440,7 +440,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c)
|
|||
{
|
||||
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)
|
||||
{
|
||||
|
@ -451,7 +451,7 @@ void mix_voice(Renderer *song, float *buf, Voice *v, int c)
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -215,6 +215,17 @@ void Renderer::recompute_amp(Voice *v)
|
|||
|
||||
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)
|
||||
{
|
||||
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. */
|
||||
void Renderer::note_on(int chan, int note, int vel)
|
||||
{
|
||||
if (vel == 0)
|
||||
{
|
||||
note_off(chan, note, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int i = voices, lowest = -1;
|
||||
float lv = 1e10, v;
|
||||
|
||||
|
@ -574,14 +591,7 @@ void Renderer::HandleEvent(int status, int parm1, int parm2)
|
|||
switch (command)
|
||||
{
|
||||
case ME_NOTEON:
|
||||
if (parm2 == 0)
|
||||
{
|
||||
note_off(chan, parm1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
note_on(chan, parm1, parm2);
|
||||
}
|
||||
note_on(chan, parm1, parm2);
|
||||
break;
|
||||
|
||||
case ME_NOTEOFF:
|
||||
|
|
|
@ -66,7 +66,7 @@ config.h
|
|||
/* For some reason the sample volume is always set to maximum in all
|
||||
patch files. Define this for a crude adjustment that may help
|
||||
equalize instrument volumes. */
|
||||
#define ADJUST_SAMPLE_VOLUMES
|
||||
//#define ADJUST_SAMPLE_VOLUMES
|
||||
|
||||
/* The number of samples to use for ramping out a dying note. Affects
|
||||
click removal. */
|
||||
|
|
Loading…
Reference in a new issue