mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-12-16 23:31:10 +00:00
8d0c48bf81
- Removed movie volume from the sound menu and renamed some of the other options to give the MIDI device name more room to display itself. - Moved the midi device selection into the main sound menu. - Added FMOD as MIDI device -1, to replace the MIDI mapper. This is still the default device. By default, it uses exactly the same DLS instruments as the Microsoft GS Wavetable Synth. If you have another set DLS level 1 patch set you want to use, set the snd_midipatchfile cvar to specify where it should load the instruments from. - Changed the ProduceMIDI function to store its output into a TArray<BYTE>. An overloaded version wraps around it to continue to supply file-writing support for external Timidity++ usage. - Added an FMOD credits banner to comply with their non-commercial license. - Reimplemented the snd_buffersize cvar for the FMOD Ex sound system. Rather than a time in ms, this is now the length in samples of the DSP buffer. Also added the snd_buffercount cvar to offer complete control over the call to FMOD::System::setDSPBufferSize(). Note that with any snd_samplerate below about 44kHz, you will need to set snd_buffersize to avoid long latencies. - Reimplemented the snd_output cvar for the FMOD Ex sound system. - Changed snd_samplerate default to 0. This now means to use the default sample rate. - Made snd_output, snd_output_format, snd_speakermode, snd_resampler, and snd_hrtf available through the menu. - Split the HRTF effect selection into its own cvar: snd_hrtf. - Removed 96000 Hz option from the menu. It's still available through the cvar, if desired. - Fixed: If Windows sound init failed, retry with DirectSound. (Apparently, WASAPI doesn't work with more than two speakers and PCM-Float output at the same time.) - Fixed: Area sounds only played from the front speakers once you got within the 2D panning area. SVN r854 (trunk)
542 lines
11 KiB
C++
542 lines
11 KiB
C++
/*
|
|
** i_music.cpp
|
|
** Plays music
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2006 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.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#else
|
|
#include <SDL.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <wordexp.h>
|
|
#include <stdio.h>
|
|
#include "mus2midi.h"
|
|
#define FALSE 0
|
|
#define TRUE 1
|
|
extern void ChildSigHandler (int signum);
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
#include "i_musicinterns.h"
|
|
#include "doomtype.h"
|
|
#include "m_argv.h"
|
|
#include "i_music.h"
|
|
#include "w_wad.h"
|
|
#include "c_console.h"
|
|
#include "c_dispatch.h"
|
|
#include "i_system.h"
|
|
#include "i_sound.h"
|
|
#include "m_swap.h"
|
|
#include "i_cd.h"
|
|
#include "tempfiles.h"
|
|
#include "templates.h"
|
|
#include "stats.h"
|
|
|
|
#include <fmod.h>
|
|
|
|
EXTERN_CVAR (Int, snd_samplerate)
|
|
EXTERN_CVAR (Int, snd_mididevice)
|
|
|
|
static bool MusicDown = true;
|
|
|
|
MusInfo *currSong;
|
|
int nomusic = 0;
|
|
float relative_volume = 1.f;
|
|
float saved_relative_volume = 1.0f; // this could be used to implement an ACS FadeMusic function
|
|
|
|
//==========================================================================
|
|
//
|
|
// CVAR snd_musicvolume
|
|
//
|
|
// Maximum volume of MOD/stream music.
|
|
//==========================================================================
|
|
|
|
CUSTOM_CVAR (Float, snd_musicvolume, 0.3f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
{
|
|
if (self < 0.f)
|
|
self = 0.f;
|
|
else if (self > 1.f)
|
|
self = 1.f;
|
|
else if (GSnd != NULL)
|
|
{
|
|
// Set general music volume.
|
|
GSnd->SetMusicVolume(clamp<float>(self * relative_volume, 0, 1));
|
|
|
|
// For music not implemented through the digital sound system,
|
|
// let them know about the changed.
|
|
if (currSong != NULL)
|
|
{
|
|
currSong->MusicVolumeChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
MusInfo::~MusInfo ()
|
|
{
|
|
}
|
|
|
|
bool MusInfo::SetPosition (int order)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void MusInfo::Update ()
|
|
{
|
|
}
|
|
|
|
void MusInfo::MusicVolumeChanged()
|
|
{
|
|
}
|
|
|
|
void MusInfo::TimidityVolumeChanged()
|
|
{
|
|
}
|
|
|
|
FString MusInfo::GetStats()
|
|
{
|
|
return "No stats available for this song";
|
|
}
|
|
|
|
void I_InitMusic (void)
|
|
{
|
|
static bool setatterm = false;
|
|
|
|
snd_musicvolume.Callback ();
|
|
|
|
nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound");
|
|
|
|
#ifdef _WIN32
|
|
I_InitMusicWin32 ();
|
|
#endif // _WIN32
|
|
|
|
if (!setatterm)
|
|
{
|
|
setatterm = true;
|
|
atterm (I_ShutdownMusic);
|
|
|
|
#ifndef _WIN32
|
|
signal (SIGCHLD, ChildSigHandler);
|
|
#endif
|
|
}
|
|
MusicDown = false;
|
|
}
|
|
|
|
|
|
void I_ShutdownMusic(void)
|
|
{
|
|
if (MusicDown)
|
|
return;
|
|
MusicDown = true;
|
|
if (currSong)
|
|
{
|
|
S_StopMusic (true);
|
|
assert (currSong == NULL);
|
|
}
|
|
#ifdef _WIN32
|
|
I_ShutdownMusicWin32 ();
|
|
#endif // _WIN32
|
|
}
|
|
|
|
|
|
void I_PlaySong (void *handle, int _looping, float rel_vol)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
|
|
if (!info || nomusic)
|
|
return;
|
|
|
|
saved_relative_volume = relative_volume = rel_vol;
|
|
info->Stop ();
|
|
info->Play (_looping ? true : false);
|
|
|
|
if (info->m_Status == MusInfo::STATE_Playing)
|
|
currSong = info;
|
|
else
|
|
currSong = NULL;
|
|
}
|
|
|
|
|
|
void I_PauseSong (void *handle)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
|
|
if (info)
|
|
info->Pause ();
|
|
}
|
|
|
|
void I_ResumeSong (void *handle)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
|
|
if (info)
|
|
info->Resume ();
|
|
}
|
|
|
|
void I_StopSong (void *handle)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
|
|
if (info)
|
|
info->Stop ();
|
|
|
|
if (info == currSong)
|
|
currSong = NULL;
|
|
}
|
|
|
|
void I_UnRegisterSong (void *handle)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
|
|
if (info)
|
|
{
|
|
delete info;
|
|
}
|
|
}
|
|
|
|
void *I_RegisterSong (const char *filename, char *musiccache, int offset, int len, int device)
|
|
{
|
|
FILE *file;
|
|
MusInfo *info = NULL;
|
|
DWORD id;
|
|
|
|
if (nomusic)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (offset != -1)
|
|
{
|
|
file = fopen (filename, "rb");
|
|
if (file == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (len == 0 && offset == 0)
|
|
{
|
|
fseek (file, 0, SEEK_END);
|
|
len = ftell (file);
|
|
fseek (file, 0, SEEK_SET);
|
|
}
|
|
else
|
|
{
|
|
fseek (file, offset, SEEK_SET);
|
|
}
|
|
|
|
if (fread (&id, 4, 1, file) != 1)
|
|
{
|
|
fclose (file);
|
|
return 0;
|
|
}
|
|
fseek (file, -4, SEEK_CUR);
|
|
}
|
|
else
|
|
{
|
|
file = NULL;
|
|
memcpy(&id, &musiccache[0], 4);
|
|
}
|
|
|
|
// Check for MUS format
|
|
if (id == MAKE_ID('M','U','S',0x1a))
|
|
{
|
|
if (GSnd != NULL && device != 0 && device != 1 && (opl_enable || device == 2) )
|
|
{
|
|
info = new OPLMUSSong (file, musiccache, len);
|
|
}
|
|
if (info == NULL)
|
|
{
|
|
#ifdef _WIN32
|
|
if (device == 1 && GSnd != NULL)
|
|
{
|
|
info = new TimiditySong (file, musiccache, len);
|
|
if (!info->IsValid())
|
|
{
|
|
delete info;
|
|
info = NULL;
|
|
}
|
|
}
|
|
if (info == NULL && (snd_mididevice >= 0 || device == 0))
|
|
{
|
|
info = new MUSSong2 (file, musiccache, len);
|
|
}
|
|
else if (info == NULL && GSnd != NULL)
|
|
#endif // _WIN32
|
|
{
|
|
if (snd_mididevice == -1)
|
|
{
|
|
TArray<BYTE> midi;
|
|
bool midi_made = false;
|
|
|
|
if (file == NULL)
|
|
{
|
|
midi_made = ProduceMIDI((BYTE *)musiccache, midi);
|
|
}
|
|
else
|
|
{
|
|
BYTE *mus = new BYTE[len];
|
|
size_t did_read = fread(mus, 1, len, file);
|
|
if (did_read == len)
|
|
{
|
|
midi_made = ProduceMIDI(mus, midi);
|
|
}
|
|
fseek(file, -(long)did_read, SEEK_CUR);
|
|
delete[] mus;
|
|
}
|
|
if (midi_made)
|
|
{
|
|
FILE *f = fopen("latest.mid", "wb");
|
|
fwrite(&midi[0], 1, midi.Size(), f);
|
|
fclose(f);
|
|
info = new StreamSong((char *)&midi[0], -1, midi.Size());
|
|
if (!info->IsValid())
|
|
{
|
|
delete info;
|
|
info = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (info == NULL)
|
|
{
|
|
info = new TimiditySong (file, musiccache, len);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check for MIDI format
|
|
else if (id == MAKE_ID('M','T','h','d'))
|
|
{
|
|
// This is a midi file
|
|
#ifdef _WIN32
|
|
if (device == 1 && GSnd != NULL)
|
|
{
|
|
info = new TimiditySong (file, musiccache, len);
|
|
if (!info->IsValid())
|
|
{
|
|
delete info;
|
|
info = NULL;
|
|
}
|
|
}
|
|
else if (info == NULL && (snd_mididevice >= 0 || device == 0))
|
|
{
|
|
info = new MIDISong2 (file, musiccache, len);
|
|
}
|
|
else if (info == NULL && GSnd != NULL)
|
|
#endif // _WIN32
|
|
{
|
|
if (snd_mididevice != -1)
|
|
{
|
|
info = new TimiditySong (file, musiccache, len);
|
|
}
|
|
}
|
|
}
|
|
// Check for RDosPlay raw OPL format
|
|
else if (id == MAKE_ID('R','A','W','A') && len >= 12)
|
|
{
|
|
DWORD fullsig[2];
|
|
|
|
if (file != NULL)
|
|
{
|
|
if (fread (fullsig, 4, 2, file) != 2)
|
|
{
|
|
fclose (file);
|
|
return 0;
|
|
}
|
|
fseek (file, -8, SEEK_CUR);
|
|
}
|
|
else
|
|
{
|
|
memcpy(fullsig, musiccache, 8);
|
|
}
|
|
|
|
if (fullsig[1] == MAKE_ID('D','A','T','A'))
|
|
{
|
|
info = new OPLMUSSong (file, musiccache, len);
|
|
}
|
|
}
|
|
// Check for Martin Fernandez's modified IMF format
|
|
else if (id == MAKE_ID('A','D','L','I'))
|
|
{
|
|
char fullhead[6];
|
|
|
|
if (file != NULL)
|
|
{
|
|
if (fread (fullhead, 1, 6, file) != 6)
|
|
{
|
|
fclose (file);
|
|
return 0;
|
|
}
|
|
fseek (file, -6, SEEK_CUR);
|
|
}
|
|
else
|
|
{
|
|
memcpy(fullhead, musiccache, 6);
|
|
}
|
|
if (fullhead[4] == 'B' && fullhead[5] == 1)
|
|
{
|
|
info = new OPLMUSSong (file, musiccache, len);
|
|
}
|
|
}
|
|
|
|
if (info == NULL)
|
|
{
|
|
// Check for CDDA "format"
|
|
if (id == (('R')|(('I')<<8)|(('F')<<16)|(('F')<<24)))
|
|
{
|
|
if (file != NULL)
|
|
{
|
|
DWORD subid;
|
|
|
|
fseek (file, 8, SEEK_CUR);
|
|
if (fread (&subid, 4, 1, file) != 1)
|
|
{
|
|
fclose (file);
|
|
return 0;
|
|
}
|
|
fseek (file, -12, SEEK_CUR);
|
|
|
|
if (subid == (('C')|(('D')<<8)|(('D')<<16)|(('A')<<24)))
|
|
{
|
|
// This is a CDDA file
|
|
info = new CDDAFile (file, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
// no FMOD => no modules/streams
|
|
// 1024 bytes is an arbitrary restriction. It's assumed that anything
|
|
// smaller than this can't possibly be a valid music file if it hasn't
|
|
// been identified already, so don't even bother trying to load it.
|
|
if (info == NULL && GSnd != NULL && len >= 1024)
|
|
{
|
|
// Let FMOD figure out what it is.
|
|
if (file != NULL)
|
|
{
|
|
fclose (file);
|
|
file = NULL;
|
|
}
|
|
info = new StreamSong (offset >=0 ? filename : musiccache, offset, len);
|
|
}
|
|
}
|
|
|
|
if (info && !info->IsValid ())
|
|
{
|
|
delete info;
|
|
info = NULL;
|
|
}
|
|
|
|
if (file != NULL)
|
|
{
|
|
fclose (file);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
void *I_RegisterCDSong (int track, int id)
|
|
{
|
|
MusInfo *info = new CDSong (track, id);
|
|
|
|
if (info && !info->IsValid ())
|
|
{
|
|
delete info;
|
|
info = NULL;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
void I_UpdateMusic()
|
|
{
|
|
if (currSong != NULL)
|
|
{
|
|
currSong->Update();
|
|
}
|
|
}
|
|
|
|
// Is the song playing?
|
|
bool I_QrySongPlaying (void *handle)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
|
|
return info ? info->IsPlaying () : false;
|
|
}
|
|
|
|
// Change to a different part of the song
|
|
bool I_SetSongPosition (void *handle, int order)
|
|
{
|
|
MusInfo *info = (MusInfo *)handle;
|
|
return info ? info->SetPosition (order) : false;
|
|
}
|
|
|
|
// Sets relative music volume. Takes $musicvolume in SNDINFO into consideration
|
|
void I_SetMusicVolume (float factor)
|
|
{
|
|
factor = clamp<float>(factor, 0, 2.0f);
|
|
relative_volume = saved_relative_volume * factor;
|
|
snd_musicvolume.Callback();
|
|
}
|
|
|
|
CCMD(testmusicvol)
|
|
{
|
|
if (argv.argc() > 1)
|
|
{
|
|
relative_volume = (float)strtod(argv[1], NULL);
|
|
snd_musicvolume.Callback();
|
|
}
|
|
else
|
|
Printf("Current relative volume is %1.2f\n", relative_volume);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// STAT music
|
|
//
|
|
//==========================================================================
|
|
|
|
ADD_STAT(music)
|
|
{
|
|
if (currSong != NULL)
|
|
{
|
|
return currSong->GetStats();
|
|
}
|
|
return "No song playing";
|
|
}
|