mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-04 13:42:55 +01:00
Step 1 on road towards Linux. Remove windows-specific code, stub it out where needed and make sure we can still compile it the way it is. Take care of Travis' build matrix to include new build configuration. Install new packages.
914 lines
22 KiB
C++
914 lines
22 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014 Ted John
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
*
|
|
* This file is part of OpenRCT2.
|
|
*
|
|
* OpenRCT2 is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*****************************************************************************/
|
|
|
|
#ifdef _WIN32
|
|
#include <dsound.h>
|
|
#endif // _WIN32
|
|
|
|
extern "C" {
|
|
#include "../config.h"
|
|
#include "../platform/platform.h"
|
|
#include "../localisation/localisation.h"
|
|
#include "audio.h"
|
|
}
|
|
#include "mixer.h"
|
|
|
|
Mixer gMixer;
|
|
|
|
Source::~Source()
|
|
{
|
|
|
|
}
|
|
|
|
unsigned long Source::GetSome(unsigned long offset, const uint8** data, unsigned long length)
|
|
{
|
|
if (offset >= Length()) {
|
|
return 0;
|
|
}
|
|
unsigned long size = length;
|
|
if (offset + length > Length()) {
|
|
size = Length() - offset;
|
|
}
|
|
return Read(offset, data, size);
|
|
}
|
|
|
|
unsigned long Source::Length()
|
|
{
|
|
return length;
|
|
}
|
|
|
|
const AudioFormat& Source::Format()
|
|
{
|
|
return format;
|
|
}
|
|
|
|
Source_Null::Source_Null()
|
|
{
|
|
length = 0;
|
|
}
|
|
|
|
unsigned long Source_Null::Read(unsigned long offset, const uint8** data, unsigned long length)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Source_Sample::Source_Sample()
|
|
{
|
|
data = 0;
|
|
length = 0;
|
|
issdlwav = false;
|
|
}
|
|
|
|
Source_Sample::~Source_Sample()
|
|
{
|
|
Unload();
|
|
}
|
|
|
|
unsigned long Source_Sample::Read(unsigned long offset, const uint8** data, unsigned long length)
|
|
{
|
|
*data = &Source_Sample::data[offset];
|
|
return length;
|
|
}
|
|
|
|
bool Source_Sample::LoadWAV(const char* filename)
|
|
{
|
|
log_verbose("Source_Sample::LoadWAV(%s)", filename);
|
|
|
|
Unload();
|
|
SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
|
|
if (rw == NULL) {
|
|
log_verbose("Error loading %s", filename);
|
|
return false;
|
|
}
|
|
|
|
SDL_AudioSpec audiospec;
|
|
memset(&audiospec, 0, sizeof(audiospec));
|
|
SDL_AudioSpec* spec = SDL_LoadWAV_RW(rw, false, &audiospec, &data, (Uint32*)&length);
|
|
SDL_RWclose(rw);
|
|
|
|
if (spec != NULL) {
|
|
format.freq = spec->freq;
|
|
#ifdef _WIN32
|
|
format.format = spec->format;
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
format.channels = spec->channels;
|
|
issdlwav = true;
|
|
} else {
|
|
log_verbose("Error loading %s, unsupported WAV format", filename);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Source_Sample::LoadCSS1(const char *filename, unsigned int offset)
|
|
{
|
|
log_verbose("Source_Sample::LoadCSS1(%s, %d)", filename, offset);
|
|
|
|
Unload();
|
|
SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
|
|
if (rw == NULL) {
|
|
log_verbose("Unable to load %s", filename);
|
|
return false;
|
|
}
|
|
|
|
Uint32 numsounds;
|
|
SDL_RWread(rw, &numsounds, sizeof(numsounds), 1);
|
|
if (offset > numsounds) {
|
|
SDL_RWclose(rw);
|
|
return false;
|
|
}
|
|
SDL_RWseek(rw, offset * 4, RW_SEEK_CUR);
|
|
Uint32 soundoffset;
|
|
SDL_RWread(rw, &soundoffset, sizeof(soundoffset), 1);
|
|
SDL_RWseek(rw, soundoffset, RW_SEEK_SET);
|
|
Uint32 soundsize;
|
|
SDL_RWread(rw, &soundsize, sizeof(soundsize), 1);
|
|
length = soundsize;
|
|
#ifdef _WIN32
|
|
WAVEFORMATEX waveformat;
|
|
SDL_RWread(rw, &waveformat, sizeof(waveformat), 1);
|
|
format.freq = waveformat.nSamplesPerSec;
|
|
format.format = AUDIO_S16LSB;
|
|
format.channels = waveformat.nChannels;
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
data = new (std::nothrow) uint8[length];
|
|
if (!data) {
|
|
log_verbose("Unable to allocate data");
|
|
SDL_RWclose(rw);
|
|
return false;
|
|
}
|
|
SDL_RWread(rw, data, length, 1);
|
|
SDL_RWclose(rw);
|
|
return true;
|
|
}
|
|
|
|
void Source_Sample::Unload()
|
|
{
|
|
if (data) {
|
|
if (issdlwav) {
|
|
SDL_FreeWAV(data);
|
|
} else {
|
|
delete[] data;
|
|
}
|
|
data = 0;
|
|
}
|
|
issdlwav = false;
|
|
length = 0;
|
|
}
|
|
|
|
bool Source_Sample::Convert(AudioFormat format)
|
|
{
|
|
#ifdef _WIN32
|
|
if(Source_Sample::format.format != format.format || Source_Sample::format.channels != format.channels || Source_Sample::format.freq != format.freq){
|
|
SDL_AudioCVT cvt;
|
|
if (SDL_BuildAudioCVT(&cvt, Source_Sample::format.format, Source_Sample::format.channels, Source_Sample::format.freq, format.format, format.channels, format.freq) < 0) {
|
|
return false;
|
|
}
|
|
cvt.len = length;
|
|
cvt.buf = (Uint8*)new uint8[cvt.len * cvt.len_mult];
|
|
memcpy(cvt.buf, data, length);
|
|
if (SDL_ConvertAudio(&cvt) < 0) {
|
|
delete[] cvt.buf;
|
|
return false;
|
|
}
|
|
Unload();
|
|
data = cvt.buf;
|
|
length = cvt.len_cvt;
|
|
Source_Sample::format = format;
|
|
return true;
|
|
}
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
return false;
|
|
}
|
|
|
|
Source_SampleStream::Source_SampleStream()
|
|
{
|
|
length = 0;
|
|
rw = NULL;
|
|
buffer = 0;
|
|
buffersize = 0;
|
|
}
|
|
|
|
Source_SampleStream::~Source_SampleStream()
|
|
{
|
|
Unload();
|
|
}
|
|
|
|
unsigned long Source_SampleStream::Read(unsigned long offset, const uint8** data, unsigned long length)
|
|
{
|
|
if (length > buffersize) {
|
|
if (buffer) {
|
|
delete[] buffer;
|
|
}
|
|
buffer = new (std::nothrow) uint8[length];
|
|
if (!buffer) {
|
|
return 0;
|
|
}
|
|
buffersize = length;
|
|
}
|
|
Sint64 currentposition = SDL_RWtell(rw);
|
|
if (currentposition == -1) {
|
|
return 0;
|
|
}
|
|
if (currentposition - databegin != offset) {
|
|
Sint64 newposition = SDL_RWseek(rw, databegin + offset, SEEK_SET);
|
|
if (newposition == -1) {
|
|
return 0;
|
|
}
|
|
currentposition = newposition;
|
|
}
|
|
*data = buffer;
|
|
int read = SDL_RWread(rw, buffer, 1, length);
|
|
if (read == -1) {
|
|
return 0;
|
|
}
|
|
return read;
|
|
}
|
|
|
|
bool Source_SampleStream::LoadWAV(SDL_RWops* rw)
|
|
{
|
|
Unload();
|
|
if (rw == NULL) {
|
|
return false;
|
|
}
|
|
Source_SampleStream::rw = rw;
|
|
Uint32 chunk_id = SDL_ReadLE32(rw);
|
|
const Uint32 RIFF = 0x46464952;
|
|
if (chunk_id != RIFF) {
|
|
log_verbose("Not a WAV file");
|
|
return false;
|
|
}
|
|
Uint32 chunk_size = SDL_ReadLE32(rw);
|
|
Uint32 chunk_format = SDL_ReadLE32(rw);
|
|
const Uint32 WAVE = 0x45564157;
|
|
if (chunk_format != WAVE) {
|
|
log_verbose("Not in WAVE format");
|
|
return false;
|
|
}
|
|
const Uint32 FMT = 0x20746D66;
|
|
Uint32 fmtchunk_size = FindChunk(rw, FMT);
|
|
if (!fmtchunk_size) {
|
|
log_verbose("Could not find FMT chunk");
|
|
return false;
|
|
}
|
|
Uint64 chunkstart = SDL_RWtell(rw);
|
|
#ifdef _WIN32
|
|
PCMWAVEFORMAT waveformat;
|
|
SDL_RWread(rw, &waveformat, sizeof(waveformat), 1);
|
|
SDL_RWseek(rw, chunkstart + fmtchunk_size, RW_SEEK_SET);
|
|
if (waveformat.wf.wFormatTag != WAVE_FORMAT_PCM) {
|
|
log_verbose("Not in proper format");
|
|
return false;
|
|
}
|
|
format.freq = waveformat.wf.nSamplesPerSec;
|
|
switch (waveformat.wBitsPerSample) {
|
|
case 8:
|
|
format.format = AUDIO_U8;
|
|
break;
|
|
case 16:
|
|
format.format = AUDIO_S16LSB;
|
|
break;
|
|
default:
|
|
log_verbose("Invalid bits per sample");
|
|
return false;
|
|
break;
|
|
}
|
|
format.channels = waveformat.wf.nChannels;
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
const Uint32 DATA = 0x61746164;
|
|
Uint32 datachunk_size = FindChunk(rw, DATA);
|
|
if (!datachunk_size) {
|
|
log_verbose("Could not find DATA chunk");
|
|
return false;
|
|
}
|
|
length = datachunk_size;
|
|
databegin = SDL_RWtell(rw);
|
|
return true;
|
|
}
|
|
|
|
Uint32 Source_SampleStream::FindChunk(SDL_RWops* rw, Uint32 wanted_id)
|
|
{
|
|
Uint32 subchunk_id = SDL_ReadLE32(rw);
|
|
Uint32 subchunk_size = SDL_ReadLE32(rw);
|
|
if (subchunk_id == wanted_id) {
|
|
return subchunk_size;
|
|
}
|
|
const Uint32 FACT = 0x74636166;
|
|
const Uint32 LIST = 0x5453494c;
|
|
const Uint32 BEXT = 0x74786562;
|
|
const Uint32 JUNK = 0x4B4E554A;
|
|
while (subchunk_id == FACT || subchunk_id == LIST || subchunk_id == BEXT || subchunk_id == JUNK) {
|
|
SDL_RWseek(rw, subchunk_size, RW_SEEK_CUR);
|
|
subchunk_id = SDL_ReadLE32(rw);
|
|
subchunk_size = SDL_ReadLE32(rw);
|
|
if (subchunk_id == wanted_id) {
|
|
return subchunk_size;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Source_SampleStream::Unload()
|
|
{
|
|
if (rw) {
|
|
SDL_RWclose(rw);
|
|
rw = NULL;
|
|
}
|
|
length = 0;
|
|
if (buffer) {
|
|
delete[] buffer;
|
|
buffer = 0;
|
|
}
|
|
buffersize = 0;
|
|
}
|
|
|
|
Channel::Channel()
|
|
{
|
|
resampler = 0;
|
|
SetRate(1);
|
|
SetVolume(SDL_MIX_MAXVOLUME);
|
|
oldvolume = 0;
|
|
oldvolume_l = 0;
|
|
oldvolume_r = 0;
|
|
SetPan(0.5f);
|
|
done = true;
|
|
stopping = false;
|
|
source = 0;
|
|
deletesourceondone = false;
|
|
group = MIXER_GROUP_NONE;
|
|
}
|
|
|
|
Channel::~Channel()
|
|
{
|
|
if (resampler) {
|
|
speex_resampler_destroy(resampler);
|
|
resampler = 0;
|
|
}
|
|
if (deletesourceondone) {
|
|
delete source;
|
|
}
|
|
}
|
|
|
|
void Channel::Play(Source& source, int loop = MIXER_LOOP_NONE)
|
|
{
|
|
Channel::source = &source;
|
|
Channel::loop = loop;
|
|
offset = 0;
|
|
done = false;
|
|
}
|
|
|
|
void Channel::SetRate(double rate)
|
|
{
|
|
Channel::rate = rate;
|
|
if (Channel::rate < 0.001) {
|
|
Channel::rate = 0.001;
|
|
}
|
|
}
|
|
|
|
void Channel::SetVolume(int volume)
|
|
{
|
|
Channel::volume = volume;
|
|
if (volume > SDL_MIX_MAXVOLUME) {
|
|
Channel::volume = SDL_MIX_MAXVOLUME;
|
|
}
|
|
if (volume < 0) {
|
|
Channel::volume = 0;
|
|
}
|
|
}
|
|
|
|
void Channel::SetPan(float pan)
|
|
{
|
|
Channel::pan = pan;
|
|
if (pan > 1) {
|
|
Channel::pan = 1;
|
|
}
|
|
if (pan < 0) {
|
|
Channel::pan = 0;
|
|
}
|
|
double decibels = (abs(Channel::pan - 0.5) * 2.0) * 100.0;
|
|
double attenuation = pow(10, decibels / 20.0);
|
|
if (Channel::pan <= 0.5) {
|
|
volume_l = 1.0;
|
|
volume_r = float(1.0 / attenuation);
|
|
} else {
|
|
volume_r = 1.0;
|
|
volume_l = float(1.0 / attenuation);
|
|
}
|
|
}
|
|
|
|
bool Channel::IsPlaying()
|
|
{
|
|
return !done;
|
|
}
|
|
|
|
unsigned long Channel::GetOffset()
|
|
{
|
|
return offset;
|
|
}
|
|
|
|
bool Channel::SetOffset(unsigned long offset)
|
|
{
|
|
if (source && offset < source->Length()) {
|
|
int samplesize = source->Format().channels * source->Format().BytesPerSample();
|
|
Channel::offset = (offset / samplesize) * samplesize;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Channel::SetGroup(int group)
|
|
{
|
|
Channel::group = group;
|
|
}
|
|
|
|
Mixer::Mixer()
|
|
{
|
|
effectbuffer = 0;
|
|
for (int i = 0; i < countof(css1sources); i++) {
|
|
css1sources[i] = 0;
|
|
}
|
|
for (int i = 0; i < countof(musicsources); i++) {
|
|
musicsources[i] = 0;
|
|
}
|
|
}
|
|
|
|
void Mixer::Init(const char* device)
|
|
{
|
|
Close();
|
|
SDL_AudioSpec want, have;
|
|
#ifdef _WIN32
|
|
SDL_zero(want);
|
|
want.freq = 44100;
|
|
want.format = AUDIO_S16SYS;
|
|
want.channels = 2;
|
|
want.samples = 1024;
|
|
want.callback = Callback;
|
|
want.userdata = this;
|
|
deviceid = SDL_OpenAudioDevice(device, 0, &want, &have, 0);
|
|
format.format = have.format;
|
|
format.channels = have.channels;
|
|
format.freq = have.freq;
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
const char* filename = get_file_path(PATH_ID_CSS1);
|
|
for (int i = 0; i < countof(css1sources); i++) {
|
|
Source_Sample* source_sample = new Source_Sample;
|
|
if (source_sample->LoadCSS1(filename, i)) {
|
|
source_sample->Convert(format); // convert to audio output format, saves some cpu usage but requires a bit more memory, optional
|
|
css1sources[i] = source_sample;
|
|
} else {
|
|
css1sources[i] = &source_null;
|
|
delete source_sample;
|
|
}
|
|
}
|
|
effectbuffer = new uint8[(have.samples * format.BytesPerSample() * format.channels)];
|
|
SDL_PauseAudioDevice(deviceid, 0);
|
|
}
|
|
|
|
void Mixer::Close()
|
|
{
|
|
Lock();
|
|
while (channels.begin() != channels.end()) {
|
|
delete *(channels.begin());
|
|
channels.erase(channels.begin());
|
|
}
|
|
Unlock();
|
|
SDL_CloseAudioDevice(deviceid);
|
|
for (int i = 0; i < countof(css1sources); i++) {
|
|
if (css1sources[i] && css1sources[i] != &source_null) {
|
|
delete css1sources[i];
|
|
css1sources[i] = 0;
|
|
}
|
|
}
|
|
for (int i = 0; i < countof(musicsources); i++) {
|
|
if (musicsources[i] && musicsources[i] != &source_null) {
|
|
delete musicsources[i];
|
|
musicsources[i] = 0;
|
|
}
|
|
}
|
|
if (effectbuffer) {
|
|
delete[] effectbuffer;
|
|
effectbuffer = 0;
|
|
}
|
|
}
|
|
|
|
void Mixer::Lock()
|
|
{
|
|
SDL_LockAudioDevice(deviceid);
|
|
}
|
|
|
|
void Mixer::Unlock()
|
|
{
|
|
SDL_UnlockAudioDevice(deviceid);
|
|
}
|
|
|
|
Channel* Mixer::Play(Source& source, int loop, bool deleteondone, bool deletesourceondone)
|
|
{
|
|
Lock();
|
|
Channel* newchannel = new (std::nothrow) Channel;
|
|
if (newchannel) {
|
|
newchannel->Play(source, loop);
|
|
newchannel->deleteondone = deleteondone;
|
|
newchannel->deletesourceondone = deletesourceondone;
|
|
channels.push_back(newchannel);
|
|
}
|
|
Unlock();
|
|
return newchannel;
|
|
}
|
|
|
|
void Mixer::Stop(Channel& channel)
|
|
{
|
|
Lock();
|
|
channel.stopping = true;
|
|
Unlock();
|
|
}
|
|
|
|
bool Mixer::LoadMusic(int pathid)
|
|
{
|
|
if (pathid >= countof(musicsources)) {
|
|
return false;
|
|
}
|
|
if (!musicsources[pathid]) {
|
|
const char* filename = get_file_path(pathid);
|
|
Source_Sample* source_sample = new Source_Sample;
|
|
if (source_sample->LoadWAV(filename)) {
|
|
musicsources[pathid] = source_sample;
|
|
return true;
|
|
} else {
|
|
delete source_sample;
|
|
musicsources[pathid] = &source_null;
|
|
return false;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void SDLCALL Mixer::Callback(void* arg, uint8* stream, int length)
|
|
{
|
|
Mixer* mixer = (Mixer*)arg;
|
|
memset(stream, 0, length);
|
|
std::list<Channel*>::iterator i = mixer->channels.begin();
|
|
while (i != mixer->channels.end()) {
|
|
mixer->MixChannel(*(*i), stream, length);
|
|
if (((*i)->done && (*i)->deleteondone) || (*i)->stopping) {
|
|
delete (*i);
|
|
i = mixer->channels.erase(i);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mixer::MixChannel(Channel& channel, uint8* data, int length)
|
|
{
|
|
#ifdef _WIN32
|
|
if (channel.source && channel.source->Length() > 0 && !channel.done && gConfigSound.sound) {
|
|
AudioFormat streamformat = channel.source->Format();
|
|
int loaded = 0;
|
|
SDL_AudioCVT cvt;
|
|
cvt.len_ratio = 1;
|
|
do {
|
|
int samplesize = format.channels * format.BytesPerSample();
|
|
int samples = length / samplesize;
|
|
int samplesloaded = loaded / samplesize;
|
|
double rate = 1;
|
|
if (format.format == AUDIO_S16SYS) {
|
|
rate = channel.rate;
|
|
}
|
|
int samplestoread = (int)((samples - samplesloaded) * rate);
|
|
int lengthloaded = 0;
|
|
if (channel.offset < channel.source->Length()) {
|
|
bool mustconvert = false;
|
|
if (MustConvert(*channel.source)) {
|
|
if (SDL_BuildAudioCVT(&cvt, streamformat.format, streamformat.channels, streamformat.freq, Mixer::format.format, Mixer::format.channels, Mixer::format.freq) == -1) {
|
|
break;
|
|
}
|
|
mustconvert = true;
|
|
}
|
|
|
|
const uint8* datastream = 0;
|
|
int toread = (int)(samplestoread / cvt.len_ratio) * samplesize;
|
|
int readfromstream = (channel.source->GetSome(channel.offset, &datastream, toread));
|
|
if (readfromstream == 0) {
|
|
break;
|
|
}
|
|
|
|
uint8* dataconverted = 0;
|
|
const uint8* tomix = 0;
|
|
|
|
if (mustconvert) {
|
|
// tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, can cause pops and static depending on sample rate and channels
|
|
if (Convert(cvt, datastream, readfromstream, &dataconverted)) {
|
|
tomix = dataconverted;
|
|
lengthloaded = cvt.len_cvt;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
tomix = datastream;
|
|
lengthloaded = readfromstream;
|
|
}
|
|
|
|
bool effectbufferloaded = false;
|
|
if (rate != 1 && format.format == AUDIO_S16SYS) {
|
|
int in_len = (int)((double)lengthloaded / samplesize);
|
|
int out_len = samples;
|
|
if (!channel.resampler) {
|
|
channel.resampler = speex_resampler_init(format.channels, format.freq, format.freq, 0, 0);
|
|
}
|
|
if (readfromstream == toread) {
|
|
// use buffer lengths for conversion ratio so that it fits exactly
|
|
speex_resampler_set_rate(channel.resampler, in_len, samples - samplesloaded);
|
|
} else {
|
|
// reached end of stream so we cant use buffer length as resampling ratio
|
|
speex_resampler_set_rate(channel.resampler, format.freq, (int)(format.freq * (1 / rate)));
|
|
}
|
|
speex_resampler_process_interleaved_int(channel.resampler, (const spx_int16_t*)tomix, (spx_uint32_t*)&in_len, (spx_int16_t*)effectbuffer, (spx_uint32_t*)&out_len);
|
|
effectbufferloaded = true;
|
|
tomix = effectbuffer;
|
|
lengthloaded = (out_len * samplesize);
|
|
}
|
|
|
|
if (channel.pan != 0.5f && format.channels == 2) {
|
|
if (!effectbufferloaded) {
|
|
memcpy(effectbuffer, tomix, lengthloaded);
|
|
effectbufferloaded = true;
|
|
tomix = effectbuffer;
|
|
}
|
|
switch (format.format) {
|
|
case AUDIO_S16SYS:
|
|
EffectPanS16(channel, (sint16*)effectbuffer, lengthloaded / samplesize);
|
|
break;
|
|
case AUDIO_U8:
|
|
EffectPanU8(channel, (uint8*)effectbuffer, lengthloaded / samplesize);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int mixlength = lengthloaded;
|
|
if (loaded + mixlength > length) {
|
|
mixlength = length - loaded;
|
|
}
|
|
|
|
float volumeadjust = (gConfigSound.master_volume / 100.0f);
|
|
if (channel.group == MIXER_GROUP_MUSIC) {
|
|
volumeadjust *= (gConfigSound.music_volume / 100.0f);
|
|
}
|
|
int startvolume = (int)(channel.oldvolume * volumeadjust);
|
|
int endvolume = (int)(channel.volume * volumeadjust);
|
|
if (channel.stopping) {
|
|
endvolume = 0;
|
|
}
|
|
int mixvolume = (int)(channel.volume * volumeadjust);
|
|
if (startvolume != endvolume) {
|
|
// fade between volume levels to smooth out sound and minimize clicks from sudden volume changes
|
|
if (!effectbufferloaded) {
|
|
memcpy(effectbuffer, tomix, lengthloaded);
|
|
effectbufferloaded = true;
|
|
tomix = effectbuffer;
|
|
}
|
|
mixvolume = SDL_MIX_MAXVOLUME; // set to max since we are adjusting the volume ourselves
|
|
int fadelength = mixlength / format.BytesPerSample();
|
|
switch (format.format) {
|
|
case AUDIO_S16SYS:
|
|
EffectFadeS16((sint16*)effectbuffer, fadelength, startvolume, endvolume);
|
|
break;
|
|
case AUDIO_U8:
|
|
EffectFadeU8((uint8*)effectbuffer, fadelength, startvolume, endvolume);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SDL_MixAudioFormat(&data[loaded], tomix, format.format, mixlength, mixvolume);
|
|
|
|
if (dataconverted) {
|
|
delete[] dataconverted;
|
|
}
|
|
|
|
channel.offset += readfromstream;
|
|
}
|
|
|
|
loaded += lengthloaded;
|
|
|
|
if (channel.loop != 0 && channel.offset >= channel.source->Length()) {
|
|
if (channel.loop != -1) {
|
|
channel.loop--;
|
|
}
|
|
channel.offset = 0;
|
|
}
|
|
} while(loaded < length && channel.loop != 0 && !channel.stopping);
|
|
|
|
channel.oldvolume = channel.volume;
|
|
channel.oldvolume_l = channel.volume_l;
|
|
channel.oldvolume_r = channel.volume_r;
|
|
if (channel.loop == 0 && channel.offset >= channel.source->Length()) {
|
|
channel.done = true;
|
|
}
|
|
}
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
}
|
|
|
|
void Mixer::EffectPanS16(Channel& channel, sint16* data, int length)
|
|
{
|
|
for (int i = 0; i < length * 2; i += 2) {
|
|
float t = (float)i / (length * 2);
|
|
data[i] = (sint16)(data[i] * ((1.0 - t) * channel.oldvolume_l + t * channel.volume_l));
|
|
data[i + 1] = (sint16)(data[i + 1] * ((1.0 - t) * channel.oldvolume_r + t * channel.volume_r));
|
|
}
|
|
}
|
|
|
|
void Mixer::EffectPanU8(Channel& channel, uint8* data, int length)
|
|
{
|
|
for (int i = 0; i < length * 2; i += 2) {
|
|
float t = (float)i / (length * 2);
|
|
data[i] = (uint8)(data[i] * ((1.0 - t) * channel.oldvolume_l + t * channel.volume_l));
|
|
data[i + 1] = (uint8)(data[i + 1] * ((1.0 - t) * channel.oldvolume_r + t * channel.volume_r));
|
|
}
|
|
}
|
|
|
|
void Mixer::EffectFadeS16(sint16* data, int length, int startvolume, int endvolume)
|
|
{
|
|
float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME;
|
|
float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME;
|
|
for (int i = 0; i < length; i++) {
|
|
float t = (float)i / length;
|
|
data[i] = (sint16)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f));
|
|
}
|
|
}
|
|
|
|
void Mixer::EffectFadeU8(uint8* data, int length, int startvolume, int endvolume)
|
|
{
|
|
float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME;
|
|
float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME;
|
|
for (int i = 0; i < length; i++) {
|
|
float t = (float)i / length;
|
|
data[i] = (uint8)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f));
|
|
}
|
|
}
|
|
|
|
bool Mixer::MustConvert(Source& source)
|
|
{
|
|
#ifdef _WIN32
|
|
const AudioFormat sourceformat = source.Format();
|
|
if (sourceformat.format != format.format || sourceformat.channels != format.channels || sourceformat.freq != format.freq) {
|
|
return true;
|
|
}
|
|
#else
|
|
STUB();
|
|
#endif // _WIN32
|
|
return false;
|
|
}
|
|
|
|
bool Mixer::Convert(SDL_AudioCVT& cvt, const uint8* data, unsigned long length, uint8** dataout)
|
|
{
|
|
if (length == 0 || cvt.len_mult == 0) {
|
|
return false;
|
|
}
|
|
cvt.len = length;
|
|
cvt.buf = (Uint8*)new uint8[cvt.len * cvt.len_mult];
|
|
memcpy(cvt.buf, data, length);
|
|
if (SDL_ConvertAudio(&cvt) < 0) {
|
|
delete[] cvt.buf;
|
|
return false;
|
|
}
|
|
*dataout = cvt.buf;
|
|
return true;
|
|
}
|
|
|
|
void Mixer_Init(const char* device)
|
|
{
|
|
gMixer.Init(device);
|
|
}
|
|
|
|
void* Mixer_Play_Effect(int id, int loop, int volume, float pan, double rate, int deleteondone)
|
|
{
|
|
if (!gConfigSound.sound) {
|
|
return 0;
|
|
}
|
|
if (id >= countof(gMixer.css1sources)) {
|
|
return 0;
|
|
}
|
|
gMixer.Lock();
|
|
Channel* channel = gMixer.Play(*gMixer.css1sources[id], loop, deleteondone != 0, false);
|
|
if (channel) {
|
|
channel->SetVolume(volume);
|
|
channel->SetPan(pan);
|
|
channel->SetRate(rate);
|
|
}
|
|
gMixer.Unlock();
|
|
return channel;
|
|
}
|
|
|
|
void Mixer_Stop_Channel(void* channel)
|
|
{
|
|
gMixer.Stop(*(Channel*)channel);
|
|
}
|
|
|
|
void Mixer_Channel_Volume(void* channel, int volume)
|
|
{
|
|
gMixer.Lock();
|
|
((Channel*)channel)->SetVolume(volume);
|
|
gMixer.Unlock();
|
|
}
|
|
|
|
void Mixer_Channel_Pan(void* channel, float pan)
|
|
{
|
|
gMixer.Lock();
|
|
((Channel*)channel)->SetPan(pan);
|
|
gMixer.Unlock();
|
|
}
|
|
|
|
void Mixer_Channel_Rate(void* channel, double rate)
|
|
{
|
|
gMixer.Lock();
|
|
((Channel*)channel)->SetRate(rate);
|
|
gMixer.Unlock();
|
|
}
|
|
|
|
int Mixer_Channel_IsPlaying(void* channel)
|
|
{
|
|
return ((Channel*)channel)->IsPlaying();
|
|
}
|
|
|
|
unsigned long Mixer_Channel_GetOffset(void* channel)
|
|
{
|
|
return ((Channel*)channel)->GetOffset();
|
|
}
|
|
|
|
int Mixer_Channel_SetOffset(void* channel, unsigned long offset)
|
|
{
|
|
return ((Channel*)channel)->SetOffset(offset);
|
|
}
|
|
|
|
void Mixer_Channel_SetGroup(void* channel, int group)
|
|
{
|
|
((Channel*)channel)->SetGroup(group);
|
|
}
|
|
|
|
void* Mixer_Play_Music(int pathid, int loop, int streaming)
|
|
{
|
|
if (!gConfigSound.sound) {
|
|
return 0;
|
|
}
|
|
if (streaming) {
|
|
const utf8 *filename = get_file_path(pathid);
|
|
|
|
SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
|
|
if (rw == NULL) {
|
|
return 0;
|
|
}
|
|
Source_SampleStream* source_samplestream = new Source_SampleStream;
|
|
if (source_samplestream->LoadWAV(rw)) {
|
|
Channel* channel = gMixer.Play(*source_samplestream, loop, false, true);
|
|
if (!channel) {
|
|
delete source_samplestream;
|
|
} else {
|
|
channel->SetGroup(MIXER_GROUP_MUSIC);
|
|
}
|
|
return channel;
|
|
} else {
|
|
delete source_samplestream;
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (gMixer.LoadMusic(pathid)) {
|
|
Channel* channel = gMixer.Play(*gMixer.musicsources[pathid], MIXER_LOOP_INFINITE, false, false);
|
|
if (channel) {
|
|
channel->SetGroup(MIXER_GROUP_MUSIC);
|
|
}
|
|
return channel;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|