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'