mirror of
https://github.com/ondra-novak/gates_of_skeldal.git
synced 2025-07-04 21:50:38 -04:00
355 lines
11 KiB
C++
355 lines
11 KiB
C++
#include "../platform.h"
|
|
#include "../sound.h"
|
|
#include <libs/logfile.h>
|
|
#include "global_context.h"
|
|
|
|
#include "sound_mixer.h"
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
#include <thread>
|
|
|
|
#define countof(array) (sizeof(array)/sizeof(array[0]))
|
|
static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len);
|
|
static SoundMixer<2> sound_mixer;
|
|
|
|
static float master_volume = 1.0;
|
|
static float sound_effect_volume = 1.0;
|
|
static float music_volume = 1.0;
|
|
static float base_freq;
|
|
bool swap_channels = false;
|
|
static void empty_deleter(const void *) {}
|
|
|
|
constexpr int BACK_BUFF_SIZE = 0x40000;
|
|
static uint8_t *music_buffer = {};
|
|
std::shared_ptr<WaveSource> music_source;
|
|
static std::unique_ptr<IMusicStream> current_music = NULL;
|
|
|
|
struct VolumePreset {
|
|
float left = 1.0;
|
|
float right = 1.0;
|
|
};
|
|
|
|
static std::unordered_map<int, VolumePreset > channel_volume_presset;
|
|
|
|
void game_sound_init_device(const INI_CONFIG_SECTION *audio_section) {
|
|
|
|
const char *dev = ini_get_string(audio_section, "device", "");
|
|
if (!dev[0]) dev = nullptr;
|
|
|
|
SDLContext::AudioConfig cfg;
|
|
cfg.audioDevice = dev;
|
|
|
|
auto r = get_sdl_global_context().init_audio(cfg, mixing_callback, nullptr);
|
|
base_freq = r.freq;
|
|
|
|
|
|
|
|
}
|
|
|
|
static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len) {
|
|
float *s = reinterpret_cast<float *>(stream);
|
|
int samples = len/8;
|
|
std::fill(s, s+2*samples, 0.0f);
|
|
sound_mixer.mix_to_buffer(s, samples);
|
|
}
|
|
|
|
char start_mixing() {
|
|
get_sdl_global_context().pause_audio(false);
|
|
return 0;
|
|
}
|
|
void stop_mixing() {
|
|
get_sdl_global_context().pause_audio(true);
|
|
|
|
}
|
|
|
|
|
|
static std::array<float,2> make_channel_volume(float left, float right) {
|
|
if (swap_channels) std::swap(left, right);
|
|
return {
|
|
left * master_volume * sound_effect_volume,
|
|
right * master_volume * sound_effect_volume,
|
|
};
|
|
}
|
|
|
|
void play_sample(int channel,const void *sample,int32_t size,int32_t lstart,int32_t sfreq,int type) {
|
|
float speed = sfreq/base_freq;
|
|
int step = type==1?1:2;
|
|
auto samples = size/step;
|
|
auto start = lstart/step;
|
|
WaveSource::Format fmt = step==1?WaveSource::Format::uint8:WaveSource::Format::int16;
|
|
auto src = std::make_shared<WaveSource>(WaveSource::WavePtr(sample,&empty_deleter),fmt,speed,samples,start);
|
|
|
|
VolumePreset &vp = channel_volume_presset[channel];
|
|
|
|
|
|
|
|
|
|
sound_mixer.set_track(channel, WaveMixer<2>(src, make_channel_volume(vp.left, vp.right), 1.0));
|
|
|
|
}
|
|
void set_channel_volume(int channel,int left,int right) {
|
|
float l = left / 32768.0;
|
|
float r = right / 32768.0;
|
|
VolumePreset &vp = channel_volume_presset[channel];
|
|
vp.left = l;
|
|
vp.right = r;
|
|
sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){
|
|
mx.set_channel_volumes_fade(make_channel_volume(vp.left, vp.right), {2e-4,2e-4});
|
|
});
|
|
}
|
|
|
|
static size_t music_buffer_write_pos = 0;
|
|
|
|
|
|
size_t copy_to_music_buffer(const void *data, size_t data_size) {
|
|
size_t rdpos = music_source->get_output_position_bytes();
|
|
if (static_cast<std::make_signed_t<std::size_t> >(rdpos) < 0) return 0;
|
|
size_t l = music_source->length_bytes();
|
|
size_t space = rdpos < music_buffer_write_pos?l - music_buffer_write_pos: rdpos - music_buffer_write_pos;
|
|
if (space == 0) return 0;
|
|
data_size = std::min(space,data_size);
|
|
std::memcpy(music_buffer+music_buffer_write_pos, data, data_size);
|
|
music_buffer_write_pos += data_size;
|
|
if (music_buffer_write_pos >= l) {
|
|
music_buffer_write_pos = 0;
|
|
}
|
|
return data_size;
|
|
}
|
|
|
|
|
|
static const char * (*end_of_song_callback)(void *ctx) = NULL;
|
|
static void *end_of_song_callback_ctx = NULL;
|
|
|
|
void set_end_of_song_callback(const char * (*cb)(void *), void *ctx) {
|
|
end_of_song_callback = cb;
|
|
end_of_song_callback_ctx = ctx;
|
|
}
|
|
|
|
void fade_music() {
|
|
std::lock_guard _(music_source->get_lock());
|
|
size_t rdpos = music_source->get_output_position_samples();
|
|
WaveSource::StereoInt16 *rdptr = reinterpret_cast<WaveSource::StereoInt16 *>(music_buffer);
|
|
size_t wrsamples = music_buffer_write_pos / sizeof(WaveSource::StereoInt16);
|
|
size_t l =music_source->length();
|
|
float_t size = (rdpos < wrsamples?0:l) + wrsamples - rdpos;
|
|
if (l) {
|
|
for (size_t i = 0; i < size; ++i) {
|
|
float fact = (size - i)/size;
|
|
rdptr[rdpos].left = rdptr[rdpos].left * fact;
|
|
rdptr[rdpos].right = rdptr[rdpos].right * fact;
|
|
++rdpos;
|
|
if (rdpos >=l) rdpos = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
constexpr int music_track_id_base = 0x1000;
|
|
|
|
static void music_buffer_deleter(const void *ptr) {
|
|
uint8_t *p = reinterpret_cast<uint8_t *>(const_cast<void *>(ptr));
|
|
delete [] p;
|
|
}
|
|
|
|
static void stop_music() {
|
|
if (music_source) {
|
|
std::lock_guard _(music_source->get_lock());
|
|
size_t l =music_source->length_bytes();
|
|
size_t rdpos = music_source->get_output_position_bytes();
|
|
size_t size = (rdpos < music_buffer_write_pos?0:l) + music_buffer_write_pos - rdpos;
|
|
music_source->stop_after_bytes(size);
|
|
}
|
|
music_source.reset();
|
|
}
|
|
|
|
void create_new_music_track(int freq, int buffsize) {
|
|
stop_music();
|
|
static int track_id = music_track_id_base;
|
|
double basef = base_freq;
|
|
double rel_speed = freq/basef;
|
|
music_buffer = new uint8_t[buffsize];
|
|
std::memset(music_buffer, 0, buffsize);
|
|
music_source = std::make_shared<WaveSource>(
|
|
WaveSource::WavePtr(music_buffer,music_buffer_deleter),
|
|
WaveSource::Format::stereo_int16, rel_speed,
|
|
buffsize/sizeof(WaveSource::StereoInt16),0);
|
|
|
|
sound_mixer.set_track(track_id, WaveMixer<2>(music_source,{music_volume*master_volume,music_volume*master_volume},1.0));
|
|
track_id = track_id ^ 1;
|
|
music_buffer_write_pos = buffsize/2;
|
|
}
|
|
|
|
static std::unique_ptr<IMusicStream> load_music_ex(const char *name) {
|
|
if (name) {
|
|
TMUSIC_STREAM *stream = music_open(name);
|
|
if (stream) {
|
|
TMUSIC_STREAM_INFO nfo = music_get_info(stream);
|
|
if (nfo.channels != 2) {
|
|
SEND_LOG("(MUSIC) Reject music - only stereo is supported");
|
|
} else if (nfo.format != 2){
|
|
SEND_LOG("(MUSIC) Reject music - only 16bit is supported");
|
|
} else {
|
|
return std::unique_ptr<IMusicStream>(static_cast<IMusicStream *>(stream));
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
|
|
}
|
|
|
|
static void handle_end_of_song() {
|
|
current_music.reset();
|
|
if (end_of_song_callback != NULL) {
|
|
const char *new_music = end_of_song_callback(end_of_song_callback_ctx);
|
|
current_music = load_music_ex(new_music);
|
|
if (current_music) {
|
|
auto nfo = current_music->get_info();
|
|
create_new_music_track(nfo.freq, BACK_BUFF_SIZE);
|
|
mix_back_sound(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
int mix_back_sound(int _) {
|
|
|
|
if (current_music==NULL) {
|
|
stop_music();
|
|
} else {
|
|
auto data = current_music->read();
|
|
while (!data.empty()) {
|
|
auto sz = copy_to_music_buffer(data.data(), data.size());
|
|
if (sz == 0) {
|
|
current_music->put_back(data);
|
|
break;
|
|
}
|
|
data = data.substr(sz);
|
|
if (data.empty()) data = current_music->read();
|
|
}
|
|
if (data.empty()) {
|
|
handle_end_of_song();
|
|
return mix_back_sound(_);
|
|
}
|
|
}
|
|
return 1;
|
|
|
|
}
|
|
|
|
//int open_backsound(char *filename);
|
|
void change_music(const char *filename) {
|
|
if (music_source) {
|
|
fade_music();
|
|
stop_music();
|
|
}
|
|
current_music = load_music_ex(filename);
|
|
if (current_music) {
|
|
auto nfo = current_music->get_info();
|
|
create_new_music_track(nfo.freq, BACK_BUFF_SIZE);
|
|
mix_back_sound(0);
|
|
}
|
|
}
|
|
|
|
//char *device_name(int device);
|
|
//void force_music_volume(int volume);
|
|
|
|
//void set_backsnd_freq(int freq);
|
|
|
|
char get_channel_state(int channel) {
|
|
char c = 0;
|
|
sound_mixer.visit_track(channel, [&](auto){c = 1;});
|
|
return c;
|
|
}
|
|
void get_channel_volume(int channel,int *left,int *right) {
|
|
VolumePreset &vp = channel_volume_presset[channel];
|
|
*left = static_cast<int>(vp.left*32768.0);
|
|
*right = static_cast<int>(vp.right*32768.0);
|
|
}
|
|
void mute_channel(int channel) {
|
|
sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){
|
|
mx.get_source()->stop();
|
|
});
|
|
}
|
|
void chan_break_loop(int channel) {
|
|
sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){
|
|
mx.get_source()->break_loop();
|
|
});
|
|
|
|
}
|
|
void chan_break_ext(int channel,const void *org_sample,int32_t size_sample) {
|
|
sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){
|
|
auto src = mx.get_source();
|
|
src->break_loop(src->length_in_samples(src->get_format(), size_sample));
|
|
});
|
|
|
|
}
|
|
|
|
|
|
static void update_music_volume(){
|
|
float v = music_volume * master_volume;
|
|
for (int i = 0; i <2; i++)
|
|
sound_mixer.visit_track(music_track_id_base+i,[&](WaveMixer<2> &m){
|
|
m.set_channel_volume(v, v);
|
|
});
|
|
}
|
|
|
|
char set_snd_effect(AUDIO_PROPERTY funct,int data) {
|
|
switch (funct) {
|
|
case SND_PING: break;
|
|
case SND_GFX: sound_effect_volume = data/256.0;break;
|
|
case SND_MUSIC: music_volume = data/128.0;update_music_volume();break;
|
|
case SND_GVOLUME: master_volume = data/256.0;update_music_volume();break;
|
|
case SND_SWAP: swap_channels = !!data;break;
|
|
default: return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
char check_snd_effect(AUDIO_PROPERTY funct) {
|
|
if (funct == SND_PING|| funct == SND_GFX || funct == SND_MUSIC || funct == SND_GVOLUME) return 1;
|
|
return 0;
|
|
}
|
|
int get_snd_effect(AUDIO_PROPERTY funct) {
|
|
switch (funct) {
|
|
case SND_PING: return 1;
|
|
case SND_GFX: return static_cast<int>(sound_effect_volume * 256.0);break;
|
|
case SND_MUSIC: return static_cast<int>(music_volume * 128.0);break;
|
|
case SND_GVOLUME: return static_cast<int>(master_volume * 256.0);break;
|
|
case SND_SWAP:return swap_channels?1:0;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
void *PrepareVideoSound(int mixfreq, int buffsize) {
|
|
create_new_music_track(mixfreq, buffsize);
|
|
return 0;
|
|
}
|
|
char LoadNextVideoFrame(void *buffer, const char *data, int size, const short *xlat, short *accnums, int32_t *writepos) {
|
|
std::size_t rdpos = music_source->get_output_position_bytes();
|
|
std::size_t wrpos = *writepos;
|
|
auto l = music_source->length_bytes();
|
|
auto space = (rdpos < wrpos?l:0) + rdpos - wrpos;
|
|
if (space < static_cast<std::size_t>(size)*2) {
|
|
sound_mixer.wait_for_advance_write_pos();
|
|
return 0;
|
|
}
|
|
short *wave = reinterpret_cast<short *>(music_buffer);
|
|
auto w = wrpos/2;
|
|
auto ls = l /2;
|
|
for (int i = 0; i < size; ++i) {
|
|
auto acc = accnums+(i & 1);
|
|
*acc = wave[(w+i) % ls] = xlat[static_cast<uint8_t>(data[i])]+*acc;
|
|
}
|
|
music_buffer_write_pos = *writepos = (wrpos + size*2)%l;
|
|
return 1;
|
|
|
|
}
|
|
void DoneVideoSound(void *buffer) {
|
|
//empty
|
|
}
|
|
|
|
const char *device_name(int )
|
|
{
|
|
return "SDL sound device";
|
|
}
|
|
|
|
|
|
|