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