diff --git a/Makefile.linux b/Makefile.linux index 077c56d0e6..9c62fad8ac 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -20,7 +20,7 @@ CFLAGS += -Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -DNEED_STRUPR LDFLAGS += -lz -ljpeg -`sdl-config --libs` `pkg-config gtk+-2.0 --libs` $(FMOD_PREFIX)/lib/libfmodex.so NASMFLAGS += -f elf -DM_TARGET_LINUX -SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ sdl/ textures/ thingdef/ xlat/) +SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ sdl/ textures/ thingdef/ xlat/ timidity/) VPATH = $(SRCDIRS) INCLUDES = $(addprefix -I,$(SRCDIRS)) INCLUDES += -Isnes_spc/snes_spc/ -I$(FMOD_PREFIX)/include/fmodex/ diff --git a/Makefile.mingw b/Makefile.mingw index 72210c8ba5..bc414439b5 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -56,7 +56,7 @@ ifeq ($(CONFIG),Release) TARGET = $(RELEASETARGET) endif -SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ win32/ textures/ thingdef/) +SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ win32/ textures/ thingdef/ timidity/) VPATH = $(SRCDIRS) CPPSRCS = $(wildcard $(addsuffix *.cpp,$(SRCDIRS))) src/xlat/parse_xlat.cpp diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 37e70f28de..9b0b8a43da 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,7 +1,26 @@ +April 10, 2008 +- Changed the MIDIStreamer to send the all notes off controller to each + channel when restarting the song, rather than emitting a single note off + event which only has 1 in 127 chance of being for a note that's playing + on that channel. Then I decided it would probably be a good idea to reset + all the controllers as well. +- Increasing the size of the internal Timidity stream buffer from 1/14 sec + (copied from the OPL player) improved its sound dramatically, so apparently + Timidity has issues with short stream buffers. It's now at 1/2 sec in + length. However, there seems to be something weird going on with + corazonazul_ff6boss.mid near the beginning where it stops and immediately + restarts a guitar on the exact same note. +- Added a new sound debugging cvar: snd_drawoutput, which can show various + oscilloscopes and spectrums. + April 10, 2008 (Changes by Graf Zahl) - Eliminated some more global variables (onmobj, DoRipping, LastRipped, MissileActor, bulletpitch and linetarget.) +April 9, 2008 +- Internal TiMidity now plays music. Unfortunately, it doesn't sound right. :( +- Changed the progdir global variable into an FString. + April 9, 2008 (Changes by Graf Zahl) - Replaced P_PathTraverse with an FPathTraverse class, rewrote all code using P_PathTraverse and got rid of a lot of global variables in the process. diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp index 686c5870a3..e4df119a06 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -22,7 +22,7 @@ gamedir will hold progdir + the game directory (id1, id2, etc) */ -char progdir[1024]; +FString progdir; static inline bool IsSeperator (int c) { diff --git a/src/cmdlib.h b/src/cmdlib.h index af31bb2641..12808a4921 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -28,7 +28,7 @@ int Q_filelength (FILE *f); bool FileExists (const char *filename); -extern char progdir[1024]; +extern FString progdir; void FixPathSeperator (char *path); static void inline FixPathSeperator (FString &path) { path.ReplaceChars('\\', '/'); } diff --git a/src/d_main.cpp b/src/d_main.cpp index 660f274a2b..d78b6dd580 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -179,6 +179,7 @@ CVAR (Float, timelimit, 0.f, CVAR_SERVERINFO); CVAR (Bool, queryiwad, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR (String, defaultiwad, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR (Int, wipetype, 1, CVAR_ARCHIVE); +CVAR (Int, snd_drawoutput, 0, 0); bool DrawFSHUD; // [RH] Draw fullscreen HUD? wadlist_t *wadfiles; // [RH] remove limit on # of loaded wads @@ -666,6 +667,11 @@ void D_Display () NoWipe = 10; } + if (snd_drawoutput && GSnd != NULL) + { + GSnd->DrawWaveDebug(snd_drawoutput); + } + if (!wipe || NoWipe < 0) { NetUpdate (); // send out any new accumulation diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index bfab365c8b..a0e4011510 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -512,5 +512,5 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2) bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) { OPLMIDIDevice *device = (OPLMIDIDevice *)userdata; - return device->ServiceStream (buff, len); + return device->ServiceStream(buff, len); } diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 055f23716c..65710560a2 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -50,6 +50,7 @@ #include "gi.h" #include "templates.h" #include "zstring.h" +#include "timidity/timidity.h" // MACROS ------------------------------------------------------------------ @@ -275,7 +276,7 @@ void S_Init () { S_ReturnChannel(Channels); } - + // no sounds are playing, and they are not paused MusicPaused = false; SoundPaused = false; diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 336b303ce5..8ecdf11434 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -244,13 +244,20 @@ int main (int argc, char **argv) atexit (call_terms); atterm (I_Quit); - if (realpath (argv[0], progdir) == NULL) - strcpy (progdir, argv[0]); - char *slash = strrchr (progdir, '/'); - if (slash) + // Should we even be doing anything with progdir on Unix systems? + char program[PATH_MAX]; + if (realpath (argv[0], program) == NULL) + strcpy (program, argv[0]); + char *slash = strrchr (program, '/'); + if (slash != NULL) + { *(slash + 1) = '\0'; + progdir = program; + } else - progdir[0] = '.', progdir[1] = '/', progdir[2] = '\0'; + { + progdir = "./"; + } C_InitConsole (80*8, 25*8, false); D_DoomMain (); diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index fd97d51454..75c4df13f5 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -72,6 +72,8 @@ extern HWND Window; #define ERRCHECK(x) +#define SPECTRUM_SIZE 256 + // TYPES ------------------------------------------------------------------- @@ -739,6 +741,23 @@ void FMODSoundRenderer::Shutdown() } } +//========================================================================== +// +// FMODSoundRenderer :: GetOutputRate +// +//========================================================================== + +float FMODSoundRenderer::GetOutputRate() +{ + int rate; + + if (FMOD_OK == Sys->getSoftwareFormat(&rate, NULL, NULL, NULL, NULL, NULL)) + { + return float(rate); + } + return 48000.f; // Guess, but this should never happen. +} + //========================================================================== // // FMODSoundRenderer :: PrintStatus @@ -1715,3 +1734,298 @@ float F_CALLBACK FMODSoundRenderer::RolloffCallback(FMOD_CHANNEL *channel, float return (powf(10.f, volume) - 1.f) / 9.f; } } + +//========================================================================== +// +// FMODSoundRenderer :: DrawWaveDebug +// +// Bit 0: ( 1) Show oscilloscope for sfx. +// Bit 1: ( 2) Show spectrum for sfx. +// Bit 2: ( 4) Show oscilloscope for music. +// Bit 3: ( 8) Show spectrum for music. +// Bit 4: (16) Show oscilloscope for all sounds. +// Bit 5: (32) Show spectrum for all sounds. +// +//========================================================================== + +void FMODSoundRenderer::DrawWaveDebug(int mode) +{ + const int window_height = 100; + float wavearray[MAXWIDTH]; + int window_size; + int numoutchans; + int y; + + if (FMOD_OK != Sys->getSoftwareFormat(NULL, NULL, &numoutchans, NULL, NULL, NULL)) + { + return; + } + + // Scale all the channel windows so one group fits completely on one row, with + // 16 pixels of padding between each window. + window_size = (screen->GetWidth() - 16) / numoutchans - 16; + + y = 16; + y = DrawChannelGroupOutput(SfxGroup, wavearray, window_size, window_height, y, mode); + y = DrawChannelGroupOutput(MusicGroup, wavearray, window_size, window_height, y, mode >> 2); + y = DrawSystemOutput(wavearray, window_size, window_height, y, mode >> 4); +} + +//========================================================================== +// +// FMODSoundRenderer :: DrawChannelGroupOutput +// +// Draws an oscilloscope and/or a spectrum for a channel group. +// +//========================================================================== + +int FMODSoundRenderer::DrawChannelGroupOutput(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, int mode) +{ + int y1, y2; + + switch (mode & 0x03) + { + case 0x01: // Oscilloscope only + return DrawChannelGroupWaveData(group, wavearray, width, height, y, false); + + case 0x02: // Spectrum only + return DrawChannelGroupSpectrum(group, wavearray, width, height, y, false); + + case 0x03: // Oscilloscope + Spectrum + width = (width + 16) / 2 - 16; + y1 = DrawChannelGroupSpectrum(group, wavearray, width, height, y, true); + y2 = DrawChannelGroupWaveData(group, wavearray, width, height, y, true); + return MAX(y1, y2); + } + return y; +} + +//========================================================================== +// +// FMODSoundRenderer :: DrawSystemOutput +// +// Like DrawChannelGroupOutput(), but uses the system object. +// +//========================================================================== + +int FMODSoundRenderer::DrawSystemOutput(float *wavearray, int width, int height, int y, int mode) +{ + int y1, y2; + + switch (mode & 0x03) + { + case 0x01: // Oscilloscope only + return DrawSystemWaveData(wavearray, width, height, y, false); + + case 0x02: // Spectrum only + return DrawSystemSpectrum(wavearray, width, height, y, false); + + case 0x03: // Oscilloscope + Spectrum + width = (width + 16) / 2 - 16; + y1 = DrawSystemSpectrum(wavearray, width, height, y, true); + y2 = DrawSystemWaveData(wavearray, width, height, y, true); + return MAX(y1, y2); + } + return y; +} + +//========================================================================== +// +// FMODSoundRenderer :: DrawChannelGroupWaveData +// +// Draws all the output channels for a specified channel group. +// Setting skip to true causes it to skip every other window. +// +//========================================================================== + +int FMODSoundRenderer::DrawChannelGroupWaveData(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip) +{ + int drawn = 0; + int x = 16; + + while (FMOD_OK == group->getWaveData(wavearray, width, drawn)) + { + drawn++; + DrawWave(wavearray, x, y, width, height); + x += (width + 16) << int(skip); + } + if (drawn) + { + y += height + 16; + } + return y; +} + +//========================================================================== +// +// FMODSoundRenderer::DrawSystemWaveData +// +// Like DrawChannelGroupWaveData, but it uses the system object to get the +// complete output. +// +//========================================================================== + +int FMODSoundRenderer::DrawSystemWaveData(float *wavearray, int width, int height, int y, bool skip) +{ + int drawn = 0; + int x = 16; + + while (FMOD_OK == Sys->getWaveData(wavearray, width, drawn)) + { + drawn++; + DrawWave(wavearray, x, y, width, height); + x += (width + 16) << int(skip); + } + if (drawn) + { + y += height + 16; + } + return y; +} + +//========================================================================== +// +// FMODSoundRenderer :: DrawWave +// +// Draws an oscilloscope at the specified coordinates on the screen. Each +// entry in the wavearray buffer has its own column. IOW, there are +// entries in wavearray. +// +//========================================================================== + +void FMODSoundRenderer::DrawWave(float *wavearray, int x, int y, int width, int height) +{ + float scale = height / 2.f; + float mid = y + scale; + int i; + + // Draw a box around the oscilloscope. + screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200)); + screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200)); + screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200)); + screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200)); + + // Draw the actual oscilloscope. + if (screen->Accel2D) + { // Drawing this with lines is super-slow without hardware acceleration, at least with + // the debug build. + float lasty = wavearray[0] * scale + mid; + float newy; + for (i = 1; i < width; ++i) + { + newy = wavearray[i] * scale + mid; + screen->DrawLine(x + i - 1, int(lasty), x + i, int(newy), -1, MAKEARGB(255,255,248,248)); + lasty = newy; + } + } + else + { + for (i = 0; i < width; ++i) + { + float y = wavearray[i] * scale + mid; + screen->DrawPixel(x + i, int(y), -1, MAKEARGB(255,255,255,255)); + } + } +} + +//========================================================================== +// +// FMODSoundRenderer :: DrawChannelGroupSpectrum +// +// Draws all the spectrum for a specified channel group. +// Setting skip to true causes it to skip every other window, starting at +// the second one. +// +//========================================================================== + +int FMODSoundRenderer::DrawChannelGroupSpectrum(FMOD::ChannelGroup *group, float *spectrumarray, int width, int height, int y, bool skip) +{ + int drawn = 0; + int x = 16; + + if (skip) + { + x += width + 16; + } + while (FMOD_OK == group->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE)) + { + drawn++; + DrawSpectrum(spectrumarray, x, y, width, height); + x += (width + 16) << int(skip); + } + if (drawn) + { + y += height + 16; + } + return y; +} + +//========================================================================== +// +// FMODSoundRenderer::DrawSystemSpectrum +// +// Like DrawChannelGroupSpectrum, but it uses the system object to get the +// complete output. +// +//========================================================================== + +int FMODSoundRenderer::DrawSystemSpectrum(float *spectrumarray, int width, int height, int y, bool skip) +{ + int drawn = 0; + int x = 16; + + if (skip) + { + x += width + 16; + } + while (FMOD_OK == Sys->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE)) + { + drawn++; + DrawSpectrum(spectrumarray, x, y, width, height); + x += (width + 16) << int(skip); + } + if (drawn) + { + y += height + 16; + } + return y; +} + +//========================================================================== +// +// FMODSoundRenderer :: DrawSpectrum +// +// Draws a spectrum at the specified coordinates on the screen. +// +//========================================================================== + +void FMODSoundRenderer::DrawSpectrum(float *spectrumarray, int x, int y, int width, int height) +{ + float scale = height / 2.f; + float mid = y + scale; + float db; + int top; + + // Draw a border and dark background for the spectrum. + screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200)); + screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200)); + screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200)); + screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200)); + screen->Dim(MAKERGB(0,0,0), 0.3f, x, y, width, height); + + // Draw the actual spectrum. + for (int i = 0; i < width; ++i) + { + db = spectrumarray[i * (SPECTRUM_SIZE - 2) / width + 1]; + db = MAX(-150.f, 10 * log10f(db) * 2); // Convert to decibels and clamp + db = 1.f - (db / -150.f); + db *= height; + top = (int)db; + if (top >= height) + { + top = height - 1; + } +// screen->Clear(x + i, int(y + height - db), x + i + 1, y + height, -1, MAKEARGB(255, 255, 255, 40)); + screen->Dim(MAKERGB(255,255,40), 0.65f, x + i, y + height - top, 1, top); + } +} diff --git a/src/sound/fmodsound.h b/src/sound/fmodsound.h index 19cc726501..00ab4411d5 100644 --- a/src/sound/fmodsound.h +++ b/src/sound/fmodsound.h @@ -16,6 +16,7 @@ public: void LoadSound (sfxinfo_t *sfx); void UnloadSound (sfxinfo_t *sfx); unsigned int GetMSLength(sfxinfo_t *sfx); + float GetOutputRate(); // Streaming sounds. SoundStream *CreateStream (SoundStreamCallback callback, int buffsamples, int flags, int samplerate, void *userdata); @@ -48,6 +49,8 @@ public: FString GatherStats (); void ResetEnvironment (); + void DrawWaveDebug(int mode); + private: bool SFXPaused; bool InitSuccess; @@ -65,6 +68,17 @@ private: void Shutdown (); void DumpDriverCaps(FMOD_CAPS caps, int minfrequency, int maxfrequency); + int DrawChannelGroupOutput(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, int mode); + int DrawSystemOutput(float *wavearray, int width, int height, int y, int mode); + + int DrawChannelGroupWaveData(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip); + int DrawSystemWaveData(float *wavearray, int width, int height, int y, bool skip); + void DrawWave(float *wavearray, int x, int y, int width, int height); + + int DrawChannelGroupSpectrum(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip); + int DrawSystemSpectrum(float *wavearray, int width, int height, int y, bool skip); + void DrawSpectrum(float *spectrumarray, int x, int y, int width, int height); + FMOD::System *Sys; FMOD::ChannelGroup *SfxGroup, *PausableSfx; FMOD::ChannelGroup *MusicGroup; diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 6a148f33c8..ea0a4e1a11 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -70,8 +70,7 @@ extern void ChildSigHandler (int signum); #include "tempfiles.h" #include "templates.h" #include "stats.h" - -#include +#include "timidity/timidity.h" EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Int, snd_mididevice) @@ -156,6 +155,8 @@ void I_InitMusic (void) { static bool setatterm = false; + Timidity::LoadConfig(); + snd_musicvolume.Callback (); nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound"); @@ -187,8 +188,9 @@ void I_ShutdownMusic(void) S_StopMusic (true); assert (currSong == NULL); } + Timidity::FreeAll(); #ifdef _WIN32 - I_ShutdownMusicWin32 (); + I_ShutdownMusicWin32(); #endif // _WIN32 } @@ -327,12 +329,16 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le */ if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL) { - info = new MUSSong2 (file, musiccache, len, true); + info = new MUSSong2 (file, musiccache, len, MIDI_OPL); } else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2)) { info = new TimiditySong (file, musiccache, len); } + else if ((snd_mididevice == -4 && device == MDEV_DEFAULT) && GSnd != NULL) + { + info = new MUSSong2(file, musiccache, len, MIDI_Timidity); + } if (info != NULL && !info->IsValid()) { delete info; @@ -373,7 +379,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le #ifdef _WIN32 if (info == NULL) { - info = new MUSSong2 (file, musiccache, len, false); + info = new MUSSong2 (file, musiccache, len, MIDI_Win); } #endif // _WIN32 } @@ -406,12 +412,16 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le */ if ((device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT)) && GSnd != NULL) { - info = new MIDISong2 (file, musiccache, len, true); + info = new MIDISong2 (file, musiccache, len, MIDI_OPL); } else if ((device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) && GSnd != NULL) { info = new TimiditySong (file, musiccache, len); } + else if ((snd_mididevice == -4 && device == MDEV_DEFAULT) && GSnd != NULL) + { + info = new MIDISong2(file, musiccache, len, MIDI_Timidity); + } if (info != NULL && !info->IsValid()) { delete info; @@ -421,7 +431,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le #ifdef _WIN32 if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI)) { - info = new MIDISong2 (file, musiccache, len, false); + info = new MIDISong2 (file, musiccache, len, MIDI_Win); } #endif // _WIN32 } diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index e807839e1e..202f6ddade 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -205,12 +205,69 @@ public: void Stop(); }; +// Internal TiMidity MIDI device -------------------------------------------- + +namespace Timidity { struct Renderer; } + +class TimidityMIDIDevice : public MIDIDevice +{ +public: + TimidityMIDIDevice(); + ~TimidityMIDIDevice(); + + int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + void Close(); + bool IsOpen() const; + int GetTechnology() const; + int SetTempo(int tempo); + int SetTimeDiv(int timediv); + int StreamOut(MIDIHDR *data); + int StreamOutSync(MIDIHDR *data); + int Resume(); + void Stop(); + int PrepareHeader(MIDIHDR *data); + int UnprepareHeader(MIDIHDR *data); + bool FakeVolume(); + bool Pause(bool paused); + bool NeedThreadedCallback(); + void PrecacheInstruments(const BYTE *instruments, int count); + +protected: + static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + bool ServiceStream (void *buff, int numbytes); + + void (*Callback)(unsigned int, void *, DWORD, DWORD); + void *CallbackData; + + void CalcTickRate(); + int PlayTick(); + + FCriticalSection CritSec; + SoundStream *Stream; + Timidity::Renderer *Renderer; + double Tempo; + double Division; + double SamplesPerTick; + double NextTickIn; + MIDIHDR *Events; + bool Started; + DWORD Position; +}; + // Base class for streaming MUS and MIDI files ------------------------------ +// MIDI device selection. +enum EMIDIDevice +{ + MIDI_Win, + MIDI_OPL, + MIDI_Timidity +}; + class MIDIStreamer : public MusInfo { public: - MIDIStreamer(bool opl); + MIDIStreamer(EMIDIDevice type); ~MIDIStreamer(); void MusicVolumeChanged(); @@ -276,7 +333,7 @@ protected: int InitialTempo; BYTE ChannelVolumes[16]; DWORD Volume; - bool UseOPLDevice; + EMIDIDevice DeviceType; bool CallbackIsThreaded; FString DumpFilename; }; @@ -286,7 +343,7 @@ protected: class MUSSong2 : public MIDIStreamer { public: - MUSSong2(FILE *file, char *musiccache, int length, bool opl); + MUSSong2(FILE *file, char *musiccache, int length, EMIDIDevice type); ~MUSSong2(); MusInfo *GetOPLDumper(const char *filename); @@ -311,7 +368,7 @@ protected: class MIDISong2 : public MIDIStreamer { public: - MIDISong2(FILE *file, char *musiccache, int length, bool opl); + MIDISong2(FILE *file, char *musiccache, int length, EMIDIDevice type); ~MIDISong2(); MusInfo *GetOPLDumper(const char *filename); diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp index 84a8ab5c06..0a9ff47931 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -204,6 +204,10 @@ FString SoundRenderer::GatherStats () return "No stats for this sound renderer."; } +void SoundRenderer::DrawWaveDebug(int mode) +{ +} + void SoundRenderer::ResetEnvironment () { } @@ -221,3 +225,4 @@ FString SoundStream::GetStats() { return "No stream stats available."; } + diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index 2e4a7e0816..3bb9bae5c2 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -75,6 +75,7 @@ public: virtual void LoadSound (sfxinfo_t *sfx) = 0; // load a sound from disk virtual void UnloadSound (sfxinfo_t *sfx) = 0; // unloads a sound from memory virtual unsigned int GetMSLength(sfxinfo_t *sfx) = 0; // Gets the length of a sound at its default frequency + virtual float GetOutputRate() = 0; // Streaming sounds. virtual SoundStream *CreateStream (SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) = 0; @@ -105,6 +106,8 @@ public: virtual void PrintDriversList () = 0; virtual FString GatherStats (); virtual void ResetEnvironment (); + + virtual void DrawWaveDebug(int mode); }; extern SoundRenderer *GSnd; diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index d7bc8f1fec..5313aaa82c 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -19,7 +19,7 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) if (!nummididevicesset) return; - if ((self >= (signed)nummididevices) || (self < -3)) + if ((self >= (signed)nummididevices) || (self < -4)) { Printf ("ID out of range. Using default device.\n"); self = 0; diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp index 11c41f27f3..612da30bab 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_midi_midiout.cpp @@ -100,8 +100,8 @@ static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // //========================================================================== -MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, bool opl) -: MIDIStreamer(opl), MusHeader(0), Tracks(0) +MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, EMIDIDevice type) +: MIDIStreamer(type), MusHeader(0), Tracks(0) { int p; int i; diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index c57ddfad67..2f923fd34f 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -69,12 +69,12 @@ extern UINT mididevice; // //========================================================================== -MIDIStreamer::MIDIStreamer(bool opl) +MIDIStreamer::MIDIStreamer(EMIDIDevice type) : #ifdef _WIN32 PlayerThread(0), ExitEvent(0), BufferDoneEvent(0), #endif - MIDI(0), Division(0), InitialTempo(500000), UseOPLDevice(opl) + MIDI(0), Division(0), InitialTempo(500000), DeviceType(type) { #ifdef _WIN32 BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -102,7 +102,7 @@ MIDIStreamer::MIDIStreamer(const char *dumpname) #ifdef _WIN32 PlayerThread(0), ExitEvent(0), BufferDoneEvent(0), #endif - MIDI(0), Division(0), InitialTempo(500000), UseOPLDevice(true), DumpFilename(dumpname) + MIDI(0), Division(0), InitialTempo(500000), DeviceType(MIDI_OPL), DumpFilename(dumpname) { #ifdef _WIN32 BufferDoneEvent = NULL; @@ -198,16 +198,23 @@ void MIDIStreamer::Play(bool looping) { MIDI = new OPLDumperMIDIDevice(DumpFilename); } - else + else switch(DeviceType) + { + case MIDI_Win: #ifdef _WIN32 - if (!UseOPLDevice) - { MIDI = new WinMIDIDevice(mididevice); - } - else + break; #endif - { + assert(0); + // Intentional fall-through for non-Windows systems. + + case MIDI_Timidity: + MIDI = new TimidityMIDIDevice; + break; + + case MIDI_OPL: MIDI = new OPLMIDIDevice; + break; } #ifndef _WIN32 @@ -682,8 +689,11 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time) { events[0] = 0; // dwDeltaTime events[1] = 0; // dwStreamID - events[2] = MIDI_NOTEOFF | i | (60 << 8) | (64<<16); - events += 3; + 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; } DoRestart(); } diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index 651f154b49..03b2fa4cec 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -84,8 +84,8 @@ static const BYTE CtrlTranslate[15] = // //========================================================================== -MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len, bool opl) -: MIDIStreamer(opl), MusHeader(0), MusBuffer(0) +MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len, EMIDIDevice type) +: MIDIStreamer(type), MusHeader(0), MusBuffer(0) { #ifdef _WIN32 if (ExitEvent == NULL) @@ -201,7 +201,7 @@ void MUSSong2::Precache() } else if (used[i] >= 135 && used[i] <= 181) { // Percussions are 100-based, not 128-based, eh? - work[j++] = (used[i] - 100) | 0x80; + work[j++] = used[i] - 100 + 128; } } MIDI->PrecacheInstruments(&work[0], j); diff --git a/src/sound/music_timidity_mididevice.cpp b/src/sound/music_timidity_mididevice.cpp new file mode 100644 index 0000000000..f0afe7b13f --- /dev/null +++ b/src/sound/music_timidity_mididevice.cpp @@ -0,0 +1,519 @@ +/* +** music_timidity_mididevice.cpp +** Provides access to TiMidity as a generic MIDI device. +** +**--------------------------------------------------------------------------- +** 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 "w_wad.h" +#include "timidity/timidity.h" + +// MACROS ------------------------------------------------------------------ + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR(Bool, timidity_watch, false, 0) + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// TimidityMIDIDevice Constructor +// +//========================================================================== + +TimidityMIDIDevice::TimidityMIDIDevice() +{ + Stream = NULL; + Tempo = 0; + Division = 0; + Events = NULL; + Started = false; + Renderer = NULL; + Renderer = new Timidity::Renderer(GSnd->GetOutputRate()); +} + +//========================================================================== +// +// TimidityMIDIDevice Destructor +// +//========================================================================== + +TimidityMIDIDevice::~TimidityMIDIDevice() +{ + Close(); + if (Renderer != NULL) + { + delete Renderer; + } +} + +//========================================================================== +// +// TimidityMIDIDevice :: Open +// +// Returns 0 on success. +// +//========================================================================== + +int TimidityMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + Stream = GSnd->CreateStream(FillStream, int(Renderer->rate / 2) * 4, + SoundStream::Float, int(Renderer->rate), this); + if (Stream == NULL) + { + return 2; + } + + Callback = callback; + CallbackData = userdata; + Tempo = 500000; + Division = 100; + CalcTickRate(); + Renderer->Reset(); + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: Close +// +//========================================================================== + +void TimidityMIDIDevice::Close() +{ + if (Stream != NULL) + { + delete Stream; + Stream = NULL; + } + Started = false; +} + +//========================================================================== +// +// TimidityMIDIDevice :: IsOpen +// +//========================================================================== + +bool TimidityMIDIDevice::IsOpen() const +{ + return Stream != NULL; +} + +//========================================================================== +// +// TimidityMIDIDevice :: GetTechnology +// +//========================================================================== + +int TimidityMIDIDevice::GetTechnology() const +{ + return MOD_SWSYNTH; +} + +//========================================================================== +// +// TimidityMIDIDevice :: SetTempo +// +//========================================================================== + +int TimidityMIDIDevice::SetTempo(int tempo) +{ + Tempo = tempo; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int TimidityMIDIDevice::SetTimeDiv(int timediv) +{ + Division = timediv; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: CalcTickRate +// +// Tempo is the number of microseconds per quarter note. +// Division is the number of ticks per quarter note. +// +//========================================================================== + +void TimidityMIDIDevice::CalcTickRate() +{ + SamplesPerTick = Renderer->rate / (1000000.0 / Tempo) / Division; +} + +//========================================================================== +// +// TimidityMIDIDevice :: Resume +// +//========================================================================== + +int TimidityMIDIDevice::Resume() +{ + if (!Started) + { + if (Stream->Play(true, 1, false)) + { + Started = true; + return 0; + } + return 1; + } + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: Stop +// +//========================================================================== + +void TimidityMIDIDevice::Stop() +{ + if (Started) + { + Stream->Stop(); + Started = false; + } +} + +//========================================================================== +// +// TimidityMIDIDevice :: StreamOutSync +// +// This version is called from the main game thread and needs to +// synchronize with the player thread. +// +//========================================================================== + +int TimidityMIDIDevice::StreamOutSync(MIDIHDR *header) +{ + CritSec.Enter(); + StreamOut(header); + CritSec.Leave(); + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: StreamOut +// +// This version is called from the player thread so does not need to +// arbitrate for access to the Events pointer. +// +//========================================================================== + +int TimidityMIDIDevice::StreamOut(MIDIHDR *header) +{ + header->lpNext = NULL; + if (Events == NULL) + { + Events = header; + NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; + Position = 0; + } + else + { + MIDIHDR **p; + + for (p = &Events; *p != NULL; p = &(*p)->lpNext) + { } + *p = header; + } + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: PrepareHeader +// +//========================================================================== + +int TimidityMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int TimidityMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// TimidityMIDIDevice :: FakeVolume +// +// Since the TiMidity output is rendered as a normal stream, its volume is +// controlled through the GSnd interface, not here. +// +//========================================================================== + +bool TimidityMIDIDevice::FakeVolume() +{ + return false; +} + +//========================================================================== +// +// TimidityMIDIDevice :: NeedThreadedCallabck +// +// OPL can service the callback directly rather than using a separate +// thread. +// +//========================================================================== + +bool TimidityMIDIDevice::NeedThreadedCallback() +{ + return false; +} + +//========================================================================== +// +// TimidityMIDIDevice :: Pause +// +//========================================================================== + +bool TimidityMIDIDevice::Pause(bool paused) +{ + if (Stream != NULL) + { + return Stream->SetPaused(paused); + } + return true; +} + +//========================================================================== +// +// TimidityMIDIDevice :: PrecacheInstruments +// +// For each entry, bit 7 set indicates that the instrument is percussion and +// the lower 7 bits contain the note number to use on MIDI channel 10, +// otherwise it is melodic and the lower 7 bits are the program number. +// +//========================================================================== + +void TimidityMIDIDevice::PrecacheInstruments(const BYTE *instruments, int count) +{ + for (int i = 0; i < count; ++i) + { + Renderer->MarkInstrument(0, instruments[i] >> 7, instruments[i] & 127); + } + Renderer->load_missing_instruments(); +} + +//========================================================================== +// +// TimidityMIDIDevice :: PlayTick +// +// event[0] = delta time +// event[1] = unused +// event[2] = event +// +//========================================================================== + +int TimidityMIDIDevice::PlayTick() +{ + DWORD delay = 0; + + while (delay == 0 && Events != NULL) + { + DWORD *event = (DWORD *)(Events->lpData + Position); + if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) + { + SetTempo(MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) + { + Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == 0) + { // Short MIDI event + int status = event[2] & 0xff; + int parm1 = (event[2] >> 8) & 0x7f; + int parm2 = (event[2] >> 16) & 0x7f; + Renderer->HandleEvent(status, parm1, parm2); + + if (timidity_watch) + { + static const char *const commands[8] = + { + "Note off", + "Note on", + "Poly press", + "Ctrl change", + "Prgm change", + "Chan press", + "Pitch bend", + "SysEx" + }; +#ifdef _WIN32 + char buffer[128]; + sprintf(buffer, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); + OutputDebugString(buffer); +#else + fprintf(stderr, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); +#endif + } + } + + // Advance to next event. + if (event[2] < 0x80000000) + { // Short message + Position += 12; + } + else + { // Long message + Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); + } + + // Did we use up this buffer? + if (Position >= Events->dwBytesRecorded) + { + Events = Events->lpNext; + Position = 0; + + if (Callback != NULL) + { + Callback(MOM_DONE, CallbackData, 0, 0); + } + } + + if (Events == NULL) + { // No more events. Just return something to keep the song playing + // while we wait for more to be submitted. + return int(Division); + } + + delay = *(DWORD *)(Events->lpData + Position); + } + return delay; +} + +//========================================================================== +// +// TimidityMIDIDevice :: ServiceStream +// +//========================================================================== + +bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes) +{ + float *samples = (float *)buff; + float *samples1; + int numsamples = numbytes / sizeof(float) / 2; + bool prev_ended = false; + bool res = true; + + samples1 = samples; + memset(buff, 0, numbytes); + + CritSec.Enter(); + while (numsamples > 0) + { + double ticky = NextTickIn; + int tick_in = int(NextTickIn); + int samplesleft = MIN(numsamples, tick_in); + + if (samplesleft > 0) + { + Renderer->ComputeOutput(samples1, samplesleft); + assert(NextTickIn == ticky); + NextTickIn -= samplesleft; + assert(NextTickIn >= 0); + numsamples -= samplesleft; + samples1 += samplesleft * 2; + } + + if (NextTickIn < 1) + { + int next = PlayTick(); + assert(next >= 0); + if (next == 0) + { // end of song + if (numsamples > 0) + { + Renderer->ComputeOutput(samples1, samplesleft); + } + res = false; + break; + } + else + { + NextTickIn += SamplesPerTick * next; + assert(NextTickIn >= 0); + } + } + } + CritSec.Leave(); + return res; +} + +//========================================================================== +// +// TimidityMIDIDevice :: FillStream static +// +//========================================================================== + +bool TimidityMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +{ + TimidityMIDIDevice *device = (TimidityMIDIDevice *)userdata; + return device->ServiceStream(buff, len); +} diff --git a/src/timidity/CHANGES b/src/timidity/CHANGES new file mode 100644 index 0000000000..045e537396 --- /dev/null +++ b/src/timidity/CHANGES @@ -0,0 +1,58 @@ +This version of TiMidity should contain all the fixes from the +September 25 2003 SDL_mixer CVS snapshot, plus extended GUS patch +support from later SDL_mixer. In addition, it contains these changes +from SDL_sound: + +* Removal of much unused or unnecessary code, such as + + + The "hooks" for putting a user interface onto TiMidity. + + The antialias filter. It wasn't active, and even at 4 kHz I + couldn't hear any difference when activating it. + + Removed all traces of LOOKUP_HACK and LOOKUP_INTERPOLATION. + According to the code comments they weren't very good anyway. + ("degrades sound quality noticeably"). I also removed the + disclaimer about the "8-bit uLaw to 16-bit PCM and the 13-bit-PCM + to 8-bit uLaw tables" disclaimer, since I believe those were the + tables I removed. + + Removed LOOKUP_SINE since it was already commented out. I think we + can count on our target audience having math co-processors + nowadays. + + Removed USE_LDEXP since it wasn't being used and "it doesn't make + much of a difference either way". + + Removed decompress hack from open_file() since it didn't look very + portable. + + Removed heaps of unnecessary constants. + + Removed unused functions. + + Assume that LINEAR_INTERPOLATION is always used, so remove all + code dealing with it not being so. It's not that I think the + difference in audio quality is that great, but since it wouldn't + compile without code changes I assume no one's used it for quite + some time... + + Assume PRECALC_LOOPS is always defined. Judging by the comments it + may not make much of a difference either way, so why maintain two + versions of the same code? + +* Made TiMidity look for its configuration file in both /etc and + /usr/local/lib/timidity. (Windows version remains unchanged.) + +* The following files have been removed: controls.c, controls.h, + filter.c, filter.h, sdl_a.c, sdl_c.c + +* Added support for loading DLS format instruments: + Timidity_LoadDLS(), Timidity_FreeDLS(), Timidity_LoadDLSSong() + +This version of TiMidity also contains my own changes for ZDoom: + +* Removed readmidi.c: TiMidity is now fed MIDI events directly to + produce output. The TimidityMIDIDevice class is responsible for + feeding TiMidity data and collecting output from it. Since + ZDoom's MIDI parser ignores SysEx messages, so does this TiMidity, + though this can be changed if necessary. + +* Removed all the precalculated math from tables.c in favor of using + real math functions. + +* All sample values are now floats, and only a stereo 32-bit float + output buffer is supported. + +* Moved everything into the Timidity namespace. diff --git a/src/timidity/COPYING b/src/timidity/COPYING new file mode 100644 index 0000000000..2fa5da3ac5 --- /dev/null +++ b/src/timidity/COPYING @@ -0,0 +1,513 @@ +Please note that the included source from Timidity, the MIDI decoder, is also + licensed under the following terms (GNU LGPL), but can also be used + separately under the GNU GPL, or the Perl Artistic License. Those licensing + terms are not reprinted here, but can be found on the web easily. + + +------------------- + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/timidity/FAQ b/src/timidity/FAQ new file mode 100644 index 0000000000..1ee0b77bfb --- /dev/null +++ b/src/timidity/FAQ @@ -0,0 +1,100 @@ +---------------------------*-indented-text-*------------------------------ + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + +-------------------------------------------------------------------------- + + Frequently Asked Questions with answers: + +-------------------------------------------------------------------------- +Q: What is it? + +A: Where? Well Chris, TiMidity is a software-only synthesizer, MIDI + renderer, MIDI to WAVE converter, realtime MIDI player for UNIX machines, + even (I've heard) a Netscape helper application. It takes a MIDI file + and writes a WAVE or raw PCM data or plays it on your digital audio + device. It sounds much more realistic than FM synthesis, but you need a + ~100Mhz processor to listen to 32kHz stereo music in the background while + you work. 11kHz mono can be played on a low-end 486, and, to some, it + still sounds better than FM. + +-------------------------------------------------------------------------- +Q: I don't have a GUS, can I use TiMidity? + +A: Yes. That's the point. You don't need a Gravis Ultrasound to use + TiMidity, you just need GUS-compatible patches, which are freely + available on the Internet. See below for pointers. + +-------------------------------------------------------------------------- +Q: I have a GUS, can I use TiMidity? + +A: The DOS port doesn't have GUS support, and TiMidity won't be taking + advantage of the board's internal synthesizer under other operating + systems either. So it kind of defeats the purpose. But you can use it. + +-------------------------------------------------------------------------- +Q: I tried playing a MIDI file I got off the Net but all I got was a + dozen warnings saying "No instrument mapped to tone bank 0, program + xx - this instrument will not be heard". What's wrong? + +A: The General MIDI standard specifies 128 melodic instruments and + some sixty percussion sounds. If you wish to play arbitrary General + MIDI files, you'll need to get more patch files. + + There's a program called Midia for SGI's, which also plays MIDI + files and has a lot more bells and whistles than TiMidity. It uses + GUS-compatible patches, too -- so you can get the 8 MB set at + ftp://archive.cs.umbc.edu/pub/midia for pretty good GM compatibility. + + There are also many excellent patches on the Ultrasound FTP sites. + I can recommend Dustin McCartney's collections gsdrum*.zip and + wow*.zip in the "[.../]sound/patches/files" directory. The huge + ProPats series (pp3-*.zip) contains good patches as well. General + MIDI files can also be found on these sites. + + This site list is from the GUS FAQ: + +> FTP Sites Archive Directories +> --------- ------------------- +> Main N.American Site: archive.orst.edu pub/packages/gravis +> wuarchive.wustl.edu systems/ibmpc/ultrasound +> Main Asian Site: nctuccca.edu.tw PC/ultrasound +> Main European Site: src.doc.ic.ac.uk packages/ultrasound +> Main Australian Site: ftp.mpx.com.au /ultrasound/general +> /ultrasound/submit +> South African Site: ftp.sun.ac.za /pub/packages/ultrasound +> Submissions: archive.epas.utoronto.ca pub/pc/ultrasound/submit +> Newly Validated Files: archive.epas.utoronto.ca pub/pc/ultrasound +> +> Mirrors: garbo.uwasa.fi mirror/ultrasound +> ftp.st.nepean.uws.edu.au pc/ultrasound +> ftp.luth.se pub/msdos/ultrasound + +-------------------------------------------------------------------------- +Q: Some files have awful clicks and pops. + +A: Find out which patch is responsible for the clicking (try "timidity + -P ". Add "strip=tail" in + the config file after its name. If this doesn't fix it, mail me the + patch. + +-------------------------------------------------------------------------- +Q: I'm playing Fantasie Impromptu in the background. When I run Netscape, + the sound gets choppy and it takes ten minutes to load. What can I do? + +A: Here are some things to try: + + - Use a lower sampling rate. + + - Use mono output. This can improve performance by 10-30%. + (Using 8-bit instead of 16-bit output makes no difference.) + + - Use a smaller number of simultaneous voices. + + - Make sure you compiled with FAST_DECAY enabled in options.h + + - Recompile with an Intel-optimized gcc for a 5-15% + performance increase. + +-------------------------------------------------------------------------- diff --git a/src/timidity/README b/src/timidity/README new file mode 100644 index 0000000000..9c9c55aad0 --- /dev/null +++ b/src/timidity/README @@ -0,0 +1,61 @@ +[This version of timidity has been stripped for simplicity in porting to SDL, +and then even further for SDL_sound] +---------------------------------*-text-*--------------------------------- + + From http://www.cgs.fi/~tt/discontinued.html : + + If you'd like to continue hacking on TiMidity, feel free. I'm + hereby extending the TiMidity license agreement: you can now + select the most convenient license for your needs from (1) the + GNU GPL, (2) the GNU LGPL, or (3) the Perl Artistic License. + +-------------------------------------------------------------------------- + + This is the README file for TiMidity v0.2i + + TiMidity is a MIDI to WAVE converter that uses Gravis +Ultrasound(*)-compatible patch files to generate digital audio data +from General MIDI files. The audio data can be played through any +sound device or stored on disk. On a fast machine, music can be +played in real time. TiMidity runs under Linux, FreeBSD, HP-UX, SunOS, and +Win32, and porting to other systems with gcc should be easy. + + TiMidity Features: + + * 32 or more dynamically allocated fully independent voices + * Compatibility with GUS patch files + * Output to 16- or 8-bit PCM or uLaw audio device, file, or + stdout at any sampling rate + * Optional interactive mode with real-time status display + under ncurses and SLang terminal control libraries. Also + a user friendly motif interface since version 0.2h + * Support for transparent loading of compressed MIDI files and + patch files + + * Support for the following MIDI events: + - Program change + - Key pressure + - Channel main volume + - Tempo + - Panning + - Damper pedal (Sustain) + - Pitch wheel + - Pitch wheel sensitivity + - Change drum set + +* The GNU General Public License can, as always, be found in the file + "../COPYING". + +* TiMidity requires sampled instruments (patches) to play MIDI files. You + should get the file "timidity-lib-0.1.tar.gz" and unpack it in the same + directory where you unpacked the source code archive. You'll want more + patches later -- read the file "FAQ" for pointers. + +* Timidity is no longer supported, but can be found by searching the web. + + + Tuukka Toivonen + +[(*) Any Registered Trademarks used anywhere in the documentation or +source code for TiMidity are acknowledged as belonging to their +respective owners.] diff --git a/src/timidity/common.cpp b/src/timidity/common.cpp new file mode 100644 index 0000000000..baf74b74a1 --- /dev/null +++ b/src/timidity/common.cpp @@ -0,0 +1,152 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + common.c + +*/ + +#include +#include +#include +#include +#include "timidity.h" +#include "zstring.h" +#include "tarray.h" +#include "i_system.h" + +namespace Timidity +{ + +/* I guess "rb" should be right for any libc */ +#define OPEN_MODE "rb" + +FString current_filename; + +static TArray PathList; + +/* Try to open a file for reading. */ +static FILE *try_to_open(const char *name, int decompress, int noise_mode) +{ + FILE *fp; + + fp = fopen(name, OPEN_MODE); + + if (!fp) + return 0; + + return fp; +} + +/* This is meant to find and open files for reading. */ +FILE *open_file(const char *name, int decompress, int noise_mode) +{ + FILE *fp; + + if (!name || !(*name)) + { + return 0; + } + + /* First try the given name */ + current_filename = name; + + if ((fp = try_to_open(current_filename, decompress, noise_mode))) + return fp; + +#ifdef ENOENT + if (noise_mode && (errno != ENOENT)) + { + return 0; + } +#endif + + if (name[0] != '/' +#ifdef _WIN32 + && name[0] != '\\' +#endif + ) + { + for (unsigned int plp = PathList.Size(); plp-- != 0; ) + { /* Try along the path then */ + current_filename = ""; + if (PathList[plp].IsNotEmpty()) + { + current_filename = PathList[plp]; + if (current_filename[current_filename.Len() - 1] != '/' +#ifdef _WIN32 + && current_filename[current_filename.Len() - 1] != '\\' +#endif + ) + { + current_filename += '/'; + } + } + current_filename += name; + if ((fp = try_to_open(current_filename, decompress, noise_mode))) + return fp; + if (noise_mode && (errno != ENOENT)) + { + return 0; + } + } + } + + /* Nothing could be opened. */ + current_filename = ""; + return 0; +} + +/* This closes files opened with open_file */ +void close_file(FILE *fp) +{ + fclose(fp); +} + +/* This is meant for skipping a few bytes in a file or fifo. */ +void skip(FILE *fp, size_t len) +{ + fseek(fp, (long)len, SEEK_CUR); +} + +/* This'll allocate memory or die. */ +void *safe_malloc(size_t count) +{ + void *p; + if (count > (1 << 21)) + { + I_Error("Timidity: Tried allocating %d bytes. This must be a bug.", count); + } + else if ((p = malloc(count))) + { + return p; + } + else + { + I_Error("Timidity: Couldn't malloc %d bytes.", count); + } + return 0; // Unreachable. +} + +/* This adds a directory to the path list */ +void add_to_pathlist(const char *s) +{ + PathList.Push(s); +} + +} diff --git a/src/timidity/dls1.h b/src/timidity/dls1.h new file mode 100644 index 0000000000..abc2075a51 --- /dev/null +++ b/src/timidity/dls1.h @@ -0,0 +1,266 @@ +/*==========================================================================; +// +// dls1.h +// +// +// Description: +// +// Interface defines and structures for the Instrument Collection Form +// RIFF DLS. +// +// +// Written by Sonic Foundry 1996. Released for public use. +// +//=========================================================================*/ + +#ifndef _INC_DLS1 +#define _INC_DLS1 + +/*////////////////////////////////////////////////////////////////////////// +// +// +// Layout of an instrument collection: +// +// +// RIFF [] 'DLS ' [dlid,colh,INSTLIST,WAVEPOOL,INFOLIST] +// +// INSTLIST +// LIST [] 'lins' +// LIST [] 'ins ' [dlid,insh,RGNLIST,ARTLIST,INFOLIST] +// LIST [] 'ins ' [dlid,insh,RGNLIST,ARTLIST,INFOLIST] +// LIST [] 'ins ' [dlid,insh,RGNLIST,ARTLIST,INFOLIST] +// +// RGNLIST +// LIST [] 'lrgn' +// LIST [] 'rgn ' [rgnh,wsmp,wlnk,ARTLIST] +// LIST [] 'rgn ' [rgnh,wsmp,wlnk,ARTLIST] +// LIST [] 'rgn ' [rgnh,wsmp,wlnk,ARTLIST] +// +// ARTLIST +// LIST [] 'lart' +// 'art1' level 1 Articulation connection graph +// 'art2' level 2 Articulation connection graph +// '3rd1' Possible 3rd party articulation structure 1 +// '3rd2' Possible 3rd party articulation structure 2 .... and so on +// +// WAVEPOOL +// ptbl [] [pool table] +// LIST [] 'wvpl' +// [path], +// [path], +// LIST [] 'wave' [dlid,RIFFWAVE] +// LIST [] 'wave' [dlid,RIFFWAVE] +// LIST [] 'wave' [dlid,RIFFWAVE] +// LIST [] 'wave' [dlid,RIFFWAVE] +// LIST [] 'wave' [dlid,RIFFWAVE] +// +// INFOLIST +// LIST [] 'INFO' +// 'icmt' 'One of those crazy comments.' +// 'icop' 'Copyright (C) 1996 Sonic Foundry' +// +/////////////////////////////////////////////////////////////////////////*/ + + +/*///////////////////////////////////////////////////////////////////////// +// FOURCC's used in the DLS file +/////////////////////////////////////////////////////////////////////////*/ + +#define FOURCC_DLS mmioFOURCC('D','L','S',' ') +#define FOURCC_DLID mmioFOURCC('d','l','i','d') +#define FOURCC_COLH mmioFOURCC('c','o','l','h') +#define FOURCC_WVPL mmioFOURCC('w','v','p','l') +#define FOURCC_PTBL mmioFOURCC('p','t','b','l') +#define FOURCC_PATH mmioFOURCC('p','a','t','h') +#define FOURCC_wave mmioFOURCC('w','a','v','e') +#define FOURCC_LINS mmioFOURCC('l','i','n','s') +#define FOURCC_INS mmioFOURCC('i','n','s',' ') +#define FOURCC_INSH mmioFOURCC('i','n','s','h') +#define FOURCC_LRGN mmioFOURCC('l','r','g','n') +#define FOURCC_RGN mmioFOURCC('r','g','n',' ') +#define FOURCC_RGNH mmioFOURCC('r','g','n','h') +#define FOURCC_LART mmioFOURCC('l','a','r','t') +#define FOURCC_ART1 mmioFOURCC('a','r','t','1') +#define FOURCC_WLNK mmioFOURCC('w','l','n','k') +#define FOURCC_WSMP mmioFOURCC('w','s','m','p') +#define FOURCC_VERS mmioFOURCC('v','e','r','s') + +/*///////////////////////////////////////////////////////////////////////// +// Articulation connection graph definitions +/////////////////////////////////////////////////////////////////////////*/ + +/* Generic Sources */ +#define CONN_SRC_NONE 0x0000 +#define CONN_SRC_LFO 0x0001 +#define CONN_SRC_KEYONVELOCITY 0x0002 +#define CONN_SRC_KEYNUMBER 0x0003 +#define CONN_SRC_EG1 0x0004 +#define CONN_SRC_EG2 0x0005 +#define CONN_SRC_PITCHWHEEL 0x0006 + +/* Midi Controllers 0-127 */ +#define CONN_SRC_CC1 0x0081 +#define CONN_SRC_CC7 0x0087 +#define CONN_SRC_CC10 0x008a +#define CONN_SRC_CC11 0x008b + +/* Generic Destinations */ +#define CONN_DST_NONE 0x0000 +#define CONN_DST_ATTENUATION 0x0001 +#define CONN_DST_PITCH 0x0003 +#define CONN_DST_PAN 0x0004 + +/* LFO Destinations */ +#define CONN_DST_LFO_FREQUENCY 0x0104 +#define CONN_DST_LFO_STARTDELAY 0x0105 + +/* EG1 Destinations */ +#define CONN_DST_EG1_ATTACKTIME 0x0206 +#define CONN_DST_EG1_DECAYTIME 0x0207 +#define CONN_DST_EG1_RELEASETIME 0x0209 +#define CONN_DST_EG1_SUSTAINLEVEL 0x020a + +/* EG2 Destinations */ +#define CONN_DST_EG2_ATTACKTIME 0x030a +#define CONN_DST_EG2_DECAYTIME 0x030b +#define CONN_DST_EG2_RELEASETIME 0x030d +#define CONN_DST_EG2_SUSTAINLEVEL 0x030e + +#define CONN_TRN_NONE 0x0000 +#define CONN_TRN_CONCAVE 0x0001 + +typedef struct _DLSID { + ULONG ulData1; + USHORT usData2; + USHORT usData3; + BYTE abData4[8]; +} DLSID, FAR *LPDLSID; + +typedef struct _DLSVERSION { + DWORD dwVersionMS; + DWORD dwVersionLS; +} DLSVERSION, FAR *LPDLSVERSION; + + +typedef struct _CONNECTION { + USHORT usSource; + USHORT usControl; + USHORT usDestination; + USHORT usTransform; + LONG lScale; +} CONNECTION, FAR *LPCONNECTION; + + +/* Level 1 Articulation Data */ + +typedef struct _CONNECTIONLIST { + ULONG cbSize; /* size of the connection list structure */ + ULONG cConnections; /* count of connections in the list */ +} CONNECTIONLIST, FAR *LPCONNECTIONLIST; + + + +/*///////////////////////////////////////////////////////////////////////// +// Generic type defines for regions and instruments +/////////////////////////////////////////////////////////////////////////*/ + +typedef struct _RGNRANGE { + USHORT usLow; + USHORT usHigh; +} RGNRANGE, FAR * LPRGNRANGE; + +#define F_INSTRUMENT_DRUMS 0x80000000 + +typedef struct _MIDILOCALE { + ULONG ulBank; + ULONG ulInstrument; +} MIDILOCALE, FAR *LPMIDILOCALE; + +/*///////////////////////////////////////////////////////////////////////// +// Header structures found in an DLS file for collection, instruments, and +// regions. +/////////////////////////////////////////////////////////////////////////*/ + +#define F_RGN_OPTION_SELFNONEXCLUSIVE 0x0001 + +typedef struct _RGNHEADER { + RGNRANGE RangeKey; /* Key range */ + RGNRANGE RangeVelocity; /* Velocity Range */ + USHORT fusOptions; /* Synthesis options for this range */ + USHORT usKeyGroup; /* Key grouping for non simultaneous play */ + /* 0 = no group, 1 up is group */ + /* for Level 1 only groups 1-15 are allowed */ +} RGNHEADER, FAR *LPRGNHEADER; + +typedef struct _INSTHEADER { + ULONG cRegions; /* Count of regions in this instrument */ + MIDILOCALE Locale; /* Intended MIDI locale of this instrument */ +} INSTHEADER, FAR *LPINSTHEADER; + +typedef struct _DLSHEADER { + ULONG cInstruments; /* Count of instruments in the collection */ +} DLSHEADER, FAR *LPDLSHEADER; + +/*//////////////////////////////////////////////////////////////////////////// +// definitions for the Wave link structure +////////////////////////////////////////////////////////////////////////////*/ + +/* **** For level 1 only WAVELINK_CHANNEL_MONO is valid **** */ +/* ulChannel allows for up to 32 channels of audio with each bit position */ +/* specifiying a channel of playback */ + +#define WAVELINK_CHANNEL_LEFT 0x0001l +#define WAVELINK_CHANNEL_RIGHT 0x0002l + +#define F_WAVELINK_PHASE_MASTER 0x0001 + +typedef struct _WAVELINK { /* any paths or links are stored right after struct */ + USHORT fusOptions; /* options flags for this wave */ + USHORT usPhaseGroup; /* Phase grouping for locking channels */ + ULONG ulChannel; /* channel placement */ + ULONG ulTableIndex; /* index into the wave pool table, 0 based */ +} WAVELINK, FAR *LPWAVELINK; + +#define POOL_CUE_NULL 0xffffffffl + +typedef struct _POOLCUE { + ULONG ulOffset; /* Offset to the entry in the list */ +} POOLCUE, FAR *LPPOOLCUE; + +typedef struct _POOLTABLE { + ULONG cbSize; /* size of the pool table structure */ + ULONG cCues; /* count of cues in the list */ +} POOLTABLE, FAR *LPPOOLTABLE; + +/*//////////////////////////////////////////////////////////////////////////// +// Structures for the "wsmp" chunk +////////////////////////////////////////////////////////////////////////////*/ + +#define F_WSMP_NO_TRUNCATION 0x0001l +#define F_WSMP_NO_COMPRESSION 0x0002l + + +typedef struct _rwsmp { + ULONG cbSize; + USHORT usUnityNote; /* MIDI Unity Playback Note */ + SHORT sFineTune; /* Fine Tune in log tuning */ + LONG lAttenuation; /* Overall Attenuation to be applied to data */ + ULONG fulOptions; /* Flag options */ + ULONG cSampleLoops; /* Count of Sample loops, 0 loops is one shot */ +} WSMPL, FAR *LPWSMPL; + + +/* This loop type is a normal forward playing loop which is continually */ +/* played until the envelope reaches an off threshold in the release */ +/* portion of the volume envelope */ + +#define WLOOP_TYPE_FORWARD 0 + +typedef struct _rloop { + ULONG cbSize; + ULONG ulType; /* Loop Type */ + ULONG ulStart; /* Start of loop in samples */ + ULONG ulLength; /* Length of loop in samples */ +} WLOOP, FAR *LPWLOOP; + +#endif /*_INC_DLS1 */ diff --git a/src/timidity/dls2.h b/src/timidity/dls2.h new file mode 100644 index 0000000000..30cec23a2d --- /dev/null +++ b/src/timidity/dls2.h @@ -0,0 +1,130 @@ +/* + + dls2.h + + Description: + + Interface defines and structures for the DLS2 extensions of DLS. + + + Written by Microsoft 1998. Released for public use. + +*/ + +#ifndef _INC_DLS2 +#define _INC_DLS2 + +/* + FOURCC's used in the DLS2 file, in addition to DLS1 chunks +*/ + +#define FOURCC_RGN2 mmioFOURCC('r','g','n','2') +#define FOURCC_LAR2 mmioFOURCC('l','a','r','2') +#define FOURCC_ART2 mmioFOURCC('a','r','t','2') +#define FOURCC_CDL mmioFOURCC('c','d','l',' ') +#define FOURCC_DLID mmioFOURCC('d','l','i','d') + +/* + Articulation connection graph definitions. These are in addition to + the definitions in the DLS1 header. +*/ + +/* Generic Sources (in addition to DLS1 sources. */ +#define CONN_SRC_POLYPRESSURE 0x0007 /* Polyphonic Pressure */ +#define CONN_SRC_CHANNELPRESSURE 0x0008 /* Channel Pressure */ +#define CONN_SRC_VIBRATO 0x0009 /* Vibrato LFO */ +#define CONN_SRC_MONOPRESSURE 0x000a /* MIDI Mono pressure */ + + +/* Midi Controllers */ +#define CONN_SRC_CC91 0x00db /* Reverb Send */ +#define CONN_SRC_CC93 0x00dd /* Chorus Send */ + + +/* Generic Destinations */ +#define CONN_DST_GAIN 0x0001 /* Same as CONN_DST_ ATTENUATION, but more appropriate terminology. */ +#define CONN_DST_KEYNUMBER 0x0005 /* Key Number Generator */ + +/* Audio Channel Output Destinations */ +#define CONN_DST_LEFT 0x0010 /* Left Channel Send */ +#define CONN_DST_RIGHT 0x0011 /* Right Channel Send */ +#define CONN_DST_CENTER 0x0012 /* Center Channel Send */ +#define CONN_DST_LEFTREAR 0x0013 /* Left Rear Channel Send */ +#define CONN_DST_RIGHTREAR 0x0014 /* Right Rear Channel Send */ +#define CONN_DST_LFE_CHANNEL 0x0015 /* LFE Channel Send */ +#define CONN_DST_CHORUS 0x0080 /* Chorus Send */ +#define CONN_DST_REVERB 0x0081 /* Reverb Send */ + +/* Vibrato LFO Destinations */ +#define CONN_DST_VIB_FREQUENCY 0x0114 /* Vibrato Frequency */ +#define CONN_DST_VIB_STARTDELAY 0x0115 /* Vibrato Start Delay */ + +/* EG1 Destinations */ +#define CONN_DST_EG1_DELAYTIME 0x020B /* EG1 Delay Time */ +#define CONN_DST_EG1_HOLDTIME 0x020C /* EG1 Hold Time */ +#define CONN_DST_EG1_SHUTDOWNTIME 0x020D /* EG1 Shutdown Time */ + + +/* EG2 Destinations */ +#define CONN_DST_EG2_DELAYTIME 0x030F /* EG2 Delay Time */ +#define CONN_DST_EG2_HOLDTIME 0x0310 /* EG2 Hold Time */ + + +/* Filter Destinations */ +#define CONN_DST_FILTER_CUTOFF 0x0500 /* Filter Cutoff Frequency */ +#define CONN_DST_FILTER_Q 0x0501 /* Filter Resonance */ + + +/* Transforms */ +#define CONN_TRN_CONVEX 0x0002 /* Convex Transform */ +#define CONN_TRN_SWITCH 0x0003 /* Switch Transform */ + + +/* Conditional chunk operators */ + #define DLS_CDL_AND 0x0001 /* X = X & Y */ + #define DLS_CDL_OR 0x0002 /* X = X | Y */ + #define DLS_CDL_XOR 0x0003 /* X = X ^ Y */ + #define DLS_CDL_ADD 0x0004 /* X = X + Y */ + #define DLS_CDL_SUBTRACT 0x0005 /* X = X - Y */ + #define DLS_CDL_MULTIPLY 0x0006 /* X = X * Y */ + #define DLS_CDL_DIVIDE 0x0007 /* X = X / Y */ + #define DLS_CDL_LOGICAL_AND 0x0008 /* X = X && Y */ + #define DLS_CDL_LOGICAL_OR 0x0009 /* X = X || Y */ + #define DLS_CDL_LT 0x000A /* X = (X < Y) */ + #define DLS_CDL_LE 0x000B /* X = (X <= Y) */ + #define DLS_CDL_GT 0x000C /* X = (X > Y) */ + #define DLS_CDL_GE 0x000D /* X = (X >= Y) */ + #define DLS_CDL_EQ 0x000E /* X = (X == Y) */ + #define DLS_CDL_NOT 0x000F /* X = !X */ + #define DLS_CDL_CONST 0x0010 /* 32-bit constant */ + #define DLS_CDL_QUERY 0x0011 /* 32-bit value returned from query */ + #define DLS_CDL_QUERYSUPPORTED 0x0012 /* Test to see if query is supported by synth */ + +/* + Loop and release +*/ + +#define WLOOP_TYPE_RELEASE 1 + +/* + WaveLink chunk +*/ + +#define F_WAVELINK_MULTICHANNEL 0x0002 + + +/* + DLSID queries for +*/ + +DEFINE_GUID(DLSID_GMInHardware, 0x178f2f24, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(DLSID_GSInHardware, 0x178f2f25, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(DLSID_XGInHardware, 0x178f2f26, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(DLSID_SupportsDLS1, 0x178f2f27, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(DLSID_SupportsDLS2, 0xf14599e5, 0x4689, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); +DEFINE_GUID(DLSID_SampleMemorySize, 0x178f2f28, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); +DEFINE_GUID(DLSID_ManufacturersID, 0xb03e1181, 0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); +DEFINE_GUID(DLSID_ProductID, 0xb03e1182, 0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); +DEFINE_GUID(DLSID_SamplePlaybackRate, 0x2a91f713, 0xa4bf, 0x11d2, 0xbb, 0xdf, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); + +#endif /* _INC_DLS2 */ diff --git a/src/timidity/instrum.cpp b/src/timidity/instrum.cpp new file mode 100644 index 0000000000..0d1fc9ee5f --- /dev/null +++ b/src/timidity/instrum.cpp @@ -0,0 +1,952 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + instrum.c + + Code to load and unload GUS-compatible instrument patches. + +*/ + +#include +#include +#include +#include + +#include "timidity.h" +#include "m_swap.h" + +namespace Timidity +{ + +extern InstrumentLayer *load_instrument_dls(Renderer *song, int drum, int bank, int instrument); + +/* Some functions get aggravated if not even the standard banks are +available. */ +ToneBank standard_tonebank, standard_drumset; + +/* This is only used for tracks that don't specify a program */ +int default_program = DEFAULT_PROGRAM; + + +static void free_instrument(Instrument *ip) +{ + Sample *sp; + int i; + if (ip == NULL) + { + return; + } + for (i = 0, sp = &(ip->sample[0]); i < ip->samples; i++, sp++) + { + if (sp->data != NULL) + { + free(sp->data); + } + } + for (i = 0, sp = &(ip->right_sample[0]); i < ip->right_samples; i++) + { + if (sp->data != NULL) + { + free(sp->data); + } + } + free(ip->sample); + if (ip->right_sample != NULL) + { + free(ip->right_sample); + } + free(ip); +} + + +static void free_layer(InstrumentLayer *lp) +{ + InstrumentLayer *next; + + for (; lp; lp = next) + { + next = lp->next; + free_instrument(lp->instrument); + free(lp); + } +} + +static void free_bank(int dr, int b) +{ + int i; + ToneBank *bank = ((dr) ? drumset[b] : tonebank[b]); + for (i = 0; i < MAXPROG; i++) + { + if (bank->tone[i].layer != NULL) + { + /* Not that this could ever happen, of course */ + if (bank->tone[i].layer != MAGIC_LOAD_INSTRUMENT) + { + free_layer(bank->tone[i].layer); + bank->tone[i].layer = NULL; + } + } + } +} + + +int convert_envelope_rate_attack(Renderer *song, BYTE rate, BYTE fastness) +{ + int r; + + r = 3 - ((rate>>6) & 0x3); + r *= 3; + r = (int)(rate & 0x3f) << r; /* 6.9 fixed point */ + + /* 15.15 fixed point. */ + return int(((r * 44100) / song->rate) * song->control_ratio) << 10; +} + +int convert_envelope_rate(Renderer *song, BYTE rate) +{ + int r; + + r = 3 - ((rate>>6) & 0x3); + r *= 3; + r = (int)(rate & 0x3f) << r; /* 6.9 fixed point */ + + /* 15.15 fixed point. */ + return int(((r * 44100) / song->rate) * song->control_ratio) + << ((song->fast_decay) ? 10 : 9); +} + +int convert_envelope_offset(BYTE offset) +{ + /* This is not too good... Can anyone tell me what these values mean? + Are they GUS-style "exponential" volumes? And what does that mean? */ + + /* 15.15 fixed point */ + return offset << (7 + 15); +} + +int convert_tremolo_sweep(Renderer *song, BYTE sweep) +{ + if (!sweep) + return 0; + + return + int(((song->control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep)); +} + +int convert_vibrato_sweep(Renderer *song, BYTE sweep, int vib_control_ratio) +{ + if (!sweep) + return 0; + + return + (int) (FSCALE((double) (vib_control_ratio) * SWEEP_TUNING, SWEEP_SHIFT) / (song->rate * sweep)); + + /* this was overflowing with seashore.pat + + ((vib_control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep); + */ +} + +int convert_tremolo_rate(Renderer *song, BYTE rate) +{ + return + int(((song->control_ratio * rate) << RATE_SHIFT) / (TREMOLO_RATE_TUNING * song->rate)); +} + +int convert_vibrato_rate(Renderer *song, BYTE rate) +{ + /* Return a suitable vibrato_control_ratio value */ + return + int((VIBRATO_RATE_TUNING * song->rate) / (rate * 2 * VIBRATO_SAMPLE_INCREMENTS)); +} + +static void reverse_data(sample_t *sp, int ls, int le) +{ + sample_t s, *ep = sp + le; + sp += ls; + le -= ls; + le /= 2; + while (le--) + { + s = *sp; + *sp++ = *ep; + *ep-- = s; + } +} + +/* +If panning or note_to_use != -1, it will be used for all samples, +instead of the sample-specific values in the instrument file. + +For note_to_use, any value <0 or >127 will be forced to 0. + +For other parameters, 1 means yes, 0 means no, other values are +undefined. + +TODO: do reverse loops right */ +static InstrumentLayer *load_instrument(Renderer *song, const char *name, int font_type, int percussion, + int panning, int amp, int cfg_tuning, int note_to_use, + int strip_loop, int strip_envelope, + int strip_tail, int bank, int gm_num, int sf_ix) +{ + InstrumentLayer *lp, *lastlp, *headlp; + Instrument *ip; + FILE *fp; + BYTE tmp[239]; + int i,j; + bool noluck = false; + bool sf2flag = false; + int right_samples = 0; + int stereo_channels = 1, stereo_layer; + int vlayer_list[19][4], vlayer, vlayer_count; + + if (!name) return 0; + + /* Open patch file */ + if ((fp = open_file(name, 1, OF_NORMAL)) == NULL) + { + /* Try with various extensions */ + FString tmp = name; + tmp += ".pat"; + if ((fp = open_file(tmp, 1, OF_NORMAL)) == NULL) + { +#ifdef unix // Windows isn't case-sensitive. + tmp.ToUpper(); + if ((fp = open_file(tmp, 1, OF_NORMAL)) == NULL) +#endif + { + noluck = true; + } + } + } + + if (noluck) + { + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Instrument `%s' can't be found.", name); + return 0; + } + + /*song->ctl->cmsg(CMSG_INFO, VERB_NOISY, "Loading instrument %s", current_filename);*/ + + /* Read some headers and do cursory sanity checks. There are loads + of magic offsets. This could be rewritten... */ + + if ((239 != fread(tmp, 1, 239, fp)) || + (memcmp(tmp, "GF1PATCH110\0ID#000002", 22) && + memcmp(tmp, "GF1PATCH100\0ID#000002", 22))) /* don't know what the + differences are */ + { + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: not an instrument", name); + return 0; + } + + /* patch layout: + * bytes: info: starts at offset: + * 12 header (see above) 0 + * 10 Gravis ID 12 + * 60 description 22 + * 1 instruments 82 + * 1 voices 83 + * 1 channels 84 + * 2 number of waveforms 85 + * 2 master volume 87 + * 4 datasize 89 + * 36 reserved, but now: 93 + * 7 "SF2EXT\0" id 93 + * 1 right samples 100 + * 28 reserved 101 + * 2 instrument number 129 + * 16 instrument name 131 + * 4 instrument size 147 + * 1 number of layers 151 + * 40 reserved 152 + * 1 layer duplicate 192 + * 1 layer number 193 + * 4 layer size 194 + * 1 number of samples 198 + * 40 reserved 199 + * 239 + * THEN, for each sample, see below + */ + + if (!memcmp(tmp + 93, "SF2EXT", 6)) + { + sf2flag = true; + vlayer_count = tmp[152]; + } + + if (tmp[82] != 1 && tmp[82] != 0) /* instruments. To some patch makers, 0 means 1 */ + { + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't handle patches with %d instruments", tmp[82]); + return 0; + } + + if (tmp[151] != 1 && tmp[151] != 0) /* layers. What's a layer? */ + { + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't handle instruments with %d layers", tmp[151]); + return 0; + } + + + if (sf2flag && vlayer_count > 0) + { + for (i = 0; i < 9; i++) + for (j = 0; j < 4; j++) + vlayer_list[i][j] = tmp[153+i*4+j]; + for (i = 9; i < 19; i++) + for (j = 0; j < 4; j++) + vlayer_list[i][j] = tmp[199+(i-9)*4+j]; + } + else + { + for (i = 0; i < 19; i++) + for (j = 0; j < 4; j++) + vlayer_list[i][j] = 0; + vlayer_list[0][0] = 0; + vlayer_list[0][1] = 127; + vlayer_list[0][2] = tmp[198]; + vlayer_list[0][3] = 0; + vlayer_count = 1; + } + + lastlp = 0; + + for (vlayer = 0; vlayer < vlayer_count; vlayer++) + { + lp = (InstrumentLayer *)safe_malloc(sizeof(InstrumentLayer)); + lp->lo = vlayer_list[vlayer][0]; + lp->hi = vlayer_list[vlayer][1]; + ip = (Instrument *)safe_malloc(sizeof(Instrument)); + lp->instrument = ip; + lp->next = 0; + + if (lastlp != NULL) + { + lastlp->next = lp; + } + else + { + headlp = lp; + } + + lastlp = lp; + + ip->type = sf2flag ? INST_SF2 : INST_GUS; + ip->samples = vlayer_list[vlayer][2]; + ip->sample = (Sample *)safe_malloc(sizeof(Sample) * ip->samples); + ip->left_samples = ip->samples; + ip->left_sample = ip->sample; + right_samples = vlayer_list[vlayer][3]; + ip->right_samples = right_samples; + if (right_samples) + { + ip->right_sample = (Sample *)safe_malloc(sizeof(Sample) * right_samples); + stereo_channels = 2; + } + else + { + ip->right_sample = NULL; + } + + song->ctl->cmsg(CMSG_INFO, VERB_NOISY, "%s%s[%d,%d] %s(%d-%d layer %d of %d)", + (percussion)? " ":"", name, + (percussion)? note_to_use : gm_num, bank, + (right_samples)? "(2) " : "", + lp->lo, lp->hi, vlayer+1, vlayer_count); + + for (stereo_layer = 0; stereo_layer < stereo_channels; stereo_layer++) + { + int sample_count; + + if (stereo_layer == 0) + { + sample_count = ip->left_samples; + } + else if (stereo_layer == 1) + { + sample_count = ip->right_samples; + } + + for (i = 0; i < sample_count; i++) + { + BYTE fractions; + int tmplong; + WORD tmpshort; + WORD sample_volume; + BYTE tmpchar; + Sample *sp; + BYTE sf2delay; + +#define READ_CHAR(thing) \ + if (1 != fread(&tmpchar, 1, 1, fp)) goto fail; \ + thing = tmpchar; +#define READ_SHORT(thing) \ + if (1 != fread(&tmpshort, 2, 1, fp)) goto fail; \ + thing = LittleShort(tmpshort); +#define READ_LONG(thing) \ + if (1 != fread(&tmplong, 4, 1, fp)) goto fail; \ + thing = LittleLong(tmplong); + + /* + * 7 sample name + * 1 fractions + * 4 length + * 4 loop start + * 4 loop end + * 2 sample rate + * 4 low frequency + * 4 high frequency + * 4 root frequency + * 2 finetune + * 1 panning + * 6 envelope rates | + * 6 envelope offsets | 18 bytes + * 3 tremolo sweep, rate, depth | + * 3 vibrato sweep, rate, depth | + * 1 sample mode + * 2 scale frequency + * 2 scale factor | from 0 to 2048 or 0 to 2 + * 2 sample volume (??) + * 34 reserved + * Now: 1 delay + * 33 reserved + */ + skip(fp, 7); /* Skip the wave name */ + + if (1 != fread(&fractions, 1, 1, fp)) + { +fail: + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Error reading sample %d", i); + if (stereo_layer == 1) + { + for (j = 0; j < i; j++) + { + free(ip->right_sample[j].data); + } + free(ip->right_sample); + i = ip->left_samples; + } + for (j = 0; j < i; j++) + { + free(ip->left_sample[j].data); + } + free(ip->left_sample); + free(ip); + free(lp); + return 0; + } + + if (stereo_layer == 0) + { + sp = &(ip->left_sample[i]); + } + else if (stereo_layer == 1) + { + sp = &(ip->right_sample[i]); + } + + READ_LONG(sp->data_length); + READ_LONG(sp->loop_start); + READ_LONG(sp->loop_end); + READ_SHORT(sp->sample_rate); + READ_LONG(sp->low_freq); + READ_LONG(sp->high_freq); + READ_LONG(sp->root_freq); + skip(fp, 2); /* Unused by GUS: Why have a "root frequency" and then "tuning"?? */ + sp->low_vel = 0; + sp->high_vel = 127; + + READ_CHAR(tmp[0]); + + if (panning == -1) + sp->panning = (tmp[0] * 8 + 4) & 0x7f; + else + sp->panning = (BYTE)(panning & 0x7F); + sp->panning |= sp->panning << 7; + + sp->resonance = 0; + sp->cutoff_freq = 0; + sp->reverberation = 0; + sp->chorusdepth = 0; + sp->exclusiveClass = 0; + sp->keyToModEnvHold = 0; + sp->keyToModEnvDecay = 0; + sp->keyToVolEnvHold = 0; + sp->keyToVolEnvDecay = 0; + + if (cfg_tuning) + { + double tune_factor = (double)(cfg_tuning) / 1200.0; + tune_factor = pow(2.0, tune_factor); + sp->root_freq = (uint32)( tune_factor * (double)sp->root_freq ); + } + + /* envelope, tremolo, and vibrato */ + if (18 != fread(tmp, 1, 18, fp)) goto fail; + + if (!tmp[13] || !tmp[14]) + { + sp->tremolo_sweep_increment = 0; + sp->tremolo_phase_increment = 0; + sp->tremolo_depth = 0; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * no tremolo"); + } + else + { + sp->tremolo_sweep_increment = convert_tremolo_sweep(song, tmp[12]); + sp->tremolo_phase_increment = convert_tremolo_rate(song, tmp[13]); + sp->tremolo_depth = tmp[14]; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " * tremolo: sweep %d, phase %d, depth %d", + sp->tremolo_sweep_increment, sp->tremolo_phase_increment, + sp->tremolo_depth); + } + + if (!tmp[16] || !tmp[17]) + { + sp->vibrato_sweep_increment = 0; + sp->vibrato_control_ratio = 0; + sp->vibrato_depth = 0; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * no vibrato"); + } + else + { + sp->vibrato_control_ratio = convert_vibrato_rate(song, tmp[16]); + sp->vibrato_sweep_increment= convert_vibrato_sweep(song, tmp[15], sp->vibrato_control_ratio); + sp->vibrato_depth = tmp[17]; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " * vibrato: sweep %d, ctl %d, depth %d", + sp->vibrato_sweep_increment, sp->vibrato_control_ratio, + sp->vibrato_depth); + + } + + READ_CHAR(sp->modes); + READ_SHORT(sp->freq_center); + READ_SHORT(sp->freq_scale); + + if (sf2flag) + { + READ_SHORT(sample_volume); + READ_CHAR(sf2delay); + READ_CHAR(sp->exclusiveClass); + skip(fp, 32); + } + else + { + skip(fp, 36); + } + + /* Mark this as a fixed-pitch instrument if such a deed is desired. */ + if (note_to_use != -1) + sp->note_to_use = (BYTE)(note_to_use); + else + sp->note_to_use = 0; + + /* seashore.pat in the Midia patch set has no Sustain. I don't + understand why, and fixing it by adding the Sustain flag to + all looped patches probably breaks something else. We do it + anyway. */ + + if (sp->modes & MODES_LOOPING) + sp->modes |= MODES_SUSTAIN; + + /* Strip any loops and envelopes we're permitted to */ + if ((strip_loop == 1) && + (sp->modes & (MODES_SUSTAIN | MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE))) + { + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Removing loop and/or sustain"); + sp->modes &=~(MODES_SUSTAIN | MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE); + } + + if (strip_envelope == 1) + { + if (sp->modes & MODES_ENVELOPE) + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Removing envelope"); + sp->modes &= ~MODES_ENVELOPE; + } + else if (strip_envelope != 0) + { + /* Have to make a guess. */ + if (!(sp->modes & (MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE))) + { + /* No loop? Then what's there to sustain? No envelope needed either... */ + sp->modes &= ~(MODES_SUSTAIN|MODES_ENVELOPE); + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " - No loop, removing sustain and envelope"); + } + else if (!memcmp(tmp, "??????", 6) || tmp[11] >= 100) + { + /* Envelope rates all maxed out? Envelope end at a high "offset"? + That's a weird envelope. Take it out. */ + sp->modes &= ~MODES_ENVELOPE; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Weirdness, removing envelope"); + } + else if (!(sp->modes & MODES_SUSTAIN)) + { + /* No sustain? Then no envelope. I don't know if this is + justified, but patches without sustain usually don't need the + envelope either... at least the Gravis ones. They're mostly + drums. I think. */ + sp->modes &= ~MODES_ENVELOPE; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - No sustain, removing envelope"); + } + } + + sp->attenuation = 0; + + for (j = ATTACK; j < DELAY; j++) + { + sp->envelope_rate[j] = + (j < 3) ? convert_envelope_rate_attack(song, tmp[j], 11) : convert_envelope_rate(song, tmp[j]); + sp->envelope_offset[j] = convert_envelope_offset(tmp[6+j]); + } + if (sf2flag) + { + if (sf2delay > 5) + { + sf2delay = 5; + } + sp->envelope_rate[DELAY] = (int)( (sf2delay * song->rate) / 1000 ); + } + else + { + sp->envelope_rate[DELAY] = 0; + } + sp->envelope_offset[DELAY] = 0; + + for (j = ATTACK; j < DELAY; j++) + { + sp->modulation_rate[j] = float(sp->envelope_rate[j]); + sp->modulation_offset[j] = float(sp->envelope_offset[j]); + } + sp->modulation_rate[DELAY] = sp->modulation_offset[DELAY] = 0; + sp->modEnvToFilterFc = 0; + sp->modEnvToPitch = 0; + sp->lfo_sweep_increment = 0; + sp->lfo_phase_increment = 0; + sp->modLfoToFilterFc = 0; + + /* Then read the sample data */ + if (((sp->modes & MODES_16BIT) && sp->data_length/2 > MAX_SAMPLE_SIZE) || + (!(sp->modes & MODES_16BIT) && sp->data_length > MAX_SAMPLE_SIZE)) + { + goto fail; + } + sp->data = (sample_t *)safe_malloc(sp->data_length + 1); + + if (1 != fread(sp->data, sp->data_length, 1, fp)) + goto fail; + + convert_sample_data(sp, sp->data); + + /* Reverse reverse loops and pass them off as normal loops */ + if (sp->modes & MODES_REVERSE) + { + int t; + /* The GUS apparently plays reverse loops by reversing the + whole sample. We do the same because the GUS does not SUCK. */ + + song->ctl->cmsg(CMSG_WARNING, VERB_NORMAL, "Reverse loop in %s", name); + reverse_data((sample_t *)sp->data, 0, sp->data_length); + sp->data[sp->data_length] = sp->data[sp->data_length - 1]; + + t = sp->loop_start; + sp->loop_start = sp->data_length - sp->loop_end; + sp->loop_end = sp->data_length - t; + + sp->modes &= ~MODES_REVERSE; + sp->modes |= MODES_LOOPING; /* just in case */ + } + + if (amp != -1) + { + sp->volume = (amp) / 100.f; + } + else if (sf2flag) + { + sp->volume = (sample_volume) / 255.f; + } + else + { +#if defined(ADJUST_SAMPLE_VOLUMES) + /* Try to determine a volume scaling factor for the sample. + This is a very crude adjustment, but things sound more + balanced with it. Still, this should be a runtime option. */ + int i, numsamps = sp->data_length; + sample_t maxamp = 0, a; + sample_t *tmp; + for (i = numsamps, tmp = sp->data; i; --i) + { + a = abs(*tmp++); + if (a > maxamp) + maxamp = a; + } + sp->volume = 1 / maxamp; + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * volume comp: %f", sp->volume); +#else + sp->volume = 1; +#endif + } + + /* Then fractional samples */ + sp->data_length <<= FRACTION_BITS; + sp->loop_start <<= FRACTION_BITS; + sp->loop_end <<= FRACTION_BITS; + + /* Adjust for fractional loop points. */ + sp->loop_start |= (fractions & 0x0F) << (FRACTION_BITS-4); + sp->loop_end |= ((fractions>>4) & 0x0F) << (FRACTION_BITS-4); + + /* If this instrument will always be played on the same note, + and it's not looped, we can resample it now. */ + if (sp->note_to_use && !(sp->modes & MODES_LOOPING)) + pre_resample(song, sp); + + if (strip_tail == 1) + { + /* Let's not really, just say we did. */ + song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Stripping tail"); + sp->data_length = sp->loop_end; + } + } /* end of sample loop */ + } /* end of stereo layer loop */ + } /* end of vlayer loop */ + + + close_file(fp); + return headlp; +} + +void convert_sample_data(Sample *sp, const void *data) +{ + /* convert everything to 32-bit floating point data */ + sample_t *newdata; + + switch (sp->modes & (MODES_16BIT | MODES_UNSIGNED)) + { + case 0: + { /* 8-bit, signed */ + SBYTE *cp = (SBYTE *)data; + newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t)); + for (int i = 0; i < sp->data_length; ++i) + { + if (cp[i] < 0) + { + newdata[i] = float(cp[i]) / 128.f; + } + else + { + newdata[i] = float(cp[i]) / 127.f; + } + } + break; + } + + case MODES_UNSIGNED: + { /* 8-bit, unsigned */ + BYTE *cp = (BYTE *)data; + newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t)); + for (int i = 0; i < sp->data_length; ++i) + { + int c = cp[i] - 128; + if (c < 0) + { + newdata[i] = float(c) / 128.f; + } + else + { + newdata[i] = float(c) / 127.f; + } + } + break; + } + + case MODES_16BIT: + { /* 16-bit, signed */ + SWORD *cp = (SWORD *)data; + /* Convert these to samples */ + sp->data_length >>= 1; + sp->loop_start >>= 1; + sp->loop_end >>= 1; + newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t)); + for (int i = 0; i < sp->data_length; ++i) + { + int c = LittleShort(cp[i]); + if (c < 0) + { + newdata[i] = float(c) / 32768.f; + } + else + { + newdata[i] = float(c) / 32767.f; + } + } + break; + } + + case MODES_16BIT | MODES_UNSIGNED: + { /* 16-bit, unsigned */ + WORD *cp = (WORD *)data; + /* Convert these to samples */ + sp->data_length >>= 1; + sp->loop_start >>= 1; + sp->loop_end >>= 1; + newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t)); + for (int i = 0; i < sp->data_length; ++i) + { + int c = LittleShort(cp[i]) - 32768; + if (c < 0) + { + newdata[i] = float(c) / 32768.f; + } + else + { + newdata[i] = float(c) / 32767.f; + } + } + break; + } + } + /* Duplicate the final sample for linear interpolation. */ + newdata[sp->data_length] = newdata[sp->data_length - 1]; + if (sp->data != NULL) + { + free(sp->data); + } + sp->data = newdata; +} + +static int fill_bank(Renderer *song, int dr, int b) +{ + int i, errors = 0; + ToneBank *bank = ((dr) ? drumset[b] : tonebank[b]); + if (bank == NULL) + { + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Huh. Tried to load instruments in non-existent %s %d", + (dr) ? "drumset" : "tone bank", b); + return 0; + } + for (i = 0; i < MAXPROG; i++) + { + if (bank->tone[i].layer == MAGIC_LOAD_INSTRUMENT) + { + bank->tone[i].layer = load_instrument_dls(song, dr, b, i); + if (bank->tone[i].layer != NULL) + { + continue; + } + if (bank->tone[i].name.IsEmpty()) + { + song->ctl->cmsg(CMSG_WARNING, (b!=0) ? VERB_VERBOSE : VERB_NORMAL, + "No instrument mapped to %s %d, program %d%s", + (dr)? "drum set" : "tone bank", b, i, + (b!=0) ? "" : " - this instrument will not be heard"); + if (b!=0) + { + /* Mark the corresponding instrument in the default + bank / drumset for loading (if it isn't already) */ + if (!dr) + { + if (!(standard_tonebank.tone[i].layer)) + standard_tonebank.tone[i].layer= + MAGIC_LOAD_INSTRUMENT; + } + else + { + if (!(standard_drumset.tone[i].layer)) + standard_drumset.tone[i].layer= + MAGIC_LOAD_INSTRUMENT; + } + } + bank->tone[i].layer=0; + errors++; + } + else if (!(bank->tone[i].layer= + load_instrument(song, bank->tone[i].name, + bank->tone[i].font_type, + (dr) ? 1 : 0, + bank->tone[i].pan, + bank->tone[i].amp, + bank->tone[i].tuning, + (bank->tone[i].note!=-1) ? + bank->tone[i].note : + ((dr) ? i : -1), + (bank->tone[i].strip_loop!=-1) ? + bank->tone[i].strip_loop : + ((dr) ? 1 : -1), + (bank->tone[i].strip_envelope != -1) ? + bank->tone[i].strip_envelope : + ((dr) ? 1 : -1), + bank->tone[i].strip_tail, + b, + ((dr) ? i + 128 : i), + bank->tone[i].sf_ix + ))) + { + song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Couldn't load instrument %s (%s %d, program %d)", + bank->tone[i].name, + (dr)? "drum set" : "tone bank", b, i); + errors++; + } + } + } + return errors; +} + +int Renderer::load_missing_instruments() +{ + int i = MAXBANK, errors = 0; + while (i--) + { + if (tonebank[i] != NULL) + errors += fill_bank(this, 0,i); + if (drumset[i] != NULL) + errors += fill_bank(this, 1,i); + } + return errors; +} + +void free_instruments() +{ + int i = 128; + while (i--) + { + if (tonebank[i] != NULL) + free_bank(0,i); + if (drumset[i] != NULL) + free_bank(1,i); + } +} + +int Renderer::set_default_instrument(const char *name) +{ + InstrumentLayer *lp; + if (!(lp = load_instrument(this, name, FONT_NORMAL, 0, -1, -1, 0, -1, -1, -1, -1, 0, -1, -1))) + return -1; + if (default_instrument) + free_layer(default_instrument); + default_instrument = lp; + default_program = SPECIAL_PROGRAM; + return 0; +} + +} diff --git a/src/timidity/instrum.obj b/src/timidity/instrum.obj new file mode 100644 index 0000000000..5ea265d40a Binary files /dev/null and b/src/timidity/instrum.obj differ diff --git a/src/timidity/instrum_dls.cpp b/src/timidity/instrum_dls.cpp new file mode 100644 index 0000000000..9987d05d12 --- /dev/null +++ b/src/timidity/instrum_dls.cpp @@ -0,0 +1,1260 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + instrum_dls.c + +*/ + +#include +#include +#include + +#include "timidity.h" +#include "doomdef.h" +#include "m_swap.h" + +#define __Sound_SetError(x) + +namespace Timidity +{ + +/*-------------------------------------------------------------------------*/ +/* * * * * * * * * * * * * * * * * load_riff.h * * * * * * * * * * * * * * */ +/*-------------------------------------------------------------------------*/ +struct RIFF_Chunk +{ + RIFF_Chunk() + { + memset(this, 0, sizeof(*this)); + } + ~RIFF_Chunk() + { + // data is not freed here because it may be owned by a parent chunk + if (child != NULL) + { + delete child; + } + if (next != NULL) + { + delete next; + } + } + + DWORD magic; + DWORD length; + DWORD subtype; + BYTE *data; + RIFF_Chunk *child; + RIFF_Chunk *next; +}; + +RIFF_Chunk *LoadRIFF(FILE *src); +void FreeRIFF(RIFF_Chunk *chunk); +void PrintRIFF(RIFF_Chunk *chunk, int level); +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*-------------------------------------------------------------------------*/ +/* * * * * * * * * * * * * * * * * load_riff.c * * * * * * * * * * * * * * */ +/*-------------------------------------------------------------------------*/ +#define RIFF MAKE_ID('R','I','F','F') +#define LIST MAKE_ID('L','I','S','T') + +static bool ChunkHasSubType(DWORD magic) +{ + return (magic == RIFF || magic == LIST); +} + +static int ChunkHasSubChunks(DWORD magic) +{ + return (magic == RIFF || magic == LIST); +} + +static void LoadSubChunks(RIFF_Chunk *chunk, BYTE *data, DWORD left) +{ + BYTE *subchunkData; + DWORD subchunkDataLen; + + while ( left > 8 ) { + RIFF_Chunk *child = new RIFF_Chunk; + RIFF_Chunk *next, *prev = NULL; + for ( next = chunk->child; next; next = next->next ) { + prev = next; + } + if ( prev ) { + prev->next = child; + } else { + chunk->child = child; + } + + child->magic = *(DWORD *)data; + data += 4; + left -= 4; + child->length = LittleLong(*(DWORD *)data); + data += 4; + left -= 4; + child->data = data; + + if ( child->length > left ) { + child->length = left; + } + + subchunkData = child->data; + subchunkDataLen = child->length; + if ( ChunkHasSubType(child->magic) && subchunkDataLen >= 4 ) { + child->subtype = *(DWORD *)subchunkData; + subchunkData += 4; + subchunkDataLen -= 4; + } + if ( ChunkHasSubChunks(child->magic) ) { + LoadSubChunks(child, subchunkData, subchunkDataLen); + } + + data += child->length + (child->length & 1); + left -= child->length + (child->length & 1); + } +} + +RIFF_Chunk *LoadRIFF(FILE *src) +{ + RIFF_Chunk *chunk; + BYTE *subchunkData; + DWORD subchunkDataLen; + + /* Allocate the chunk structure */ + chunk = new RIFF_Chunk; + + /* Make sure the file is in RIFF format */ + fread(&chunk->magic, 4, 1, src); + fread(&chunk->length, 4, 1, src); + chunk->length = LittleLong(chunk->length); + if ( chunk->magic != RIFF ) { + __Sound_SetError("Not a RIFF file"); + delete chunk; + return NULL; + } + chunk->data = (BYTE *)malloc(chunk->length); + if ( chunk->data == NULL ) { + __Sound_SetError(ERR_OUT_OF_MEMORY); + delete chunk; + return NULL; + } + if ( fread(chunk->data, chunk->length, 1, src) != 1 ) { + __Sound_SetError(ERR_IO_ERROR); + FreeRIFF(chunk); + return NULL; + } + subchunkData = chunk->data; + subchunkDataLen = chunk->length; + if ( ChunkHasSubType(chunk->magic) && subchunkDataLen >= 4 ) { + chunk->subtype = *(DWORD *)subchunkData; + subchunkData += 4; + subchunkDataLen -= 4; + } + if ( ChunkHasSubChunks(chunk->magic) ) { + LoadSubChunks(chunk, subchunkData, subchunkDataLen); + } + return chunk; +} + +void FreeRIFF(RIFF_Chunk *chunk) +{ + free(chunk->data); + delete chunk; +} + +void PrintRIFF(RIFF_Chunk *chunk, int level) +{ + static char prefix[128]; + + if ( level == sizeof(prefix)-1 ) { + return; + } + if ( level > 0 ) { + prefix[(level-1)*2] = ' '; + prefix[(level-1)*2+1] = ' '; + } + prefix[level*2] = '\0'; + printf("%sChunk: %c%c%c%c (%d bytes)", prefix, + ((chunk->magic >> 0) & 0xFF), + ((chunk->magic >> 8) & 0xFF), + ((chunk->magic >> 16) & 0xFF), + ((chunk->magic >> 24) & 0xFF), chunk->length); + if ( chunk->subtype ) { + printf(" subtype: %c%c%c%c", + ((chunk->subtype >> 0) & 0xFF), + ((chunk->subtype >> 8) & 0xFF), + ((chunk->subtype >> 16) & 0xFF), + ((chunk->subtype >> 24) & 0xFF)); + } + printf("\n"); + if ( chunk->child ) { + printf("%s{\n", prefix); + PrintRIFF(chunk->child, level + 1); + printf("%s}\n", prefix); + } + if ( chunk->next ) { + PrintRIFF(chunk->next, level); + } + if ( level > 0 ) { + prefix[(level-1)*2] = '\0'; + } +} + +#ifdef TEST_MAIN_RIFF + +main(int argc, char *argv[]) +{ + int i; + for ( i = 1; i < argc; ++i ) { + RIFF_Chunk *chunk; + SDL_RWops *src = SDL_RWFromFile(argv[i], "rb"); + if ( !src ) { + fprintf(stderr, "Couldn't open %s: %s", argv[i], SDL_GetError()); + continue; + } + chunk = LoadRIFF(src); + if ( chunk ) { + PrintRIFF(chunk, 0); + FreeRIFF(chunk); + } else { + fprintf(stderr, "Couldn't load %s: %s\n", argv[i], SDL_GetError()); + } + SDL_RWclose(src); + } +} + +#endif // TEST_MAIN +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*-------------------------------------------------------------------------*/ +/* * * * * * * * * * * * * * * * * load_dls.h * * * * * * * * * * * * * * */ +/*-------------------------------------------------------------------------*/ +/* This code is based on the DLS spec version 1.1, available at: +http://www.midi.org/about-midi/dls/dlsspec.shtml +*/ + +/* Some typedefs so the public dls headers don't need to be modified */ +#define FAR +typedef SWORD SHORT; +typedef WORD USHORT; +typedef SDWORD LONG; +typedef DWORD ULONG; +#define mmioFOURCC MAKE_ID +#define DEFINE_GUID(A, B, C, E, F, G, H, I, J, K, L, M) + +#include "dls1.h" +#include "dls2.h" + +struct WaveFMT +{ + WORD wFormatTag; + WORD wChannels; + DWORD dwSamplesPerSec; + DWORD dwAvgBytesPerSec; + WORD wBlockAlign; + WORD wBitsPerSample; +}; + +struct DLS_Wave +{ + WaveFMT *format; + BYTE *data; + DWORD length; + WSMPL *wsmp; + WLOOP *wsmp_loop; +}; + +struct DLS_Region +{ + RGNHEADER *header; + WAVELINK *wlnk; + WSMPL *wsmp; + WLOOP *wsmp_loop; + CONNECTIONLIST *art; + CONNECTION *artList; +}; + +struct DLS_Instrument +{ + const char *name; + INSTHEADER *header; + DLS_Region *regions; + CONNECTIONLIST *art; + CONNECTION *artList; +}; + +struct DLS_Data +{ + RIFF_Chunk *chunk; + + DWORD cInstruments; + DLS_Instrument *instruments; + + POOLTABLE *ptbl; + POOLCUE *ptblList; + DLS_Wave *waveList; + + const char *name; + const char *artist; + const char *copyright; + const char *comments; +}; + +DLS_Data *LoadDLS(FILE *src); +void FreeDLS(DLS_Data *chunk); +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*-------------------------------------------------------------------------*/ +/* * * * * * * * * * * * * * * * * load_dls.c * * * * * * * * * * * * * * */ +/*-------------------------------------------------------------------------*/ + +#define FOURCC_LIST mmioFOURCC('L','I','S','T') +#define FOURCC_FMT mmioFOURCC('f','m','t',' ') +#define FOURCC_DATA mmioFOURCC('d','a','t','a') +#define FOURCC_INFO mmioFOURCC('I','N','F','O') +#define FOURCC_IARL mmioFOURCC('I','A','R','L') +#define FOURCC_IART mmioFOURCC('I','A','R','T') +#define FOURCC_ICMS mmioFOURCC('I','C','M','S') +#define FOURCC_ICMT mmioFOURCC('I','C','M','T') +#define FOURCC_ICOP mmioFOURCC('I','C','O','P') +#define FOURCC_ICRD mmioFOURCC('I','C','R','D') +#define FOURCC_IENG mmioFOURCC('I','E','N','G') +#define FOURCC_IGNR mmioFOURCC('I','G','N','R') +#define FOURCC_IKEY mmioFOURCC('I','K','E','Y') +#define FOURCC_IMED mmioFOURCC('I','M','E','D') +#define FOURCC_INAM mmioFOURCC('I','N','A','M') +#define FOURCC_IPRD mmioFOURCC('I','P','R','D') +#define FOURCC_ISBJ mmioFOURCC('I','S','B','J') +#define FOURCC_ISFT mmioFOURCC('I','S','F','T') +#define FOURCC_ISRC mmioFOURCC('I','S','R','C') +#define FOURCC_ISRF mmioFOURCC('I','S','R','F') +#define FOURCC_ITCH mmioFOURCC('I','T','C','H') + + +static void FreeRegions(DLS_Instrument *instrument) +{ + if ( instrument->regions ) { + free(instrument->regions); + } +} + +static void AllocRegions(DLS_Instrument *instrument) +{ + int datalen = (instrument->header->cRegions * sizeof(DLS_Region)); + FreeRegions(instrument); + instrument->regions = (DLS_Region *)malloc(datalen); + if ( instrument->regions ) { + memset(instrument->regions, 0, datalen); + } +} + +static void FreeInstruments(DLS_Data *data) +{ + if ( data->instruments ) { + DWORD i; + for ( i = 0; i < data->cInstruments; ++i ) { + FreeRegions(&data->instruments[i]); + } + free(data->instruments); + } +} + +static void AllocInstruments(DLS_Data *data) +{ + int datalen = (data->cInstruments * sizeof(DLS_Instrument)); + FreeInstruments(data); + data->instruments = (DLS_Instrument *)malloc(datalen); + if ( data->instruments ) { + memset(data->instruments, 0, datalen); + } +} + +static void FreeWaveList(DLS_Data *data) +{ + if ( data->waveList ) { + free(data->waveList); + } +} + +static void AllocWaveList(DLS_Data *data) +{ + int datalen = (data->ptbl->cCues * sizeof(DLS_Wave)); + FreeWaveList(data); + data->waveList = (DLS_Wave *)malloc(datalen); + if ( data->waveList ) { + memset(data->waveList, 0, datalen); + } +} + +static void Parse_colh(DLS_Data *data, RIFF_Chunk *chunk) +{ + data->cInstruments = LittleLong(*(DWORD *)chunk->data); + AllocInstruments(data); +} + +static void Parse_insh(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument) +{ + INSTHEADER *header = (INSTHEADER *)chunk->data; + header->cRegions = LittleLong(header->cRegions); + header->Locale.ulBank = LittleLong(header->Locale.ulBank); + header->Locale.ulInstrument = LittleLong(header->Locale.ulInstrument); + instrument->header = header; + AllocRegions(instrument); +} + +static void Parse_rgnh(DLS_Data *data, RIFF_Chunk *chunk, DLS_Region *region) +{ + RGNHEADER *header = (RGNHEADER *)chunk->data; + header->RangeKey.usLow = LittleShort(header->RangeKey.usLow); + header->RangeKey.usHigh = LittleShort(header->RangeKey.usHigh); + header->RangeVelocity.usLow = LittleShort(header->RangeVelocity.usLow); + header->RangeVelocity.usHigh = LittleShort(header->RangeVelocity.usHigh); + header->fusOptions = LittleShort(header->fusOptions); + header->usKeyGroup = LittleShort(header->usKeyGroup); + region->header = header; +} + +static void Parse_wlnk(DLS_Data *data, RIFF_Chunk *chunk, DLS_Region *region) +{ + WAVELINK *wlnk = (WAVELINK *)chunk->data; + wlnk->fusOptions = LittleShort(wlnk->fusOptions); + wlnk->usPhaseGroup = LittleShort(wlnk->usPhaseGroup); + wlnk->ulChannel = LittleShort(wlnk->ulChannel); + wlnk->ulTableIndex = LittleShort(wlnk->ulTableIndex); + region->wlnk = wlnk; +} + +static void Parse_wsmp(DLS_Data *data, RIFF_Chunk *chunk, WSMPL **wsmp_ptr, WLOOP **wsmp_loop_ptr) +{ + DWORD i; + WSMPL *wsmp = (WSMPL *)chunk->data; + WLOOP *loop; + wsmp->cbSize = LittleLong(wsmp->cbSize); + wsmp->usUnityNote = LittleShort(wsmp->usUnityNote); + wsmp->sFineTune = LittleShort(wsmp->sFineTune); + wsmp->lAttenuation = LittleLong(wsmp->lAttenuation); + wsmp->fulOptions = LittleLong(wsmp->fulOptions); + wsmp->cSampleLoops = LittleLong(wsmp->cSampleLoops); + loop = (WLOOP *)((BYTE *)chunk->data + wsmp->cbSize); + *wsmp_ptr = wsmp; + *wsmp_loop_ptr = loop; + for ( i = 0; i < wsmp->cSampleLoops; ++i ) { + loop->cbSize = LittleLong(loop->cbSize); + loop->ulType = LittleLong(loop->ulType); + loop->ulStart = LittleLong(loop->ulStart); + loop->ulLength = LittleLong(loop->ulLength); + ++loop; + } +} + +static void Parse_art(DLS_Data *data, RIFF_Chunk *chunk, CONNECTIONLIST **art_ptr, CONNECTION **artList_ptr) +{ + DWORD i; + CONNECTIONLIST *art = (CONNECTIONLIST *)chunk->data; + CONNECTION *artList; + art->cbSize = LittleLong(art->cbSize); + art->cConnections = LittleLong(art->cConnections); + artList = (CONNECTION *)((BYTE *)chunk->data + art->cbSize); + *art_ptr = art; + *artList_ptr = artList; + for ( i = 0; i < art->cConnections; ++i ) { + artList->usSource = LittleShort(artList->usSource); + artList->usControl = LittleShort(artList->usControl); + artList->usDestination = LittleShort(artList->usDestination); + artList->usTransform = LittleShort(artList->usTransform); + artList->lScale = LittleLong(artList->lScale); + ++artList; + } +} + +static void Parse_lart(DLS_Data *data, RIFF_Chunk *chunk, CONNECTIONLIST **conn_ptr, CONNECTION **connList_ptr) +{ + /* FIXME: This only supports one set of connections */ + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_ART1: + case FOURCC_ART2: + Parse_art(data, chunk, conn_ptr, connList_ptr); + return; + } + } +} + +static void Parse_rgn(DLS_Data *data, RIFF_Chunk *chunk, DLS_Region *region) +{ + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_RGNH: + Parse_rgnh(data, chunk, region); + break; + case FOURCC_WLNK: + Parse_wlnk(data, chunk, region); + break; + case FOURCC_WSMP: + Parse_wsmp(data, chunk, ®ion->wsmp, ®ion->wsmp_loop); + break; + case FOURCC_LART: + case FOURCC_LAR2: + Parse_lart(data, chunk, ®ion->art, ®ion->artList); + break; + } + } +} + +static void Parse_lrgn(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument) +{ + DWORD region = 0; + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_RGN: + case FOURCC_RGN2: + if ( region < instrument->header->cRegions ) { + Parse_rgn(data, chunk, &instrument->regions[region++]); + } + break; + } + } +} + +static void Parse_INFO_INS(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument) +{ + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_INAM: /* Name */ + instrument->name = (const char *)chunk->data; + break; + } + } +} + +static void Parse_ins(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument) +{ + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_INSH: + Parse_insh(data, chunk, instrument); + break; + case FOURCC_LRGN: + Parse_lrgn(data, chunk, instrument); + break; + case FOURCC_LART: + case FOURCC_LAR2: + Parse_lart(data, chunk, &instrument->art, &instrument->artList); + break; + case FOURCC_INFO: + Parse_INFO_INS(data, chunk, instrument); + break; + } + } +} + +static void Parse_lins(DLS_Data *data, RIFF_Chunk *chunk) +{ + DWORD instrument = 0; + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_INS: + if ( instrument < data->cInstruments ) { + Parse_ins(data, chunk, &data->instruments[instrument++]); + } + break; + } + } +} + +static void Parse_ptbl(DLS_Data *data, RIFF_Chunk *chunk) +{ + DWORD i; + POOLTABLE *ptbl = (POOLTABLE *)chunk->data; + ptbl->cbSize = LittleLong(ptbl->cbSize); + ptbl->cCues = LittleLong(ptbl->cCues); + data->ptbl = ptbl; + data->ptblList = (POOLCUE *)((BYTE *)chunk->data + ptbl->cbSize); + for ( i = 0; i < ptbl->cCues; ++i ) { + data->ptblList[i].ulOffset = LittleLong(data->ptblList[i].ulOffset); + } + AllocWaveList(data); +} + +static void Parse_fmt(DLS_Data *data, RIFF_Chunk *chunk, DLS_Wave *wave) +{ + WaveFMT *fmt = (WaveFMT *)chunk->data; + fmt->wFormatTag = LittleShort(fmt->wFormatTag); + fmt->wChannels = LittleShort(fmt->wChannels); + fmt->dwSamplesPerSec = LittleLong(fmt->dwSamplesPerSec); + fmt->dwAvgBytesPerSec = LittleLong(fmt->dwAvgBytesPerSec); + fmt->wBlockAlign = LittleShort(fmt->wBlockAlign); + fmt->wBitsPerSample = LittleShort(fmt->wBitsPerSample); + wave->format = fmt; +} + +static void Parse_data(DLS_Data *data, RIFF_Chunk *chunk, DLS_Wave *wave) +{ + wave->data = chunk->data; + wave->length = chunk->length; +} + +static void Parse_wave(DLS_Data *data, RIFF_Chunk *chunk, DLS_Wave *wave) +{ + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_FMT: + Parse_fmt(data, chunk, wave); + break; + case FOURCC_DATA: + Parse_data(data, chunk, wave); + break; + case FOURCC_WSMP: + Parse_wsmp(data, chunk, &wave->wsmp, &wave->wsmp_loop); + break; + } + } +} + +static void Parse_wvpl(DLS_Data *data, RIFF_Chunk *chunk) +{ + DWORD wave = 0; + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_wave: + if ( wave < data->ptbl->cCues ) { + Parse_wave(data, chunk, &data->waveList[wave++]); + } + break; + } + } +} + +static void Parse_INFO_DLS(DLS_Data *data, RIFF_Chunk *chunk) +{ + for ( chunk = chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_IARL: /* Archival Location */ + break; + case FOURCC_IART: /* Artist */ + data->artist = (const char *)chunk->data; + break; + case FOURCC_ICMS: /* Commisioned */ + break; + case FOURCC_ICMT: /* Comments */ + data->comments = (const char *)chunk->data; + break; + case FOURCC_ICOP: /* Copyright */ + data->copyright = (const char *)chunk->data; + break; + case FOURCC_ICRD: /* Creation Date */ + break; + case FOURCC_IENG: /* Engineer */ + break; + case FOURCC_IGNR: /* Genre */ + break; + case FOURCC_IKEY: /* Keywords */ + break; + case FOURCC_IMED: /* Medium */ + break; + case FOURCC_INAM: /* Name */ + data->name = (const char *)chunk->data; + break; + case FOURCC_IPRD: /* Product */ + break; + case FOURCC_ISBJ: /* Subject */ + break; + case FOURCC_ISFT: /* Software */ + break; + case FOURCC_ISRC: /* Source */ + break; + case FOURCC_ISRF: /* Source Form */ + break; + case FOURCC_ITCH: /* Technician */ + break; + } + } +} + +DLS_Data *LoadDLS(FILE *src) +{ + RIFF_Chunk *chunk; + DLS_Data *data = (DLS_Data *)malloc(sizeof(*data)); + if ( !data ) { + __Sound_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + memset(data, 0, sizeof(*data)); + + data->chunk = LoadRIFF(src); + if ( !data->chunk ) { + FreeDLS(data); + return NULL; + } + + for ( chunk = data->chunk->child; chunk; chunk = chunk->next ) { + DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic; + switch(magic) { + case FOURCC_COLH: + Parse_colh(data, chunk); + break; + case FOURCC_LINS: + Parse_lins(data, chunk); + break; + case FOURCC_PTBL: + Parse_ptbl(data, chunk); + break; + case FOURCC_WVPL: + Parse_wvpl(data, chunk); + break; + case FOURCC_INFO: + Parse_INFO_DLS(data, chunk); + break; + } + } + return data; +} + +void FreeDLS(DLS_Data *data) +{ + if ( data->chunk ) { + FreeRIFF(data->chunk); + } + FreeInstruments(data); + FreeWaveList(data); + free(data); +} + +static const char *SourceToString(USHORT usSource) +{ + static char unknown[32]; + switch(usSource) { + case CONN_SRC_NONE: + return "NONE"; + case CONN_SRC_LFO: + return "LFO"; + case CONN_SRC_KEYONVELOCITY: + return "KEYONVELOCITY"; + case CONN_SRC_KEYNUMBER: + return "KEYNUMBER"; + case CONN_SRC_EG1: + return "EG1"; + case CONN_SRC_EG2: + return "EG2"; + case CONN_SRC_PITCHWHEEL: + return "PITCHWHEEL"; + case CONN_SRC_CC1: + return "CC1"; + case CONN_SRC_CC7: + return "CC7"; + case CONN_SRC_CC10: + return "CC10"; + case CONN_SRC_CC11: + return "CC11"; + case CONN_SRC_POLYPRESSURE: + return "POLYPRESSURE"; + case CONN_SRC_CHANNELPRESSURE: + return "CHANNELPRESSURE"; + case CONN_SRC_VIBRATO: + return "VIBRATO"; + case CONN_SRC_MONOPRESSURE: + return "MONOPRESSURE"; + case CONN_SRC_CC91: + return "CC91"; + case CONN_SRC_CC93: + return "CC93"; + default: + sprintf(unknown, "UNKNOWN (0x%04x)", usSource); + return unknown; + } +} + +static const char *TransformToString(USHORT usTransform) +{ + static char unknown[32]; + switch (usTransform) { + case CONN_TRN_NONE: + return "NONE"; + case CONN_TRN_CONCAVE: + return "CONCAVE"; + case CONN_TRN_CONVEX: + return "CONVEX"; + case CONN_TRN_SWITCH: + return "SWITCH"; + default: + sprintf(unknown, "UNKNOWN (0x%04x)", usTransform); + return unknown; + } +} + +static const char *DestinationToString(USHORT usDestination) +{ + static char unknown[32]; + switch (usDestination) { + case CONN_DST_NONE: + return "NONE"; + case CONN_DST_ATTENUATION: + return "ATTENUATION"; + case CONN_DST_PITCH: + return "PITCH"; + case CONN_DST_PAN: + return "PAN"; + case CONN_DST_LFO_FREQUENCY: + return "LFO_FREQUENCY"; + case CONN_DST_LFO_STARTDELAY: + return "LFO_STARTDELAY"; + case CONN_DST_EG1_ATTACKTIME: + return "EG1_ATTACKTIME"; + case CONN_DST_EG1_DECAYTIME: + return "EG1_DECAYTIME"; + case CONN_DST_EG1_RELEASETIME: + return "EG1_RELEASETIME"; + case CONN_DST_EG1_SUSTAINLEVEL: + return "EG1_SUSTAINLEVEL"; + case CONN_DST_EG2_ATTACKTIME: + return "EG2_ATTACKTIME"; + case CONN_DST_EG2_DECAYTIME: + return "EG2_DECAYTIME"; + case CONN_DST_EG2_RELEASETIME: + return "EG2_RELEASETIME"; + case CONN_DST_EG2_SUSTAINLEVEL: + return "EG2_SUSTAINLEVEL"; + case CONN_DST_KEYNUMBER: + return "KEYNUMBER"; + case CONN_DST_LEFT: + return "LEFT"; + case CONN_DST_RIGHT: + return "RIGHT"; + case CONN_DST_CENTER: + return "CENTER"; + case CONN_DST_LEFTREAR: + return "LEFTREAR"; + case CONN_DST_RIGHTREAR: + return "RIGHTREAR"; + case CONN_DST_LFE_CHANNEL: + return "LFE_CHANNEL"; + case CONN_DST_CHORUS: + return "CHORUS"; + case CONN_DST_REVERB: + return "REVERB"; + case CONN_DST_VIB_FREQUENCY: + return "VIB_FREQUENCY"; + case CONN_DST_VIB_STARTDELAY: + return "VIB_STARTDELAY"; + case CONN_DST_EG1_DELAYTIME: + return "EG1_DELAYTIME"; + case CONN_DST_EG1_HOLDTIME: + return "EG1_HOLDTIME"; + case CONN_DST_EG1_SHUTDOWNTIME: + return "EG1_SHUTDOWNTIME"; + case CONN_DST_EG2_DELAYTIME: + return "EG2_DELAYTIME"; + case CONN_DST_EG2_HOLDTIME: + return "EG2_HOLDTIME"; + case CONN_DST_FILTER_CUTOFF: + return "FILTER_CUTOFF"; + case CONN_DST_FILTER_Q: + return "FILTER_Q"; + default: + sprintf(unknown, "UNKNOWN (0x%04x)", usDestination); + return unknown; + } +} + +static void PrintArt(const char *type, CONNECTIONLIST *art, CONNECTION *artList) +{ + DWORD i; + printf("%s Connections:\n", type); + for ( i = 0; i < art->cConnections; ++i ) { + printf(" Source: %s, Control: %s, Destination: %s, Transform: %s, Scale: %d\n", + SourceToString(artList[i].usSource), + SourceToString(artList[i].usControl), + DestinationToString(artList[i].usDestination), + TransformToString(artList[i].usTransform), + artList[i].lScale); + } +} + +static void PrintWave(DLS_Wave *wave, DWORD index) +{ + WaveFMT *format = wave->format; + if ( format ) { + printf(" Wave %u: Format: %hu, %hu channels, %u Hz, %hu bits (length = %u)\n", index, format->wFormatTag, format->wChannels, format->dwSamplesPerSec, format->wBitsPerSample, wave->length); + } + if ( wave->wsmp ) { + DWORD i; + printf(" wsmp->usUnityNote = %hu\n", wave->wsmp->usUnityNote); + printf(" wsmp->sFineTune = %hd\n", wave->wsmp->sFineTune); + printf(" wsmp->lAttenuation = %d\n", wave->wsmp->lAttenuation); + printf(" wsmp->fulOptions = 0x%8.8x\n", wave->wsmp->fulOptions); + printf(" wsmp->cSampleLoops = %u\n", wave->wsmp->cSampleLoops); + for ( i = 0; i < wave->wsmp->cSampleLoops; ++i ) { + WLOOP *loop = &wave->wsmp_loop[i]; + printf(" Loop %u:\n", i); + printf(" ulStart = %u\n", loop->ulStart); + printf(" ulLength = %u\n", loop->ulLength); + } + } +} + +static void PrintRegion(DLS_Region *region, DWORD index) +{ + printf(" Region %u:\n", index); + if ( region->header ) { + printf(" RangeKey = { %hu - %hu }\n", region->header->RangeKey.usLow, region->header->RangeKey.usHigh); + printf(" RangeVelocity = { %hu - %hu }\n", region->header->RangeVelocity.usLow, region->header->RangeVelocity.usHigh); + printf(" fusOptions = 0x%4.4hx\n", region->header->fusOptions); + printf(" usKeyGroup = %hu\n", region->header->usKeyGroup); + } + if ( region->wlnk ) { + printf(" wlnk->fusOptions = 0x%4.4hx\n", region->wlnk->fusOptions); + printf(" wlnk->usPhaseGroup = %hu\n", region->wlnk->usPhaseGroup); + printf(" wlnk->ulChannel = %u\n", region->wlnk->ulChannel); + printf(" wlnk->ulTableIndex = %u\n", region->wlnk->ulTableIndex); + } + if ( region->wsmp ) { + DWORD i; + printf(" wsmp->usUnityNote = %hu\n", region->wsmp->usUnityNote); + printf(" wsmp->sFineTune = %hd\n", region->wsmp->sFineTune); + printf(" wsmp->lAttenuation = %d\n", region->wsmp->lAttenuation); + printf(" wsmp->fulOptions = 0x%8.8x\n", region->wsmp->fulOptions); + printf(" wsmp->cSampleLoops = %u\n", region->wsmp->cSampleLoops); + for ( i = 0; i < region->wsmp->cSampleLoops; ++i ) { + WLOOP *loop = ®ion->wsmp_loop[i]; + printf(" Loop %u:\n", i); + printf(" ulStart = %u\n", loop->ulStart); + printf(" ulLength = %u\n", loop->ulLength); + } + } + if ( region->art && region->art->cConnections > 0 ) { + PrintArt("Region", region->art, region->artList); + } +} + +static void PrintInstrument(DLS_Instrument *instrument, DWORD index) +{ + printf("Instrument %u:\n", index); + if ( instrument->name ) { + printf(" Name: %s\n", instrument->name); + } + if ( instrument->header ) { + DWORD i; + printf(" ulBank = 0x%8.8x\n", instrument->header->Locale.ulBank); + printf(" ulInstrument = %u\n", instrument->header->Locale.ulInstrument); + printf(" Regions: %u\n", instrument->header->cRegions); + for ( i = 0; i < instrument->header->cRegions; ++i ) { + PrintRegion(&instrument->regions[i], i); + } + } + if ( instrument->art && instrument->art->cConnections > 0 ) { + PrintArt("Instrument", instrument->art, instrument->artList); + } +}; + +void PrintDLS(DLS_Data *data) +{ + printf("DLS Data:\n"); + printf("cInstruments = %u\n", data->cInstruments); + if ( data->instruments ) { + DWORD i; + for ( i = 0; i < data->cInstruments; ++i ) { + PrintInstrument(&data->instruments[i], i); + } + } + if ( data->ptbl && data->ptbl->cCues > 0 ) { + DWORD i; + printf("Cues: "); + for ( i = 0; i < data->ptbl->cCues; ++i ) { + if ( i > 0 ) { + printf(", "); + } + printf("%u", data->ptblList[i].ulOffset); + } + printf("\n"); + } + if ( data->waveList ) { + DWORD i; + printf("Waves:\n"); + for ( i = 0; i < data->ptbl->cCues; ++i ) { + PrintWave(&data->waveList[i], i); + } + } + if ( data->name ) { + printf("Name: %s\n", data->name); + } + if ( data->artist ) { + printf("Artist: %s\n", data->artist); + } + if ( data->copyright ) { + printf("Copyright: %s\n", data->copyright); + } + if ( data->comments ) { + printf("Comments: %s\n", data->comments); + } +} + +#ifdef TEST_MAIN_DLS +} + +int main(int argc, char *argv[]) +{ + int i; + for ( i = 1; i < argc; ++i ) { + Timidity::DLS_Data *data; + FILE *src = fopen(argv[i], "rb"); + if ( !src ) { + fprintf(stderr, "Couldn't open %s: %s", argv[i], strerror(errno)); + continue; + } + data = Timidity::LoadDLS(src); + if ( data ) { + Timidity::PrintRIFF(data->chunk, 0); + Timidity::PrintDLS(data); + Timidity::FreeDLS(data); + } else { + fprintf(stderr, "Couldn't load %s: %s\n", argv[i], strerror(errno)); + } + fclose(src); + } + return 0; +} + +namespace Timidity +{ +#endif // TEST_MAIN +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*-------------------------------------------------------------------------*/ +/* * * * * * * * * * * * * * * * * instrum_dls.c * * * * * * * * * * * * * */ +/*-------------------------------------------------------------------------*/ + +#ifndef TEST_MAIN_DLS + +DLS_Data *Timidity_LoadDLS(FILE *src) +{ + return LoadDLS(src); +} + +void Timidity_FreeDLS(DLS_Data *patches) +{ + FreeDLS(patches); +} + +/* convert timecents to sec */ +static double to_msec(int timecent) +{ + if (timecent == 0x80000000 || timecent == 0) + return 0.0; + return 1000.0 * pow(2.0, (double)(timecent / 65536) / 1200.0); +} + +/* convert decipercent to {0..1} */ +static double to_normalized_percent(int decipercent) +{ + return ((double)(decipercent / 65536)) / 1000.0; +} + +/* convert from 8bit value to fractional offset (15.15) */ +static SDWORD to_offset(int offset) +{ + return (SDWORD)offset << (7+15); +} + +/* calculate ramp rate in fractional unit; +* diff = 8bit, time = msec +*/ +static SDWORD calc_rate(Renderer *song, int diff, int sample_rate, double msec) +{ + double rate; + + if(msec < 6) + msec = 6; + if(diff == 0) + diff = 255; + diff <<= (7+15); + rate = ((double)diff / song->rate) * song->control_ratio * 1000.0 / msec; + return (SDWORD)rate; +} + +static int load_connection(ULONG cConnections, CONNECTION *artList, USHORT destination) +{ + ULONG i; + int value = 0; + for (i = 0; i < cConnections; ++i) { + CONNECTION *conn = &artList[i]; + if(conn->usDestination == destination) { + // The formula for the destination is: + // usDestination = usDestination + usTransform(usSource * (usControl * lScale)) + // Since we are only handling source/control of NONE and identity + // transform, this simplifies to: usDestination = usDestination + lScale + if (conn->usSource == CONN_SRC_NONE && + conn->usControl == CONN_SRC_NONE && + conn->usTransform == CONN_TRN_NONE) + value += conn->lScale; + } + } + return value; +} + +static void load_region_dls(Renderer *song, Sample *sample, DLS_Instrument *ins, DWORD index) +{ + DLS_Region *rgn = &ins->regions[index]; + DLS_Wave *wave = &song->patches->waveList[rgn->wlnk->ulTableIndex]; + + if (!(rgn->header->fusOptions & F_RGN_OPTION_SELFNONEXCLUSIVE)) + { + sample->exclusiveClass = (SBYTE)rgn->header->usKeyGroup; + } + sample->low_freq = SDWORD(note_to_freq(rgn->header->RangeKey.usLow)); + sample->high_freq = SDWORD(note_to_freq(rgn->header->RangeKey.usHigh)); + sample->root_freq = SDWORD(note_to_freq(rgn->wsmp->usUnityNote)); + sample->low_vel = rgn->header->RangeVelocity.usLow; + sample->high_vel = rgn->header->RangeVelocity.usHigh; + + sample->modes = wave->format->wBitsPerSample == 8 ? MODES_UNSIGNED : MODES_16BIT; + sample->sample_rate = wave->format->dwSamplesPerSec; + sample->data = NULL; + sample->data_length = wave->length; + convert_sample_data(sample, wave->data); + if (rgn->wsmp->cSampleLoops) { + sample->modes |= (MODES_LOOPING|MODES_SUSTAIN); + sample->loop_start = rgn->wsmp_loop->ulStart / 2; + sample->loop_end = sample->loop_start + (rgn->wsmp_loop->ulLength / 2); + } + sample->volume = 1.0f; + + if (sample->modes & MODES_SUSTAIN) { + int value; + double attack, hold, decay, release; int sustain; + CONNECTIONLIST *art = NULL; + CONNECTION *artList = NULL; + + if (ins->art && ins->art->cConnections > 0 && ins->artList) { + art = ins->art; + artList = ins->artList; + } else { + art = rgn->art; + artList = rgn->artList; + } + + value = load_connection(art->cConnections, artList, CONN_DST_EG1_ATTACKTIME); + attack = to_msec(value); + value = load_connection(art->cConnections, artList, CONN_DST_EG1_HOLDTIME); + hold = to_msec(value); + value = load_connection(art->cConnections, artList, CONN_DST_EG1_DECAYTIME); + decay = to_msec(value); + value = load_connection(art->cConnections, artList, CONN_DST_EG1_RELEASETIME); + release = to_msec(value); + value = load_connection(art->cConnections, artList, CONN_DST_EG1_SUSTAINLEVEL); + sustain = (int)((1.0 - to_normalized_percent(value)) * 250.0); + value = load_connection(art->cConnections, artList, CONN_DST_PAN); + sample->panning = (int)((0.5 + to_normalized_percent(value)) * 16383.f); + + /* + printf("%d, Rate=%d LV=%d HV=%d Low=%d Hi=%d Root=%d Pan=%d Attack=%f Hold=%f Sustain=%d Decay=%f Release=%f\n", index, sample->sample_rate, rgn->header->RangeVelocity.usLow, rgn->header->RangeVelocity.usHigh, sample->low_freq, sample->high_freq, sample->root_freq, sample->panning, attack, hold, sustain, decay, release); + */ + + sample->envelope_offset[ATTACK] = to_offset(255); + sample->envelope_rate[ATTACK] = calc_rate(song, 255, sample->sample_rate, attack); + + sample->envelope_offset[HOLD] = to_offset(250); + sample->envelope_rate[HOLD] = calc_rate(song, 5, sample->sample_rate, hold); + + sample->envelope_offset[DECAY] = to_offset(sustain); + sample->envelope_rate[DECAY] = calc_rate(song, 255 - sustain, sample->sample_rate, decay); + + sample->envelope_offset[RELEASE] = to_offset(0); + sample->envelope_rate[RELEASE] = calc_rate(song, 5 + sustain, sample->sample_rate, release); + + sample->envelope_offset[RELEASEB] = to_offset(0); + sample->envelope_rate[RELEASEB] = to_offset(1); + + sample->envelope_offset[RELEASEC] = to_offset(0); + sample->envelope_rate[RELEASEC] = to_offset(1); + + sample->modes |= MODES_ENVELOPE; + } + for (int j = ATTACK; j < DELAY; j++) + { + sample->modulation_rate[j] = float(sample->envelope_rate[j]); + sample->modulation_offset[j] = float(sample->envelope_offset[j]); + } + + sample->data_length <<= FRACTION_BITS; + sample->loop_start <<= FRACTION_BITS; + sample->loop_end <<= FRACTION_BITS; +} + +InstrumentLayer *load_instrument_dls(Renderer *song, int drum, int bank, int instrument) +{ + InstrumentLayer *layer; + DWORD i; + DLS_Instrument *dls_ins; + + if (song->patches == NULL) + return NULL; + + drum = drum ? 0x80000000 : 0; + for (i = 0; i < song->patches->cInstruments; ++i) { + dls_ins = &song->patches->instruments[i]; + if ((dls_ins->header->Locale.ulBank & 0x80000000) == drum && + ((dls_ins->header->Locale.ulBank >> 8) & 0xFF) == bank && + dls_ins->header->Locale.ulInstrument == instrument) + break; + } + if (i == song->patches->cInstruments && !bank) { + for (i = 0; i < song->patches->cInstruments; ++i) { + dls_ins = &song->patches->instruments[i]; + if ((dls_ins->header->Locale.ulBank & 0x80000000) == drum && + dls_ins->header->Locale.ulInstrument == instrument) + break; + } + } + if (i == song->patches->cInstruments) { +// SNDDBG(("Couldn't find %s instrument %d in bank %d\n", drum ? "drum" : "melodic", instrument, bank)); + return NULL; + } + + layer = (InstrumentLayer *)safe_malloc(sizeof(InstrumentLayer)); + layer->lo = 0; + layer->hi = 127; + layer->instrument = (Instrument *)safe_malloc(sizeof(Instrument)); + layer->instrument->type = INST_DLS; + layer->instrument->samples = dls_ins->header->cRegions; + layer->instrument->sample = (Sample *)safe_malloc(layer->instrument->samples * sizeof(Sample)); + layer->instrument->left_samples = layer->instrument->samples; + layer->instrument->left_sample = layer->instrument->sample; + layer->instrument->right_samples = 0; + layer->instrument->right_sample = NULL; + memset(layer->instrument->sample, 0, layer->instrument->samples * sizeof(Sample)); + /* + printf("Found %s instrument %d in bank %d named %s with %d regions\n", drum ? "drum" : "melodic", instrument, bank, dls_ins->name, inst->samples); + */ + for (i = 0; i < dls_ins->header->cRegions; ++i) { + load_region_dls(song, &layer->instrument->sample[i], dls_ins, i); + } + return layer; +} +#endif /* !TEST_MAIN_DLS */ + +} diff --git a/src/timidity/mix.cpp b/src/timidity/mix.cpp new file mode 100644 index 0000000000..7e155e049b --- /dev/null +++ b/src/timidity/mix.cpp @@ -0,0 +1,584 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + mix.c + +*/ + +#include +#include +#include + +#include "timidity.h" + +namespace Timidity +{ + +/* Returns 1 if envelope runs out */ +int recompute_envelope(Voice *v) +{ + int stage; + + stage = v->envelope_stage; + + if (stage >= DELAY) + { + /* Envelope ran out. */ + int tmp = (v->status == VOICE_DIE); /* Already displayed as dead */ + v->status = VOICE_FREE; + return 1; + } + + if (v->sample->modes & MODES_ENVELOPE) + { + if (v->status == VOICE_ON || v->status == VOICE_SUSTAINED) + { + if (stage > DECAY) + { + /* Freeze envelope until note turns off. Trumpets want this. */ + v->envelope_increment = 0; + return 0; + } + } + } + v->envelope_stage = stage + 1; + + if (v->envelope_volume == v->sample->envelope_offset[stage]) + return recompute_envelope(v); + v->envelope_target = v->sample->envelope_offset[stage]; + v->envelope_increment = v->sample->envelope_rate[stage]; + if (v->envelope_target < v->envelope_volume) + v->envelope_increment = -v->envelope_increment; + return 0; +} + +void apply_envelope_to_amp(Voice *v) +{ + double lamp = v->left_amp, ramp; + if (v->panned == PANNED_MYSTERY) + { + ramp = v->right_amp; + + if (v->tremolo_phase_increment) + { + lamp *= v->tremolo_volume; + ramp *= v->tremolo_volume; + } + if (v->sample->modes & MODES_ENVELOPE) + { + double vol = calc_vol(v->envelope_volume / float(1 << 30)); + lamp *= vol; + ramp *= vol; + } + + v->left_mix = float(lamp); + v->right_mix = float(ramp); + } + else + { + if (v->tremolo_phase_increment) + lamp *= v->tremolo_volume; + if (v->sample->modes & MODES_ENVELOPE) + lamp *= calc_vol(v->envelope_volume / float(1 << 30)); + + v->left_mix = float(lamp); + } +} + +static int update_envelope(Voice *v) +{ + v->envelope_volume += v->envelope_increment; + /* Why is there no ^^ operator?? */ + if (((v->envelope_increment < 0) && + (v->envelope_volume <= v->envelope_target)) || + ((v->envelope_increment > 0) && + (v->envelope_volume >= v->envelope_target))) + { + v->envelope_volume = v->envelope_target; + if (recompute_envelope(v)) + return 1; + } + return 0; +} + +static void update_tremolo(Voice *v) +{ + int depth = v->sample->tremolo_depth << 7; + + if (v->tremolo_sweep) + { + /* Update sweep position */ + + v->tremolo_sweep_position += v->tremolo_sweep; + if (v->tremolo_sweep_position >= (1 << SWEEP_SHIFT)) + { + /* Swept to max amplitude */ + v->tremolo_sweep = 0; + } + else + { + /* Need to adjust depth */ + depth *= v->tremolo_sweep_position; + depth >>= SWEEP_SHIFT; + } + } + + v->tremolo_phase += v->tremolo_phase_increment; + + v->tremolo_volume = (float) + (1.0 - FSCALENEG((sine(v->tremolo_phase >> RATE_SHIFT) + 1.0) + * depth * TREMOLO_AMPLITUDE_TUNING, + 17)); + + /* I'm not sure about the +1.0 there -- it makes tremoloed voices' + volumes on average the lower the higher the tremolo amplitude. */ +} + +/* Returns 1 if the note died */ +static int update_signal(Voice *v) +{ + if (v->envelope_increment && update_envelope(v)) + return 1; + + if (v->tremolo_phase_increment) + update_tremolo(v); + + apply_envelope_to_amp(v); + return 0; +} + +static void mix_mystery_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix, + right = v->right_mix; + int cc; + sample_t s; + + if (!(cc = v->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + + left = v->left_mix; + right = v->right_mix; + } + + while (count) + { + if (cc < count) + { + count -= cc; + while (cc--) + { + s = *sp++; + lp[0] += left * s; + lp[1] += right * s; + lp += 2; + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + right = v->right_mix; + } + else + { + v->control_counter = cc - count; + while (count--) + { + s = *sp++; + lp[0] += left * s; + lp[1] += right * s; + lp += 2; + } + return; + } + } +} + +static void mix_center_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + int cc; + sample_t s; + + if (!(cc = v->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + + while (count) + { + if (cc < count) + { + count -= cc; + while (cc--) + { + s = *sp++ * left; + lp[0] += s; + lp[1] += s; + lp += 2; + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + else + { + v->control_counter = cc - count; + while (count--) + { + s = *sp++ * left; + lp[0] += s; + lp[1] += s; + lp += 2; + } + return; + } + } +} + +static void mix_single_left_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + int cc; + + if (!(cc = v->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + + while (count) + { + if (cc < count) + { + count -= cc; + while (cc--) + { + lp[0] += *sp++ * left; + lp += 2; + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + else + { + v->control_counter = cc - count; + while (count--) + { + lp[0] += *sp++ * left; + lp += 2; + } + return; + } + } +} + +static void mix_single_right_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + int cc; + + if (!(cc = v->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + + while (count) + { + if (cc < count) + { + count -= cc; + while (cc--) + { + lp[1] += *sp++ * left; + lp += 2; + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + else + { + v->control_counter = cc - count; + while (count--) + { + lp[1] += *sp++ * left; + lp += 2; + } + return; + } + } +} + +static void mix_mono_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + int cc; + + if (!(cc = v->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + + while (count) + { + if (cc < count) + { + count -= cc; + while (cc--) + { + *lp++ += *sp++ * left; + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = v->left_mix; + } + else + { + v->control_counter = cc - count; + while (count--) + { + *lp++ += *sp++ * left; + } + return; + } + } +} + +static void mix_mystery(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix, + right = v->right_mix; + sample_t s; + + while (count--) + { + s = *sp++; + lp[0] += s * left; + lp[1] += s * right; + lp += 2; + } +} + +static void mix_center(const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + sample_t s; + + while (count--) + { + s = *sp++ * left; + lp[0] += s; + lp[1] += s; + lp += 2; + } +} + +static void mix_single_left(const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + + while (count--) + { + lp[0] += *sp++ * left; + lp += 2; + } +} +static void mix_single_right(const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + + while (count--) + { + lp[1] += *sp++ * left; + lp += 2; + } +} + +static void mix_mono(const sample_t *sp, float *lp, Voice *v, int count) +{ + final_volume_t + left = v->left_mix; + + while (count--) + { + *lp++ += *sp++ * left; + } +} + +/* Ramp a note out in c samples */ +static void ramp_out(const sample_t *sp, float *lp, Voice *v, int c) +{ + final_volume_t left, right, li, ri; + + sample_t s = 0; /* silly warning about uninitialized s */ + + /* Fix by James Caldwell */ + if ( c == 0 ) c = 1; + + left = v->left_mix; + li = -(left/c); + if (li == 0) li = -1; + + /* printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */ + + if (v->panned == PANNED_MYSTERY) + { + right = v->right_mix; + ri = -(right/c); + while (c--) + { + left += li; if (left < 0) left = 0; + right += ri; if (right < 0) right = 0; + s = *sp++; + lp[0] += s * left; + lp[1] += s * right; + lp += 2; + } + } + else if (v->panned == PANNED_CENTER) + { + while (c--) + { + left += li; + if (left < 0) + return; + s = *sp++ * left; + lp[0] += s; + lp[1] += s; + lp += 2; + } + } + else if (v->panned == PANNED_LEFT) + { + while (c--) + { + left += li; + if (left < 0) + return; + lp[0] += *sp++ * left; + lp += 2; + } + } + else if (v->panned == PANNED_RIGHT) + { + while (c--) + { + left += li; + if (left < 0) + return; + s = *sp++; + lp[1] += *sp++ * left; + lp += 2; + } + } +} + + +/**************** interface function ******************/ + +void mix_voice(Renderer *song, float *buf, Voice *v, int c) +{ + int count = c; + sample_t *sp; + if (c < 0) + { + return; + } + if (v->status == VOICE_DIE) + { + if (count >= MAX_DIE_TIME) + count = MAX_DIE_TIME; + sp = resample_voice(song, v, &count); + ramp_out(sp, buf, v, count); + v->status = VOICE_FREE; + } + else + { + sp = resample_voice(song, v, &count); + if (count < 0) + { + return; + } + if (v->panned == PANNED_MYSTERY) + { + if (v->envelope_increment || v->tremolo_phase_increment) + mix_mystery_signal(song->control_ratio, sp, buf, v, count); + else + mix_mystery(song->control_ratio, sp, buf, v, count); + } + else if (v->panned == PANNED_CENTER) + { + if (v->envelope_increment || v->tremolo_phase_increment) + mix_center_signal(song->control_ratio, sp, buf, v, count); + else + mix_center(sp, buf, v, count); + } + else + { + /* It's either full left or full right. In either case, + every other sample is 0. Just get the offset right: */ + + if (v->envelope_increment || v->tremolo_phase_increment) + { + if (v->panned == PANNED_RIGHT) + mix_single_right_signal(song->control_ratio, sp, buf, v, count); + else + mix_single_left_signal(song->control_ratio, sp, buf, v, count); + } + else + { + if (v->panned == PANNED_RIGHT) + mix_single_right(sp, buf, v, count); + else + mix_single_left(sp, buf, v, count); + } + } + } +} + +} diff --git a/src/timidity/playmidi.cpp b/src/timidity/playmidi.cpp new file mode 100644 index 0000000000..cbe6afb2d7 --- /dev/null +++ b/src/timidity/playmidi.cpp @@ -0,0 +1,1575 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + playmidi.c -- random stuff in need of rearrangement + +*/ + +#include +#include +#include +#include + +#include "timidity.h" + +#pragma intrinsic(pow) + +namespace Timidity +{ + +static bool opt_stereo_surround = false; +bool adjust_panning_immediately = false; + +void Renderer::reset_voices() +{ + for (int i = 0; i < MAX_VOICES; i++) + voice[i].status = VOICE_FREE; +} + +/* Process the Reset All Controllers event */ +void Renderer::reset_controllers(int c) +{ + channel[c].volume = (100 << 7) | 100; + channel[c].expression = 0x3fff; + channel[c].sustain = 0; + channel[c].pitchbend = 0x2000; + channel[c].pitchfactor = 0; /* to be computed */ + + channel[c].reverberation = 0; + channel[c].chorusdepth = 0; +} + +void Renderer::reset_midi() +{ + for (int i = 0; i < MAXCHAN; i++) + { + reset_controllers(i); + /* The rest of these are unaffected by the Reset All Controllers event */ + channel[i].program = default_program; + channel[i].panning = NO_PANNING; + channel[i].pitchsens = 200; + channel[i].bank = 0; /* tone bank or drum set */ + channel[i].harmoniccontent = 64; + channel[i].releasetime = 64; + channel[i].attacktime = 64; + channel[i].brightness = 64; + channel[i].sfx = 0; + } + reset_voices(); +} + +void Renderer::select_sample(int v, Instrument *ip) +{ + float f, cdiff, diff, midfreq; + int s, i; + Sample *sp, *closest; + + s = ip->samples; + sp = ip->sample; + + if (s == 1) + { + voice[v].sample = sp; + return; + } + + f = voice[v].orig_frequency; + /* + No suitable sample found! We'll select the sample whose root + frequency is closest to the one we want. (Actually we should + probably convert the low, high, and root frequencies to MIDI note + values and compare those.) */ + + cdiff = 1e10; + closest = sp = ip->sample; + midfreq = float(sp->low_freq + sp->high_freq) / 2; + for (i = 0; i < s; i++) + { + diff = sp->root_freq - f; + /* But the root freq. can perfectly well lie outside the keyrange + * frequencies, so let's try: + */ + /* diff = midfreq - f; */ + if (diff < 0) diff = -diff; + if (diff < cdiff) + { + cdiff = diff; + closest = sp; + } + sp++; + } + voice[v].sample = closest; + return; +} + + + +void Renderer::select_stereo_samples(int v, InstrumentLayer *lp) +{ + Instrument *ip; + InstrumentLayer *nlp, *bestvel; + int diffvel, midvel, mindiff; + + /* select closest velocity */ + bestvel = lp; + mindiff = 500; + for (nlp = lp; nlp; nlp = nlp->next) + { + midvel = (nlp->hi + nlp->lo)/2; + if (!midvel) + { + diffvel = 127; + } + else if (voice[v].velocity < nlp->lo || voice[v].velocity > nlp->hi) + { + diffvel = 200; + } + else + { + diffvel = voice[v].velocity - midvel; + } + if (diffvel < 0) + { + diffvel = -diffvel; + } + if (diffvel < mindiff) + { + mindiff = diffvel; + bestvel = nlp; + } + } + ip = bestvel->instrument; + + if (ip->right_sample) + { + ip->sample = ip->right_sample; + ip->samples = ip->right_samples; + select_sample(v, ip); + voice[v].right_sample = voice[v].sample; + } + else + { + voice[v].right_sample = NULL; + } + ip->sample = ip->left_sample; + ip->samples = ip->left_samples; + select_sample(v, ip); +} + + +void Renderer::recompute_freq(int v) +{ + Channel *ch = &channel[voice[v].channel]; + int + sign = (voice[v].sample_increment < 0), /* for bidirectional loops */ + pb = ch->pitchbend; + double a; + + if (!voice[v].sample->sample_rate) + return; + + if (voice[v].vibrato_control_ratio) + { + /* This instrument has vibrato. Invalidate any precomputed + sample_increments. */ + + int i = VIBRATO_SAMPLE_INCREMENTS; + while (i--) + voice[v].vibrato_sample_increment[i]=0; + } + + if (pb == 0x2000 || pb < 0 || pb > 0x3FFF) + { + voice[v].frequency = voice[v].orig_frequency; + } + else + { + pb -= 0x2000; + if (ch->pitchfactor == 0) + { + /* Damn. Somebody bent the pitch. */ + ch->pitchfactor = pow(2.f, ((abs(pb) * ch->pitchsens) / (8191.f * 1200.f))); + } + if (pb < 0) + { + voice[v].frequency = voice[v].orig_frequency / ch->pitchfactor; + } + else + { + voice[v].frequency = voice[v].orig_frequency * ch->pitchfactor; + } + } + + a = FSCALE(((double)(voice[v].sample->sample_rate) * voice[v].frequency) / + ((double)(voice[v].sample->root_freq) * rate), + FRACTION_BITS); + + if (sign) + a = -a; /* need to preserve the loop direction */ + + voice[v].sample_increment = (int)(a); +} + +void Renderer::recompute_amp(Voice *v) +{ + double tempamp; + int chan = v->channel; + int panning = v->panning; + double vol = channel[chan].volume / 16383.f; + double expr = calc_vol(channel[chan].expression / 16383.f); + double vel = calc_vol(v->velocity / 127.f); + + if (channel[chan].kit) + { + int note = v->sample->note_to_use; + if (note > 0 && drumvolume[chan][note] >= 0) { vol = drumvolume[chan][note] / 127.f; } + if (note > 0 && drumpanpot[chan][note] >= 0) { panning = drumvolume[chan][note]; panning |= panning << 7; } + } + + vol = calc_vol(vol); + tempamp = vel * vol * expr * v->sample->volume; + + if (panning > (60 << 7) && panning < (68 << 7)) + { + v->panned = PANNED_CENTER; + v->left_amp = (float)(tempamp * 0.70710678118654752440084436210485); // * sqrt(0.5) + } + else if (panning < (5 << 7)) + { + v->panned = PANNED_LEFT; + v->left_amp = (float)tempamp; + } + else if (panning > (123 << 7)) + { + v->panned = PANNED_RIGHT; + v->left_amp = (float)tempamp; /* left_amp will be used */ + } + else + { + double pan = panning / 16384.0; + v->panned = PANNED_MYSTERY; + v->left_amp = (float)(tempamp * sqrt(1.0 - pan)); + v->right_amp = (float)(tempamp * sqrt(pan)); + } +} + + +#define NOT_CLONE 0 +#define STEREO_CLONE 1 +#define REVERB_CLONE 2 +#define CHORUS_CLONE 3 + + +/* just a variant of note_on() */ +int Renderer::vc_alloc(int j) +{ + int i = voices; + + while (i--) + { + if (i != j && (voice[i].status & VOICE_FREE)) + { + return i; + } + } + return -1; +} + +void Renderer::kill_others(int i) +{ + int j = voices; + + if (!voice[i].sample->exclusiveClass) return; + + while (j--) + { + if (voice[j].status & (VOICE_FREE|VOICE_OFF|VOICE_DIE)) continue; + if (i == j) continue; + if (voice[i].channel != voice[j].channel) continue; + if (voice[j].sample->note_to_use) + { + if (voice[j].sample->exclusiveClass != voice[i].sample->exclusiveClass) continue; + kill_note(j); + } + } +} + + +void Renderer::clone_voice(Instrument *ip, int v, int note, int vel, int clone_type, int variationbank) +{ + int w, played_note, chorus = 0, reverb = 0, milli; + int chan = voice[v].channel; + + if (clone_type == STEREO_CLONE) + { + if (!voice[v].right_sample && variationbank != 3) return; + if (variationbank == 6) return; + } + + if (channel[chan].kit) + { + reverb = drumreverberation[chan][voice[v].note]; + chorus = drumchorusdepth[chan][voice[v].note]; + } + else + { + reverb = channel[chan].reverberation; + chorus = channel[chan].chorusdepth; + } + + if (clone_type == REVERB_CLONE) chorus = 0; + else if (clone_type == CHORUS_CLONE) reverb = 0; + else if (clone_type == STEREO_CLONE) reverb = chorus = 0; + + if (reverb > 127) reverb = 127; + if (chorus > 127) chorus = 127; + + if (clone_type == CHORUS_CLONE) + { + if (variationbank == 32) chorus = 30; + else if (variationbank == 33) chorus = 60; + else if (variationbank == 34) chorus = 90; + } + + chorus /= 2; /* This is an ad hoc adjustment. */ + + if (!reverb && !chorus && clone_type != STEREO_CLONE) return; + + if ( (w = vc_alloc(v)) < 0 ) return; + + voice[w] = voice[v]; + if (clone_type == STEREO_CLONE) voice[v].clone_voice = w; + voice[w].clone_voice = v; + voice[w].clone_type = clone_type; + + voice[w].sample = voice[v].right_sample; + voice[w].velocity = vel; + + milli = int(rate / 1000); + + if (clone_type == STEREO_CLONE) + { + int left, right, leftpan, rightpan; + int panrequest = voice[v].panning; + if (variationbank == 3) + { + voice[v].panning = 0; + voice[w].panning = 127; + } + else + { + if (voice[v].sample->panning > voice[w].sample->panning) + { + left = w; + right = v; + } + else + { + left = v; + right = w; + } +#define INSTRUMENT_SEPARATION 12 + leftpan = panrequest - INSTRUMENT_SEPARATION / 2; + rightpan = leftpan + INSTRUMENT_SEPARATION; + if (leftpan < 0) + { + leftpan = 0; + rightpan = leftpan + INSTRUMENT_SEPARATION; + } + if (rightpan > 127) + { + rightpan = 127; + leftpan = rightpan - INSTRUMENT_SEPARATION; + } + voice[left].panning = leftpan; + voice[right].panning = rightpan; + voice[right].echo_delay = 20 * milli; + } + } + + voice[w].volume = voice[w].sample->volume; + + if (reverb) + { + if (opt_stereo_surround) + { + if (voice[w].panning > 64) + voice[w].panning = 127; + else + voice[w].panning = 0; + } + else + { + if (voice[v].panning < 64) + voice[w].panning = 64 + reverb/2; + else + voice[w].panning = 64 - reverb/2; + } + + /* try 98->99 for melodic instruments ? (bit much for percussion) */ + /* voice[w].volume *= calc_vol(((127 - reverb) / 8 + 98) / 127.f); */ + voice[w].volume = float(voice[w].volume * calc_vol((911 - reverb) / (8 * 127.f))); + + voice[w].echo_delay += reverb * milli; + voice[w].envelope_rate[DECAY] *= 2; + voice[w].envelope_rate[RELEASE] /= 2; + + if (XG_System_reverb_type >= 0) + { + int subtype = XG_System_reverb_type & 0x07; + int rtype = XG_System_reverb_type >> 3; + switch (rtype) + { + case 0: /* no effect */ + break; + case 1: /* hall */ + if (subtype) voice[w].echo_delay += 100 * milli; + break; + case 2: /* room */ + voice[w].echo_delay /= 2; + break; + case 3: /* stage */ + voice[w].velocity = voice[v].velocity; + break; + case 4: /* plate */ + voice[w].panning = voice[v].panning; + break; + case 16: /* white room */ + voice[w].echo_delay = 0; + break; + case 17: /* tunnel */ + voice[w].echo_delay *= 2; + voice[w].velocity /= 2; + break; + case 18: /* canyon */ + voice[w].echo_delay *= 2; + break; + case 19: /* basement */ + voice[w].velocity /= 2; + break; + default: + break; + } + } + } + played_note = voice[w].sample->note_to_use; + if (!played_note) + { + played_note = note & 0x7f; + if (variationbank == 35) played_note += 12; + else if (variationbank == 36) played_note -= 12; + else if (variationbank == 37) played_note += 7; + else if (variationbank == 36) played_note -= 7; + } +#if 0 + played_note = ( (played_note - voice[w].sample->freq_center) * voice[w].sample->freq_scale ) / 1024 + + voice[w].sample->freq_center; +#endif + voice[w].note = played_note; + voice[w].orig_frequency = note_to_freq(played_note); + + if (chorus) + { + if (opt_stereo_surround) + { + if (voice[v].panning < 64) + voice[w].panning = voice[v].panning + 32; + else + voice[w].panning = voice[v].panning - 32; + } + + if (!voice[w].vibrato_control_ratio) + { + voice[w].vibrato_control_ratio = 100; + voice[w].vibrato_depth = 6; + voice[w].vibrato_sweep = 74; + } + voice[w].volume *= 0.40f; + voice[v].volume = voice[w].volume; + recompute_amp(&voice[v]); + apply_envelope_to_amp(&voice[v]); + + voice[w].vibrato_sweep = chorus/2; + voice[w].vibrato_depth /= 2; + if (!voice[w].vibrato_depth) voice[w].vibrato_depth = 2; + voice[w].vibrato_control_ratio /= 2; + voice[w].echo_delay += 30 * milli; + + if (XG_System_chorus_type >= 0) + { + int subtype = XG_System_chorus_type & 0x07; + int chtype = 0x0f & (XG_System_chorus_type >> 3); + float chorus_factor; + + switch (chtype) + { + case 0: /* no effect */ + break; + case 1: /* chorus */ + chorus /= 3; + chorus_factor = pow(2.f, chorus / (256.f * 12.f)); + if(channel[ voice[w].channel ].pitchbend + chorus < 0x2000) + { + voice[w].orig_frequency = voice[w].orig_frequency * chorus_factor; + } + else + { + voice[w].orig_frequency = voice[w].orig_frequency / chorus_factor; + } + if (subtype) + { + voice[w].vibrato_depth *= 2; + } + break; + case 2: /* celeste */ + voice[w].orig_frequency += (voice[w].orig_frequency/128) * chorus; + break; + case 3: /* flanger */ + voice[w].vibrato_control_ratio = 10; + voice[w].vibrato_depth = 100; + voice[w].vibrato_sweep = 8; + voice[w].echo_delay += 200 * milli; + break; + case 4: /* symphonic : cf Children of the Night /128 bad, /1024 ok */ + voice[w].orig_frequency += (voice[w].orig_frequency/512) * chorus; + voice[v].orig_frequency -= (voice[v].orig_frequency/512) * chorus; + recompute_freq(v); + break; + case 8: /* phaser */ + break; + default: + break; + } + } + else + { + float chorus_factor; + chorus /= 3; + chorus_factor = pow(2.f, chorus / (256.f * 12.f)); + if (channel[ voice[w].channel ].pitchbend + chorus < 0x2000) + { + voice[w].orig_frequency = voice[w].orig_frequency * chorus_factor; + } + else + { + voice[w].orig_frequency = voice[w].orig_frequency / chorus_factor; + } + } + } +#if 0 + voice[w].loop_start = voice[w].sample->loop_start; + voice[w].loop_end = voice[w].sample->loop_end; +#endif + voice[w].echo_delay_count = voice[w].echo_delay; + if (reverb) voice[w].echo_delay *= 2; + + recompute_freq(w); + recompute_amp(&voice[w]); + if (voice[w].sample->modes & MODES_ENVELOPE) + { + /* Ramp up from 0 */ + voice[w].envelope_stage = ATTACK; + voice[w].modulation_stage = ATTACK; + voice[w].envelope_volume = 0; + voice[w].modulation_volume = 0; + voice[w].control_counter = 0; + voice[w].modulation_counter = 0; + recompute_envelope(&voice[w]); + /*recompute_modulation(w);*/ + } + else + { + voice[w].envelope_increment = 0; + voice[w].modulation_increment = 0; + } + apply_envelope_to_amp(&voice[w]); +} + + +void Renderer::xremap(int *banknumpt, int *this_notept, int this_kit) +{ + int i, newmap; + int banknum = *banknumpt; + int this_note = *this_notept; + int newbank, newnote; + + if (!this_kit) + { + if (banknum == SFXBANK && tonebank[SFXBANK]) + { + return; + } + if (banknum == SFXBANK && tonebank[120]) + { + *banknumpt = 120; + } + return; + } + if (this_kit != 127 && this_kit != 126) + { + return; + } + for (i = 0; i < XMAPMAX; i++) + { + newmap = xmap[i][0]; + if (!newmap) return; + if (this_kit == 127 && newmap != XGDRUM) continue; + if (this_kit == 126 && newmap != SFXDRUM1) continue; + if (xmap[i][1] != banknum) continue; + if (xmap[i][3] != this_note) continue; + newbank = xmap[i][2]; + newnote = xmap[i][4]; + if (newbank == banknum && newnote == this_note) return; + if (!drumset[newbank]) return; + if (!drumset[newbank]->tone[newnote].layer) return; + if (drumset[newbank]->tone[newnote].layer == MAGIC_LOAD_INSTRUMENT) return; + *banknumpt = newbank; + *this_notept = newnote; + return; + } +} + + +void Renderer::start_note(int ch, int this_note, int this_velocity, int i) +{ + InstrumentLayer *lp; + Instrument *ip; + int j, banknum; + int played_note, drumpan = NO_PANNING; + int rt; + int attacktime, releasetime, decaytime, variationbank; + int brightness = channel[ch].brightness; + int harmoniccontent = channel[ch].harmoniccontent; + int drumsflag = channel[ch].kit; + int this_prog = channel[ch].program; + + if (channel[ch].sfx) + { + banknum = channel[ch].sfx; + } + else + { + banknum = channel[ch].bank; + } + + voice[i].velocity = this_velocity; + + if (XG_System_On) xremap(&banknum, &this_note, drumsflag); + /* if (current_config_pc42b) pcmap(&banknum, &this_note, &this_prog, &drumsflag); */ + + if (drumsflag) + { + if (NULL == drumset[banknum] || NULL == (lp = drumset[banknum]->tone[this_note].layer)) + { + if (!(lp = drumset[0]->tone[this_note].layer)) + return; /* No instrument? Then we can't play. */ + } + ip = lp->instrument; + if (ip->type == INST_GUS && ip->samples != 1) + { + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, + "Strange: percussion instrument with %d samples!", ip->samples); + } + + if (ip->sample->note_to_use) /* Do we have a fixed pitch? */ + { + voice[i].orig_frequency = note_to_freq(ip->sample->note_to_use); + drumpan = drumpanpot[ch][(int)ip->sample->note_to_use]; + drumpan |= drumpan << 7; + } + else + voice[i].orig_frequency = note_to_freq(this_note & 0x7F); + } + else + { + if (channel[ch].program == SPECIAL_PROGRAM) + { + lp = default_instrument; + } + else if (NULL == tonebank[channel[ch].bank] || NULL == (lp = tonebank[channel[ch].bank]->tone[channel[ch].program].layer)) + { + if (NULL == (lp = tonebank[0]->tone[this_prog].layer)) + return; /* No instrument? Then we can't play. */ + } + ip = lp->instrument; + if (ip->sample->note_to_use) /* Fixed-pitch instrument? */ + voice[i].orig_frequency = note_to_freq(ip->sample->note_to_use); + else + voice[i].orig_frequency = note_to_freq(this_note & 0x7F); + } + + select_stereo_samples(i, lp); + + played_note = voice[i].sample->note_to_use; + + if (!played_note || !drumsflag) played_note = this_note & 0x7f; +#if 0 + played_note = ( (played_note - voice[i].sample->freq_center) * voice[i].sample->freq_scale ) / 1024 + + voice[i].sample->freq_center; +#endif + voice[i].status = VOICE_ON; + voice[i].channel = ch; + voice[i].note = played_note; + voice[i].velocity = this_velocity; + voice[i].sample_offset = 0; + voice[i].sample_increment = 0; /* make sure it isn't negative */ + + voice[i].tremolo_phase = 0; + voice[i].tremolo_phase_increment = voice[i].sample->tremolo_phase_increment; + voice[i].tremolo_sweep = voice[i].sample->tremolo_sweep_increment; + voice[i].tremolo_sweep_position = 0; + + voice[i].vibrato_sweep = voice[i].sample->vibrato_sweep_increment; + voice[i].vibrato_sweep_position = 0; + voice[i].vibrato_depth = voice[i].sample->vibrato_depth; + voice[i].vibrato_control_ratio = voice[i].sample->vibrato_control_ratio; + voice[i].vibrato_control_counter = voice[i].vibrato_phase=0; +// voice[i].vibrato_delay = voice[i].sample->vibrato_delay; + + kill_others(i); + + for (j = 0; j < VIBRATO_SAMPLE_INCREMENTS; j++) + voice[i].vibrato_sample_increment[j] = 0; + + attacktime = channel[ch].attacktime; + releasetime = channel[ch].releasetime; + decaytime = 64; + variationbank = channel[ch].variationbank; + + switch (variationbank) + { + case 8: + attacktime = 64+32; + break; + case 12: + decaytime = 64-32; + break; + case 16: + brightness = 64+16; + break; + case 17: + brightness = 64+32; + break; + case 18: + brightness = 64-16; + break; + case 19: + brightness = 64-32; + break; + case 20: + harmoniccontent = 64+16; + break; +#if 0 + case 24: + voice[i].modEnvToFilterFc=2.0; + voice[i].sample->cutoff_freq = 800; + break; + case 25: + voice[i].modEnvToFilterFc=-2.0; + voice[i].sample->cutoff_freq = 800; + break; + case 27: + voice[i].modLfoToFilterFc=2.0; + voice[i].lfo_phase_increment=109; + voice[i].lfo_sweep=122; + voice[i].sample->cutoff_freq = 800; + break; + case 28: + voice[i].modLfoToFilterFc=-2.0; + voice[i].lfo_phase_increment=109; + voice[i].lfo_sweep=122; + voice[i].sample->cutoff_freq = 800; + break; +#endif + default: + break; + } + + for (j = ATTACK; j < MAXPOINT; j++) + { + voice[i].envelope_rate[j] = voice[i].sample->envelope_rate[j]; + voice[i].envelope_offset[j] = voice[i].sample->envelope_offset[j]; + } + + voice[i].echo_delay = voice[i].envelope_rate[DELAY]; + voice[i].echo_delay_count = voice[i].echo_delay; + + if (attacktime != 64) + { + rt = voice[i].envelope_rate[ATTACK]; + rt = rt + ( (64-attacktime)*rt ) / 100; + if (rt > 1000) + { + voice[i].envelope_rate[ATTACK] = rt; + } + } + if (releasetime != 64) + { + rt = voice[i].envelope_rate[RELEASE]; + rt = rt + ( (64-releasetime)*rt ) / 100; + if (rt > 1000) + { + voice[i].envelope_rate[RELEASE] = rt; + } + } + if (decaytime != 64) + { + rt = voice[i].envelope_rate[DECAY]; + rt = rt + ( (64-decaytime)*rt ) / 100; + if (rt > 1000) + { + voice[i].envelope_rate[DECAY] = rt; + } + } + + if (channel[ch].panning != NO_PANNING) + { + voice[i].panning = channel[ch].panning; + } + else + { + voice[i].panning = voice[i].sample->panning; + } + if (drumpan != NO_PANNING) + { + voice[i].panning = drumpan; + } + + if (variationbank == 1) + { + int pan = voice[i].panning; + int disturb = 0; + /* If they're close up (no reverb) and you are behind the pianist, + * high notes come from the right, so we'll spread piano etc. notes + * out horizontally according to their pitches. + */ + if (this_prog < 21) + { + int n = voice[i].velocity - 32; + if (n < 0) n = 0; + if (n > 64) n = 64; + pan = pan/2 + n; + } + /* For other types of instruments, the music sounds more alive if + * notes come from slightly different directions. However, instruments + * do drift around in a sometimes disconcerting way, so the following + * might not be such a good idea. + */ + else + { + disturb = (voice[i].velocity/32 % 8) + (voice[i].note % 8); /* /16? */ + } + + if (pan < 64) + { + pan += disturb; + } + else + { + pan -= disturb; + } + pan = pan < 0 ? 0 : pan > 127 ? 127 : pan; + voice[i].panning = pan | (pan << 7); + } + + recompute_freq(i); + recompute_amp(&voice[i]); + if (voice[i].sample->modes & MODES_ENVELOPE) + { + /* Ramp up from 0 */ + voice[i].envelope_stage = ATTACK; + voice[i].envelope_volume = 0; + voice[i].control_counter = 0; + recompute_envelope(&voice[i]); + } + else + { + voice[i].envelope_increment = 0; + } + apply_envelope_to_amp(&voice[i]); + + voice[i].clone_voice = -1; + voice[i].clone_type = NOT_CLONE; + + clone_voice(ip, i, this_note, this_velocity, STEREO_CLONE, variationbank); + clone_voice(ip, i, this_note, this_velocity, CHORUS_CLONE, variationbank); + clone_voice(ip, i, this_note, this_velocity, REVERB_CLONE, variationbank); +} + +void Renderer::kill_note(int i) +{ + voice[i].status = VOICE_DIE; + if (voice[i].clone_voice >= 0) + { + voice[ voice[i].clone_voice ].status = VOICE_DIE; + } +} + + +/* Only one instance of a note can be playing on a single channel. */ +void Renderer::note_on(int chan, int note, int vel) +{ + int i = voices, lowest = -1; + float lv = 1e10, v; + + while (i--) + { + if (voice[i].status == VOICE_FREE) + { + lowest = i; /* Can't get a lower volume than silence */ + } + else if (voice[i].channel == chan && (voice[i].note == note || channel[chan].mono)) + { + kill_note(i); + } + } + + if (lowest != -1) + { + /* Found a free voice. */ + start_note(chan, note, vel, lowest); + return; + } + + /* Look for the decaying note with the lowest volume */ + if (lowest == -1) + { + i = voices; + while (i--) + { + if ( (voice[i].status & ~(VOICE_ON | VOICE_DIE | VOICE_FREE)) && + (!voice[i].clone_type)) + { + v = voice[i].left_mix; + if ((voice[i].panned == PANNED_MYSTERY) && (voice[i].right_mix > v)) + { + v = voice[i].right_mix; + } + if (v < lv) + { + lv = v; + lowest = i; + } + } + } + } + + if (lowest != -1) + { + int cl = voice[lowest].clone_voice; + + /* This can still cause a click, but if we had a free voice to + spare for ramping down this note, we wouldn't need to kill it + in the first place... Still, this needs to be fixed. Perhaps + we could use a reserve of voices to play dying notes only. */ + + if (cl >= 0) + { + if (voice[cl].clone_type == STEREO_CLONE || + (!voice[cl].clone_type && voice[lowest].clone_type == STEREO_CLONE)) + { + voice[cl].status = VOICE_FREE; + } + else if (voice[cl].clone_voice == lowest) + { + voice[cl].clone_voice = -1; + } + } + + cut_notes++; + voice[lowest].status = VOICE_FREE; + start_note(chan, note, vel, lowest); + } + else + { + lost_notes++; + } +} + +void Renderer::finish_note(int i) +{ + if (voice[i].sample->modes & MODES_ENVELOPE) + { + /* We need to get the envelope out of Sustain stage */ + voice[i].envelope_stage = RELEASE; + voice[i].status = VOICE_OFF; + recompute_envelope(&voice[i]); + apply_envelope_to_amp(&voice[i]); + } + else + { + /* Set status to OFF so resample_voice() will let this voice out + of its loop, if any. In any case, this voice dies when it + hits the end of its data (ofs>=data_length). */ + voice[i].status = VOICE_OFF; + } + + int v; + if ( (v = voice[i].clone_voice) >= 0) + { + voice[i].clone_voice = -1; + finish_note(v); + } +} + +void Renderer::note_off(int chan, int note, int vel) +{ + int i = voices, v; + while (i--) + { + if (voice[i].status == VOICE_ON && + voice[i].channel == chan && + voice[i].note == note) + { + if (channel[chan].sustain) + { + voice[i].status = VOICE_SUSTAINED; + + if ( (v = voice[i].clone_voice) >= 0) + { + if (voice[v].status == VOICE_ON) + voice[v].status = VOICE_SUSTAINED; + } + } + else + { + finish_note(i); + } + return; + } + } +} + +/* Process the All Notes Off event */ +void Renderer::all_notes_off(int c) +{ + int i = voices; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, "All notes off on channel %d", c); + while (i--) + { + if (voice[i].status == VOICE_ON && voice[i].channel == c) + { + if (channel[c].sustain) + { + voice[i].status = VOICE_SUSTAINED; + } + else + { + finish_note(i); + } + } + } +} + +/* Process the All Sounds Off event */ +void Renderer::all_sounds_off(int c) +{ + int i = voices; + while (i--) + { + if (voice[i].channel == c && + voice[i].status != VOICE_FREE && + voice[i].status != VOICE_DIE) + { + kill_note(i); + } + } +} + +void Renderer::adjust_pressure(int chan, int note, int amount) +{ + int i = voices; + while (i--) + { + if (voice[i].status == VOICE_ON && + voice[i].channel == chan && + voice[i].note == note) + { + voice[i].velocity = amount; + recompute_amp(&voice[i]); + apply_envelope_to_amp(&voice[i]); + return; + } + } +} + +void Renderer::adjust_panning(int c) +{ + int i = voices; + while (i--) + { + if ((voice[i].channel == c) && + (voice[i].status == VOICE_ON || voice[i].status == VOICE_SUSTAINED)) + { + if (voice[i].clone_type != NOT_CLONE) continue; + voice[i].panning = channel[c].panning; + recompute_amp(&voice[i]); + apply_envelope_to_amp(&voice[i]); + } + } +} + +void Renderer::drop_sustain(int c) +{ + int i = voices; + while (i--) + { + if (voice[i].status == VOICE_SUSTAINED && voice[i].channel == c) + finish_note(i); + } +} + +void Renderer::adjust_pitchbend(int c) +{ + int i = voices; + while (i--) + { + if (voice[i].status != VOICE_FREE && voice[i].channel == c) + { + recompute_freq(i); + } + } +} + +void Renderer::adjust_volume(int c) +{ + int i = voices; + while (i--) + { + if (voice[i].channel == c && + (voice[i].status == VOICE_ON || voice[i].status == VOICE_SUSTAINED)) + { + recompute_amp(&voice[i]); + apply_envelope_to_amp(&voice[i]); + } + } +} + +void Renderer::HandleEvent(int status, int parm1, int parm2) +{ + int command = status & 0xF0; + int chan = status & 0x0F; + + switch (command) + { + case ME_NOTEON: + parm1 += channel[chan].transpose; + if (parm2 == 0) + { + note_off(chan, parm1, 0); + } + else + { + note_on(chan, parm1, parm2); + } + break; + + case ME_NOTEOFF: + parm1 += channel[chan].transpose; + note_off(chan, parm1, parm2); + break; + + case ME_KEYPRESSURE: + adjust_pressure(chan, parm1, parm2); + break; + + case ME_CONTROLCHANGE: + HandleController(chan, parm1, parm2); + break; + + case ME_PROGRAM: + /* if (ISDRUMCHANNEL(chan)) { */ + if (channel[chan].kit) { + /* Change drum set */ + channel[chan].bank = parm1; + } + else + { + channel[chan].program = parm1; + } + break; + + case ME_CHANNELPRESSURE: + /* Unimplemented */ + break; + + case ME_PITCHWHEEL: + channel[chan].pitchbend = parm1 | (parm2 << 7); + channel[chan].pitchfactor = 0; + /* Adjust for notes already playing */ + adjust_pitchbend(chan); + break; + } +} + +void Renderer::HandleController(int chan, int ctrl, int val) +{ + switch (ctrl) + { + /* These should be the SCC-1 tone bank switch + commands. I don't know why there are two, or + why the latter only allows switching to bank 0. + Also, some MIDI files use 0 as some sort of + continuous controller. This will cause lots of + warnings about undefined tone banks. */ + case CTRL_BANK_SELECT: + if (XG_System_On) + { + if (val == SFX_BANKTYPE) + { + channel[chan].sfx = SFXBANK; + channel[chan].kit = 0; + } + else + { + channel[chan].sfx = 0; + channel[chan].kit = val; + } + } + else + { + channel[chan].bank = val; + } + break; + + case CTRL_BANK_SELECT+32: + if (XG_System_On) + { + channel[chan].bank = val; + } + break; + + case CTRL_VOLUME: + channel[chan].volume = (channel[chan].volume & 0x007F) | (val << 7); + adjust_volume(chan); + break; + + case CTRL_VOLUME+32: + channel[chan].volume = (channel[chan].volume & 0x3F80) | (val); + adjust_volume(chan); + break; + + case CTRL_EXPRESSION: + channel[chan].expression = (channel[chan].expression & 0x007F) | (val << 7); + adjust_volume(chan); + break; + + case CTRL_EXPRESSION+32: + channel[chan].expression = (channel[chan].expression & 0x3F80) | (val); + adjust_volume(chan); + break; + + case CTRL_PAN: + channel[chan].panning = (channel[chan].panning & 0x007F) | (val << 7); + if (adjust_panning_immediately) + { + adjust_panning(chan); + } + break; + + case CTRL_PAN+32: + channel[chan].panning = (channel[chan].panning & 0x3F80) | (val); + if (adjust_panning_immediately) + { + adjust_panning(chan); + } + break; + + case CTRL_SUSTAIN: + channel[chan].sustain = val; + if (val == 0) + { + drop_sustain(chan); + } + break; + + case CTRL_HARMONICCONTENT: + channel[chan].harmoniccontent = val; + break; + + case CTRL_RELEASETIME: + channel[chan].releasetime = val; + break; + + case CTRL_ATTACKTIME: + channel[chan].attacktime = val; + break; + + case CTRL_BRIGHTNESS: + channel[chan].brightness = val; + break; + + case CTRL_REVERBERATION: + channel[chan].reverberation = val; + break; + + case CTRL_CHORUSDEPTH: + channel[chan].chorusdepth = val; + break; + + case CTRL_NRPN_LSB: + channel[chan].nrpn = (channel[chan].nrpn & 0x3F80) | (val); + channel[chan].nrpn_mode = true; + break; + + case CTRL_NRPN_MSB: + channel[chan].nrpn = (channel[chan].nrpn & 0x007F) | (val << 7); + channel[chan].nrpn_mode = true; + break; + + case CTRL_RPN_LSB: + channel[chan].rpn = (channel[chan].rpn & 0x3F80) | (val); + channel[chan].nrpn_mode = false; + break; + + case CTRL_RPN_MSB: + channel[chan].rpn = (channel[chan].rpn & 0x007F) | (val << 7); + channel[chan].nrpn_mode = false; + break; + + case CTRL_DATA_ENTRY: + if (channel[chan].nrpn_mode) + { + DataEntryCoarseNRPN(chan, channel[chan].nrpn, val); + } + else + { + DataEntryCoarseRPN(chan, channel[chan].rpn, val); + } + break; + + case CTRL_DATA_ENTRY+32: + if (channel[chan].nrpn_mode) + { + DataEntryFineNRPN(chan, channel[chan].nrpn, val); + } + else + { + DataEntryFineRPN(chan, channel[chan].rpn, val); + } + break; + + case CTRL_ALL_SOUNDS_OFF: + all_sounds_off(chan); + break; + + case CTRL_RESET_CONTROLLERS: + reset_controllers(chan); + break; + + case CTRL_ALL_NOTES_OFF: + all_notes_off(chan); + break; + } +} + +void Renderer::DataEntryCoarseRPN(int chan, int rpn, int val) +{ + switch (rpn) + { + case RPN_PITCH_SENS: + channel[chan].pitchsens = (channel[chan].pitchsens % 100) + (val * 100); + channel[chan].pitchfactor = 0; + break; + + // TiMidity resets the pitch sensitivity when a song attempts to write to + // RPN_RESET. My docs tell me this is just a dummy value that is guaranteed + // to not cause future data entry to go anywhere until a new RPN is set. + } +} + +void Renderer::DataEntryFineRPN(int chan, int rpn, int val) +{ + switch (rpn) + { + case RPN_PITCH_SENS: + channel[chan].pitchsens = (channel[chan].pitchsens / 100) * 100 + val; + channel[chan].pitchfactor = 0; + break; + } +} + +// [RH] Q. What device are we pretending to be by responding to these NRPNs? +void Renderer::DataEntryCoarseNRPN(int chan, int nrpn, int val) +{ + switch (nrpn & 0x3F80) + { + case 0x0080: + if (nrpn == NRPN_BRIGHTNESS) + { + channel[chan].brightness = val; + } + else if (nrpn == NRPN_HARMONICCONTENT) + { + channel[chan].harmoniccontent = val; + } + break; + + case NRPN_DRUMVOLUME: + drumvolume[chan][nrpn & 0x007F] = val; + break; + + case NRPN_DRUMPANPOT: + if (val == 0) + { + val = 127 * rand() / RAND_MAX; + } + drumpanpot[chan][nrpn & 0x007F] = val; + break; + + case NRPN_DRUMREVERBERATION: + drumreverberation[chan][nrpn & 0x007F] = val; + break; + + case NRPN_DRUMCHORUSDEPTH: + drumchorusdepth[chan][nrpn & 0x007F] = val; + break; + } +} + +void Renderer::DataEntryFineNRPN(int chan, int nrpn, int val) +{ + // We don't care about fine data entry for any NRPN at this time. +} + +void Renderer::HandleLongMessage(const BYTE *data, int len) +{ + // SysEx handling goes here. +} + +#if 0 +MidiSong *Timidity_LoadSong(char *midifile) +{ + MidiSong *song; + int32 events; + SDL_RWops *rw; + + /* Allocate memory for the song */ + song = (MidiSong *)safe_malloc(sizeof(*song)); + memset(song, 0, sizeof(*song)); + + /* Open the file */ + strcpy(midi_name, midifile); + + rw = SDL_RWFromFile(midifile, "rb"); + if ( rw != NULL ) { + song->events=read_midi_file(rw, &events, &song->samples); + SDL_RWclose(rw); + } + + /* Make sure everything is okay */ + if (!song->events) { + free(song); + song = NULL; + } + return(song); +} + +MidiSong *Timidity_LoadSong_RW(SDL_RWops *rw) +{ + MidiSong *song; + int32 events; + + /* Allocate memory for the song */ + song = (MidiSong *)safe_malloc(sizeof(*song)); + memset(song, 0, sizeof(*song)); + + strcpy(midi_name, "SDLrwops source"); + + song->events=read_midi_file(rw, &events, &song->samples); + + /* Make sure everything is okay */ + if (!song->events) { + free(song); + song = NULL; + } + return(song); +} + +void Timidity_Start(MidiSong *song) +{ + load_missing_instruments(); + master_volume = 1; + sample_count = song->samples; + event_list = song->events; + lost_notes=cut_notes=0; + + skip_to(0); + midi_playing = 1; +} + +int Timidity_Active(void) +{ + return(midi_playing); +} + +void Timidity_Stop(void) +{ + midi_playing = 0; +} + +void Timidity_FreeSong(MidiSong *song) +{ + if (free_instruments_afterwards) + free_instruments(); + + free(song->events); + free(song); +} + +void Timidity_Close(void) +{ + if (resample_buffer) { + free(resample_buffer); + resample_buffer=NULL; + } + if (common_buffer) { + free(common_buffer); + common_buffer=NULL; + } + free_instruments(); + free_pathlist(); +} +#endif + +void Renderer::Reset() +{ + int i; + + lost_notes = cut_notes = 0; + GM_System_On = GS_System_On = XG_System_On = 0; + XG_System_reverb_type = XG_System_chorus_type = XG_System_variation_type = 0; + memset(&drumvolume, -1, sizeof(drumvolume)); + memset(&drumchorusdepth, -1, sizeof(drumchorusdepth)); + memset(&drumreverberation, -1, sizeof(drumreverberation)); + memset(&drumpanpot, NO_PANNING, sizeof(drumpanpot)); + for (i = 0; i < MAXCHAN; ++i) + { + channel[i].kit = ISDRUMCHANNEL(i) ? 127 : 0; + channel[i].brightness = 64; + channel[i].harmoniccontent = 64; + channel[i].variationbank = 0; + channel[i].chorusdepth = 0; + channel[i].reverberation = 0; + channel[i].transpose = 0; + } + reset_midi(); +} + +} diff --git a/src/timidity/resample.cpp b/src/timidity/resample.cpp new file mode 100644 index 0000000000..9c9b72d6ae --- /dev/null +++ b/src/timidity/resample.cpp @@ -0,0 +1,608 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + resample.c + +*/ + +#include +#include +#include + +#include "timidity.h" + +namespace Timidity +{ + +#define RESAMPLATION {\ + int o = ofs >> FRACTION_BITS, m = ofs & FRACTION_MASK; \ + *dest++ = src[o] + (src[o + 1] - src[o]) * m / (1 << FRACTION_BITS);\ + } + +#define FINALINTERP if (ofs == le) *dest++ = src[ofs >> FRACTION_BITS]; +/* So it isn't interpolation. At least it's final. */ + +/*************** resampling with fixed increment *****************/ + +static sample_t *rs_plain(sample_t *resample_buffer, Voice *v, int *countptr) +{ + /* Play sample until end, then free the voice. */ + + const sample_t + *src = v->sample->data; + sample_t + *dest = resample_buffer; + int + ofs = v->sample_offset, + incr = v->sample_increment, + le = v->sample->data_length, + count = *countptr; + + int i; + + if (incr < 0) incr = -incr; /* In case we're coming out of a bidir loop */ + + /* Precalc how many times we should go through the loop. + NOTE: Assumes that incr > 0 and that ofs <= le */ + i = (le - ofs) / incr + 1; + + if (i > count) + { + i = count; + count = 0; + } + else + { + count -= i; + } + + while (i--) + { + RESAMPLATION; + ofs += incr; + } + + if (ofs >= le) + { + FINALINTERP; + v->status = VOICE_FREE; + *countptr -= count + 1; + } + + v->sample_offset = ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_loop(sample_t *resample_buffer, Voice *vp, int count) +{ + /* Play sample until end-of-loop, skip back and continue. */ + + int + ofs = vp->sample_offset, + incr = vp->sample_increment, + le = vp->sample->loop_end, + ll = le - vp->sample->loop_start; + sample_t + *dest = resample_buffer; + const sample_t + *src = vp->sample->data; + + int i; + + while (count) + { + if (ofs >= le) + /* NOTE: Assumes that ll > incr and that incr > 0. */ + ofs -= ll; + /* Precalc how many times we should go through the loop */ + i = (le - ofs) / incr + 1; + if (i > count) + { + i = count; + count = 0; + } + else + { + count -= i; + } + while (i--) + { + RESAMPLATION; + ofs += incr; + } + } + + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_bidir(sample_t *resample_buffer, Voice *vp, int count) +{ + int + ofs = vp->sample_offset, + incr = vp->sample_increment, + le = vp->sample->loop_end, + ls = vp->sample->loop_start; + sample_t + *dest = resample_buffer; + const sample_t + *src = vp->sample->data; + + int + le2 = le << 1, + ls2 = ls << 1, + i; + /* Play normally until inside the loop region */ + + if (ofs <= ls) + { + /* NOTE: Assumes that incr > 0, which is NOT always the case + when doing bidirectional looping. I have yet to see a case + where both ofs <= ls AND incr < 0, however. */ + i = (ls - ofs) / incr + 1; + if (i > count) + { + i = count; + count = 0; + } + else + { + count -= i; + } + while (i--) + { + RESAMPLATION; + ofs += incr; + } + } + + /* Then do the bidirectional looping */ + + while(count) + { + /* Precalc how many times we should go through the loop */ + i = ((incr > 0 ? le : ls) - ofs) / incr + 1; + if (i > count) + { + i = count; + count = 0; + } + else + { + count -= i; + } + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (ofs >= le) + { + /* fold the overshoot back in */ + ofs = le2 - ofs; + incr *= -1; + } + else if (ofs <= ls) + { + ofs = ls2 - ofs; + incr *= -1; + } + } + + vp->sample_increment = incr; + vp->sample_offset = ofs; /* Update offset */ + return resample_buffer; +} + +/*********************** vibrato versions ***************************/ + +/* We only need to compute one half of the vibrato sine cycle */ +static int vib_phase_to_inc_ptr(int phase) +{ + if (phase < VIBRATO_SAMPLE_INCREMENTS/2) + return VIBRATO_SAMPLE_INCREMENTS/2-1-phase; + else if (phase >= 3*VIBRATO_SAMPLE_INCREMENTS/2) + return 5*VIBRATO_SAMPLE_INCREMENTS/2-1-phase; + else + return phase-VIBRATO_SAMPLE_INCREMENTS/2; +} + +static int update_vibrato(float output_rate, Voice *vp, int sign) +{ + int depth; + int phase; + double a, pb; + + if (vp->vibrato_phase++ >= 2*VIBRATO_SAMPLE_INCREMENTS-1) + vp->vibrato_phase=0; + phase = vib_phase_to_inc_ptr(vp->vibrato_phase); + + if (vp->vibrato_sample_increment[phase]) + { + if (sign) + return -vp->vibrato_sample_increment[phase]; + else + return vp->vibrato_sample_increment[phase]; + } + + /* Need to compute this sample increment. */ + depth = vp->sample->vibrato_depth << 7; + + if (vp->vibrato_sweep) + { + /* Need to update sweep */ + vp->vibrato_sweep_position += vp->vibrato_sweep; + if (vp->vibrato_sweep_position >= (1<vibrato_sweep=0; + else + { + /* Adjust depth */ + depth *= vp->vibrato_sweep_position; + depth >>= SWEEP_SHIFT; + } + } + + a = FSCALE(((double)(vp->sample->sample_rate) * vp->frequency) / + ((double)(vp->sample->root_freq) * output_rate), + FRACTION_BITS); + + pb = (sine(vp->vibrato_phase * (1.0/(2*VIBRATO_SAMPLE_INCREMENTS))) + * (double)(depth) * VIBRATO_AMPLITUDE_TUNING); + + a *= pow(2.0, pb / (8191 * 12.f)); + + /* If the sweep's over, we can store the newly computed sample_increment */ + if (!vp->vibrato_sweep) + vp->vibrato_sample_increment[phase] = (int) a; + + if (sign) + a = -a; /* need to preserve the loop direction */ + + return (int) a; +} + +static sample_t *rs_vib_plain(sample_t *resample_buffer, float rate, Voice *vp, int *countptr) +{ + /* Play sample until end, then free the voice. */ + + sample_t + *dest = resample_buffer; + const sample_t + *src = vp->sample->data; + int + le = vp->sample->data_length, + ofs = vp->sample_offset, + incr = vp->sample_increment, + count = *countptr; + int + cc = vp->vibrato_control_counter; + + /* This has never been tested */ + + if (incr < 0) incr = -incr; /* In case we're coming out of a bidir loop */ + + while (count--) + { + if (!cc--) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(rate, vp, 0); + } + RESAMPLATION; + ofs += incr; + if (ofs >= le) + { + FINALINTERP; + vp->status = VOICE_FREE; + *countptr -= count+1; + break; + } + } + + vp->vibrato_control_counter = cc; + vp->sample_increment = incr; + vp->sample_offset = ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_vib_loop(sample_t *resample_buffer, float rate, Voice *vp, int count) +{ + /* Play sample until end-of-loop, skip back and continue. */ + + int + ofs = vp->sample_offset, + incr = vp->sample_increment, + le = vp->sample->loop_end, + ll = le - vp->sample->loop_start; + sample_t + *dest = resample_buffer; + const sample_t + *src = vp->sample->data; + int + cc = vp->vibrato_control_counter; + + int i; + int + vibflag=0; + + while (count) + { + /* Hopefully the loop is longer than an increment */ + if (ofs >= le) + ofs -= ll; + /* Precalc how many times to go through the loop, taking + the vibrato control ratio into account this time. */ + i = (le - ofs) / incr + 1; + if (i > count) i = count; + if (i > cc) + { + i = cc; + vibflag = 1; + } + else + { + cc -= i; + } + count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (vibflag) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(rate, vp, 0); + vibflag = 0; + } + } + + vp->vibrato_control_counter = cc; + vp->sample_increment = incr; + vp->sample_offset = ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_vib_bidir(sample_t *resample_buffer, float rate, Voice *vp, int count) +{ + int + ofs = vp->sample_offset, + incr = vp->sample_increment, + le = vp->sample->loop_end, + ls = vp->sample->loop_start; + sample_t + *dest = resample_buffer; + const sample_t + *src = vp->sample->data; + int + cc = vp->vibrato_control_counter; + + int + le2 = le << 1, + ls2 = ls << 1, + i; + int + vibflag = 0; + + /* Play normally until inside the loop region */ + while (count && (ofs <= ls)) + { + i = (ls - ofs) / incr + 1; + if (i > count) + { + i = count; + } + if (i > cc) + { + i = cc; + vibflag = 1; + } + else + { + cc -= i; + } + count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (vibflag) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(rate, vp, 0); + vibflag = 0; + } + } + + /* Then do the bidirectional looping */ + + while (count) + { + /* Precalc how many times we should go through the loop */ + i = ((incr > 0 ? le : ls) - ofs) / incr + 1; + if(i > count) + { + i = count; + } + if(i > cc) + { + i = cc; + vibflag = 1; + } + else + { + cc -= i; + } + count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (vibflag) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(rate, vp, (incr < 0)); + vibflag = 0; + } + if (ofs >= le) + { + /* fold the overshoot back in */ + ofs = le2 - ofs; + incr *= -1; + } + else if (ofs <= ls) + { + ofs = ls2 - ofs; + incr *= -1; + } + } + + vp->vibrato_control_counter = cc; + vp->sample_increment = incr; + vp->sample_offset = ofs; /* Update offset */ + return resample_buffer; +} + +sample_t *resample_voice(Renderer *song, Voice *vp, int *countptr) +{ + int ofs; + BYTE modes; + + if (vp->sample->sample_rate == 0) + { + /* Pre-resampled data -- just update the offset and check if + we're out of data. */ + ofs = vp->sample_offset >> FRACTION_BITS; /* Kind of silly to use FRACTION_BITS here... */ + if (*countptr >= (vp->sample->data_length >> FRACTION_BITS) - ofs) + { + /* Note finished. Free the voice. */ + vp->status = VOICE_FREE; + + /* Let the caller know how much data we had left */ + *countptr = (vp->sample->data_length >> FRACTION_BITS) - ofs; + } + else + { + vp->sample_offset += *countptr << FRACTION_BITS; + } + return vp->sample->data + ofs; + } + + /* Need to resample. Use the proper function. */ + modes = vp->sample->modes; + + if (vp->vibrato_control_ratio) + { + if ((modes & MODES_LOOPING) && + ((modes & MODES_ENVELOPE) || + (vp->status == VOICE_ON || vp->status == VOICE_SUSTAINED))) + { + if (modes & MODES_PINGPONG) + return rs_vib_bidir(song->resample_buffer, song->rate, vp, *countptr); + else + return rs_vib_loop(song->resample_buffer, song->rate, vp, *countptr); + } + else + { + return rs_vib_plain(song->resample_buffer, song->rate, vp, countptr); + } + } + else + { + if ((modes & MODES_LOOPING) && + ((modes & MODES_ENVELOPE) || + (vp->status == VOICE_ON || vp->status == VOICE_SUSTAINED))) + { + if (modes & MODES_PINGPONG) + return rs_bidir(song->resample_buffer, vp, *countptr); + else + return rs_loop(song->resample_buffer, vp, *countptr); + } + else + { + return rs_plain(song->resample_buffer, vp, countptr); + } + } +} + +void pre_resample(Renderer *song, Sample *sp) +{ + double a, xdiff; + int incr, ofs, newlen, count; + sample_t *newdata, *dest, *src = sp->data; + sample_t v1, v2, v3, v4, *vptr; + static const char note_name[12][3] = + { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; +return; + song->ctl->cmsg(CMSG_INFO, VERB_NOISY, " * pre-resampling for note %d (%s%d)", + sp->note_to_use, + note_name[sp->note_to_use % 12], (sp->note_to_use & 0x7F) / 12); + + a = (sp->sample_rate * note_to_freq(sp->note_to_use)) / (sp->root_freq * song->rate); + if (a <= 0) + return; + newlen = (int)(sp->data_length / a); + if (newlen < 0 || (newlen >> FRACTION_BITS) > MAX_SAMPLE_SIZE) + return; + + dest = newdata = (sample_t *)safe_malloc(newlen >> (FRACTION_BITS - 2)); + + count = (newlen >> FRACTION_BITS) - 1; + ofs = incr = (sp->data_length - (1 << FRACTION_BITS)) / count; + + if (--count) + *dest++ = src[0]; + + /* Since we're pre-processing and this doesn't have to be done in + real-time, we go ahead and do the full sliding cubic interpolation. */ + while (--count) + { + vptr = src + (ofs >> FRACTION_BITS); + v1 = (vptr == src) ? *vptr : *(vptr - 1); + v2 = *vptr; + v3 = *(vptr + 1); + v4 = *(vptr + 2); + xdiff = FSCALENEG(ofs & FRACTION_MASK, FRACTION_BITS); + *dest++ = sample_t(v2 + (xdiff / 6.0) * (-2 * v1 - 3 * v2 + 6 * v3 - v4 + + xdiff * (3 * (v1 - 2 * v2 + v3) + xdiff * (-v1 + 3 * (v2 - v3) + v4)))); + ofs += incr; + } + + if (ofs & FRACTION_MASK) + { + RESAMPLATION + } + else + { + *dest++ = src[ofs >> FRACTION_BITS]; + } + + sp->data_length = newlen; + sp->loop_start = int(sp->loop_start / a); + sp->loop_end = int(sp->loop_end / a); + free(sp->data); + sp->data = newdata; + sp->sample_rate = 0; +} + +} diff --git a/src/timidity/tables.cpp b/src/timidity/tables.cpp new file mode 100644 index 0000000000..2eee674785 --- /dev/null +++ b/src/timidity/tables.cpp @@ -0,0 +1,224 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#include "timidity.h" + +namespace Timidity +{ + +/* $Id: tables.c 1404 2004-08-21 12:27:02Z slouken $ Greg Lee */ +const BYTE xmap[XMAPMAX][5] = +{ + { SFXBANK, 0, 0, 120, 0 }, + { SFXBANK, 0, 1, 120, 1 }, + { SFXBANK, 0, 2, 120, 2 }, + { SFXBANK, 0, 3, 120, 3 }, + { SFXBANK, 0, 4, 120, 4 }, + { SFXBANK, 0, 5, 120, 5 }, + { SFXBANK, 0, 16, 120, 16 }, + { SFXBANK, 0, 32, 120, 32 }, + { SFXBANK, 0, 33, 120, 33 }, + { SFXBANK, 0, 34, 120, 34 }, + { SFXBANK, 0, 35, 120, 35 }, + { SFXBANK, 0, 36, 120, 36 }, + { SFXBANK, 0, 48, 120, 48 }, + { SFXBANK, 0, 49, 120, 49 }, + { SFXBANK, 0, 50, 120, 50 }, + { SFXBANK, 0, 51, 120, 51 }, + { SFXBANK, 0, 52, 120, 52 }, + { SFXBANK, 0, 54, 120, 54 }, + { SFXBANK, 0, 55, 120, 55 }, + { SFXBANK, 0, 64, 120, 64 }, + { SFXBANK, 0, 65, 120, 65 }, + { SFXBANK, 0, 66, 120, 66 }, + { SFXBANK, 0, 67, 120, 67 }, + { SFXBANK, 0, 68, 120, 68 }, + { SFXBANK, 0, 69, 120, 69 }, + { SFXBANK, 0, 70, 120, 70 }, + { SFXBANK, 0, 80, 120, 80 }, + { SFXBANK, 0, 81, 120, 81 }, + { SFXBANK, 0, 82, 120, 82 }, + { SFXBANK, 0, 83, 120, 83 }, + { SFXBANK, 0, 84, 120, 84 }, + { SFXBANK, 0, 85, 120, 85 }, + { SFXBANK, 0, 86, 120, 86 }, + { SFXBANK, 0, 87, 120, 87 }, + { SFXBANK, 0, 88, 120, 88 }, + { SFXBANK, 0, 89, 120, 89 }, + { SFXBANK, 0, 90, 120, 90 }, + { SFXBANK, 0, 96, 120, 96 }, + { SFXBANK, 0, 97, 120, 97 }, + { SFXBANK, 0, 98, 120, 98 }, + { SFXBANK, 0, 99, 120, 99 }, + { SFXBANK, 0, 100, 120, 100 }, + { SFXBANK, 0, 101, 120, 101 }, + { SFXBANK, 0, 112, 120, 112 }, + { SFXBANK, 0, 113, 120, 113 }, + { SFXBANK, 0, 114, 120, 114 }, + { SFXBANK, 0, 115, 120, 115 }, + { SFXDRUM1, 0, 36, 121, 36 }, + { SFXDRUM1, 0, 37, 121, 37 }, + { SFXDRUM1, 0, 38, 121, 38 }, + { SFXDRUM1, 0, 39, 121, 39 }, + { SFXDRUM1, 0, 40, 121, 40 }, + { SFXDRUM1, 0, 41, 121, 41 }, + { SFXDRUM1, 0, 52, 121, 52 }, + { SFXDRUM1, 0, 68, 121, 68 }, + { SFXDRUM1, 0, 69, 121, 69 }, + { SFXDRUM1, 0, 70, 121, 70 }, + { SFXDRUM1, 0, 71, 121, 71 }, + { SFXDRUM1, 0, 72, 121, 72 }, + { SFXDRUM1, 0, 84, 121, 84 }, + { SFXDRUM1, 0, 85, 121, 85 }, + { SFXDRUM1, 0, 86, 121, 86 }, + { SFXDRUM1, 0, 87, 121, 87 }, + { SFXDRUM1, 0, 88, 121, 88 }, + { SFXDRUM1, 0, 90, 121, 90 }, + { SFXDRUM1, 0, 91, 121, 91 }, + { SFXDRUM1, 1, 36, 122, 36 }, + { SFXDRUM1, 1, 37, 122, 37 }, + { SFXDRUM1, 1, 38, 122, 38 }, + { SFXDRUM1, 1, 39, 122, 39 }, + { SFXDRUM1, 1, 40, 122, 40 }, + { SFXDRUM1, 1, 41, 122, 41 }, + { SFXDRUM1, 1, 42, 122, 42 }, + { SFXDRUM1, 1, 52, 122, 52 }, + { SFXDRUM1, 1, 53, 122, 53 }, + { SFXDRUM1, 1, 54, 122, 54 }, + { SFXDRUM1, 1, 55, 122, 55 }, + { SFXDRUM1, 1, 56, 122, 56 }, + { SFXDRUM1, 1, 57, 122, 57 }, + { SFXDRUM1, 1, 58, 122, 58 }, + { SFXDRUM1, 1, 59, 122, 59 }, + { SFXDRUM1, 1, 60, 122, 60 }, + { SFXDRUM1, 1, 61, 122, 61 }, + { SFXDRUM1, 1, 62, 122, 62 }, + { SFXDRUM1, 1, 68, 122, 68 }, + { SFXDRUM1, 1, 69, 122, 69 }, + { SFXDRUM1, 1, 70, 122, 70 }, + { SFXDRUM1, 1, 71, 122, 71 }, + { SFXDRUM1, 1, 72, 122, 72 }, + { SFXDRUM1, 1, 73, 122, 73 }, + { SFXDRUM1, 1, 84, 122, 84 }, + { SFXDRUM1, 1, 85, 122, 85 }, + { SFXDRUM1, 1, 86, 122, 86 }, + { SFXDRUM1, 1, 87, 122, 87 }, + { XGDRUM, 0, 25, 40, 38 }, + { XGDRUM, 0, 26, 40, 40 }, + { XGDRUM, 0, 27, 40, 39 }, + { XGDRUM, 0, 28, 40, 30 }, + { XGDRUM, 0, 29, 0, 25 }, + { XGDRUM, 0, 30, 0, 85 }, + { XGDRUM, 0, 31, 0, 38 }, + { XGDRUM, 0, 32, 0, 37 }, + { XGDRUM, 0, 33, 0, 36 }, + { XGDRUM, 0, 34, 0, 38 }, + { XGDRUM, 0, 62, 0, 101 }, + { XGDRUM, 0, 63, 0, 102 }, + { XGDRUM, 0, 64, 0, 103 }, + { XGDRUM, 8, 25, 40, 38 }, + { XGDRUM, 8, 26, 40, 40 }, + { XGDRUM, 8, 27, 40, 39 }, + { XGDRUM, 8, 28, 40, 40 }, + { XGDRUM, 8, 29, 8, 25 }, + { XGDRUM, 8, 30, 8, 85 }, + { XGDRUM, 8, 31, 8, 38 }, + { XGDRUM, 8, 32, 8, 37 }, + { XGDRUM, 8, 33, 8, 36 }, + { XGDRUM, 8, 34, 8, 38 }, + { XGDRUM, 8, 62, 8, 101 }, + { XGDRUM, 8, 63, 8, 102 }, + { XGDRUM, 8, 64, 8, 103 }, + { XGDRUM, 16, 25, 40, 38 }, + { XGDRUM, 16, 26, 40, 40 }, + { XGDRUM, 16, 27, 40, 39 }, + { XGDRUM, 16, 28, 40, 40 }, + { XGDRUM, 16, 29, 16, 25 }, + { XGDRUM, 16, 30, 16, 85 }, + { XGDRUM, 16, 31, 16, 38 }, + { XGDRUM, 16, 32, 16, 37 }, + { XGDRUM, 16, 33, 16, 36 }, + { XGDRUM, 16, 34, 16, 38 }, + { XGDRUM, 16, 62, 16, 101 }, + { XGDRUM, 16, 63, 16, 102 }, + { XGDRUM, 16, 64, 16, 103 }, + { XGDRUM, 24, 25, 40, 38 }, + { XGDRUM, 24, 26, 40, 40 }, + { XGDRUM, 24, 27, 40, 39 }, + { XGDRUM, 24, 28, 24, 100 }, + { XGDRUM, 24, 29, 24, 25 }, + { XGDRUM, 24, 30, 24, 15 }, + { XGDRUM, 24, 31, 24, 38 }, + { XGDRUM, 24, 32, 24, 37 }, + { XGDRUM, 24, 33, 24, 36 }, + { XGDRUM, 24, 34, 24, 38 }, + { XGDRUM, 24, 62, 24, 101 }, + { XGDRUM, 24, 63, 24, 102 }, + { XGDRUM, 24, 64, 24, 103 }, + { XGDRUM, 24, 78, 0, 17 }, + { XGDRUM, 24, 79, 0, 18 }, + { XGDRUM, 25, 25, 40, 38 }, + { XGDRUM, 25, 26, 40, 40 }, + { XGDRUM, 25, 27, 40, 39 }, + { XGDRUM, 25, 28, 25, 100 }, + { XGDRUM, 25, 29, 25, 25 }, + { XGDRUM, 25, 30, 25, 15 }, + { XGDRUM, 25, 31, 25, 38 }, + { XGDRUM, 25, 32, 25, 37 }, + { XGDRUM, 25, 33, 25, 36 }, + { XGDRUM, 25, 34, 25, 38 }, + { XGDRUM, 25, 78, 0, 17 }, + { XGDRUM, 25, 79, 0, 18 }, + { XGDRUM, 32, 25, 40, 38 }, + { XGDRUM, 32, 26, 40, 40 }, + { XGDRUM, 32, 27, 40, 39 }, + { XGDRUM, 32, 28, 40, 40 }, + { XGDRUM, 32, 29, 32, 25 }, + { XGDRUM, 32, 30, 32, 85 }, + { XGDRUM, 32, 31, 32, 38 }, + { XGDRUM, 32, 32, 32, 37 }, + { XGDRUM, 32, 33, 32, 36 }, + { XGDRUM, 32, 34, 32, 38 }, + { XGDRUM, 32, 62, 32, 101 }, + { XGDRUM, 32, 63, 32, 102 }, + { XGDRUM, 32, 64, 32, 103 }, + { XGDRUM, 40, 25, 40, 38 }, + { XGDRUM, 40, 26, 40, 40 }, + { XGDRUM, 40, 27, 40, 39 }, + { XGDRUM, 40, 28, 40, 40 }, + { XGDRUM, 40, 29, 40, 25 }, + { XGDRUM, 40, 30, 40, 85 }, + { XGDRUM, 40, 31, 40, 39 }, + { XGDRUM, 40, 32, 40, 37 }, + { XGDRUM, 40, 33, 40, 36 }, + { XGDRUM, 40, 34, 40, 38 }, + { XGDRUM, 40, 38, 40, 39 }, + { XGDRUM, 40, 39, 0, 39 }, + { XGDRUM, 40, 40, 40, 38 }, + { XGDRUM, 40, 42, 0, 42 }, + { XGDRUM, 40, 46, 0, 46 }, + { XGDRUM, 40, 62, 40, 101 }, + { XGDRUM, 40, 63, 40, 102 }, + { XGDRUM, 40, 64, 40, 103 }, + { XGDRUM, 40, 87, 40, 87 } +}; + +} diff --git a/src/timidity/timidity.cpp b/src/timidity/timidity.cpp new file mode 100644 index 0000000000..4c744339c6 --- /dev/null +++ b/src/timidity/timidity.cpp @@ -0,0 +1,513 @@ +/* + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + timidity.c +*/ + +#include +#include + +#include "timidity.h" +#include "templates.h" +#include "m_alloc.h" +#include "cmdlib.h" +#include "c_cvars.h" +#include "i_system.h" + +CVAR(String, timidity_config, CONFIG_FILE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +namespace Timidity +{ + +ToneBank *tonebank[MAXBANK], *drumset[MAXBANK]; + +static FString def_instr_name; + + +#define MAXWORDS 10 + +static int read_config_file(const char *name) +{ + FILE *fp; + char tmp[1024], *w[MAXWORDS], *cp; + ToneBank *bank = NULL; + int i, j, k, line = 0, words; + static int rcf_count = 0; + + if (rcf_count > 50) + { + Printf("Timidity: Probable source loop in configuration files\n"); + return (-1); + } + + if (!(fp = open_file(name, 1, OF_VERBOSE))) + return -1; + + while (fgets(tmp, sizeof(tmp), fp)) + { + line++; + w[words = 0] = strtok(tmp, " \t\r\n\240"); + if (!w[0]) continue; + + /* Originally the TiMidity++ extensions were prefixed like this */ + if (strcmp(w[0], "#extension") == 0) + words = -1; + else if (*w[0] == '#') + continue; + + while (w[words] && *w[words] != '#' && (words < MAXWORDS)) + w[++words] = strtok(0, " \t\r\n\240"); + + /* + * TiMidity++ adds a number of extensions to the config file format. + * Many of them are completely irrelevant to SDL_sound, but at least + * we shouldn't choke on them. + * + * Unfortunately the documentation for these extensions is often quite + * vague, gramatically strange or completely absent. + */ + if ( + !strcmp(w[0], "comm") /* "comm" program second */ + || !strcmp(w[0], "HTTPproxy") /* "HTTPproxy" hostname:port */ + || !strcmp(w[0], "FTPproxy") /* "FTPproxy" hostname:port */ + || !strcmp(w[0], "mailaddr") /* "mailaddr" your-mail-address */ + || !strcmp(w[0], "opt") /* "opt" timidity-options */ + ) + { + /* + * + "comm" sets some kind of comment -- the documentation is too + * vague for me to understand at this time. + * + "HTTPproxy", "FTPproxy" and "mailaddr" are for reading data + * over a network, rather than from the file system. + * + "opt" specifies default options for TiMidity++. + * + * These are all quite useless for our version of TiMidity, so + * they can safely remain no-ops. + */ + } + else if (!strcmp(w[0], "timeout")) /* "timeout" program second */ + { + /* + * Specifies a timeout value of the program. A number of seconds + * before TiMidity kills the note. This may be useful to implement + * later, but I don't see any urgent need for it. + */ + Printf("FIXME: Implement \"timeout\" in TiMidity config.\n"); + } + else if (!strcmp(w[0], "copydrumset") /* "copydrumset" drumset */ + || !strcmp(w[0], "copybank")) /* "copybank" bank */ + { + /* + * Copies all the settings of the specified drumset or bank to + * the current drumset or bank. May be useful later, but not a + * high priority. + */ + Printf("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]); + } + else if (!strcmp(w[0], "undef")) /* "undef" progno */ + { + /* + * Undefines the tone "progno" of the current tone bank (or + * drum set?). Not a high priority. + */ + Printf("FIXME: Implement \"undef\" in TiMidity config.\n"); + } + else if (!strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */ + { + /* + * Sets the alternate assign for drum set. Whatever that's + * supposed to mean. + */ + Printf("FIXME: Implement \"altassign\" in TiMidity config.\n"); + } + else if (!strcmp(w[0], "soundfont") || !strcmp(w[0], "font")) + { + /* + * I can't find any documentation for these, but I guess they're + * an alternative way of loading/unloading instruments. + * + * "soundfont" sf_file "remove" + * "soundfont" sf_file ["order=" order] ["cutoff=" cutoff] + * ["reso=" reso] ["amp=" amp] + * "font" "exclude" bank preset keynote + * "font" "order" order bank preset keynote + */ + Printf("FIXME: Implmement \"%s\" in TiMidity config.\n", w[0]); + } + else if (!strcmp(w[0], "progbase")) + { + /* + * The documentation for this makes absolutely no sense to me, but + * apparently it sets some sort of base offset for tone numbers. + * Why anyone would want to do this is beyond me. + */ + Printf("FIXME: Implement \"progbase\" in TiMidity config.\n"); + } + else if (!strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */ + { + /* + * This extension is the one we will need to implement, as it is + * used by the "eawpats". Unfortunately I cannot find any + * documentation whatsoever for it, but it looks like it's used + * for remapping one instrument to another somehow. + */ + Printf("FIXME: Implement \"map\" in TiMidity config.\n"); + } + + /* Standard TiMidity config */ + + else if (!strcmp(w[0], "dir")) + { + if (words < 2) + { + Printf("%s: line %d: No directory given\n", name, line); + return -2; + } + for (i = 1; i < words; i++) + add_to_pathlist(w[i]); + } + else if (!strcmp(w[0], "source")) + { + if (words < 2) + { + Printf("%s: line %d: No file name given\n", name, line); + return -2; + } + for (i=1; i 127) + { + Printf("%s: line %d: Drum set must be between 0 and 127\n", name, line); + return -2; + } + if (drumset[i] == NULL) + { + drumset[i] = new ToneBank; + } + bank = drumset[i]; + } + else if (!strcmp(w[0], "bank")) + { + if (words < 2) + { + Printf("%s: line %d: No bank number given\n", name, line); + return -2; + } + i = atoi(w[1]); + if (i < 0 || i > 127) + { + Printf("%s: line %d: Tone bank must be between 0 and 127\n", name, line); + return -2; + } + if (tonebank[i] == NULL) + { + tonebank[i] = new ToneBank; + } + bank = tonebank[i]; + } + else + { + if ((words < 2) || (*w[0] < '0' || *w[0] > '9')) + { + Printf("%s: line %d: syntax error\n", name, line); + return -2; + } + i = atoi(w[0]); + if (i < 0 || i > 127) + { + Printf("%s: line %d: Program must be between 0 and 127\n", name, line); + return -2; + } + if (bank == NULL) + { + Printf("%s: line %d: Must specify tone bank or drum set before assignment\n", name, line); + return -2; + } + bank->tone[i].name = w[1]; + bank->tone[i].note = bank->tone[i].amp = bank->tone[i].pan = + bank->tone[i].strip_loop = bank->tone[i].strip_envelope = + bank->tone[i].strip_tail = -1; + + for (j = 2; j '9')) + { + Printf("%s: line %d: amplification must be between 0 and %d\n", name, line, MAX_AMPLIFICATION); + return -2; + } + bank->tone[i].amp = k; + } + else if (!strcmp(w[j], "note")) + { + k = atoi(cp); + if ((k < 0 || k > 127) || (*cp < '0' || *cp > '9')) + { + Printf("%s: line %d: note must be between 0 and 127\n", name, line); + return -2; + } + bank->tone[i].note = k; + } + else if (!strcmp(w[j], "pan")) + { + if (!strcmp(cp, "center")) + k = 64; + else if (!strcmp(cp, "left")) + k = 0; + else if (!strcmp(cp, "right")) + k = 127; + else + k = ((atoi(cp)+100) * 100) / 157; + if ((k < 0 || k > 127) || + (k == 0 && *cp != '-' && (*cp < '0' || *cp > '9'))) + { + Printf("%s: line %d: panning must be left, right, " + "center, or between -100 and 100\n", name, line); + return -2; + } + bank->tone[i].pan = k; + } + else if (!strcmp(w[j], "keep")) + { + if (!strcmp(cp, "env")) + bank->tone[i].strip_envelope = 0; + else if (!strcmp(cp, "loop")) + bank->tone[i].strip_loop = 0; + else + { + Printf("%s: line %d: keep must be env or loop\n", name, line); + return -2; + } + } + else if (!strcmp(w[j], "strip")) + { + if (!strcmp(cp, "env")) + bank->tone[i].strip_envelope = 1; + else if (!strcmp(cp, "loop")) + bank->tone[i].strip_loop = 1; + else if (!strcmp(cp, "tail")) + bank->tone[i].strip_tail = 1; + else + { + Printf("%s: line %d: strip must be env, loop, or tail\n", name, line); + return -2; + } + } + else + { + Printf("%s: line %d: bad patch option %s\n", name, line, w[j]); + return -2; + } + } + } + } + if (ferror(fp)) + { + Printf("Can't read %s: %s\n", name, strerror(errno)); + close_file(fp); + return -2; + } + close_file(fp); + return 0; +} + +void FreeAll() +{ + free_instruments(); + for (int i = 0; i < MAXBANK; ++i) + { + if (tonebank[i] != NULL) + { + delete tonebank[i]; + tonebank[i] = NULL; + } + if (drumset[i] != NULL) + { + delete drumset[i]; + drumset[i] = NULL; + } + } +} + +int LoadConfig() +{ + static bool set_initial_path = false; + + if (!set_initial_path) + { +#ifdef _WIN32 + add_to_pathlist("\\TIMIDITY"); + add_to_pathlist(progdir); +#else + add_to_pathlist("/usr/local/lib/timidity"); + add_to_pathlist("/etc/timidity"); + add_to_pathlist("/etc"); +#endif + set_initial_path = true; + } + + /* Some functions get aggravated if not even the standard banks are available. */ + if (tonebank[0] == NULL) + { + tonebank[0] = new ToneBank; + drumset[0] = new ToneBank; + } + + return read_config_file(timidity_config); +} + +Renderer::Renderer(float sample_rate) +{ + ctl = new ControlMode; + rate = sample_rate; + patches = NULL; + default_instrument = NULL; +#ifdef FAST_DECAY + fast_decay = true; +#else + fast_decay = false; +#endif + resample_buffer_size = 0; + resample_buffer = NULL; + + control_ratio = clamp(int(rate / CONTROLS_PER_SECOND), 1, MAX_CONTROL_RATIO); + if (def_instr_name.IsNotEmpty()) + set_default_instrument(def_instr_name); + + voices = DEFAULT_VOICES; + memset(voice, 0, sizeof(voice)); + memset(drumvolume, 0, sizeof(drumvolume)); + memset(drumpanpot, 0, sizeof(drumpanpot)); + memset(drumreverberation, 0, sizeof(drumreverberation)); + memset(drumchorusdepth, 0, sizeof(drumchorusdepth)); + drumchannels = DEFAULT_DRUMCHANNELS; +} + +Renderer::~Renderer() +{ + if (resample_buffer != NULL) + { + M_Free(resample_buffer); + } +} + +void Renderer::ComputeOutput(float *buffer, int count) +{ + // count is in samples, not bytes. + if (count <= 0) + { + return; + } + Voice *v = &voice[0]; + + memset(buffer, 0, sizeof(float)*count*2); // An integer 0 is also a float 0. + if (resample_buffer_size < count) + { + resample_buffer_size = count; + resample_buffer = (sample_t *)M_Realloc(resample_buffer, count * sizeof(float) * 2); + } + for (int i = 0; i < voices; i++, v++) + { + if (v->status != VOICE_FREE) + { + if (v->sample_offset == 0 && v->echo_delay_count) + { + if (v->echo_delay_count >= count) + { + v->echo_delay_count -= count; + } + else + { + mix_voice(this, buffer + v->echo_delay_count, v, count - v->echo_delay_count); + v->echo_delay_count = 0; + } + } + else + { + mix_voice(this, buffer, v, count); + } + } + } +} + +void Renderer::MarkInstrument(int banknum, int percussion, int instr) +{ + ToneBank *bank; + + if (banknum >= MAXBANK) + { + return; + } + if (percussion) + { + bank = drumset[banknum]; + } + else + { + bank = tonebank[banknum]; + } + if (bank == NULL) + { + return; + } + if (bank->tone[instr].layer == NULL) + { + bank->tone[instr].layer = MAGIC_LOAD_INSTRUMENT; + } +} + +ControlMode::~ControlMode() +{ +} + +void ControlMode::cmsg(int type, int verbosity_level, const char *fmt, ...) +{ +} + +} diff --git a/src/timidity/timidity.h b/src/timidity/timidity.h new file mode 100644 index 0000000000..ea8ed4cb2b --- /dev/null +++ b/src/timidity/timidity.h @@ -0,0 +1,604 @@ +/* + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef TIMIDITY_H +#define TIMIDITY_H + +#include "doomtype.h" +#include "zstring.h" + +namespace Timidity +{ + +/* +config.h +*/ + +/* Acoustic Grand Piano seems to be the usual default instrument. */ +#define DEFAULT_PROGRAM 0 + +/* 9 here is MIDI channel 10, which is the standard percussion channel. + Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too. + On the other hand, some files know that 16 is not a drum channel and + try to play music on it. This is now a runtime option, so this isn't + a critical choice anymore. */ +#define DEFAULT_DRUMCHANNELS (1<<9) +/*#define DEFAULT_DRUMCHANNELS ((1<<9) | (1<<15))*/ + +/* Default sampling rate, default polyphony, and maximum polyphony. + All but the last can be overridden from the command line. */ +#define DEFAULT_RATE 32000 +#define DEFAULT_VOICES 32 +#define MAX_VOICES 256 +#define MAXCHAN 16 +#define MAXNOTE 128 + +/* 1000 here will give a control ratio of 22:1 with 22 kHz output. + Higher CONTROLS_PER_SECOND values allow more accurate rendering + of envelopes and tremolo. The cost is CPU time. */ +#define CONTROLS_PER_SECOND 1000 + +/* Make envelopes twice as fast. Saves ~20% CPU time (notes decay + faster) and sounds more like a GUS. There is now a command line + option to toggle this as well. */ +//#define FAST_DECAY + +/* How many bits to use for the fractional part of sample positions. + This affects tonal accuracy. The entire position counter must fit + in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of + a sample is 1048576 samples (2 megabytes in memory). The GUS gets + by with just 9 bits and a little help from its friends... + "The GUS does not SUCK!!!" -- a happy user :) */ +#define FRACTION_BITS 12 + +/* 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 + +/* The number of samples to use for ramping out a dying note. Affects + click removal. */ +#define MAX_DIE_TIME 20 + +/**************************************************************************/ +/* Anything below this shouldn't need to be changed unless you're porting + to a new machine with other than 32-bit, big-endian words. */ +/**************************************************************************/ + +/* change FRACTION_BITS above, not these */ +#define INTEGER_BITS (32 - FRACTION_BITS) +#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS) +#define FRACTION_MASK (~ INTEGER_MASK) +#define MAX_SAMPLE_SIZE (1 << INTEGER_BITS) + +/* This is enforced by some computations that must fit in an int */ +#define MAX_CONTROL_RATIO 255 + +#define MAX_AMPLIFICATION 800 + +/* The TiMiditiy configuration file */ +#define CONFIG_FILE "timidity.cfg" + +typedef float sample_t; +typedef float final_volume_t; +#define FINAL_VOLUME(v) (v) + +#define FSCALE(a,b) ((a) * (float)(1<<(b))) +#define FSCALENEG(a,b) ((a) * (1.0L / (float)(1<<(b)))) + +/* Vibrato and tremolo Choices of the Day */ +#define SWEEP_TUNING 38 +#define VIBRATO_AMPLITUDE_TUNING 1.0L +#define VIBRATO_RATE_TUNING 38 +#define TREMOLO_AMPLITUDE_TUNING 1.0L +#define TREMOLO_RATE_TUNING 38 + +#define SWEEP_SHIFT 16 +#define RATE_SHIFT 5 + +#define VIBRATO_SAMPLE_INCREMENTS 32 + +#ifndef PI + #define PI 3.14159265358979323846 +#endif + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +// [RH] MinGW's pow() function is terribly slow compared to VC8's +// (I suppose because it's using an old version from MSVCRT.DLL). +// On an Opteron running x86-64 Linux, this also ended up being about +// 100 cycles faster than libm's pow(), which is why I'm using this +// for GCC in general and not just for MinGW. + +extern __inline__ double pow_x87_inline(double x,double y) +{ + double result; + + if (y == 0) + { + return 1; + } + if (x == 0) + { + if (y > 0) + { + return 0; + } + else + { + union { double fp; long long ip; } infinity; + infinity.ip = 0x7FF0000000000000ll; + return infinity.fp; + } + } + __asm__ ( + "fyl2x\n\t" + "fld %%st(0)\n\t" + "frndint\n\t" + "fxch\n\t" + "fsub %%st(1),%%st(0)\n\t" + "f2xm1\n\t" + "fld1\n\t" + "faddp\n\t" + "fxch\n\t" + "fld1\n\t" + "fscale\n\t" + "fstp %%st(1)\n\t" + "fmulp\n\t" + : "=t" (result) + : "0" (x), "u" (y) + : "st(1)", "st(7)", "%3", "%4" ); + return result; +} +#define pow pow_x87_inline +#endif + +/* +common.h +*/ + +extern FString current_filename; + +/* Noise modes for open_file */ +#define OF_SILENT 0 +#define OF_NORMAL 1 +#define OF_VERBOSE 2 + +extern FILE *open_file(const char *name, int decompress, int noise_mode); +extern void add_to_pathlist(const char *s); +extern void close_file(FILE *fp); +extern void skip(FILE *fp, size_t len); +extern void *safe_malloc(size_t count); + +/* +controls.h +*/ + +#define CMSG_INFO 0 +#define CMSG_WARNING 1 +#define CMSG_ERROR 2 +#define CMSG_FATAL 3 +#define CMSG_TRACE 4 +#define CMSG_TIME 5 +#define CMSG_TOTAL 6 +#define CMSG_FILE 7 +#define CMSG_TEXT 8 + +#define VERB_NORMAL 0 +#define VERB_VERBOSE 1 +#define VERB_NOISY 2 +#define VERB_DEBUG 3 +#define VERB_DEBUG_SILLY 4 + +struct ControlMode +{ + virtual ~ControlMode(); + void cmsg(int type, int verbosity_level, const char *fmt, ...); +}; + + +/* +instrum.h +*/ + +struct Sample +{ + SDWORD + loop_start, loop_end, data_length, + sample_rate, low_vel, high_vel, low_freq, high_freq, root_freq; + SDWORD + envelope_rate[7], envelope_offset[7]; + float + modulation_rate[7], modulation_offset[7]; + float + volume, resonance, + modEnvToFilterFc, modEnvToPitch, modLfoToFilterFc; + sample_t *data; + SDWORD + tremolo_sweep_increment, tremolo_phase_increment, + lfo_sweep_increment, lfo_phase_increment, + vibrato_sweep_increment, vibrato_control_ratio, + cutoff_freq; + BYTE + reverberation, chorusdepth, + tremolo_depth, vibrato_depth, + modes, + attenuation; + WORD + freq_center, panning; + SBYTE + note_to_use, exclusiveClass; + SWORD + keyToModEnvHold, keyToModEnvDecay, + keyToVolEnvHold, keyToVolEnvDecay; + SDWORD + freq_scale; +}; + +void convert_sample_data(Sample *sample, const void *data); +void free_instruments(); + +/* Bits in modes: */ +#define MODES_16BIT (1<<0) +#define MODES_UNSIGNED (1<<1) +#define MODES_LOOPING (1<<2) +#define MODES_PINGPONG (1<<3) +#define MODES_REVERSE (1<<4) +#define MODES_SUSTAIN (1<<5) +#define MODES_ENVELOPE (1<<6) +#define MODES_FAST_RELEASE (1<<7) + +#define INST_GUS 0 +#define INST_SF2 1 +#define INST_DLS 2 + +struct Instrument +{ + int type; + int samples; + Sample *sample; + int left_samples; + Sample *left_sample; + int right_samples; + Sample *right_sample; +}; + +struct InstrumentLayer +{ + BYTE lo, hi; + Instrument *instrument; + InstrumentLayer *next; +}; + +struct cfg_type +{ + int font_code; + int num; + const char *name; +}; + +#define FONT_NORMAL 0 +#define FONT_FFF 1 +#define FONT_SBK 2 +#define FONT_TONESET 3 +#define FONT_DRUMSET 4 +#define FONT_PRESET 5 + +struct ToneBankElement +{ + ToneBankElement() : layer(NULL), font_type(0), sf_ix(0), tuning(0), + note(0), amp(0), pan(0), strip_loop(0), strip_envelope(0), strip_tail(0) + {} + + FString name; + InstrumentLayer *layer; + int font_type, sf_ix, tuning; + int note, amp, pan, strip_loop, strip_envelope, strip_tail; +}; + +/* A hack to delay instrument loading until after reading the +entire MIDI file. */ +#define MAGIC_LOAD_INSTRUMENT ((InstrumentLayer *)(-1)) + +#define MAXPROG 128 +#define MAXBANK 130 +#define SFXBANK (MAXBANK-1) +#define SFXDRUM1 (MAXBANK-2) +#define SFXDRUM2 (MAXBANK-1) +#define XGDRUM 1 + +struct ToneBank +{ + FString name; + ToneBankElement tone[MAXPROG]; +}; + + +#define SPECIAL_PROGRAM -1 + +extern void pcmap(int *b, int *v, int *p, int *drums); + +/* +mix.h +*/ + +extern void mix_voice(struct Renderer *song, float *buf, struct Voice *v, int c); +extern int recompute_envelope(struct Voice *v); +extern void apply_envelope_to_amp(struct Voice *v); + +/* +playmidi.h +*/ + +/* Midi events */ +#define ME_NOTEOFF 0x80 +#define ME_NOTEON 0x90 +#define ME_KEYPRESSURE 0xA0 +#define ME_CONTROLCHANGE 0xB0 +#define ME_PROGRAM 0xC0 +#define ME_CHANNELPRESSURE 0xD0 +#define ME_PITCHWHEEL 0xE0 + +/* Controllers */ +#define CTRL_BANK_SELECT 0 +#define CTRL_DATA_ENTRY 6 +#define CTRL_VOLUME 7 +#define CTRL_PAN 10 +#define CTRL_EXPRESSION 11 +#define CTRL_SUSTAIN 64 +#define CTRL_HARMONICCONTENT 71 +#define CTRL_RELEASETIME 72 +#define CTRL_ATTACKTIME 73 +#define CTRL_BRIGHTNESS 74 +#define CTRL_REVERBERATION 91 +#define CTRL_CHORUSDEPTH 93 +#define CTRL_NRPN_LSB 98 +#define CTRL_NRPN_MSB 99 +#define CTRL_RPN_LSB 100 +#define CTRL_RPN_MSB 101 +#define CTRL_ALL_SOUNDS_OFF 120 +#define CTRL_RESET_CONTROLLERS 121 +#define CTRL_ALL_NOTES_OFF 123 + +/* NRPNs */ +#define NRPN_BRIGHTNESS 0x00A0 +#define NRPN_HARMONICCONTENT 0x00A1 +#define NRPN_DRUMVOLUME (26<<7) // drum number in low 7 bits +#define NRPN_DRUMPANPOT (28<<7) // " +#define NRPN_DRUMREVERBERATION (29<<7) // " +#define NRPN_DRUMCHORUSDEPTH (30<<7) // " + +/* RPNs */ +#define RPN_PITCH_SENS 0x0000 +#define RPN_FINE_TUNING 0x0001 +#define RPN_COARSE_TUNING 0x0002 +#define RPN_RESET 0x3fff + +#define SFX_BANKTYPE 64 + +struct Channel +{ + int + bank, program, sustain, pitchbend, + mono, /* one note only on this channel -- not implemented yet */ + /* new stuff */ + variationbank, reverberation, chorusdepth, harmoniccontent, + releasetime, attacktime, brightness, kit, sfx, + /* end new */ + pitchsens; + WORD + volume, expression; + SWORD + panning; + WORD + rpn, nrpn; + bool + nrpn_mode; + char + transpose; + float + pitchfactor; /* precomputed pitch bend factor to save some fdiv's */ +}; + +/* Causes the instrument's default panning to be used. */ +#define NO_PANNING -1 +/* envelope points */ +#define MAXPOINT 7 + +struct Voice +{ + BYTE + status, channel, note, velocity, clone_type; + Sample *sample; + Sample *left_sample; + Sample *right_sample; + int clone_voice; + float + orig_frequency, frequency; + int + sample_offset, loop_start, loop_end; + int + envelope_volume, modulation_volume; + int + envelope_target, modulation_target; + int + tremolo_sweep, tremolo_sweep_position, tremolo_phase, + lfo_sweep, lfo_sweep_position, lfo_phase, + vibrato_sweep, vibrato_sweep_position, vibrato_depth, + echo_delay_count; + int + echo_delay, + sample_increment, + envelope_increment, + modulation_increment, + tremolo_phase_increment, + lfo_phase_increment; + + final_volume_t left_mix, right_mix; + + float + left_amp, right_amp, + volume, tremolo_volume, lfo_volume; + int + vibrato_sample_increment[VIBRATO_SAMPLE_INCREMENTS]; + int + envelope_rate[MAXPOINT], envelope_offset[MAXPOINT]; + int + vibrato_phase, vibrato_control_ratio, vibrato_control_counter, + envelope_stage, modulation_stage, control_counter, + modulation_delay, modulation_counter, panning, panned; + +}; + +/* Voice status options: */ +#define VOICE_FREE 0 +#define VOICE_ON 1 +#define VOICE_SUSTAINED 2 +#define VOICE_OFF 3 +#define VOICE_DIE 4 + +/* Voice panned options: */ +#define PANNED_MYSTERY 0 +#define PANNED_LEFT 1 +#define PANNED_RIGHT 2 +#define PANNED_CENTER 3 +/* Anything but PANNED_MYSTERY only uses the left volume */ + +/* Envelope stages: */ +#define ATTACK 0 +#define HOLD 1 +#define DECAY 2 +#define RELEASE 3 +#define RELEASEB 4 +#define RELEASEC 5 +#define DELAY 6 + +#define ISDRUMCHANNEL(c) ((drumchannels & (1<<(c)))) + +/* +resample.h +*/ + +extern sample_t *resample_voice(struct Renderer *song, Voice *v, int *countptr); +extern void pre_resample(struct Renderer *song, Sample *sp); + +/* +tables.h +*/ + +#define sine(x) (sin((2*PI/1024.0) * (x))) +#define note_to_freq(x) (float(8175.7989473096690661233836992789 * pow(2.0, (x) / 12.0))) + +// Use TiMidity++'s volume equation rather than TiMidity's, since it's louder. +//#define calc_vol(x) (pow(2.0,((x)*6.0 - 6.0))) +#define calc_vol(x) (pow((double)(x), (double)1.66096404744)) + +#define XMAPMAX 800 +extern const BYTE xmap[XMAPMAX][5]; + +/* +timidity.h +*/ +struct DLS_Data; +extern int LoadConfig(); +extern void FreeAll(); + +extern ToneBank *tonebank[MAXBANK]; +extern ToneBank *drumset[MAXBANK]; + +struct Renderer +{ + ControlMode *ctl; + float rate; + DLS_Data *patches; + InstrumentLayer *default_instrument; + int default_program; + bool fast_decay; + int resample_buffer_size; + sample_t *resample_buffer; + Channel channel[16]; + Voice voice[MAX_VOICES]; + signed char drumvolume[MAXCHAN][MAXNOTE]; + signed char drumpanpot[MAXCHAN][MAXNOTE]; + signed char drumreverberation[MAXCHAN][MAXNOTE]; + signed char drumchorusdepth[MAXCHAN][MAXNOTE]; + int control_ratio, amp_with_poly; + int drumchannels; + int adjust_panning_immediately; + int voices; + int GM_System_On; + int XG_System_On; + int GS_System_On; + int XG_System_reverb_type; + int XG_System_chorus_type; + int XG_System_variation_type; + int lost_notes, cut_notes; + + Renderer(float sample_rate); + ~Renderer(); + + void HandleEvent(int status, int parm1, int parm2); + void HandleLongMessage(const BYTE *data, int len); + void HandleController(int chan, int ctrl, int val); + void ComputeOutput(float *buffer, int num_samples); + void MarkInstrument(int bank, int percussion, int instr); + void Reset(); + + int load_missing_instruments(); + int set_default_instrument(const char *name); + int convert_tremolo_sweep(BYTE sweep); + int convert_vibrato_sweep(BYTE sweep, int vib_control_ratio); + int convert_tremolo_rate(BYTE rate); + int convert_vibrato_rate(BYTE rate); + + void recompute_amp(Voice *v); + int vc_alloc(int not_this_voice); + void kill_others(int voice); + void clone_voice(Instrument *ip, int v, int note, int vel, int clone_type, int variationbank); + void xremap(int *banknumpt, int *this_notept, int this_kit); + void start_note(int chan, int note, int vel, int voice); + + void note_on(int chan, int note, int vel); + void note_off(int chan, int note, int vel); + void all_notes_off(int chan); + void all_sounds_off(int chan); + void adjust_pressure(int chan, int note, int amount); + void adjust_panning(int chan); + void drop_sustain(int chan); + void adjust_pitchbend(int chan); + void adjust_volume(int chan); + + void reset_voices(); + void reset_controllers(int chan); + void reset_midi(); + + void select_sample(int voice, Instrument *instr); + void select_stereo_samples(int voice, InstrumentLayer *layer); + void recompute_freq(int voice); + + void kill_note(int voice); + void finish_note(int voice); + + void DataEntryCoarseRPN(int chan, int rpn, int val); + void DataEntryFineRPN(int chan, int rpn, int val); + void DataEntryCoarseNRPN(int chan, int nrpn, int val); + void DataEntryFineNRPN(int chan, int nrpn, int val); +}; + +} +#endif diff --git a/src/v_draw.cpp b/src/v_draw.cpp index f063b89941..b071154792 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -56,6 +56,34 @@ int CleanWidth, CleanHeight; CVAR (Bool, hud_scale, false, CVAR_ARCHIVE); +// For routines that take RGB colors, cache the previous lookup in case there +// are several repetitions with the same color. +static int LastPal = -1; +static uint32 LastRGB; + +static int PalFromRGB(uint32 rgb) +{ + if (LastPal >= 0 && LastRGB == rgb) + { + return LastPal; + } + // Quick check for black and white. + if (rgb == MAKEARGB(255,0,0,0)) + { + LastPal = GPalette.BlackIndex; + } + else if (rgb == MAKEARGB(255,255,255,255)) + { + LastPal = GPalette.WhiteIndex; + } + else + { + LastPal = ColorMatcher.Pick(RPART(rgb), GPART(rgb), BPART(rgb)); + } + LastRGB = rgb; + return LastPal; +} + void STACK_ARGS DCanvas::DrawTexture (FTexture *img, int x, int y, int tags_first, ...) { va_list tags; @@ -757,15 +785,7 @@ void DCanvas::DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 real if (palColor < 0) { - // Quick check for black. - if (realcolor == MAKEARGB(255,0,0,0)) - { - palColor = 0; - } - else - { - palColor = ColorMatcher.Pick(RPART(realcolor), GPART(realcolor), BPART(realcolor)); - } + palColor = PalFromRGB(realcolor); } Lock(); @@ -918,22 +938,52 @@ void DCanvas::DrawPixel(int x, int y, int palColor, uint32 realcolor) { if (palColor < 0) { - // Quick check for black. - if (realcolor == MAKEARGB(255,0,0,0)) - { - palColor = 0; - } - else - { - palColor = ColorMatcher.Pick(RPART(realcolor), GPART(realcolor), BPART(realcolor)); - } + palColor = PalFromRGB(realcolor); } - Lock(); - GetBuffer()[GetPitch() * y + x] = (BYTE)palColor; - Unlock(); + Buffer[Pitch * y + x] = (BYTE)palColor; } +//========================================================================== +// +// DCanvas :: Clear +// +// Set an area to a specified color. +// +//========================================================================== + +void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color) +{ + int x, y; + BYTE *dest; + + if (left == right || top == bottom) + { + return; + } + + assert(left < right); + assert(top < bottom); + + if (palcolor < 0) + { + if (APART(color) != 255) + { + Dim(color, APART(color)/255.f, left, top, right - left, bottom - top); + return; + } + + palcolor = PalFromRGB(color); + } + + dest = Buffer + top * Pitch + left; + x = right - left; + for (y = top; y < bottom; y++) + { + memset(dest, palcolor, x); + dest += Pitch; + } +} /********************************/ /* */ diff --git a/src/v_video.cpp b/src/v_video.cpp index f23b9f3b0c..fc51f6ad9b 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -284,55 +284,6 @@ void DCanvas::FlatFill (int left, int top, int right, int bottom, FTexture *src, } } -//========================================================================== -// -// DCanvas :: Clear -// -// Set an area to a specified color. -// -//========================================================================== - -void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color) -{ - int x, y; - BYTE *dest; - - if (left == right || top == bottom) - { - return; - } - - assert(left < right); - assert(top < bottom); - - if (palcolor < 0) - { - if (APART(color) != 255) - { - Dim(color, APART(color)/255.f, left, top, right - left, bottom - top); - return; - } - - // Quick check for black. - if (color == MAKEARGB(255,0,0,0)) - { - palcolor = 0; - } - else - { - palcolor = ColorMatcher.Pick(RPART(color), GPART(color), BPART(color)); - } - } - - dest = Buffer + top * Pitch + left; - x = right - left; - for (y = top; y < bottom; y++) - { - memset(dest, palcolor, x); - dest += Pitch; - } -} - //========================================================================== // // DCanvas :: Dim diff --git a/src/win32/i_main.cpp b/src/win32/i_main.cpp index 4119debf5e..67673ed398 100644 --- a/src/win32/i_main.cpp +++ b/src/win32/i_main.cpp @@ -823,9 +823,18 @@ void DoMain (HINSTANCE hInstance) atterm (I_Quit); // Figure out what directory the program resides in. - GetModuleFileName (NULL, progdir, 1024); - *(strrchr (progdir, '\\') + 1) = 0; - FixPathSeperator (progdir); + char *program; + + if (_get_pgmptr(&program) != 0) + { + I_FatalError("Could not determine program location."); + } + progdir = program; + program = progdir.LockBuffer(); + *(strrchr(program, '\\') + 1) = '\0'; + FixPathSeperator(program); + progdir.Truncate((long)strlen(program)); + progdir.UnlockBuffer(); /* height = GetSystemMetrics (SM_CYFIXEDFRAME) * 2 + GetSystemMetrics (SM_CYCAPTION) + 12 * 32; diff --git a/zdoom.vcproj b/zdoom.vcproj index a55ae97b54..5d2e0cbb5b 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2868,6 +2868,10 @@ RelativePath="src\sound\music_stream.cpp" > + + @@ -2927,6 +2931,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +