qzdoom-gpl/src/sound/music_midi_timidity.cpp
Randy Heit e815474cbe - Fixed: ACS improperly calculated the address of local variables when
returning from one function to another function when the function that
  was called was used as part of an expression.
- Fixed: Using Thing_Hate with arg0 (hater) set to 0 from an open script
  could crash.
- Fixed: Some items along ledges in Hexen's MAP32 (Orchard of Lamentations)
  appeared at the bottom of the ledge (and consequently inside it) instead
  of on top of it because the items were placed directly on the lines.
  AActor::LinkToWorldForMapThing() needs to use the original R_PointOnLineSide()
  code to handle situations like this. Previously, it just used the original
  code for straight horizontal/vertical lines and used the new code for
  diagonal lines.
- Fixed: FWadCollection::MergeLumps() used in incorrect realloc.
- Fixed: FPlayList::NextLine() did not properly handle blank lines in the
  playlist.
- Changed: Decals now use lightweight thinkers instead of actors. (76 bytes
  versus 396, so you save 320k if you have 1024 decals present.)
- Fixed: Wads added with pullin were loaded immediately after the IWAD.
  Exec files are now processed immediately before -file but after autoloading
  wads in D_DoomMain().
- Fixed: sdl/i_system.h unconditionally defined SHARE_DIR, preventing
  redefinition from the command line.
- Fixed: The standard way to include SDL.h is <SDL.h>, not <SDL/SDL.h>.
- Fixed: Returned FActiveInterpolation::HashKey()'s return type to size_t,
  avoiding a pointer truncation warning.


SVN r30 (trunk)
2006-04-12 01:50:09 +00:00

685 lines
15 KiB
C++

#include "i_musicinterns.h"
#include "c_cvars.h"
#include "cmdlib.h"
#if !defined(_WIN32) && 0
// Under Linux, buffer output from Timidity to try to avoid "bubbles"
// in the sound output.
class FPipeBuffer
{
public:
FPipeBuffer::FPipeBuffer (int fragSize, int nFrags, int pipe);
~FPipeBuffer ();
int ReadFrag (BYTE *Buf);
private:
int PipeHandle;
int FragSize;
int BuffSize;
int WritePos, ReadPos;
BYTE *Buffer;
bool GotFull;
SDL_mutex *BufferMutex;
SDL_thread *Reader;
static int ThreadProc (void *data);
};
#endif
#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <wordexp.h>
int ChildQuit;
void ChildSigHandler (int signum)
{
ChildQuit = waitpid (-1, NULL, WNOHANG);
}
#endif
#ifdef _WIN32
const char TimiditySong::EventName[] = "TiMidity Killer";
CVAR (String, timidity_exe, "timidity.exe", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
#else
CVAR (String, timidity_exe, "timidity", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
#endif
CVAR (String, timidity_extargs, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // extra args to pass to Timidity
CVAR (String, timidity_chorus, "0", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (String, timidity_reverb, "0", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, timidity_stereo, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, timidity_8bit, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, timidity_byteswap, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CUSTOM_CVAR (Int, timidity_pipe, 60, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{ // pipe size in ms
if (timidity_pipe < 0)
{ // a negative size makes no sense
timidity_pipe = 0;
}
}
CUSTOM_CVAR (Int, timidity_frequency, 22050, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{ // Clamp frequency to Timidity's limits
if (self < 4000)
self = 4000;
else if (self > 65000)
self = 65000;
}
void TimiditySong::Play (bool looping)
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (LaunchTimidity ())
{
if (m_Stream != NULL)
{
if (m_Stream->Play (snd_musicvolume))
{
m_Status = STATE_Playing;
}
}
else
{ // Assume success if not mixing with FMOD
m_Status = STATE_Playing;
}
}
}
void TimiditySong::Stop ()
{
if (m_Status != STATE_Stopped)
{
if (m_Stream != NULL)
{
m_Stream->Stop ();
}
#ifdef _WIN32
if (ChildProcess != INVALID_HANDLE_VALUE)
{
SetEvent (KillerEvent);
if (WaitForSingleObject (ChildProcess, 500) != WAIT_OBJECT_0)
{
TerminateProcess (ChildProcess, 666);
}
CloseHandle (ChildProcess);
ChildProcess = INVALID_HANDLE_VALUE;
}
#else
if (ChildProcess != -1)
{
if (kill (ChildProcess, SIGTERM) != 0)
{
kill (ChildProcess, SIGKILL);
}
waitpid (ChildProcess, NULL, 0);
ChildProcess = -1;
}
#endif
}
m_Status = STATE_Stopped;
}
TimiditySong::~TimiditySong ()
{
Stop ();
#if _WIN32
if (WriteWavePipe != INVALID_HANDLE_VALUE)
{
CloseHandle (WriteWavePipe);
WriteWavePipe = INVALID_HANDLE_VALUE;
}
if (ReadWavePipe != INVALID_HANDLE_VALUE)
{
CloseHandle (ReadWavePipe);
ReadWavePipe = INVALID_HANDLE_VALUE;
}
if (KillerEvent != INVALID_HANDLE_VALUE)
{
CloseHandle (KillerEvent);
KillerEvent = INVALID_HANDLE_VALUE;
}
#else
if (WavePipe[1] != -1)
{
close (WavePipe[1]);
WavePipe[1] = -1;
}
if (WavePipe[0] != -1)
{
close (WavePipe[0]);
WavePipe[0] = -1;
}
#endif
}
TimiditySong::TimiditySong (FILE *file, int len)
: DiskName ("zmid"),
#ifdef _WIN32
ReadWavePipe (INVALID_HANDLE_VALUE), WriteWavePipe (INVALID_HANDLE_VALUE),
KillerEvent (INVALID_HANDLE_VALUE),
ChildProcess (INVALID_HANDLE_VALUE),
Validated (false)
#else
ChildProcess (-1)
#endif
{
bool success;
FILE *f;
#ifndef _WIN32
WavePipe[0] = WavePipe[1] = -1;
#endif
if (DiskName == NULL)
{
Printf (PRINT_BOLD, "Could not create temp music file\n");
return;
}
f = fopen (DiskName, "wb");
if (f == NULL)
{
Printf (PRINT_BOLD, "Could not open temp music file\n");
return;
}
BYTE *buf = new BYTE[len];
fread (buf, 1, len, file);
// The file type has already been checked before this class instance was
// created, so we only need to check one character to determine if this
// is a MUS or MIDI file and write it to disk as appropriate.
if (buf[1] == 'T')
{
success = (fwrite (buf, 1, len, f) == (size_t)len);
}
else
{
success = ProduceMIDI (buf, f);
}
fclose (f);
delete[] buf;
if (success)
{
PrepTimidity ();
}
else
{
Printf (PRINT_BOLD, "Could not write temp music file\n");
}
}
void TimiditySong::PrepTimidity ()
{
int pipeSize;
#ifdef _WIN32
static SECURITY_ATTRIBUTES inheritable = { sizeof(inheritable), NULL, TRUE };
if (!Validated && !ValidateTimidity ())
{
return;
}
Validated = true;
KillerEvent = CreateEvent (NULL, FALSE, FALSE, EventName);
if (KillerEvent == INVALID_HANDLE_VALUE)
{
Printf (PRINT_BOLD, "Could not create TiMidity++ kill event.\n");
return;
}
#endif // WIN32
CommandLine.Format ("%s %s -EFchorus=%s -EFreverb=%s -s%d ",
*timidity_exe, *timidity_extargs,
*timidity_chorus, *timidity_reverb, *timidity_frequency);
pipeSize = (timidity_pipe * timidity_frequency / 1000)
<< (timidity_stereo + !timidity_8bit);
if (pipeSize != 0)
{
#ifdef _WIN32
// Round pipe size up to nearest power of 2 to try and avoid partial
// buffer reads in FillStream() under NT. This does not seem to be an
// issue under 9x.
int bitmask = pipeSize & -pipeSize;
while (bitmask < pipeSize)
bitmask <<= 1;
pipeSize = bitmask;
if (!CreatePipe (&ReadWavePipe, &WriteWavePipe, &inheritable, pipeSize))
#else // WIN32
if (pipe (WavePipe) == -1)
#endif
{
Printf (PRINT_BOLD, "Could not create a data pipe for TiMidity++.\n");
pipeSize = 0;
}
else
{
m_Stream = GSnd->CreateStream (FillStream, pipeSize,
(timidity_stereo ? 0 : SoundStream::Mono) |
(timidity_8bit ? SoundStream::Bits8 : 0),
timidity_frequency, this);
if (m_Stream == NULL)
{
Printf (PRINT_BOLD, "Could not create music stream.\n");
pipeSize = 0;
#ifdef _WIN32
CloseHandle (WriteWavePipe);
CloseHandle (ReadWavePipe);
ReadWavePipe = WriteWavePipe = INVALID_HANDLE_VALUE;
#else
close (WavePipe[1]);
close (WavePipe[0]);
WavePipe[0] = WavePipe[1] = -1;
#endif
}
}
if (pipeSize == 0)
{
Printf (PRINT_BOLD, "If your soundcard cannot play more than one\n"
"wave at a time, you will hear no music.\n");
}
else
{
CommandLine += "-o - -Ors";
}
}
if (pipeSize == 0)
{
CommandLine += "-Od";
}
CommandLine += timidity_stereo ? 'S' : 'M';
CommandLine += timidity_8bit ? '8' : '1';
if (timidity_byteswap)
{
CommandLine += 'x';
}
LoopPos = CommandLine.Len() + 4;
CommandLine += " -idl ";
CommandLine += DiskName.GetName();
}
#ifdef _WIN32
// Check that this TiMidity++ knows about the TiMidity Killer event.
// If not, then we can't use it, because Win32 provides no other way
// to conveniently signal it to quit. The check is done by simply
// searching for the event's name somewhere in the executable.
bool TimiditySong::ValidateTimidity ()
{
char foundPath[MAX_PATH];
char *filePart;
DWORD pathLen;
DWORD fileLen;
HANDLE diskFile;
HANDLE mapping;
const BYTE *exeBase;
const BYTE *exeEnd;
const BYTE *exe;
bool good;
pathLen = SearchPath (NULL, timidity_exe, NULL, MAX_PATH, foundPath, &filePart);
if (pathLen == 0)
{
Printf (PRINT_BOLD, "Please set the timidity_exe cvar to the location of TiMidity++\n");
return false;
}
if (pathLen > MAX_PATH)
{
Printf (PRINT_BOLD, "The path to TiMidity++ is too long\n");
return false;
}
diskFile = CreateFile (foundPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (diskFile == INVALID_HANDLE_VALUE)
{
Printf (PRINT_BOLD, "Could not access %s\n", foundPath);
return false;
}
fileLen = GetFileSize (diskFile, NULL);
mapping = CreateFileMapping (diskFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (mapping == NULL)
{
Printf (PRINT_BOLD, "Could not create mapping for %s\n", foundPath);
CloseHandle (diskFile);
return false;
}
exeBase = (const BYTE *)MapViewOfFile (mapping, FILE_MAP_READ, 0, 0, 0);
if (exeBase == NULL)
{
Printf (PRINT_BOLD, "Could not map %s\n", foundPath);
CloseHandle (mapping);
CloseHandle (diskFile);
return false;
}
good = false;
try
{
for (exe = exeBase, exeEnd = exeBase+fileLen; exe < exeEnd; )
{
const char *tSpot = (const char *)memchr (exe, 'T', exeEnd - exe);
if (tSpot == NULL)
{
break;
}
if (memcmp (tSpot+1, EventName+1, sizeof(EventName)-1) == 0)
{
good = true;
break;
}
exe = (const BYTE *)tSpot + 1;
}
}
catch (...)
{
Printf (PRINT_BOLD, "Error reading %s\n", foundPath);
}
if (!good)
{
Printf (PRINT_BOLD, "ZDoom requires a special version of TiMidity++\n");
}
UnmapViewOfFile ((LPVOID)exeBase);
CloseHandle (mapping);
CloseHandle (diskFile);
return good;
}
#endif // _WIN32
bool TimiditySong::LaunchTimidity ()
{
if (CommandLine.Len() == 0)
{
return false;
}
// Tell Timidity whether it should loop or not
CommandLine[LoopPos] = m_Looping ? 'l' : ' ';
DPrintf ("cmd: \x1cG%s\n", CommandLine.GetChars());
#ifdef _WIN32
STARTUPINFO startup = { sizeof(startup), };
PROCESS_INFORMATION procInfo;
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup.hStdInput = INVALID_HANDLE_VALUE;
startup.hStdOutput = WriteWavePipe != INVALID_HANDLE_VALUE ?
WriteWavePipe : GetStdHandle (STD_OUTPUT_HANDLE);
startup.hStdError = GetStdHandle (STD_ERROR_HANDLE);
startup.lpTitle = "TiMidity (ZDoom Launched)";
startup.wShowWindow = SW_SHOWMINNOACTIVE;
if (CreateProcess (NULL, CommandLine.GetChars(), NULL, NULL, TRUE,
/*HIGH_PRIORITY_CLASS|*/DETACHED_PROCESS, NULL, NULL, &startup, &procInfo))
{
ChildProcess = procInfo.hProcess;
//SetThreadPriority (procInfo.hThread, THREAD_PRIORITY_HIGHEST);
CloseHandle (procInfo.hThread); // Don't care about the created thread
return true;
}
char hres[9];
LPTSTR msgBuf;
HRESULT err = GetLastError ();
if (!FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, err, 0, (LPTSTR)&msgBuf, 0, NULL))
{
sprintf (hres, "%08lx", err);
msgBuf = hres;
}
Printf (PRINT_BOLD, "Could not run timidity with the command line:\n%s\n"
"Reason: %s\n", CommandLine.GetChars(), msgBuf);
if (msgBuf != hres)
{
LocalFree (msgBuf);
}
return false;
#else
if (WavePipe[0] != -1 && WavePipe[1] == -1 && m_Stream != NULL)
{
// Timidity was previously launched, so the write end of the pipe
// is closed, and the read end is still open. Close the pipe
// completely and reopen it.
close (WavePipe[0]);
WavePipe[0] = -1;
delete m_Stream;
m_Stream = NULL;
PrepTimidity ();
}
int forkres;
wordexp_t words;
switch (wordexp (CommandLine.GetChars(), &words, 0))
{
case 0: // all good
break;
case WRDE_NOSPACE:
wordfree (&words);
default:
return false;
}
forkres = fork ();
if (forkres == 0)
{
close (WavePipe[0]);
dup2 (WavePipe[1], STDOUT_FILENO);
freopen ("/dev/null", "r", stdin);
freopen ("/dev/null", "w", stderr);
close (WavePipe[1]);
printf ("exec %s\n", words.we_wordv[0]);
execvp (words.we_wordv[0], words.we_wordv);
exit (0); // if execvp succeeds, we never get here
}
else if (forkres < 0)
{
Printf (PRINT_BOLD, "Could not fork when trying to start timidity\n");
}
else
{
// printf ("child is %d\n", forkres);
ChildProcess = forkres;
close (WavePipe[1]);
WavePipe[1] = -1;
}
wordfree (&words);
return ChildProcess != -1;
#endif // _WIN32
}
bool TimiditySong::FillStream (SoundStream *stream, void *buff, int len, void *userdata)
{
TimiditySong *song = (TimiditySong *)userdata;
#ifdef _WIN32
DWORD avail, got, didget;
if (!PeekNamedPipe (song->ReadWavePipe, NULL, 0, NULL, &avail, NULL) || avail == 0)
{ // If nothing is available from the pipe, play silence.
memset (buff, 0, len);
}
else
{
didget = 0;
for (;;)
{
ReadFile (song->ReadWavePipe, (BYTE *)buff+didget, len-didget, &got, NULL);
didget += got;
if (didget >= (DWORD)len)
break;
// Give TiMidity a chance to output something more to the pipe
Sleep (10);
if (!PeekNamedPipe (song->ReadWavePipe, NULL, 0, NULL, &avail, NULL) || avail == 0)
{
memset ((BYTE *)buff+didget, 0, len-didget);
break;
}
}
}
#else
ssize_t got;
got = read (song->WavePipe[0], (BYTE *)buff, len);
if (got < len)
{
memset ((BYTE *)buff+got, 0, len-got);
}
if (ChildQuit == song->ChildProcess)
{
ChildQuit = 0;
// printf ("child gone\n");
song->ChildProcess = -1;
return false;
}
#endif
return true;
}
bool TimiditySong::IsPlaying ()
{
#ifdef _WIN32
if (ChildProcess != INVALID_HANDLE_VALUE)
{
if (WaitForSingleObject (ChildProcess, 0) != WAIT_TIMEOUT)
{ // Timidity has quit
CloseHandle (ChildProcess);
ChildProcess = INVALID_HANDLE_VALUE;
#else
if (ChildProcess != -1)
{
if (waitpid (ChildProcess, NULL, WNOHANG) == ChildProcess)
{
ChildProcess = -1;
#endif
if (m_Looping)
{
if (!LaunchTimidity ())
{
Stop ();
return false;
}
return true;
}
return false;
}
return true;
}
return false;
}
#if !defined(_WIN32) && 0
FPipeBuffer::FPipeBuffer (int fragSize, int nFrags, int pipe)
: PipeHandle (pipe),
FragSize (fragSize),
BuffSize (fragSize * nFrags),
WritePos (0), ReadPos (0), GotFull (false),
Reader (0), PleaseExit (0)
{
Buffer = new BYTE[BuffSize];
if (Buffer != NULL)
{
BufferMutex = SDL_CreateMutex ();
if (BufferMutex == NULL)
{
Reader = SDL_CreateThread (ThreadProc, (void *)this);
}
}
}
FPipeBuffer::~FPipeBuffer ()
{
if (Reader != NULL)
{ // I like the Win32 IPC facilities better than SDL's
// Fortunately, this is a simple thread, so I can cheat
// like this.
SDL_KillThread (ThreadProc);
}
if (BufferMutex != NULL)
{
SDL_DestroyMutex (BufferMutex);
}
if (Buffer != NULL)
{
delete[] Buffer;
}
}
int FPipeBuffer::ReadFrag (BYTE *buf)
{
int startavvail;
int avail;
int pos;
int opos;
if (SDL_mutexP (BufferMutex) == -1)
return 0;
if (WritePos > ReadPos)
{
avail = WritePos - ReadPos;
}
else
{
avail = BuffSize - ReadPos + WritePos;
}
if (avail > FragSize)
avail = FragSize;
startavail = avali;
pos = ReadPos;
opos = 0;
while (avail != 0)
{
int thistime;
thistime = (pos + avail > BuffSize) ? BuffSize - pos : avail;
memcpy (buf + opos, Buffer + pos, thistime);
if (thistime != avail)
{
pos = 0;
avail -= thistime;
}
opos += thistime;
}
ReadPos = pos;
SDL_mutexV (BufferMutex);
return startavail;
}
#endif // !_WIN32