1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-23 23:04:36 +01:00

A new custom software mixer, supports playing streams at any rate, which is needed to emulate the directsound SetFrequency effects (when a ride malfunctions and the music slows down and stops). The speex resampler is added for this. Currently samples can be played and their volumes and looping set on an arbitrary number of channels. Support for FLAC and other streaming formats can be added.

This commit is contained in:
zsilencer
2014-09-05 19:29:22 -06:00
parent a284d446ee
commit bf805057f2
16 changed files with 2704 additions and 28 deletions

View File

@@ -215,14 +215,14 @@ int dsound_create_primary_buffer(int a, int device, int channels, int samples, i
}
dsdevice = &RCT2_GLOBAL(RCT2_ADDRESS_DSOUND_DEVICES, rct_dsdevice*)[device];
}
memset(&RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info), 0, sizeof(rct_audio_info));
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).var_0 = 1;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).channels = channels;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).samples = samples;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).var_8 = samples * RCT2_GLOBAL(0x01425B4C, uint16);
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).bytes = bits * channels / 8;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).bits = bits;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, rct_audio_info).var_E = 0;
memset(&RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX), 0, sizeof(WAVEFORMATEX));
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).wFormatTag = 1;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).nChannels = channels;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).nSamplesPerSec = samples;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).nAvgBytesPerSec = samples * RCT2_GLOBAL(0x01425B4C, uint16);
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).nBlockAlign = bits * channels / 8;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).wBitsPerSample = bits;
RCT2_GLOBAL(RCT2_ADDRESS_AUDIO_INFO, WAVEFORMATEX).cbSize = 0;
DSBUFFERDESC bufferdesc;
memset(&bufferdesc, 0, sizeof(bufferdesc));
bufferdesc.dwSize = sizeof(bufferdesc);
@@ -466,8 +466,8 @@ int sub_4015E7(int channel)
} else {
sound_channel->var_168 = 1;
sound_channel->var_15C = read;
rct_audio_info* audio_info = sound_channel->hmem;
uint16 v = ((audio_info->var_E != 8) - 1) & 0x80;
LPWAVEFORMATEX waveformat = sound_channel->hmem;
uint16 v = ((waveformat->nBlockAlign != 8) - 1) & 0x80;
memset(&buf1[read], v, buf1size - r);
}
}
@@ -531,7 +531,7 @@ MMRESULT mmio_open(char* filename, HMMIO* hmmio, HGLOBAL* hmem, LPMMCKINFO mmcki
HMMIO hmmio1;
MMRESULT result;
MMCKINFO mmckinfo1;
rct_audio_info audio_info;
WAVEFORMATEX waveformat;
hmemold = hmem;
*hmem = 0;
@@ -555,8 +555,8 @@ MMRESULT mmio_open(char* filename, HMMIO* hmmio, HGLOBAL* hmem, LPMMCKINFO mmcki
result = 57601;
goto label20;
}
if (mmioRead(hmmio1, (HPSTR)&audio_info, 16) == 16) {
if (audio_info.var_0 == 1) {
if (mmioRead(hmmio1, (HPSTR)&waveformat, 16) == 16) {
if (waveformat.wFormatTag == 1) {
//strcpy(audio_info.var_0, "\x01");
hmem = 0;
label11:
@@ -566,7 +566,7 @@ MMRESULT mmio_open(char* filename, HMMIO* hmmio, HGLOBAL* hmem, LPMMCKINFO mmcki
result = 57344;
goto label20;
}
memcpy(hmemold2, &audio_info, 16);
memcpy(hmemold2, &waveformat, 16);
*((uint16*)*hmemold + 8) = (uint16)hmem;
if (!(uint16)hmem || mmioRead(hmmio1, (char*)*hmemold + 18, (uint16)hmem) == (uint16)hmem) {
result = mmioAscend(hmmio1, &mmckinfo1, 0);

View File

@@ -59,16 +59,6 @@ typedef struct rct_sound {
struct rct_sound* next;
} rct_sound;
typedef struct {
uint16 var_0;
uint16 channels;
uint32 samples;
uint32 var_8;
uint16 bytes;
uint16 bits;
uint16 var_E;
} rct_audio_info;
typedef struct {
uint32 var_0;
uint32 var_4;

401
src/mixer.cpp Normal file
View File

@@ -0,0 +1,401 @@
/*****************************************************************************
* 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/>.
*****************************************************************************/
#include <math.h>
#include <SDL.h>
#include <string.h>
extern "C" {
#include "audio.h"
#include "config.h"
}
#include "mixer.h"
Sample::Sample()
{
data = 0;
length = 0;
issdlwav = false;
}
Sample::~Sample()
{
Unload();
}
bool Sample::Load(char* filename)
{
Unload();
SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
if (!rw) {
SDL_RWclose(rw);
return false;
}
SDL_AudioSpec audiospec;
memset(&audiospec, 0, sizeof(audiospec));
SDL_AudioSpec* spec = SDL_LoadWAV_RW(rw, false, &audiospec, &data, &length);
if (spec != NULL) {
format.freq = spec->freq;
format.format = spec->format;
format.channels = spec->channels;
} else {
issdlwav = true;
}
return true;
}
bool Sample::LoadCSS1(char* filename, unsigned int offset)
{
Unload();
SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
if (!rw) {
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;
WAVEFORMATEX waveformat;
SDL_RWread(rw, &waveformat, sizeof(waveformat), 1);
format.freq = waveformat.nSamplesPerSec;
format.format = AUDIO_S16;
format.channels = (Uint8)waveformat.nChannels;
data = new Uint8[length];
SDL_RWread(rw, data, length, 1);
SDL_RWclose(rw);
return true;
}
void Sample::Unload()
{
SDL_LockAudio();
if (data) {
if (issdlwav) {
SDL_FreeWAV(data);
} else {
delete[] data;
}
data = 0;
}
issdlwav = false;
length = 0;
SDL_UnlockAudio();
}
bool Sample::Convert(AudioFormat format)
{
if(Sample::format.format != format.format || Sample::format.channels != format.channels || Sample::format.freq != format.freq){
SDL_AudioCVT cvt;
if (SDL_BuildAudioCVT(&cvt, Sample::format.format, Sample::format.channels, Sample::format.freq, format.format, format.channels, format.freq) < 0) {
return false;
}
cvt.len = length;
cvt.buf = 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;
Sample::format = format;
}
return true;
}
const Uint8* Sample::Data()
{
return data;
}
int Sample::Length()
{
return length;
}
Stream::Stream()
{
sourcetype = SOURCE_NONE;
}
int Stream::GetSome(int offset, const Uint8** data, int length)
{
int size = length;
switch(sourcetype) {
case SOURCE_SAMPLE:
if (offset >= sample->Length()) {
return 0;
}
if (offset + length > sample->Length()) {
size = sample->Length() - offset;
}
*data = &sample->Data()[offset];
return size;
break;
}
return 0;
}
int Stream::Length()
{
switch(sourcetype) {
case SOURCE_SAMPLE:
return sample->Length();
break;
}
return 0;
}
void Stream::SetSource_Sample(Sample& sample)
{
sourcetype = SOURCE_SAMPLE;
Stream::sample = &sample;
}
const AudioFormat* Stream::Format()
{
switch(sourcetype) {
case SOURCE_SAMPLE:
return &sample->format;
break;
}
return 0;
}
Channel::Channel()
{
rate = 1;
resampler = 0;
SetVolume(SDL_MIX_MAXVOLUME);
}
Channel::~Channel()
{
if (resampler) {
speex_resampler_destroy(resampler);
resampler = 0;
}
}
void Channel::Play(Stream& stream, int loop = 0)
{
Channel::stream = &stream;
Channel::loop = loop;
offset = 0;
}
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 (Channel::volume > SDL_MIX_MAXVOLUME) {
Channel::volume = SDL_MIX_MAXVOLUME;
}
if (Channel::volume < 0) {
Channel::volume = 0;
}
}
void Mixer::Init(char* device)
{
Close();
SDL_AudioSpec want, have;
SDL_zero(want);
want.freq = 22050;
want.format = AUDIO_S16;
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;
char css1filename[260];
strcpy(css1filename, gGeneral_config.game_path);
strcat(css1filename, "\\Data\\css1.dat");
for (int i = 0; i < 63; i++) {
css1samples[i].LoadCSS1(css1filename, i);
css1samples[i].Convert(format); // convert to audio output format, saves some cpu usage but requires a bit more memory, optional
css1streams[i].SetSource_Sample(css1samples[i]);
}
SDL_PauseAudioDevice(deviceid, 0);
}
void Mixer::Close()
{
SDL_CloseAudioDevice(deviceid);
}
void SDLCALL Mixer::Callback(void* arg, Uint8* stream, int length)
{
Mixer* mixer = (Mixer*)arg;
memset(stream, 0, length);
for (int i = 0; i < 10; i++) {
mixer->MixChannel(mixer->channels[i], stream, length);
}
}
void Mixer::MixChannel(Channel& channel, Uint8* data, int length)
{
if (channel.stream) {
if (!channel.resampler) {
channel.resampler = speex_resampler_init(format.channels, format.freq, format.freq, 0, 0);
}
AudioFormat channelformat = *channel.stream->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;
int samplestoread = (int)ceil((samples - samplesloaded) * channel.rate);
int lengthloaded = 0;
if (channel.offset < channel.stream->Length()) {
bool mustconvert = false;
if (MustConvert(*channel.stream)) {
if (SDL_BuildAudioCVT(&cvt, channelformat.format, channelformat.channels, channelformat.freq, Mixer::format.format, Mixer::format.channels, Mixer::format.freq) == -1) {
break;
}
mustconvert = true;
}
const Uint8* datastream;
int readfromstream = (channel.stream->GetSome(channel.offset, &datastream, (int)(((samplestoread) * samplesize) / cvt.len_ratio)) / channelformat.BytesPerSample()) * channelformat.BytesPerSample();
if (readfromstream == 0) {
break;
}
int volume = channel.volume;
if (mustconvert) {
Uint8* dataconverted;
if (Convert(cvt, datastream, readfromstream, &dataconverted)) {
if (channel.rate != 1 && format.format == AUDIO_S16) {
spx_uint32_t in_len = (spx_uint32_t)(ceil((double)cvt.len_cvt / samplesize));
Uint8* out = new Uint8[length + 200]; // needs some extra, otherwise resampler sometimes doesn't process all the input samples
spx_uint32_t out_len = samples + 20;
speex_resampler_set_rate(channel.resampler, format.freq, (int)(format.freq * (1 / channel.rate)));
speex_resampler_process_interleaved_int(channel.resampler, (const spx_int16_t*)dataconverted, &in_len, (spx_int16_t*)out, &out_len);
int mixlength = (out_len * samplesize);
if (loaded + mixlength > length) { // check for overflow
mixlength = length - loaded;
}
lengthloaded = (out_len * samplesize);
SDL_MixAudioFormat(&data[loaded], out, format.format, mixlength, volume);
delete[] out;
} else {
lengthloaded = (cvt.len_cvt / samplesize) * samplesize;
int mixlength = lengthloaded;
if (loaded + cvt.len_cvt > length) {
mixlength = length - loaded;
}
SDL_MixAudioFormat(&data[loaded], dataconverted, format.format, mixlength, volume);
}
delete[] dataconverted;
}
} else {
if (channel.rate != 1 && format.format == AUDIO_S16) {
spx_uint32_t in_len = (spx_uint32_t)(ceil((double)readfromstream / samplesize));
Uint8* out = new Uint8[length + 200];
spx_uint32_t out_len = samples + 20;
speex_resampler_set_rate(channel.resampler, format.freq, (int)(format.freq * (1 / channel.rate)));
speex_resampler_process_interleaved_int(channel.resampler, (const spx_int16_t*)datastream, &in_len, (spx_int16_t*)out, &out_len);
int mixlength = (out_len * samplesize);
if (loaded + mixlength > length) {
mixlength = length - loaded;
}
lengthloaded = (out_len * samplesize);
SDL_MixAudioFormat(&data[loaded], out, format.format, mixlength, volume);
delete[] out;
} else {
lengthloaded = readfromstream;
int mixlength = lengthloaded;
if (loaded + readfromstream > length) {
mixlength = length - loaded;
}
SDL_MixAudioFormat(&data[loaded], datastream, format.format, mixlength, volume);
}
}
channel.offset += readfromstream;
}
loaded += lengthloaded;
if (channel.loop != 0 && channel.offset >= channel.stream->Length()) {
if (channel.loop != -1) {
channel.loop--;
}
channel.offset = 0;
}
} while(loaded < length || (loaded < length && channel.loop != 0 && channel.offset == 0));
}
}
bool Mixer::MustConvert(Stream& stream)
{
const AudioFormat* streamformat = stream.Format();
if (!streamformat) {
return false;
}
if (streamformat->format != format.format || streamformat->channels != format.channels || streamformat->freq != format.freq) {
return true;
}
return false;
}
bool Mixer::Convert(SDL_AudioCVT& cvt, const Uint8* data, int length, Uint8** dataout)
{
if (length == 0 || cvt.len_mult == 0) {
return false;
}
cvt.len = length;
cvt.buf = (Uint8*)new char[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(char* device)
{
static Mixer mixer;
mixer.Init(device);
}

127
src/mixer.h Normal file
View File

@@ -0,0 +1,127 @@
/*****************************************************************************
* 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/>.
*****************************************************************************/
#ifndef _MIXER_H_
#define _MIXER_H_
#include "rct2.h"
#ifdef __cplusplus
extern "C" {
#include <speex/speex_resampler.h>
}
struct AudioFormat {
int BytesPerSample() const { return (SDL_AUDIO_BITSIZE(format)) / 8; };
int freq;
SDL_AudioFormat format;
Uint8 channels;
};
class Sample
{
public:
Sample();
~Sample();
bool Load(char* filename);
bool LoadCSS1(char* filename, unsigned int offset);
void Unload();
bool Convert(AudioFormat format);
const Uint8* Data();
int Length();
friend class Stream;
private:
AudioFormat format;
Uint8* data;
Uint32 length;
bool issdlwav;
};
class Stream
{
public:
Stream();
int GetSome(int offset, const Uint8** data, int length);
int Length();
void SetSource_Sample(Sample& sample);
const AudioFormat* Format();
friend class Mixer;
private:
enum {
SOURCE_NONE = 0,
SOURCE_SAMPLE
} sourcetype;
Sample* sample;
};
class Channel
{
public:
Channel();
~Channel();
void Play(Stream& stream, int loop);
void SetRate(double rate);
void SetVolume(int volume);
friend class Mixer;
private:
int loop;
int offset;
double rate;
int volume;
SpeexResamplerState* resampler;
Stream* stream;
};
class Mixer
{
public:
void Init(char* device);
void Close();
private:
static void SDLCALL Callback(void* arg, Uint8* data, int length);
void MixChannel(Channel& channel, Uint8* buffer, int length);
bool MustConvert(Stream& stream);
bool Convert(SDL_AudioCVT& cvt, const Uint8* data, int length, Uint8** dataout);
SDL_AudioDeviceID deviceid;
AudioFormat format;
Sample css1samples[63];
Stream css1streams[63];
Channel channels[10];
};
extern "C"
{
#endif
void Mixer_Init(char* device);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -74,7 +74,7 @@ typedef struct {
char path[260];
uint32 var_20C;
uint8 pad_210[0x100];
char addon[15][0x80];
char addon[16][0x80];
uint32 addons; //0xB10
} rct2_install_info;

View File

@@ -39,6 +39,7 @@
#include "intro.h"
#include "language.h"
#include "map.h"
#include "mixer.h"
#include "news_item.h"
#include "object.h"
#include "osinterface.h"
@@ -88,6 +89,7 @@ __declspec(dllexport) int StartOpenRCT(HINSTANCE hInstance, HINSTANCE hPrevInsta
config_init();
language_open(gGeneral_config.language);
rct2_init();
Mixer_Init(NULL);
rct2_loop();
osinterface_free();
exit(0);

View File

@@ -33,6 +33,7 @@
#include "config.h"
#include "gfx.h"
#include "language.h"
#include "mixer.h"
#include "osinterface.h"
#include "sprites.h"
#include "string_ids.h"
@@ -503,6 +504,9 @@ static void window_options_dropdown()
switch (widgetIndex) {
case WIDX_SOUND_DROPDOWN:
audio_init2(dropdownIndex);
if (dropdownIndex < gAudioDeviceCount) {
Mixer_Init(gAudioDevices[dropdownIndex].name);
}
/*#ifdef _MSC_VER
__asm movzx ax, dropdownIndex
#else