Implement System40.ini/AliceStart.ini mixers

Implement "VolumeValancer" mixer channels from the .ini file. Mixers are
now recursive, with the master and voice channels being parent nodes
(this seems to be hardcoded in system 4, or at least I haven't found
where the hierarchy is specified).

Also implement .wai file support (needed for mixer channel info on sound
files).
This commit is contained in:
Nunuhara Cabbage
2021-10-03 09:26:48 -07:00
parent 96356094d9
commit f53184d8f7
7 changed files with 275 additions and 66 deletions
+15
View File
@@ -28,10 +28,25 @@ struct bgi {
int volume;
};
struct wai {
//int uk1;
//int uk2;
int channel;
//int uk3; // version 4+
};
void bgi_read(const char *path);
struct bgi *bgi_get(int no);
void wai_load(const char *path);
struct wai *wai_get(int no);
void mixer_init(void);
int mixer_get_numof(void);
const char *mixer_get_name(int n);
int mixer_get_volume(int n, int *volume);
int mixer_set_volume(int n, int volume);
int mixer_get_mute(int n, int *mute);
int mixer_set_mute(int n, int mute);
struct channel;
enum asset_type;
+3 -2
View File
@@ -341,8 +341,9 @@ void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples)
}
// write to buffer
left = sts_mixer__clamp_sample(left);
right = sts_mixer__clamp_sample(right);
// NOTE: xsystem4 change: use mixer gain (not sure why this isn't implemented upstream...)
left = sts_mixer__clamp_sample(left * mixer->gain);
right = sts_mixer__clamp_sample(right * mixer->gain);
switch (mixer->audio_format) {
case STS_MIXER_SAMPLE_FORMAT_8:
*out_8++ = (char)(left * 127.0f);
+2
View File
@@ -30,6 +30,8 @@ struct config {
int view_height;
size_t mixer_nr_channels;
char **mixer_channels;
int *mixer_volumes;
int default_volume;
char *bgi_path;
char *wai_path;
+59
View File
@@ -15,8 +15,11 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include "system4.h"
#include "system4/file.h"
#include "mixer.h"
#include "little_endian.h"
#define BGI_MAX 100
@@ -91,3 +94,59 @@ struct bgi *bgi_get(int no)
}
return NULL;
}
static struct wai *wai_data = NULL;
static int nr_wai = 0;
void wai_load(const char *path)
{
size_t len;
uint8_t *file = file_read(path, &len);
if (len < 48 || file[0] != 'X' || file[1] != 'I' || file[2] != '2' || file[3] != '\0') {
WARNING("Not a .wai file: %s", path);
goto end;
}
int count = LittleEndian_getDW(file, 8) - 1;
if (count < 0) {
WARNING("Invalid .wai count: %d", count);
goto end;
}
int version = LittleEndian_getDW(file, 12);
if (version < 3 || version > 4) {
WARNING("Unsupported .wai version: %d", version);
goto end;
}
uint8_t *data;
int record_size;
if (version == 4) {
data = file + 40;
record_size = 4*4;
} else {
data = file + 36;
record_size = 3*4;
}
if (len - (version == 4 ? 40 : 36) < (size_t)count*record_size) {
WARNING(".wai file truncated");
goto end;
}
wai_data = xcalloc(count, sizeof(struct wai));
for (int i = 0; i < count; i++) {
wai_data[i].channel = LittleEndian_getDW(data, i*record_size + 8);
}
nr_wai = count;
end:
free(file);
}
struct wai *wai_get(int no)
{
no--;
if (no < 0 || no >= nr_wai)
return NULL;
return &wai_data[no];
}
+180 -20
View File
@@ -28,6 +28,8 @@
#include "mixer.h"
#include "xsystem4.h"
#define clamp(min_value, max_value, value) min(max_value, max(min_value, value))
/*
* The actual mixer implementation is contained in this header.
* The rest of this file implements a high level interface for managing mixer
@@ -51,6 +53,7 @@ struct channel {
// archive data
struct archive_data *dfile;
int no;
int mixer_no;
// audio file data
SNDFILE *file;
@@ -73,15 +76,34 @@ struct channel {
struct fade fade;
};
struct mixer {
sts_mixer_t mixer;
sts_mixer_stream_t stream;
int voice;
atomic_bool muted;
float data[CHUNK_SIZE * 2];
char *name;
struct mixer *parent;
struct mixer **children;
int nr_children;
};
static struct mixer *master = NULL;
static struct mixer *mixers = NULL;
static int nr_mixers = 0;
static SDL_AudioDeviceID audio_device = 0;
static sts_mixer_t mixer;
/*
* The SDL2 audio callback.
*/
static void audio_callback(possibly_unused void *data, Uint8 *stream, int len)
{
sts_mixer_mix_audio(&mixer, stream, len / (sizeof(float) * 2));
sts_mixer_mix_audio(&master->mixer, stream, len / (sizeof(float) * 2));
if (master->muted) {
memset(stream, 0, len);
}
}
/*
@@ -122,7 +144,6 @@ static bool cb_loop(struct channel *ch)
* Callback to refill the stream's audio data.
* Called from sys_mixer_mix_audio.
*/
//static int refill_stream(sts_mixer_sample_t *sample, void *data)
static int cb_read_frames(struct channel *ch, float *out, sf_count_t frame_count)
{
sf_count_t num_read = 0;
@@ -210,7 +231,7 @@ static int refill_stream(sts_mixer_sample_t *sample, void *data)
// set gain for fade
if (ch->fade.fading) {
float gain = cb_calc_fade(&ch->fade, ch->frame);
mixer.voices[ch->voice].gain = gain;
mixers[ch->mixer_no].mixer.voices[ch->voice].gain = gain;
ch->volume = gain * 100.0;
if (ch->frame >= ch->fade.end_pos) {
@@ -224,6 +245,17 @@ static int refill_stream(sts_mixer_sample_t *sample, void *data)
return r;
}
static int refill_mixer(sts_mixer_sample_t *sample, void *data)
{
struct mixer *mixer = data;
sts_mixer_mix_audio(&mixer->mixer, &mixer->data, CHUNK_SIZE);
if (mixer->muted) {
memset(mixer->data, 0, sizeof(float) * sample->length);
}
return STS_STREAM_CONTINUE;
}
int channel_play(struct channel *ch)
{
SDL_LockAudioDevice(audio_device);
@@ -231,7 +263,7 @@ int channel_play(struct channel *ch)
SDL_UnlockAudioDevice(audio_device);
return 0;
}
ch->voice = sts_mixer_play_stream(&mixer, &ch->stream, 1.0f);
ch->voice = sts_mixer_play_stream(&mixers[ch->mixer_no].mixer, &ch->stream, 1.0f);
SDL_UnlockAudioDevice(audio_device);
return 1;
}
@@ -243,7 +275,7 @@ int channel_stop(struct channel *ch)
SDL_UnlockAudioDevice(audio_device);
return 0;
}
sts_mixer_stop_voice(&mixer, ch->voice);
sts_mixer_stop_voice(&mixers[ch->mixer_no].mixer, ch->voice);
ch->voice = -1;
SDL_UnlockAudioDevice(audio_device);
return 1;
@@ -291,7 +323,7 @@ int channel_fade(struct channel *ch, int time, int volume, bool stop)
ch->fade.start_pos = ch->frame;
ch->fade.start_volume = (float)ch->volume / 100.0;
ch->fade.end_pos = ch->fade.start_pos + ((time * ch->info.samplerate) / 1000);
ch->fade.end_volume = min(1.0, max(0.0, (float)volume / 100.0));
ch->fade.end_volume = clamp(0.0f, 1.0f, (float)volume / 100.0f);
SDL_UnlockAudioDevice(audio_device);
return 1;
}
@@ -395,7 +427,7 @@ static sf_count_t channel_vio_seek(sf_count_t offset, int whence, void *data)
ch->offset = ch->dfile->size + offset;
break;
}
ch->offset = min((sf_count_t)ch->dfile->size, max(0, ch->offset));
ch->offset = clamp(0, (sf_count_t)ch->dfile->size, ch->offset);
return ch->offset;
}
@@ -459,17 +491,28 @@ struct channel *channel_open(enum asset_type type, int no)
ch->stream.sample.data = ch->data;
ch->voice = -1;
struct bgi *bgi = bgi_get(no);
if (bgi) {
ch->volume = min(100, max(0, bgi->volume));
ch->loop_start = min(ch->info.frames, max(0, bgi->loop_start));
ch->loop_end = min(ch->info.frames, max(0, bgi->loop_end));
ch->loop_count = max(0, bgi->loop_count);
} else {
if (type == ASSET_SOUND) {
struct wai *wai = wai_get(no);
ch->volume = 100;
ch->loop_start = 0;
ch->loop_end = ch->info.frames;
ch->loop_count = type == ASSET_SOUND ? 1 : 0;
ch->loop_count = 1;
ch->mixer_no = wai ? wai->channel : 1;
} else {
struct bgi *bgi = bgi_get(no);
if (bgi) {
ch->volume = clamp(0, 100, bgi->volume);
ch->loop_start = clamp(0, ch->info.frames, bgi->loop_start);
ch->loop_end = clamp(0, ch->info.frames, bgi->loop_end);
ch->loop_count = max(0, bgi->loop_count);
ch->mixer_no = clamp(0, nr_mixers, bgi->channel);
} else {
ch->volume = 100;
ch->loop_start = 0;
ch->loop_end = ch->info.frames;
ch->loop_count = 0;
ch->mixer_no = 0;
}
}
ch->no = no;
@@ -490,8 +533,81 @@ void channel_close(struct channel *ch)
free(ch);
}
#define SJIS_MASTER "\x83\x7d\x83\x58\x83\x5e\x81\x5b"
#define SJIS_VOICE "\x89\xb9\x90\xba"
void mixer_init(void)
{
// initialize mixer naming
if (!config.mixer_nr_channels) {
nr_mixers = 3;
mixers = xcalloc(nr_mixers, sizeof(struct mixer));
mixers[0].name = "Music";
mixers[1].name = "Sound";
mixers[2].name = "Master";
} else {
// NOTE: Older games don't have an explicit master channel.
// We add an extra mixer in this case for consistency.
int need_master = 1;
if (!strcmp(config.mixer_channels[0], "Master") || !strcmp(config.mixer_channels[0], SJIS_MASTER)) {
need_master = 0;
}
nr_mixers = config.mixer_nr_channels + need_master;
mixers = xcalloc(nr_mixers, sizeof(struct mixer));
for (unsigned i = 0; i < config.mixer_nr_channels; i++) {
mixers[i].name = config.mixer_channels[i];
}
if (need_master) {
mixers[nr_mixers-1].name = "Master";
master = &mixers[nr_mixers-1];
} else {
master = &mixers[0];
}
}
// initialize mixer hierarchy
// NOTE: "Master" and "Voice" are special nodes; all channels following
// them in the list become their children. This appears to be
// hardcoded behavior in system 4.
struct mixer *parent = master;
for (int i = 0; i < nr_mixers; i++) {
if (&mixers[i] == master)
continue;
mixers[i].parent = parent;
parent->children = xrealloc_array(parent->children, parent->nr_children, parent->nr_children+1, sizeof(struct mixer*));
parent->children[parent->nr_children++] = &mixers[i];
if (!strcmp(mixers[i].name, "Voice") || !strcmp(mixers[i].name, SJIS_VOICE)) {
parent = &mixers[i];
}
}
// initialize mixers
for (int i = 0; i < nr_mixers; i++) {
sts_mixer_init(&mixers[i].mixer, 44100, STS_MIXER_SAMPLE_FORMAT_FLOAT);
int volume = i < (int)config.mixer_nr_channels ? config.mixer_volumes[i] : config.default_volume;
mixers[i].mixer.gain = clamp(0.0f, 1.0f, (float)volume / 100.0f);
}
// initialize mixer streams
for (int i = 0; i < nr_mixers; i++) {
if (&mixers[i] == master)
continue;
mixers[i].stream.userdata = &mixers[i];
mixers[i].stream.callback = refill_mixer;
mixers[i].stream.sample.frequency = 44100;
mixers[i].stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_FLOAT;
mixers[i].stream.sample.length = CHUNK_SIZE * 2;
mixers[i].stream.sample.data = mixers[i].data;
mixers[i].voice = sts_mixer_play_stream(&mixers[i].parent->mixer, &mixers[i].stream, 1.0f);
}
// read audio metadata
if (config.bgi_path)
bgi_read(config.bgi_path);
if (config.wai_path)
wai_load(config.wai_path);
// initialize SDL audio
SDL_AudioSpec have;
SDL_AudioSpec want = {
.format = AUDIO_F32,
@@ -501,9 +617,53 @@ void mixer_init(void)
.callback = audio_callback,
};
audio_device = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
sts_mixer_init(&mixer, 44100, STS_MIXER_SAMPLE_FORMAT_FLOAT);
if (config.bgi_path)
bgi_read(config.bgi_path);
SDL_PauseAudioDevice(audio_device, 0);
}
int mixer_get_numof(void)
{
return nr_mixers;
}
const char *mixer_get_name(int n)
{
if (n < 0 || n >= nr_mixers)
return NULL;
return mixers[n].name;
}
int mixer_get_volume(int n, int *volume)
{
if (n < 0 || n >= nr_mixers) {
return 0;
}
SDL_LockAudioDevice(audio_device);
*volume = clamp(0, 100, (int)(mixers[n].mixer.gain * 100));
SDL_UnlockAudioDevice(audio_device);
return 1;
}
int mixer_set_volume(int n, int volume)
{
if (n < 0 || n >= nr_mixers)
return 0;
SDL_LockAudioDevice(audio_device);
mixers[n].mixer.gain = clamp(0.0f, 1.0f, (float)volume / 100.0f);
SDL_UnlockAudioDevice(audio_device);
return 1;
}
int mixer_get_mute(int n, int *mute)
{
if (n < 0 || n >= nr_mixers)
return 0;
*mute = mixers[n].muted;
return 1;
}
int mixer_set_mute(int n, int mute)
{
if (n < 0 || n >= nr_mixers)
return 0;
mixers[n].muted = !!mute;
return 1;
}
+9 -44
View File
@@ -18,56 +18,21 @@
#include "system4/string.h"
#include "mixer.h"
#include "xsystem4.h"
#include "hll.h"
static int SystemService_GetMixerNumof(void)
{
return config.mixer_nr_channels;
}
static int SystemService_GetMixerName(int n, struct string **name)
{
if (n < 0 || (size_t)n >= config.mixer_nr_channels)
const char *r = mixer_get_name(n);
if (!r)
return 0;
*name = make_string(config.mixer_channels[n], strlen(config.mixer_channels[n]));
*name = make_string(r, strlen(r));
return 1;
}
static int SystemService_GetMixerVolume(int n, int *volume)
{
if (n < 0 || (size_t)n >= config.mixer_nr_channels)
return 0;
*volume = 100;
return 1;
}
static int SystemService_GetMixerMute(int n, int *mute)
{
if (n < 0 || (size_t)n >= config.mixer_nr_channels)
return 0;
*mute = 0;
return true;
}
//bool SystemService_SetMixerName(int nNum, string szName);
static int SystemService_SetMixerVolume(int n, possibly_unused int volume)
{
if (n < 0 || (size_t)n >= config.mixer_nr_channels)
return 0;
// TODO
return 1;
}
static int SystemService_SetMixerMute(int n, possibly_unused int mute)
{
if (n < 0 || (size_t)n >= config.mixer_nr_channels)
return 0;
// TODO
return 1;
}
//int SystemService_GetGameVersion(void);
static void SystemService_GetGameName(struct string **game_name)
{
@@ -163,13 +128,13 @@ static bool SystemService_GetMouseCursorConfig(int type, int *value)
static void SystemService_RestrainScreensaver(void) { }
HLL_LIBRARY(SystemService,
HLL_EXPORT(GetMixerNumof, SystemService_GetMixerNumof),
HLL_EXPORT(GetMixerNumof, mixer_get_numof),
HLL_EXPORT(GetMixerName, SystemService_GetMixerName),
HLL_EXPORT(GetMixerVolume, SystemService_GetMixerVolume),
HLL_EXPORT(GetMixerMute, SystemService_GetMixerMute),
HLL_EXPORT(GetMixerVolume, mixer_get_volume),
HLL_EXPORT(GetMixerMute, mixer_get_mute),
HLL_TODO_EXPORT(SetMixerName, SystemService_SetMixerName),
HLL_EXPORT(SetMixerVolume, SystemService_SetMixerVolume),
HLL_EXPORT(SetMixerMute, SystemService_SetMixerMute),
HLL_EXPORT(SetMixerVolume, mixer_set_volume),
HLL_EXPORT(SetMixerMute, mixer_set_mute),
HLL_TODO_EXPORT(GetGameVersion, SystemService_GetGameVersion),
HLL_EXPORT(GetGameName, SystemService_GetGameName),
HLL_TODO_EXPORT(AddURLMenu, SystemService_AddURLMenu),
+7
View File
@@ -51,6 +51,7 @@ struct config config = {
.view_height = 600,
.mixer_nr_channels = 0,
.mixer_channels = NULL,
.default_volume = 100,
.joypad = false,
};
@@ -120,6 +121,7 @@ static void read_mixer_channels(struct ini_entry *entry)
config.mixer_nr_channels = entry->value.list_size;
config.mixer_channels = xcalloc(entry->value.list_size, sizeof(char*));
config.mixer_volumes = xcalloc(entry->value.list_size, sizeof(int));
for (size_t i = 0; i < entry->value.list_size; i++) {
if (entry->value.list[i].type == INI_NULL) {
WARNING("No name given for VolumeValancer[%d]", i);
@@ -127,6 +129,8 @@ static void read_mixer_channels(struct ini_entry *entry)
}
if (entry->value.list[i].type == INI_STRING) {
config.mixer_channels[i] = strdup(entry->value.list[i].s->text);
// XXX: assumes default_volume was already initialized
config.mixer_volumes[i] = config.default_volume;
} else if (entry->value.list[i].type == INI_LIST) {
if (entry->value.list[i].list_size != 2)
ERROR("ini value for 'VolumeValancer' is list of wrong size");
@@ -135,6 +139,7 @@ static void read_mixer_channels(struct ini_entry *entry)
if (entry->value.list[i].list[1].type != INI_INTEGER)
ERROR("ini value for 'VolumeValancer' level is not an integer");
config.mixer_channels[i] = strdup(entry->value.list[i].list[0].s->text);
config.mixer_volumes[i] = entry->value.list[i].list[1].i;
}
}
}
@@ -159,6 +164,8 @@ static void read_config(const char *path)
config.view_height = ini_integer(&ini[i]);
} else if (!strcmp(ini[i].name->text, "VolumeValancer")) {
read_mixer_channels(&ini[i]);
} else if (!strcmp(ini[i].name->text, "DefaultVolumeRate")) {
config.default_volume = ini_integer(&ini[i]);
}
ini_free_entry(&ini[i]);
}