From fc8e7eee22c05edebfb2dffa5e89d4149e42af85 Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Thu, 30 Sep 2021 21:38:14 -0700 Subject: [PATCH] Replace SDL_mixer with libsndfile + sts_mixer Replace SDL_mixer with an audio implementation using libsndfile, sts_mixer (a single-file header library) and SDL's basic audio interface. This allows for much more flexibility in controlling audio playback. Fading is now properly implemented, as well as various other functions from the SACT2/KiwiSoundEngine interface. sts_mixer can support recursive mixing, so it will be possible to implement the "VolumeValancer" mixer channels specified in System40.ini in the future (once I figure out how the mapping of audio files to mixer channels is supposed to work). --- include/audio.h | 99 +++---- include/id_pool.h | 31 ++ include/mixer.h | 51 ++++ include/sts_mixer.h | 512 ++++++++++++++++++++++++++++++++ meson.build | 5 +- src/audio.c | 604 +++++++++++++++----------------------- src/audio_mixer.c | 501 +++++++++++++++++++++++++++++++ src/hll/KiwiSoundEngine.c | 124 +++----- src/hll/SACT2.c | 58 ++-- src/id_pool.c | 75 +++++ src/meson.build | 5 +- 11 files changed, 1513 insertions(+), 552 deletions(-) create mode 100644 include/id_pool.h create mode 100644 include/mixer.h create mode 100644 include/sts_mixer.h create mode 100644 src/audio_mixer.c create mode 100644 src/id_pool.c diff --git a/include/audio.h b/include/audio.h index d23d69a..307f943 100644 --- a/include/audio.h +++ b/include/audio.h @@ -26,56 +26,57 @@ bool audio_play_sound(int sound_no); bool wav_exists(int no); bool bgm_exists(int no); -int wav_prepare(int ch, int no); -int bgm_prepare(int ch, int no); -int wav_unprepare(int ch); -int bgm_unprepare(int ch); -int wav_play(int ch); -int bgm_play(int ch); -int wav_stop(int ch); -int bgm_stop(int ch); -bool wav_is_playing(int ch); -bool bgm_is_playing(int ch); -//int wav_set_loop_count(int channel, int count); -//int bgm_set_loop_count(int channel, int count); -//int wav_get_loop_count(int channel); -//int bgm_get_loop_count(int channel); -//int wav_set_loop_start_pos(int channel, int pos); -//int bgm_set_loop_start_pos(int channel, int pos); -//int wav_set_loop_end_pos(int channel, int pos); -//int bgm_set_loop_end_pos(int channel, int pos); -int wav_fade(int ch, int time, int volume, bool stop); -int bgm_fade(int ch, int time, int volume, bool stop); -//int wav_stop_fade(int channel); -//int bgm_stop_fade(int channel); -//bool wav_is_fading(int channel); -//bool bgm_is_fading(int channel); -//int wav_pause(int channel); -//int bgm_pause(int channel); -//int wav_restart(int channel); -//int bgm_restart(int channel); -//bool wav_is_paused(int channel); -//bool bgm_is_paused(int channel); -//int wav_get_pos(int channel); -//int bgm_get_pos(int channel); -//int wav_get_length(int channel); -//int bgm_get_length(int channel); -//int wav_get_sample_pos(int channel); -//int bgm_get_sample_pos(int channel); -//int wav_get_sample_length(int channel); -//int bgm_get_sample_length(int channel); -//int wav_seek(int channel, int pos); -//int bgm_seek(int channel, int pos); +int wav_prepare(int id, int no); +int bgm_prepare(int id, int no); +int wav_unprepare(int id); +int bgm_unprepare(int id); +int wav_play(int id); +int bgm_play(int id); +int wav_stop(int id); +int bgm_stop(int id); +bool wav_is_playing(int id); +bool bgm_is_playing(int id); +int wav_set_loop_count(int id, int count); +int bgm_set_loop_count(int id, int count); +int wav_get_loop_count(int id); +int bgm_get_loop_count(int id); +int wav_set_loop_start_pos(int id, int pos); +int bgm_set_loop_start_pos(int id, int pos); +int wav_set_loop_end_pos(int id, int pos); +int bgm_set_loop_end_pos(int id, int pos); +int wav_fade(int id, int time, int volume, bool stop); +int bgm_fade(int id, int time, int volume, bool stop); +int wav_stop_fade(int id); +int bgm_stop_fade(int id); +bool wav_is_fading(int id); +bool bgm_is_fading(int id); +int wav_pause(int id); +int bgm_pause(int id); +int wav_restart(int id); +int bgm_restart(int id); +bool wav_is_paused(int id); +bool bgm_is_paused(int id); +int wav_get_pos(int id); +int bgm_get_pos(int id); +int wav_get_length(int id); +int bgm_get_length(int id); +int wav_get_sample_pos(int id); +int bgm_get_sample_pos(int id); +int wav_get_sample_length(int id); +int bgm_get_sample_length(int id); +int wav_seek(int id, int pos); +int bgm_seek(int id, int pos); int wav_get_unused_channel(void); int bgm_get_unused_channel(void); -int wav_reverse_LR(int ch); -//int bgm_reverse_LR(int channel); -//int wav_get_volume(int channel); -//int bgm_get_volume(int channel); -int wav_get_time_length(int ch); -//int wav_get_group_num(int channel); -//int bgm_get_group_num(int channel); -//int wav_prepare_from_file(int channel, char *filename); -//int bgm_prepare_from_file(int channel, char *filename); +int wav_reverse_LR(int id); +int bgm_reverse_LR(int id); +int wav_get_volume(int id); +int bgm_get_volume(int id); +int wav_get_time_length(int id); +int bgm_get_time_length(int id); +//int wav_get_group_num(int id); +//int bgm_get_group_num(int id); +//int wav_prepare_from_file(int id, char *filename); +//int bgm_prepare_from_file(int id, char *filename); #endif /* SYSTEM4_AUDIO_H */ diff --git a/include/id_pool.h b/include/id_pool.h new file mode 100644 index 0000000..42a17c0 --- /dev/null +++ b/include/id_pool.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program 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 2 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 . + */ + +#ifndef SYSTEM4_ID_POOL_H +#define SYSTEM4_ID_POOL_H + +struct id_pool { + void **slots; + int nr_slots; +}; + +void id_pool_init(struct id_pool *pool); +void id_pool_release(struct id_pool *pool, int id); +int id_pool_get_unused(struct id_pool *pool); +void *id_pool_get(struct id_pool *pool, int id); +void *id_pool_set(struct id_pool *pool, int id, void *data); + +#endif /* SYSTEM4_ID_POOL_H */ diff --git a/include/mixer.h b/include/mixer.h new file mode 100644 index 0000000..9f32c24 --- /dev/null +++ b/include/mixer.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program 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 2 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 . + */ + +#ifndef SYSTEM4_MIXER_H +#define SYSTEM4_MIXER_H + +#include + +void mixer_init(void); + +struct channel; +enum asset_type; + +struct channel *channel_open(enum asset_type type, int no); +void channel_close(struct channel *ch); +int channel_play(struct channel *ch); +int channel_stop(struct channel *ch); +int channel_is_playing(struct channel *ch); +int channel_set_loop_count(struct channel *ch, int count); +int channel_get_loop_count(struct channel *ch); +int channel_set_loop_start_pos(struct channel *ch, int pos); +int channel_set_loop_end_pos(struct channel *ch, int pos); +int channel_fade(struct channel *ch, int time, int volume, bool stop); +int channel_stop_fade(struct channel *ch); +int channel_is_fading(struct channel *ch); +int channel_pause(struct channel *ch); +int channel_restart(struct channel *ch); +int channel_is_paused(struct channel *ch); +int channel_get_pos(struct channel *ch); +int channel_get_length(struct channel *ch); +int channel_get_sample_pos(struct channel *ch); +int channel_get_sample_length(struct channel *ch); +int channel_seek(struct channel *ch, int pos); +int channel_reverse_LR(struct channel *ch); +int channel_get_volume(struct channel *ch); +int channel_get_time_length(struct channel *ch); + +#endif /* SYSTEM4_MIXER_H */ diff --git a/include/sts_mixer.h b/include/sts_mixer.h new file mode 100644 index 0000000..7336eed --- /dev/null +++ b/include/sts_mixer.h @@ -0,0 +1,512 @@ +/////////////////////////////////////////////////////////////////////////////// +// sts_mixer.h - v0.01 +// written 2016 by Sebastian Steinhauer +// +// LICENSE +// Public domain. See "unlicense" statement at the end of this file. +// +// ABOUT +// A simple stereo audio mixer which is capable of mixing samples and audio streams. +// Samples can be played with different gain, pitch and panning. +// Streams can be played with different gain. +// This library has no malloc/free. All structs have to be "prepared" by the user. So you can enroll your own memory management. +// You have to implement/provide a real audio-backend to hear something from the speakers. +// A good starting point would be SDL2 where you can use an audio callback to feed the audio device. +// +// USAGE +// Please note that most audio systems will run in a separate thread. So you have to take care about locking before modifying the sts_mixer_t state. +// See the example at the end of the file. +// +// VERSION HISTORY +// 0.01 (2016-05-01) initial version +// +#ifndef __INCLUDED__STS_MIXER_H__ +#define __INCLUDED__STS_MIXER_H__ + + +// The number of concurrent voices (channels) which are used to mix the audio. +// If you need more, use a higher number by setting #define STS_MIXER_VOICE n before including this header. +#ifndef STS_MIXER_VOICES +#define STS_MIXER_VOICES 32 +#endif // STS_MIXER_VOICES + +// Defines the various audio formats. Note that they are all on system endianess. +enum { + STS_MIXER_SAMPLE_FORMAT_NONE, // no format + STS_MIXER_SAMPLE_FORMAT_8, // signed 8-bit + STS_MIXER_SAMPLE_FORMAT_16, // signed 16-bit + STS_MIXER_SAMPLE_FORMAT_32, // signed 32-bit + STS_MIXER_SAMPLE_FORMAT_FLOAT // floats +}; + + +//////////////////////////////////////////////////////////////////////////////// +// +// SAMPLES +// +// A sample is a *MONO* piece of audio which is loaded fully to memory. +// It can be played with various gains, pitches and pannings. +// +typedef struct { + unsigned int length; // length in samples (so 1024 samples of STS_MIXER_SAMPLE_FORMAT_16 would be 2048 bytes) + unsigned int frequency; // frequency of this sample (e.g. 44100, 22000 ...) + int audio_format; // one of STS_MIXER_SAMPLE_FORMAT_* + void* data; // pointer to the sample data, sts_mixer makes no copy, so you have to keep them in memory +} sts_mixer_sample_t; + + +//////////////////////////////////////////////////////////////////////////////// +// +// STREAMS +// +// A stream is *STEREO* audio which will be decoded/loaded as needed. +// It can be played with various gains. No panning or pitching. +// + +// The callback which will be called when the stream needs more data. +typedef int (*sts_mixer_stream_callback)(sts_mixer_sample_t* sample, void* userdata); + +typedef struct { + void* userdata; // a userdata pointer which will passed to the callback + sts_mixer_stream_callback callback; // this callback will be called when the stream needs more data + sts_mixer_sample_t sample; // the current stream "sample" which holds the current piece of audio +} sts_mixer_stream_t; + + +//////////////////////////////////////////////////////////////////////////////// +// +// VOICES +// +// A voice is an audio source which will be used during mixing. +// It can play nothing, a sample or a stream. +// Most of those fields are considered "private" and you should not play around with those. +// +typedef struct { + int state; + sts_mixer_sample_t* sample; + sts_mixer_stream_t* stream; + float position; + float gain; + float pitch; + float pan; +} sts_mixer_voice_t; + + +//////////////////////////////////////////////////////////////////////////////// +// +// MIXER +// +// The mixer state. +// +typedef struct { + float gain; // the global gain (you can change it if you want to change to overall volume) + unsigned int frequency; // the frequency for the output of mixed audio data + int audio_format; // the audio format for the output of mixed audio data + sts_mixer_voice_t voices[STS_MIXER_VOICES]; // holding all audio voices for this state +} sts_mixer_t; + + +//////////////////////////////////////////////////////////////////////////////// +// +// API +// + +// "Initializes" a new sts_mixer state. +void sts_mixer_init(sts_mixer_t* mixer, unsigned int frequency, int audio_format); +// "Shutdown" the mixer state. It will simply reset all fields. +void sts_mixer_shutdown(sts_mixer_t* mixer); + +// Return the number of active voices. Active voices are voices that play either a stream or a sample. +int sts_mixer_get_active_voices(sts_mixer_t* mixer); + +// Play the given sample with the gain, pitch and panning. +// Panning can be something between -1.0f (fully left) ... +1.0f (fully right) +// Please note that pitch will be clamped so it cannot reach 0.0f (would be useless). +// Returns the number of the voice where this sample will be played or -1 if no voice was free. +int sts_mixer_play_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample, float gain, float pitch, float pan); + +// Plays the given stream with the gain. +// Returns the number of the voice where this stream will be played or -1 if no voice was free. +int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float gain); + +// Stops voice with the given voice no. You can pass the returned number of sts_mixer_play_sample / sts_mixer_play_stream here. +void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice); + +// Stops all voices playing the given sample. Useful when you want to delete the sample and make sure it is not used anymore. +void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample); + +// Stops all voices playing the given stream. Useful when you want to delete the stream and make sure it is not used anymore. +void sts_mixer_stop_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream); + +// The mixing function. You should call the function if you need to pass more audio data to the audio device. +// Typically this function is called in a separate thread or something like that. +// It will write audio data in the specified format and frequency of the mixer state. +void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples); + + +#endif // __INCLUDED__STS_MIXER_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// +#ifdef STS_MIXER_IMPLEMENTATION + +enum { + STS_MIXER_VOICE_STOPPED, + STS_MIXER_VOICE_PLAYING, + STS_MIXER_VOICE_STREAMING +}; + +enum { + STS_STREAM_COMPLETE = 0, + STS_STREAM_CONTINUE = 1 +}; + +static float sts_mixer__clamp(const float value, const float min, const float max) { + if (value < min) return min; + else if (value > max) return max; + else return value; +} + + +static float sts_mixer__clamp_sample(const float sample) { + if (sample < -1.0f) return -1.0f; + else if (sample > 1.0f) return 1.0f; + else return sample; +} + + +static float sts_mixer__get_sample(sts_mixer_sample_t* sample, unsigned int position) { + switch (sample->audio_format) { + case STS_MIXER_SAMPLE_FORMAT_8: + return (float)((char*)sample->data)[position] / 127.0f; + case STS_MIXER_SAMPLE_FORMAT_16: + return (float)((short*)sample->data)[position] / 32767.0f; + case STS_MIXER_SAMPLE_FORMAT_32: + return (float)((int*)sample->data)[position] / 2147483647.0f; + case STS_MIXER_SAMPLE_FORMAT_FLOAT: + return ((float*)sample->data)[position]; + default: + return 0.0f; + } +} + + +static void sts_mixer__reset_voice(sts_mixer_t* mixer, const int i) { + sts_mixer_voice_t* voice = &mixer->voices[i]; + voice->state = STS_MIXER_VOICE_STOPPED; + voice->sample = 0; + voice->stream = 0; + voice->position = voice->gain = voice->pitch = voice->pan = 0.0f; +} + + +static int sts_mixer__find_free_voice(sts_mixer_t* mixer) { + int i; + + for (i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].state == STS_MIXER_VOICE_STOPPED) return i; + } + return -1; +} + + +void sts_mixer_init(sts_mixer_t* mixer, unsigned int frequency, int audio_format) { + int i; + + for (i = 0; i < STS_MIXER_VOICES; ++i) sts_mixer__reset_voice(mixer, i); + mixer->frequency = frequency; + mixer->gain = 1.0f; + mixer->audio_format = audio_format; +} + + +void sts_mixer_shutdown(sts_mixer_t* mixer) { + sts_mixer_init(mixer, 0, 0); +} + + +int sts_mixer_get_active_voices(sts_mixer_t* mixer) { + int i, active; + for (i = 0, active = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].state != STS_MIXER_VOICE_STOPPED) ++active; + } + return active; +} + + +int sts_mixer_play_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample, float gain, float pitch, float pan) { + int i; + sts_mixer_voice_t* voice; + + i = sts_mixer__find_free_voice(mixer); + if (i >= 0) { + voice = &mixer->voices[i]; + voice->gain = gain; + voice->pitch = sts_mixer__clamp(pitch, 0.1f, 10.0f); + voice->pan = sts_mixer__clamp(pan * 0.5f, -0.5f, 0.5f); + voice->position = 0.0f; + voice->sample = sample; + voice->stream = 0; + voice->state = STS_MIXER_VOICE_PLAYING; + } + return i; +} + + +int sts_mixer_play_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream, float gain) { + int i; + sts_mixer_voice_t* voice; + + i = sts_mixer__find_free_voice(mixer); + if (i >= 0) { + voice = &mixer->voices[i]; + voice->gain = gain; + voice->position = 0.0f; + voice->sample = 0; + voice->stream = stream; + voice->state = STS_MIXER_VOICE_STREAMING; + } + return i; +} + + +void sts_mixer_stop_voice(sts_mixer_t* mixer, int voice) { + if (voice >= 0 && voice < STS_MIXER_VOICES) sts_mixer__reset_voice(mixer, voice); +} + + +void sts_mixer_stop_sample(sts_mixer_t* mixer, sts_mixer_sample_t* sample) { + int i; + + for (i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].sample == sample) sts_mixer__reset_voice(mixer, i); + } +} + + +void sts_mixer_stop_stream(sts_mixer_t* mixer, sts_mixer_stream_t* stream) { + int i; + + for (i = 0; i < STS_MIXER_VOICES; ++i) { + if (mixer->voices[i].stream == stream) sts_mixer__reset_voice(mixer, i); + } +} + + +void sts_mixer_mix_audio(sts_mixer_t* mixer, void* output, unsigned int samples) { + sts_mixer_voice_t* voice; + unsigned int i, position; + float left, right, advance, sample; + char* out_8 = (char*)output; + short* out_16 = (short*)output; + int* out_32 = (int*)output; + float* out_float = (float*)output; + + // mix all voices + advance = 1.0f / (float)mixer->frequency; + for (; samples > 0; --samples) { + left = right = 0.0f; + for (i = 0; i < STS_MIXER_VOICES; ++i) { + voice = &mixer->voices[i]; + if (voice->state == STS_MIXER_VOICE_PLAYING) { + position = (int)voice->position; + if (position < voice->sample->length) { + sample = sts_mixer__clamp_sample(sts_mixer__get_sample(voice->sample, position) * voice->gain); + left += sts_mixer__clamp_sample(sample * (0.5f - voice->pan)); + right += sts_mixer__clamp_sample(sample * (0.5f + voice->pan)); + voice->position += (float)voice->sample->frequency * advance * voice->pitch; + } else sts_mixer__reset_voice(mixer, i); + } else if (voice->state == STS_MIXER_VOICE_STREAMING) { + int status = 1; + position = ((int)voice->position) * 2; + if (position >= voice->stream->sample.length) { + // buffer empty...refill + status = voice->stream->callback(&voice->stream->sample, voice->stream->userdata); + voice->position = 0.0f; + position = 0; + } + left += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position) * voice->gain); + right += sts_mixer__clamp_sample(sts_mixer__get_sample(&voice->stream->sample, position + 1) * voice->gain); + // added in xsystem4: allow stopping stream via callback return value + if (status == STS_STREAM_COMPLETE) { + sts_mixer_stop_voice(mixer, i); + } else { + voice->position += (float)voice->stream->sample.frequency * advance; + } + } + } + + // write to buffer + left = sts_mixer__clamp_sample(left); + right = sts_mixer__clamp_sample(right); + switch (mixer->audio_format) { + case STS_MIXER_SAMPLE_FORMAT_8: + *out_8++ = (char)(left * 127.0f); + *out_8++ = (char)(right * 127.0f); + break; + case STS_MIXER_SAMPLE_FORMAT_16: + *out_16++ = (short)(left * 32767.0f); + *out_16++ = (short)(right * 32767.0f); + break; + case STS_MIXER_SAMPLE_FORMAT_32: + *out_32++ = (int)(left * 2147483647.0f); + *out_32++ = (int)(right * 2147483647.0f); + break; + case STS_MIXER_SAMPLE_FORMAT_FLOAT: + *out_float++ = left; + *out_float++ = right; + break; + } + } +} +#endif // STS_MIXER_IMPLEMENTATION +//////////////////////////////////////////////////////////////////////////////// +// EXAMPLE +// This is a very simple example loading a stream and a sample using +// dr_flac.h (https://github.com/mackron/dr_libs) and SDL2. You can of course also use stb_vorbis or something similar :) +// Please note how the audio thread of SDL2 will be locked when the mixer state get's modified. This is important! +// Also there's no error checking in the entire example code, so beware. +// +#if 0 +#include "SDL.h" + + +#define DR_FLAC_IMPLEMENTATION +#include "dr_flac.h" +#define STS_MIXER_IMPLEMENTATION +#include "sts_mixer.h" + + +SDL_AudioDeviceID audio_device = 0; +sts_mixer_t mixer; + + +// encapsulate drflac and some buffer with the sts_mixer_stream_t +typedef struct { + drflac* flac; // FLAC decoder state + sts_mixer_stream_t stream; // mixer stream + int32_t data[4096*2]; // static sample buffer +} mystream_t; + + +// SDL2 audio callback +static void audio_callback(void* userdata, Uint8* stream, int len) { + (void)(userdata); + sts_mixer_mix_audio(&mixer, stream, len / (sizeof(int) * 2)); +} + + +// load a sample +static void load_sample(sts_mixer_sample_t* sample, const char *filename) { + drflac* flac = drflac_open_file(filename); + sample->frequency = flac->sampleRate; + sample->audio_format = STS_MIXER_SAMPLE_FORMAT_32; + sample->length = flac->totalSampleCount; + sample->data = malloc(sample->length * sizeof(int32_t)); + drflac_read_s32(flac, sample->length, (int32_t*)sample->data); + drflac_close(flac); +} + + +// the callback to refill the stream data +static void refill_stream(sts_mixer_sample_t* sample, void* userdata) { + mystream_t* stream = (mystream_t*)userdata; + if (drflac_read_s32(stream->flac, sample->length, stream->data) < sample->length) drflac_seek_to_sample(stream->flac, 0); +} + + +// load a stream +static void load_stream(mystream_t* stream, const char *filename) { + stream->flac = drflac_open_file(filename); + stream->stream.userdata = stream; + stream->stream.callback = refill_stream; + stream->stream.sample.frequency = stream->flac->sampleRate; + stream->stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_32; + stream->stream.sample.length = 4096*2; + stream->stream.sample.data = stream->data; + refill_stream(&stream->stream.sample, stream); +} + + +// helper to get random [0.0f..1.0f values +static float randf() { + return (float)(rand()) / (float)RAND_MAX; +} + + +int main(int argc, char *argv[]) { + SDL_AudioSpec want, have; + sts_mixer_sample_t sample; + mystream_t stream; + + + (void)(argc); (void)(argv); + + // init SDL2 + audio + want.format = AUDIO_S32SYS; + want.freq = 44100; + want.channels = 2; + want.userdata = NULL; + want.samples = 4096; + want.callback = audio_callback; + SDL_Init(SDL_INIT_AUDIO); + audio_device = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + + // init sts_mixer and load things + sts_mixer_init(&mixer, 44100, STS_MIXER_SAMPLE_FORMAT_32); + load_sample(&sample, "effect.flac"); + load_stream(&stream, "music.flac"); + + // play the stream + sts_mixer_play_stream(&mixer, &stream.stream, 0.7f); + + // start audio processing and do a loop for audio effects + SDL_PauseAudioDevice(audio_device, 0); + for (;;) { + // !!!IMPORTANT!!! lock the audio thread before modifying data in the sts_mixer !!! + SDL_LockAudioDevice(audio_device); + // play a sample with random gain, pitch and panning + sts_mixer_play_sample(&mixer, &sample, randf(), 0.5f + randf(), -1.0f + randf() * 2.0f); + // unlock audio thread again + SDL_UnlockAudioDevice(audio_device); + + // wait ... + SDL_Delay(76); + } + SDL_PauseAudioDevice(audio_device, 1); + SDL_CloseAudioDevice(audio_device); + + SDL_Quit(); + return 0; +} +#endif // 0 +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/meson.build b/meson.build index d97d355..1c38fa0 100644 --- a/meson.build +++ b/meson.build @@ -7,15 +7,16 @@ zlib = dependency('zlib') libm = meson.get_compiler('c').find_library('m', required: false) sdl2 = dependency('sdl2') sdl2ttf = dependency('SDL2_ttf') -sdl2mixer = dependency('SDL2_mixer') ffi = dependency('libffi') gl = dependency('GL') glew = dependency('glew') tj = dependency('libturbojpeg') webp = dependency('libwebp') png = dependency('libpng') -chibi = dependency('chibi-scheme', required : get_option('debugger')) cglm = dependency('cglm', fallback : ['cglm', 'cglm_dep']) +sndfile = dependency('sndfile') + +chibi = dependency('chibi-scheme', required : get_option('debugger')) flex = find_program('flex') bison = find_program('bison') diff --git a/src/audio.c b/src/audio.c index bf98d47..da31d32 100644 --- a/src/audio.c +++ b/src/audio.c @@ -14,133 +14,39 @@ * along with this program; if not, see . */ -#include +#include #include "system4.h" -#include "system4/archive.h" -#include "system4/file.h" #include "asset_manager.h" #include "audio.h" +#include "id_pool.h" +#include "mixer.h" #include "xsystem4.h" -#define AUDIO_SLOT_ALLOC_STEP 256 +/* + * Common audio interface for SACT2/KiwiSoundEngine. + * This is just a wrapper over the generic audio interface in audio_mixer.c. + */ -struct wav_slot { - Mix_Chunk *chunk; - struct archive_data *dfile; - int channel; - int no; - bool reverse; -}; - -struct bgm_slot { - Mix_Music *music; - struct archive_data *dfile; - int no; -}; - -struct { - struct wav_slot *slots; - int nr_slots; -} wav; - -struct { - struct bgm_slot *slots; - int nr_slots; - int playing; -} bgm; +static struct id_pool wav; +static struct id_pool bgm; // Anonymous channels for playing sounds in-engine. audio_update must be called // periodically to clean up channels that have finished playing. #define NR_ANONYMOUS_CHANNELS 64 static int anonymous_channels[NR_ANONYMOUS_CHANNELS]; -#define BGI_MAX 100 - -struct bgi { - int no; - int loopno; - int looptop; - int len; -}; - -static int bgi_nfile; -static struct bgi bgi_data[BGI_MAX]; - -static char *bgi_gets(char *buf, int n, FILE *fp) { - char *s = buf; - int c; - while (--n > 0 && (c = fgetc(fp)) != EOF) { - c = c >> 4 | (c & 0xf) << 4; // decrypt - *s++ = c; - if (c == '\n') - break; - } - if (s == buf && c == EOF) - return NULL; - *s = '\0'; - return buf; -} - -static void bgi_read(const char *path) -{ - FILE *fp = fopen(path, "rb"); - if (!fp) { - WARNING("Failed to open bgi file: %s", path); - return; - } - - char buf[100]; - while (bgi_nfile < BGI_MAX && bgi_gets(buf, sizeof(buf), fp)) { - int terminator; - if (sscanf(buf, " %d, %d, %d, %d, %d", - &bgi_data[bgi_nfile].no, - &bgi_data[bgi_nfile].loopno, - &bgi_data[bgi_nfile].looptop, - &bgi_data[bgi_nfile].len, - &terminator) != 5 - || terminator != -1) { - continue; - } - bgi_nfile++; - } -} - -possibly_unused static struct bgi *bgi_find(int no) { - for (int i = 0; i < bgi_nfile; i++) { - if (bgi_data[i].no == no) - return &bgi_data[i]; - } - return NULL; -} - void audio_init(void) { - Mix_Init(MIX_INIT_MP3 | MIX_INIT_OGG); - if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { - WARNING("Audio initialization failed"); - } - - wav.slots = xcalloc(16, sizeof(struct wav_slot)); - wav.nr_slots = 16; - - bgm.slots = xcalloc(16, sizeof(struct bgm_slot)); - bgm.nr_slots = 16; - bgm.playing = -1; - - if (config.bgi_path) - bgi_read(config.bgi_path); + id_pool_init(&wav); + id_pool_init(&bgm); for (int i = 0; i < NR_ANONYMOUS_CHANNELS; i++) { anonymous_channels[i] = -1; } -} -void audio_fini(void) -{ - Mix_CloseAudio(); - Mix_Quit(); + mixer_init(); } void audio_update(void) @@ -174,298 +80,252 @@ bool audio_play_sound(int sound_no) return false; } -static void wav_realloc(int new_size) +bool wav_exists(int no) { return asset_exists(ASSET_SOUND, no); } +bool bgm_exists(int no) { return asset_exists(ASSET_BGM, no); } + +static int audio_prepare(struct id_pool *pool, int id, enum asset_type type, int no) { - wav.slots = xrealloc(wav.slots, new_size * sizeof(struct wav_slot)); - for (int i = wav.nr_slots; i < new_size; i++) { - wav.slots[i].chunk = NULL; - wav.slots[i].channel = -1; - wav.slots[i].reverse = false; - } - wav.nr_slots = new_size; -} - -static void bgm_realloc(int new_size) -{ - bgm.slots = xrealloc(bgm.slots, new_size * sizeof(struct bgm_slot)); - for (int i = bgm.nr_slots; i < new_size; i++) { - bgm.slots[i].music = NULL; - } - bgm.nr_slots = new_size; -} - -static struct wav_slot *wav_alloc_channel(int ch) -{ - if (ch >= wav.nr_slots) - wav_realloc(ch + AUDIO_SLOT_ALLOC_STEP); - return &wav.slots[ch]; -} - -static struct bgm_slot *bgm_alloc_channel(int ch) -{ - if (ch >= bgm.nr_slots) - bgm_realloc(ch + AUDIO_SLOT_ALLOC_STEP); - return &bgm.slots[ch]; -} - -int wav_get_unused_channel(void) -{ - for (int i = 0; i < wav.nr_slots; i++) { - if (!wav.slots[i].chunk) - return i; - } - - int slot = wav.nr_slots; - wav_realloc(slot + AUDIO_SLOT_ALLOC_STEP); - return slot; -} - -int bgm_get_unused_channel(void) -{ - for (int i = 0; i < bgm.nr_slots; i++) { - if (!bgm.slots[i].music) - return i; - } - - int slot = bgm.nr_slots; - bgm_realloc(slot + AUDIO_SLOT_ALLOC_STEP); - return slot; -} - -static struct wav_slot *wav_get_slot(int ch) -{ - if (ch < 0 || ch >= wav.nr_slots) - return NULL; - return &wav.slots[ch]; -} - -static struct bgm_slot *bgm_get_slot(int ch) -{ - if (ch < 0 || ch >= bgm.nr_slots) - return NULL; - return &bgm.slots[ch]; -} - -bool wav_exists(int no) -{ - return asset_exists(ASSET_SOUND, no); -} - -bool bgm_exists(int no) -{ - return asset_exists(ASSET_BGM, no); -} - -static bool wav_load_chunk(int no, struct wav_slot *slot) -{ - struct archive_data *dfile = asset_get(ASSET_SOUND, no); - if (!dfile) { - WARNING("Failed to load WAV %d", no); - return false; - } - - Mix_Chunk *chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(dfile->data, dfile->size), 1); - if (!chunk) { - WARNING("WAV %d: not a valid audio file", no); - return false; - } - - slot->chunk = chunk; - slot->dfile = dfile; - slot->no = no; - return true; -} - -static bool bgm_load_music(int no, struct bgm_slot *slot) -{ - struct archive_data *dfile = asset_get(ASSET_BGM, no); - if (!dfile) { - WARNING("Failed to load WAV %d", no); - return false; - } - - Mix_MusicType type = MUS_WAV; - const char *ext = file_extension(dfile->name); - if (!strcmp(ext, "ogg")) { - type = MUS_OGG; - } - - Mix_Music *music = Mix_LoadMUSType_RW(SDL_RWFromConstMem(dfile->data, dfile->size), type, SDL_TRUE); - if (!music) { - WARNING("BGM %d: not a valid audio file", no); - return false; - } - - slot->music = music; - slot->dfile = dfile; - slot->no = no; - return true; -} - -static void wav_unload_slot(struct wav_slot *slot) -{ - if (slot->chunk) { - if (slot->channel >= 0) - Mix_HaltChannel(slot->channel); - Mix_FreeChunk(slot->chunk); - slot->chunk = NULL; - archive_free_data(slot->dfile); - slot->dfile = NULL; - } - slot->channel = -1; -} - -static void bgm_unload_slot(struct bgm_slot *slot) -{ - if (slot->music) { - Mix_FreeMusic(slot->music); - slot->music = NULL; - archive_free_data(slot->dfile); - slot->dfile = NULL; - } -} - -int wav_prepare(int ch, int no) -{ - if (ch < 0) + if (id < 0) return 0; - - struct wav_slot *slot = wav_alloc_channel(ch); - wav_unload_slot(slot); - return wav_load_chunk(no, slot); + struct channel *ch = channel_open(type, no); + struct channel *old = id_pool_set(pool, id, ch); + if (old) + channel_close(old); + return !!ch; } -int bgm_prepare(int ch, int no) +int wav_prepare(int id, int no) { return audio_prepare(&wav, id, ASSET_SOUND, no); } +int bgm_prepare(int id, int no) { return audio_prepare(&bgm, id, ASSET_BGM, no); } + +static int audio_unprepare(struct id_pool *pool, int id) { - if (ch < 0) + struct channel *ch = id_pool_get(pool, id); + if (!ch) return 0; - - struct bgm_slot *slot = bgm_alloc_channel(ch); - bgm_unload_slot(slot); - return bgm_load_music(no, slot); -} - -int wav_unprepare(int ch) -{ - struct wav_slot *slot = wav_get_slot(ch); - if (!slot) - return 0; - - wav_unload_slot(slot); + channel_close(ch); + id_pool_release(pool, id); return 1; } -int bgm_unprepare(int ch) +int wav_unprepare(int id) { return audio_unprepare(&wav, id); } +int bgm_unprepare(int id) { return audio_unprepare(&bgm, id); } + +static int audio_play(struct id_pool *pool, int id) { - struct bgm_slot *slot = bgm_get_slot(ch); - if (!slot) + struct channel *ch = id_pool_get(pool, id); + if (!ch) { + // NOTE: SACT2 brings up error GUI here + WARNING("Tried to play from unprepared audio channel"); return 0; - - bgm_unload_slot(slot); - return 1; -} - -int wav_play(int ch) -{ - struct wav_slot *slot = wav_get_slot(ch); - if (!slot || !slot->chunk) - return 0; - - if (slot->channel >= 0 && Mix_Paused(slot->channel)) { - Mix_Resume(slot->channel); - return 1; } - slot->channel = Mix_PlayChannel(-1, slot->chunk, 0); - if (slot->channel < 0) - return 0; - - Mix_SetReverseStereo(slot->channel, slot->reverse); - return 1; + return channel_play(ch); } +int wav_play(int id) { return audio_play(&wav, id); } +int bgm_play(int id) { return audio_play(&bgm, id); } -int bgm_play(int ch) +static int audio_stop(struct id_pool *pool, int id) { - struct bgm_slot *slot = bgm_get_slot(ch); - if (!slot || !slot->music) + struct channel *ch = id_pool_get(pool, id); + if (!ch) { return 0; - - bgm.playing = slot - bgm.slots; - return !Mix_PlayMusic(slot->music, -1); -} - -int wav_stop(int ch) -{ - struct wav_slot *slot = wav_get_slot(ch); - if (!slot) - return 0; - - if (slot->channel >= 0) - Mix_HaltChannel(slot->channel); - return 1; -} - -int bgm_stop(int ch) -{ - // FIXME: what if channel isn't playing? - if (bgm.playing == ch) { - Mix_FadeOutMusic(10); - bgm.playing = -1; } - return 1; + return channel_stop(ch); } +int wav_stop(int id) { return audio_stop(&wav, id); } +int bgm_stop(int id) { return audio_stop(&bgm, id); } -bool wav_is_playing(int ch) +static int audio_is_playing(struct id_pool *pool, int id) { - struct wav_slot *slot = wav_get_slot(ch); - return slot && slot->channel >= 0 && Mix_Playing(slot->channel); + struct channel *ch = id_pool_get(pool, id); + return ch && channel_is_playing(ch); } +bool wav_is_playing(int id) { return audio_is_playing(&wav, id); } +bool bgm_is_playing(int id) { return audio_is_playing(&bgm, id); } -bool bgm_is_playing(int ch) +static int audio_set_loop_count(struct id_pool *pool, int id, int count) { - return bgm.playing == ch && Mix_PlayingMusic(); -} - -int wav_fade(int ch, possibly_unused int time, possibly_unused int volume, bool stop) -{ - // TODO: fade - if (stop) - wav_stop(ch); - return 1; -} - -int bgm_fade(int ch, possibly_unused int time, possibly_unused int volume, bool stop) -{ - // TODO: fade - if (stop) - bgm_stop(ch); - return 1; -} - -int wav_reverse_LR(int ch) -{ - struct wav_slot *slot = wav_get_slot(ch); - if (!slot) + struct channel *ch = id_pool_get(pool, id); + if (!ch) return 0; - slot->reverse = true; - if (slot->channel >= 0) { - Mix_SetReverseStereo(slot->channel, 1); - } - return 1; + return channel_set_loop_count(ch, count); } +int wav_set_loop_count(int id, int count) { return audio_set_loop_count(&wav, id, count); } +int bgm_set_loop_count(int id, int count) { return audio_set_loop_count(&bgm, id, count); } -int wav_get_time_length(int ch) +static int audio_get_loop_count(struct id_pool *pool, int id) { - struct wav_slot *slot = wav_get_slot(ch); - if (!slot || !slot->chunk) + struct channel *ch = id_pool_get(pool, id); + if (!ch) return 0; - - int freq = 0; - uint16_t fmt = 0; - int chans = 0; - - Mix_QuerySpec(&freq, &fmt, &chans); - - uint32_t points = slot->chunk->alen / ((fmt & 0xFF) / 8); - uint32_t frames = points / chans; - return (frames * 1000) / freq; + return channel_get_loop_count(ch); } +int wav_get_loop_count(int id) { return audio_get_loop_count(&wav, id); } +int bgm_get_loop_count(int id) { return audio_get_loop_count(&bgm, id); } + +static int audio_set_loop_start_pos(struct id_pool *pool, int id, int pos) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_set_loop_start_pos(ch, pos); +} +int wav_set_loop_start_pos(int id, int pos) { return audio_set_loop_start_pos(&wav, id, pos); } +int bgm_set_loop_start_pos(int id, int pos) { return audio_set_loop_start_pos(&bgm, id, pos); } + +static int audio_set_loop_end_pos(struct id_pool *pool, int id, int pos) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_set_loop_end_pos(ch, pos); +} +int wav_set_loop_end_pos(int id, int pos) { return audio_set_loop_end_pos(&wav, id, pos); } +int bgm_set_loop_end_pos(int id, int pos) { return audio_set_loop_end_pos(&bgm, id, pos); } + +static int audio_fade(struct id_pool *pool, int id, int time, int volume, bool stop) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_fade(ch, time, volume, stop); +} +int wav_fade(int id, int time, int volume, bool stop) { return audio_fade(&wav, id, time, volume, stop); } +int bgm_fade(int id, int time, int volume, bool stop) { return audio_fade(&bgm, id, time, volume, stop); } + +static int audio_stop_fade(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_stop_fade(ch); +} +int wav_stop_fade(int id) { return audio_stop_fade(&wav, id); } +int bgm_stop_fade(int id) { return audio_stop_fade(&bgm, id); } + +static bool audio_is_fading(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_is_fading(ch); +} +bool wav_is_fading(int id) { return audio_is_fading(&wav, id); } +bool bgm_is_fading(int id) { return audio_is_fading(&bgm, id); } + +static int audio_pause(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_pause(ch); +} +int wav_pause(int id) { return audio_pause(&wav, id); } +int bgm_pause(int id) { return audio_pause(&bgm, id); } + +static int audio_restart(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_restart(ch); +} +int wav_restart(int id) { return audio_restart(&wav, id); } +int bgm_restart(int id) { return audio_restart(&bgm, id); } + +static bool audio_is_paused(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_is_paused(ch); +} +bool wav_is_paused(int id) { return audio_is_paused(&wav, id); } +bool bgm_is_paused(int id) { return audio_is_paused(&bgm, id); } + +static int audio_get_pos(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_get_pos(ch); +} +int wav_get_pos(int id) { return audio_get_pos(&wav, id); } +int bgm_get_pos(int id) { return audio_get_pos(&bgm, id); } + +static int audio_get_length(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_get_length(ch); +} +int wav_get_length(int id) { return audio_get_length(&wav, id); } +int bgm_get_length(int id) { return audio_get_length(&bgm, id); } + +static int audio_get_sample_pos(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_get_sample_pos(ch); +} +int wav_get_sample_pos(int id) { return audio_get_sample_pos(&wav, id); } +int bgm_get_sample_pos(int id) { return audio_get_sample_pos(&bgm, id); } + +static int audio_get_sample_length(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_get_sample_length(ch); +} +int wav_get_sample_length(int id) { return audio_get_sample_length(&wav, id); } +int bgm_get_sample_length(int id) { return audio_get_sample_length(&bgm, id); } + +static int audio_seek(struct id_pool *pool, int id, int pos) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_seek(ch, pos); +} +int wav_seek(int id, int pos) { return audio_seek(&wav, id, pos); } +int bgm_seek(int id, int pos) { return audio_seek(&bgm, id, pos); } + +int wav_get_unused_channel(void) { return id_pool_get_unused(&wav); } +int bgm_get_unused_channel(void) { return id_pool_get_unused(&bgm); } + +static int audio_reverse_LR(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_reverse_LR(ch); +} +int wav_reverse_LR(int id) { return audio_reverse_LR(&wav, id); } +int bgm_reverse_LR(int id) { return audio_reverse_LR(&bgm, id); } + +static int audio_get_volume(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_get_volume(ch); +} +int wav_get_volume(int id) { return audio_get_volume(&wav, id); } +int bgm_get_volume(int id) { return audio_get_volume(&bgm, id); } + +static int audio_get_time_length(struct id_pool *pool, int id) +{ + struct channel *ch = id_pool_get(pool, id); + if (!ch) + return 0; + return channel_get_time_length(ch); +} +int wav_get_time_length(int id) { return audio_get_time_length(&wav, id); } +int bgm_get_time_length(int id) { return audio_get_time_length(&bgm, id); } + +//int wav_get_group_num(int channel); +//int bgm_get_group_num(int channel); +//int wav_prepare_from_file(int channel, char *filename); +//int bgm_prepare_from_file(int channel, char *filename); diff --git a/src/audio_mixer.c b/src/audio_mixer.c new file mode 100644 index 0000000..d16c51a --- /dev/null +++ b/src/audio_mixer.c @@ -0,0 +1,501 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program 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 2 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 . + */ + +#include +#include + +#include +#include + +#include "system4.h" +#include "system4/archive.h" + +#include "asset_manager.h" +#include "audio.h" +#include "mixer.h" +#include "xsystem4.h" + +/* + * The actual mixer implementation is contained in this header. + * The rest of this file implements a high level interface for managing mixer + * channels (loading audio, starting/stopping, etc.) + */ +#define STS_MIXER_IMPLEMENTATION +#include "sts_mixer.h" + +#define CHUNK_SIZE 1024 + +struct fade { + atomic_bool fading; + bool stop; + uint_least32_t start_pos; + uint_least32_t end_pos; + float start_volume; + float end_volume; +}; + +struct channel { + // archive data + struct archive_data *dfile; + int no; + + // audio file data + SNDFILE *file; + SF_INFO info; + sf_count_t offset; + + // stream data + atomic_int voice; + sts_mixer_stream_t stream; + float data[CHUNK_SIZE * 2]; + + // main thread read-only + atomic_uint_least32_t frame; + atomic_uint volume; + + atomic_bool swapped; + uint_least32_t loop_start; + uint_least32_t loop_end; + atomic_uint loop_count; + struct fade fade; +}; + +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)); +} + +/* + * Seek to the specified position in the stream. + * Returns true if the seek succeeded, otherwise returns false. + */ +static bool cb_seek(struct channel *ch, uint_least32_t pos) +{ + if (pos > ch->info.frames) { + pos = ch->info.frames; + } + sf_count_t r = sf_seek(ch->file, pos, SEEK_SET); + if (r < 0) { + WARNING("sf_seek failed"); + return false; + } + ch->frame = r; + return true; +} + +/* + * Seek to loop start, if the stream should loop. + * Returns true if the stream should loop, false if it should stop. + */ +static bool cb_loop(struct channel *ch) +{ + if (!cb_seek(ch, ch->loop_start) || ch->loop_count == 1) { + ch->voice = -1; + return false; + } + if (ch->loop_count > 1) { + ch->loop_count--; + } + return true; +} + +/* + * 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; + + // handle case where chunk crosses loop point (seamless) + // NOTE: it's assumed that the length of the loop is greater than the chunk length + if (ch->frame + frame_count >= ch->loop_end) { + // read frames up to loop_end + num_read = sf_readf_float(ch->file, out, ch->loop_end - ch->frame); + // adjust parameters for later + ch->frame += num_read; + out += num_read; + frame_count -= num_read; + // seek to loop_start + if (!cb_loop(ch)) + return STS_STREAM_COMPLETE; + } else if (ch->frame >= ch->loop_end) { + // seek to loop_start + if (!cb_loop(ch)) + return STS_STREAM_COMPLETE; + } + + // read remaining data + sf_count_t n = sf_readf_float(ch->file, out, frame_count); + num_read += n; + ch->frame += n; + out += num_read; + frame_count -= num_read; + + // XXX: This *shouldn't* be necessary, but sometimes libsndfile seems to stop reading + // just before the end of file (i.e. ch->frame + frame_count is < ch->info.frames, + // yet sf_readf_float returns less that the requested amount of frames). + // Not sure if this is a bug in xsystem4 or libsndfile + if (frame_count > 0) { + if (!cb_loop(ch)) + return STS_STREAM_COMPLETE; + num_read += sf_readf_float(ch->file, out, frame_count); + } + + return STS_STREAM_CONTINUE; +} + +/* + * Calculate the gain for a fade at a given sample. + */ +static float cb_calc_fade(struct fade *fade, uint_least32_t sample) +{ + if (sample >= fade->end_pos) { + return fade->stop ? 0.0 : fade->end_volume; + } + + float progress = (float)(sample - fade->start_pos) / (float)(fade->end_pos - fade->start_pos); + float delta_v = fade->end_volume - fade->start_volume; + float gain = fade->start_volume + delta_v * progress; + if (gain < 0.0) return 0.0; + if (gain > 1.0) return 1.0; + return gain; +} + +static int refill_stream(sts_mixer_sample_t *sample, void *data) +{ + struct channel *ch = data; + memset(ch->data, 0, sizeof(float) * sample->length); + + // read audio data from file + int r = cb_read_frames(ch, ch->data, CHUNK_SIZE); + + // convert mono to stereo + if (ch->info.channels == 1) { + for (int i = CHUNK_SIZE-1; i >= 0; i--) { + ch->data[i*2+1] = ch->data[i]; + ch->data[i*2] = ch->data[i]; + } + } + + // reverse LR channels + else if (ch->swapped) { + for (int i = 0; i < CHUNK_SIZE; i++) { + float tmp = ch->data[i*2]; + ch->data[i*2] = ch->data[i*2+1]; + ch->data[i*2+1] = tmp; + } + } + + // set gain for fade + if (ch->fade.fading) { + float gain = cb_calc_fade(&ch->fade, ch->frame); + mixer.voices[ch->voice].gain = gain; + ch->volume = gain * 100.0; + + if (ch->frame >= ch->fade.end_pos) { + ch->fade.fading = false; + if (ch->fade.stop) { + r = STS_STREAM_COMPLETE; + } + } + } + + return r; +} + +int channel_play(struct channel *ch) +{ + SDL_LockAudioDevice(audio_device); + if (ch->voice >= 0) { + SDL_UnlockAudioDevice(audio_device); + return 0; + } + ch->voice = sts_mixer_play_stream(&mixer, &ch->stream, 1.0f); + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_stop(struct channel *ch) +{ + SDL_LockAudioDevice(audio_device); + if (ch->voice < 0) { + SDL_UnlockAudioDevice(audio_device); + return 0; + } + sts_mixer_stop_voice(&mixer, ch->voice); + ch->voice = -1; + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_is_playing(struct channel *ch) +{ + return ch->voice >= 0; +} + +int channel_set_loop_count(struct channel *ch, int count) +{ + SDL_LockAudioDevice(audio_device); + ch->loop_count = count; + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_get_loop_count(struct channel *ch) +{ + return ch->loop_count; +} + +int channel_set_loop_start_pos(struct channel *ch, int pos) +{ + SDL_LockAudioDevice(audio_device); + ch->loop_start = pos; + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_set_loop_end_pos(struct channel *ch, int pos) +{ + SDL_LockAudioDevice(audio_device); + ch->loop_end = pos; + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_fade(struct channel *ch, int time, int volume, bool stop) +{ + SDL_LockAudioDevice(audio_device); + ch->fade.fading = true; + ch->fade.stop = 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)); + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_stop_fade(struct channel *ch) +{ + SDL_LockAudioDevice(audio_device); + // XXX: we need to set the volume to end_volume and potentially stop the + // stream here; better to let the callback do it + ch->fade.end_pos = ch->frame; + SDL_UnlockAudioDevice(audio_device); + return 1; +} + +int channel_is_fading(struct channel *ch) +{ + return ch->fade.fading; +} + +int channel_pause(possibly_unused struct channel *ch) +{ + // NOTE: not implemented in Sengoku Rance's SACT2.dll + WARNING("channel_pause not implemented"); + return 0; +} + +int channel_restart(possibly_unused struct channel *ch) +{ + // NOTE: not implemented in Sengoku Rance's SACT2.dll + WARNING("channel_restart not implemented"); + return 0; +} + +int channel_is_paused(possibly_unused struct channel *ch) +{ + return 0; +} + +int channel_get_pos(struct channel *ch) +{ + return (ch->frame * 1000) / ch->info.samplerate; +} + +int channel_get_length(struct channel *ch) +{ + // FIXME: how is this different than channel_get_time_length? + return (ch->info.frames * 1000) / ch->info.samplerate; +} + +int channel_get_sample_pos(struct channel *ch) +{ + return ch->frame; +} + +int channel_get_sample_length(struct channel *ch) +{ + return ch->info.frames; +} + +int channel_seek(struct channel *ch, int pos) +{ + // NOTE: SACT2.Music_Seek doesn't seem to do anything in Sengoku Rance... + SDL_LockAudioDevice(audio_device); + int r = cb_seek(ch, (pos * ch->info.samplerate) / 1000); + SDL_UnlockAudioDevice(audio_device); + return r; +} + +int channel_reverse_LR(struct channel *ch) +{ + ch->swapped = !ch->swapped; + return 1; +} + +int channel_get_volume(struct channel *ch) +{ + return ch->volume; +} + +int channel_get_time_length(struct channel *ch) +{ + return (ch->info.frames * 1000) / ch->info.samplerate; +} + +static sf_count_t channel_vio_get_filelen(void *data) +{ + return ((struct channel*)data)->dfile->size; +} + +static sf_count_t channel_vio_seek(sf_count_t offset, int whence, void *data) +{ + struct channel *ch = data; + switch (whence) { + case SEEK_CUR: + ch->offset += offset; + break; + case SEEK_SET: + ch->offset = offset; + break; + case SEEK_END: + ch->offset = ch->dfile->size + offset; + break; + } + ch->offset = min((sf_count_t)ch->dfile->size, max(0, ch->offset)); + return ch->offset; +} + +static sf_count_t channel_vio_read(void *ptr, sf_count_t count, void *data) +{ + struct channel *ch = data; + sf_count_t c = min(count, (sf_count_t)ch->dfile->size - ch->offset); + memcpy(ptr, ch->dfile->data + ch->offset, c); + ch->offset += c; + return c; +} + +static sf_count_t channel_vio_write(possibly_unused const void *ptr, + possibly_unused sf_count_t count, + possibly_unused void *user_data) +{ + ERROR("sndfile vio write not supported"); +} + +static sf_count_t channel_vio_tell(void *data) +{ + return ((struct channel*)data)->offset; +} + +static SF_VIRTUAL_IO channel_vio = { + .get_filelen = channel_vio_get_filelen, + .seek = channel_vio_seek, + .read = channel_vio_read, + .write = channel_vio_write, + .tell = channel_vio_tell +}; + +struct channel *channel_open(enum asset_type type, int no) +{ + struct channel *ch = xcalloc(1, sizeof(struct channel)); + + // get file from archive + ch->dfile = asset_get(type, no); + if (!ch->dfile) { + WARNING("Failed to load %s %d", type == ASSET_SOUND ? "WAV" : "BGM", no); + goto error; + } + + // open file + ch->file = sf_open_virtual(&channel_vio, SFM_READ, &ch->info, ch); + if (sf_error(ch->file) != SF_ERR_NO_ERROR) { + WARNING("Failed to open %s %d: %s", type == ASSET_SOUND ? "WAV" : "BGM", no, sf_strerror(ch->file)); + goto error; + } + if (ch->info.channels > 2) { + WARNING("Audio file has more than 2 channels: %s %d", type == ASSET_SOUND ? "WAV" : "BGM", no); + goto error; + } + + // create stream + ch->stream.userdata = ch; + ch->stream.callback = refill_stream; + ch->stream.sample.frequency = ch->info.samplerate; + ch->stream.sample.audio_format = STS_MIXER_SAMPLE_FORMAT_FLOAT; + ch->stream.sample.length = CHUNK_SIZE * 2; + ch->stream.sample.data = ch->data; + //refill_stream(&ch->stream.sample, ch); + ch->voice = -1; + + ch->volume = 100; + ch->loop_start = 0; + ch->loop_end = ch->info.frames; + ch->loop_count = type == ASSET_SOUND ? 1 : 0; + ch->no = no; + + return ch; + +error: + if (ch->dfile) + archive_free_data(ch->dfile); + free(ch); + return NULL; +} + +void channel_close(struct channel *ch) +{ + channel_stop(ch); + sf_close(ch->file); + archive_free_data(ch->dfile); + free(ch); +} + +void mixer_init(void) +{ + SDL_AudioSpec have; + SDL_AudioSpec want = { + .format = AUDIO_F32, + .freq = 44100, + .channels = 2, + .samples = CHUNK_SIZE, + .callback = audio_callback, + }; + audio_device = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + + sts_mixer_init(&mixer, 44100, STS_MIXER_SAMPLE_FORMAT_FLOAT); + + SDL_PauseAudioDevice(audio_device, 0); +} diff --git a/src/hll/KiwiSoundEngine.c b/src/hll/KiwiSoundEngine.c index 02148af..06421ee 100644 --- a/src/hll/KiwiSoundEngine.c +++ b/src/hll/KiwiSoundEngine.c @@ -19,51 +19,18 @@ #include "hll.h" #include "audio.h" -HLL_WARN_UNIMPLEMENTED( , void, KiwiSoundEngine, SetGlobalFocus, int nNum); +HLL_WARN_UNIMPLEMENTED( , void, KiwiSoundEngine, SetGlobalFocus, possibly_unused int nNum); + int KiwiSoundEngine_Music_IsExist(int nNum) { return bgm_exists(nNum-1); } + int KiwiSoundEngine_Music_Prepare(int nCh, int nNum) { return bgm_prepare(nCh, nNum-1); } -int KiwiSoundEngine_Music_Unprepare(int nCh) -{ - return bgm_unprepare(nCh); -} -int KiwiSoundEngine_Music_Play(int nCh) -{ - return bgm_play(nCh); -} -int KiwiSoundEngine_Music_Stop(int nCh) -{ - return bgm_stop(nCh); -} -int KiwiSoundEngine_Music_IsPlay(int nCh) -{ - return bgm_is_playing(nCh); -} -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_SetLoopCount, int nCh, int nCount); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_GetLoopCount, int nCh); -//int KiwiSoundEngine_Music_SetLoopStartPos(int nCh, int dwPos); -//int KiwiSoundEngine_Music_SetLoopEndPos(int nCh, int dwPos); -int KiwiSoundEngine_Music_Fade(int nCh, int nTime, int nVolume, int bStop) -{ - return bgm_fade(nCh, nTime, nVolume, bStop); -} -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_StopFade, int nCh); -HLL_WARN_UNIMPLEMENTED(0, int, KiwiSoundEngine, Music_IsFade, int nCh); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_Pause, int nCh); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_Restart, int nCh); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_IsPause, int nCh); -HLL_QUIET_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_GetPos, int nCh); -HLL_QUIET_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_GetLength, int nCh); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_GetSamplePos, int nCh); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_GetSampleLength, int nCh); -HLL_WARN_UNIMPLEMENTED(1, int, KiwiSoundEngine, Music_Seek, int nCh, int dwPos); -//int KiwiSoundEngine_Music_MillisecondsToSamples(int nMillisec, int nSamplesPerSec); -//int KiwiSoundEngine_Music_GetFormat(int nCh, int *pnSamplesPerSec, int *pnBitsPerSample, int *pnChannels); + int KiwiSoundEngine_Sound_IsExist(int nNum) { return wav_exists(nNum-1); @@ -72,34 +39,9 @@ int KiwiSoundEngine_Sound_Prepare(int nCh, int nNum) { return wav_prepare(nCh, nNum-1); } -int KiwiSoundEngine_Sound_Unprepare(int nCh) -{ - return wav_unprepare(nCh); -} -int KiwiSoundEngine_Sound_Play(int nCh) -{ - return wav_play(nCh); -} -int KiwiSoundEngine_Sound_Stop(int nCh) -{ - return wav_stop(nCh); -} -int KiwiSoundEngine_Sound_IsPlay(int nCh) -{ - return wav_is_playing(nCh); -} -int KiwiSoundEngine_Sound_Fade(int nCh, int nTime, int nVolume, int bStop) -{ - return wav_fade(nCh, nTime, nVolume, bStop); -} -//int KiwiSoundEngine_Sound_StopFade(int nCh); -//int KiwiSoundEngine_Sound_IsFade(int nCh); -int KiwiSoundEngine_Sound_GetTimeLength(int nCh) -{ - return wav_get_time_length(nCh); -} + //int KiwiSoundEngine_Sound_GetGroupNum(int nCh); -static int KiwiSoundEngine_Sound_GetGroupNumFromDataNum(int no) +static int KiwiSoundEngine_Sound_GetGroupNumFromDataNum(possibly_unused int no) { return 0; } @@ -110,37 +52,37 @@ HLL_LIBRARY(KiwiSoundEngine, HLL_EXPORT(SetGlobalFocus, KiwiSoundEngine_SetGlobalFocus), HLL_EXPORT(Music_IsExist, KiwiSoundEngine_Music_IsExist), HLL_EXPORT(Music_Prepare, KiwiSoundEngine_Music_Prepare), - HLL_EXPORT(Music_Unprepare, KiwiSoundEngine_Music_Unprepare), - HLL_EXPORT(Music_Play, KiwiSoundEngine_Music_Play), - HLL_EXPORT(Music_Stop, KiwiSoundEngine_Music_Stop), - HLL_EXPORT(Music_IsPlay, KiwiSoundEngine_Music_IsPlay), - HLL_EXPORT(Music_SetLoopCount, KiwiSoundEngine_Music_SetLoopCount), - HLL_EXPORT(Music_GetLoopCount, KiwiSoundEngine_Music_GetLoopCount), - //HLL_EXPORT(Music_SetLoopStartPos, KiwiSoundEngine_Music_SetLoopStartPos), - //HLL_EXPORT(Music_SetLoopEndPos, KiwiSoundEngine_Music_SetLoopEndPos), - HLL_EXPORT(Music_Fade, KiwiSoundEngine_Music_Fade), - HLL_EXPORT(Music_StopFade, KiwiSoundEngine_Music_StopFade), - HLL_EXPORT(Music_IsFade, KiwiSoundEngine_Music_IsFade), - HLL_EXPORT(Music_Pause, KiwiSoundEngine_Music_Pause), - HLL_EXPORT(Music_Restart, KiwiSoundEngine_Music_Restart), - HLL_EXPORT(Music_IsPause, KiwiSoundEngine_Music_IsPause), - HLL_EXPORT(Music_GetPos, KiwiSoundEngine_Music_GetPos), - HLL_EXPORT(Music_GetLength, KiwiSoundEngine_Music_GetLength), - HLL_EXPORT(Music_GetSamplePos, KiwiSoundEngine_Music_GetSamplePos), - HLL_EXPORT(Music_GetSampleLength, KiwiSoundEngine_Music_GetSampleLength), - HLL_EXPORT(Music_Seek, KiwiSoundEngine_Music_Seek), + HLL_EXPORT(Music_Unprepare, bgm_unprepare), + HLL_EXPORT(Music_Play, bgm_play), + HLL_EXPORT(Music_Stop, bgm_stop), + HLL_EXPORT(Music_IsPlay, bgm_is_playing), + HLL_EXPORT(Music_SetLoopCount, bgm_set_loop_count), + HLL_EXPORT(Music_GetLoopCount, bgm_get_loop_count), + HLL_EXPORT(Music_SetLoopStartPos, bgm_set_loop_start_pos), + HLL_EXPORT(Music_SetLoopEndPos, bgm_set_loop_end_pos), + HLL_EXPORT(Music_Fade, bgm_fade), + HLL_EXPORT(Music_StopFade, bgm_stop_fade), + HLL_EXPORT(Music_IsFade, bgm_is_fading), + HLL_EXPORT(Music_Pause, bgm_pause), + HLL_EXPORT(Music_Restart, bgm_restart), + HLL_EXPORT(Music_IsPause, bgm_is_paused), + HLL_EXPORT(Music_GetPos, bgm_get_pos), + HLL_EXPORT(Music_GetLength, bgm_get_length), + HLL_EXPORT(Music_GetSamplePos, bgm_get_sample_pos), + HLL_EXPORT(Music_GetSampleLength, bgm_get_sample_length), + HLL_EXPORT(Music_Seek, bgm_seek), //HLL_EXPORT(Music_MillisecondsToSamples, KiwiSoundEngine_Music_MillisecondsToSamples), //HLL_EXPORT(Music_GetFormat, KiwiSoundEngine_Music_GetFormat), HLL_EXPORT(Sound_IsExist, KiwiSoundEngine_Sound_IsExist), HLL_EXPORT(Sound_Prepare, KiwiSoundEngine_Sound_Prepare), - HLL_EXPORT(Sound_Unprepare, KiwiSoundEngine_Sound_Unprepare), - HLL_EXPORT(Sound_Play, KiwiSoundEngine_Sound_Play), - HLL_EXPORT(Sound_Stop, KiwiSoundEngine_Sound_Stop), - HLL_EXPORT(Sound_IsPlay, KiwiSoundEngine_Sound_IsPlay), - HLL_EXPORT(Sound_Fade, KiwiSoundEngine_Sound_Fade), - //HLL_EXPORT(Sound_StopFade, KiwiSoundEngine_Sound_StopFade), - //HLL_EXPORT(Sound_IsFade, KiwiSoundEngine_Sound_IsFade), - HLL_EXPORT(Sound_GetTimeLength, KiwiSoundEngine_Sound_GetTimeLength), + HLL_EXPORT(Sound_Unprepare, wav_unprepare), + HLL_EXPORT(Sound_Play, wav_play), + HLL_EXPORT(Sound_Stop, wav_stop), + HLL_EXPORT(Sound_IsPlay, wav_is_playing), + HLL_EXPORT(Sound_Fade, wav_fade), + HLL_EXPORT(Sound_StopFade, wav_stop_fade), + HLL_EXPORT(Sound_IsFade, wav_is_fading), + HLL_EXPORT(Sound_GetTimeLength, wav_get_time_length), //HLL_EXPORT(Sound_GetGroupNum, KiwiSoundEngine_Sound_GetGroupNum), HLL_EXPORT(Sound_GetGroupNumFromDataNum, KiwiSoundEngine_Sound_GetGroupNumFromDataNum) //HLL_EXPORT(Sound_PrepareFromFile, KiwiSoundEngine_Sound_PrepareFromFile) diff --git a/src/hll/SACT2.c b/src/hll/SACT2.c index e65a165..6b20ef0 100644 --- a/src/hll/SACT2.c +++ b/src/hll/SACT2.c @@ -723,22 +723,6 @@ int sact_Music_Prepare(int ch, int n) return bgm_prepare(ch, n - 1); } -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_SetLoopCount, int ch, int count); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_GetLoopCount, int ch); -//int SACT2_Music_SetLoopStartPos(int ch, int pos); -//int SACT2_Music_SetLoopEndPos(int ch, int pos); - -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_StopFade, int ch); -HLL_WARN_UNIMPLEMENTED(0, int, SACT2, Music_IsFade, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_Pause, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_Restart, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_IsPause, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_GetPos, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_GetLength, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_GetSamplePos, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_GetSampleLength, int ch); -HLL_WARN_UNIMPLEMENTED(1, int, SACT2, Music_Seek, int ch, int pos); - int sact_Sound_IsExist(int n) { return wav_exists(n - 1); @@ -914,21 +898,21 @@ int SACT2_SP_GetBrightness(int sp_no) HLL_EXPORT(Music_Play, bgm_play), \ HLL_EXPORT(Music_Stop, bgm_stop), \ HLL_EXPORT(Music_IsPlay, bgm_is_playing), \ - HLL_EXPORT(Music_SetLoopCount, SACT2_Music_SetLoopCount), \ - HLL_EXPORT(Music_GetLoopCount, SACT2_Music_GetLoopCount), \ - HLL_TODO_EXPORT(Music_SetLoopStartPos, SACT2_Music_SetLoopStartPos), \ - HLL_TODO_EXPORT(Music_SetLoopEndPos, SACT2_Music_SetLoopEndPos), \ + HLL_EXPORT(Music_SetLoopCount, bgm_set_loop_count), \ + HLL_EXPORT(Music_GetLoopCount, bgm_get_loop_count), \ + HLL_EXPORT(Music_SetLoopStartPos, bgm_set_loop_start_pos), \ + HLL_EXPORT(Music_SetLoopEndPos, bgm_set_loop_end_pos), \ HLL_EXPORT(Music_Fade, bgm_fade), \ - HLL_EXPORT(Music_StopFade, SACT2_Music_StopFade), \ - HLL_EXPORT(Music_IsFade, SACT2_Music_IsFade), \ - HLL_EXPORT(Music_Pause, SACT2_Music_Pause), \ - HLL_EXPORT(Music_Restart, SACT2_Music_Restart), \ - HLL_EXPORT(Music_IsPause, SACT2_Music_IsPause), \ - HLL_EXPORT(Music_GetPos, SACT2_Music_GetPos), \ - HLL_EXPORT(Music_GetLength, SACT2_Music_GetLength), \ - HLL_EXPORT(Music_GetSamplePos, SACT2_Music_GetSamplePos), \ - HLL_EXPORT(Music_GetSampleLength, SACT2_Music_GetSampleLength), \ - HLL_EXPORT(Music_Seek, SACT2_Music_Seek), \ + HLL_EXPORT(Music_StopFade, bgm_stop_fade), \ + HLL_EXPORT(Music_IsFade, bgm_is_fading), \ + HLL_EXPORT(Music_Pause, bgm_pause), \ + HLL_EXPORT(Music_Restart, bgm_restart), \ + HLL_EXPORT(Music_IsPause, bgm_is_paused), \ + HLL_EXPORT(Music_GetPos, bgm_get_pos), \ + HLL_EXPORT(Music_GetLength, bgm_get_length), \ + HLL_EXPORT(Music_GetSamplePos, bgm_get_sample_pos), \ + HLL_EXPORT(Music_GetSampleLength, bgm_get_sample_length), \ + HLL_EXPORT(Music_Seek, bgm_seek), \ HLL_EXPORT(Sound_IsExist, sact_Sound_IsExist), \ HLL_EXPORT(Sound_GetUnuseChannel, wav_get_unused_channel), \ HLL_EXPORT(Sound_Prepare, sact_Sound_Prepare), \ @@ -936,15 +920,15 @@ int SACT2_SP_GetBrightness(int sp_no) HLL_EXPORT(Sound_Play, wav_play), \ HLL_EXPORT(Sound_Stop, wav_stop), \ HLL_EXPORT(Sound_IsPlay, wav_is_playing), \ - HLL_TODO_EXPORT(Sound_SetLoopCount, SACT2_Sound_SetLoopCount), \ - HLL_TODO_EXPORT(Sound_GetLoopCount, SACT2_Sound_GetLoopCount), \ + HLL_TODO_EXPORT(Sound_SetLoopCount, wav_set_loop_count), \ + HLL_TODO_EXPORT(Sound_GetLoopCount, wav_get_loop_count), \ HLL_EXPORT(Sound_Fade, wav_fade), \ - HLL_TODO_EXPORT(Sound_StopFade, SACT2_Sound_StopFade), \ - HLL_TODO_EXPORT(Sound_IsFade, SACT2_Sound_IsFade), \ - HLL_TODO_EXPORT(Sound_GetPos, SACT2_Sound_GetPos), \ - HLL_TODO_EXPORT(Sound_GetLength, SACT2_Sound_GetLength), \ + HLL_TODO_EXPORT(Sound_StopFade, wav_stop_fade), \ + HLL_TODO_EXPORT(Sound_IsFade, wav_is_fade), \ + HLL_TODO_EXPORT(Sound_GetPos, wav_get_pos), \ + HLL_TODO_EXPORT(Sound_GetLength, wav_get_length), \ HLL_EXPORT(Sound_ReverseLR, wav_reverse_LR), \ - HLL_TODO_EXPORT(Sound_GetVolume, SACT2_Sound_GetVolume), \ + HLL_TODO_EXPORT(Sound_GetVolume, wav_get_volume), \ HLL_EXPORT(Sound_GetTimeLength, wav_get_time_length), \ HLL_TODO_EXPORT(Sound_GetGroupNum, SACT2_Sound_GetGroupNum), \ HLL_TODO_EXPORT(Sound_PrepareFromFile, SACT2_Sound_PrepareFromFile), \ diff --git a/src/id_pool.c b/src/id_pool.c new file mode 100644 index 0000000..be43f30 --- /dev/null +++ b/src/id_pool.c @@ -0,0 +1,75 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * This program 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 2 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 . + */ + +#include + +#include "system4.h" +#include "id_pool.h" + +/* + * Generic data structure for managing a pool of objects accessed by integer IDs. + */ + +#define ID_POOL_INIT_SLOTS 32 +#define ID_POOL_ALLOC_HEADROOM 256 + +void id_pool_init(struct id_pool *pool) +{ + pool->slots = xcalloc(ID_POOL_INIT_SLOTS, sizeof(void*)); + pool->nr_slots = ID_POOL_INIT_SLOTS; +} + +static void id_pool_realloc(struct id_pool *pool, int new_size) +{ + pool->slots = xrealloc_array(pool->slots, pool->nr_slots, new_size, sizeof(void*)); + pool->nr_slots = new_size; +} + +void id_pool_release(struct id_pool *pool, int id) +{ + if (pool->slots[id]) + pool->slots[id] = NULL; +} + +int id_pool_get_unused(struct id_pool *pool) +{ + for (int i = 0; i < pool->nr_slots; i++) { + if (!pool->slots[i]) + return i; + } + + int id = pool->nr_slots; + id_pool_realloc(pool, id + ID_POOL_ALLOC_HEADROOM); + return id; +} + +void *id_pool_get(struct id_pool *pool, int id) +{ + if (id < 0 || id >= pool->nr_slots) + return NULL; + return pool->slots[id]; +} + +void *id_pool_set(struct id_pool *pool, int id, void *data) +{ + assert(id >= 0); + if (id >= pool->nr_slots) + id_pool_realloc(pool, id + ID_POOL_ALLOC_HEADROOM); + + void *old = pool->slots[id]; + pool->slots[id] = data; + return old; +} diff --git a/src/meson.build b/src/meson.build index 199cb77..5798336 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ # sources for xsystem4 xsystem4 = ['audio.c', + 'audio_mixer.c', 'asset_manager.c', 'cJSON.c', 'draw.c', @@ -8,6 +9,7 @@ xsystem4 = ['audio.c', 'fnl_render.c', 'format.c', 'heap.c', + 'id_pool.c', 'input.c', 'page.c', 'resume.c', @@ -77,7 +79,8 @@ xsystem4 = ['audio.c', 'hll/VSFile.c', ] -xsystem4_deps = [libm, zlib, sdl2, sdl2ttf, sdl2mixer, ffi, gl, glew, cglm, libsys4_dep] +xsystem4_deps = [libm, zlib, sdl2, sdl2ttf, ffi, gl, glew, cglm, sndfile, libsys4_dep] + if chibi.found() xsystem4_deps += chibi xsystem4 += 'debugger.c'