From e631f339dd6d610d4659e4917d96cc22c1c61dfc Mon Sep 17 00:00:00 2001 From: Ondrej Novak Date: Tue, 18 Feb 2025 10:32:37 +0100 Subject: [PATCH] bass, treble, control crt filter in setup, fix lock multiaction --- game/interfac.c | 2 +- game/macros.c | 9 +-- game/setup.c | 2 +- game/skeldal.c | 5 +- platform/sdl/CMakeLists.txt | 3 +- platform/sdl/sdl_context.cpp | 33 ++++++++--- platform/sdl/sdl_context.h | 3 + platform/sdl/sound.cpp | 19 +++++-- platform/sdl/sound_filter.cpp | 104 ++++++++++++++++++++++++++++++++++ platform/sdl/sound_filter.h | 72 +++++++++++++++++++++++ platform/sdl/sound_mixer.h | 50 +++++++++++++++- 11 files changed, 279 insertions(+), 23 deletions(-) create mode 100644 platform/sdl/sound_filter.cpp create mode 100644 platform/sdl/sound_filter.h diff --git a/game/interfac.c b/game/interfac.c index 1fc4be6..f41aae6 100644 --- a/game/interfac.c +++ b/game/interfac.c @@ -1555,7 +1555,7 @@ THAGGLERESULT smlouvat_dlg(int cena,int puvod,int pocet,int posledni, int money, char ask_save_dialog(char *name_buffer, size_t name_size) { const char *str_label = texty[98]; - if (str_label == 0) str_label ="Ulo\x91it hru jako"; +// if (str_label == 0) str_label ="Ulo\x91it hru jako"; set_font(H_FBOLD,RGB555(31,31,31)); add_window(170,130,300,100,H_WINTXTR,3,20,20); diff --git a/game/macros.c b/game/macros.c index a914227..99355d8 100644 --- a/game/macros.c +++ b/game/macros.c @@ -739,10 +739,11 @@ void call_macro_ex(int side, int flags, int runatside) { z = mrec.action_list; if (z->general.flags & flags) { int jmp_to = -1; - char cancel_enabled = 1; int stindex = z - first_macro; + char cancel = 0; if (!z->general.once || !macro_state_block.states[stindex]) { macro_state_block.states[stindex] = 1; + cancel = z->general.cancel; count_actions++; switch (z->general.action) { default: @@ -789,14 +790,14 @@ void call_macro_ex(int side, int flags, int runatside) { enter_shop(z->text.textindex); break; case MA_CLOCK: - cancel_enabled = decode_lock(z->clock.znak, z->clock.string, + cancel = decode_lock(z->clock.znak, z->clock.string, z->clock.codenum); break; case MA_CACTN: cancel_action(z->cactn.sector, z->cactn.dir); break; case MA_LOCK: - cancel_enabled = if_lock(side, z->lock.key_id, + cancel = if_lock(side, z->lock.key_id, z->lock.thieflevel, &z->lock); break; case MA_SWAPS: @@ -879,7 +880,7 @@ void call_macro_ex(int side, int flags, int runatside) { if (jmp_to != -1) { mrec = go_macro(runatside, jmp_to); program_counter = jmp_to; - } else if (z->general.cancel && cancel_enabled) { + } else if (cancel) { break; } else { program_counter++; diff --git a/game/setup.c b/game/setup.c index 88445d4..a1020e8 100644 --- a/game/setup.c +++ b/game/setup.c @@ -145,7 +145,7 @@ void new_setup() int i; static int textxp[]={ 75, 75,435,435,434,535,535,535,434,434,434,434,434, 35,410,510}; static int textyp[]={275,305, 65, 95,125, 65, 95,125,185,215,245,275,305,235, 40, 40}; - static int textc[]={ 53, 54, 56, 57, 58, 56, 57, 58,140,141,142,143,144 ,51, 55, 59}; + static int textc[]={ 53, 99, 56, 57, 58, 56, 57, 58,140,141,142,143,144 ,51, 55, 59}; mix_back_sound(256000-16384); memset(&ctl,0,sizeof(ctl)); diff --git a/game/skeldal.c b/game/skeldal.c index 0c22c46..aab3c21 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -802,7 +802,10 @@ void cti_texty(void) exit(1); } - lang_patch_stringtable(&texty, "ui.csv", ""); + //patch stringtable + if (!texty[98]) str_replace(&texty,98,"Ulo\x91it hru jako"); + if (!texty[99]) str_replace(&texty,99,"CRT Filter (>720p)"); + lang_patch_stringtable(&texty, "ui.csv", ""); } diff --git a/platform/sdl/CMakeLists.txt b/platform/sdl/CMakeLists.txt index ed2cb06..b5ac4a4 100644 --- a/platform/sdl/CMakeLists.txt +++ b/platform/sdl/CMakeLists.txt @@ -4,5 +4,6 @@ add_library(skeldal_sdl sdl_context.cpp BGraph2.cpp input.cpp - sound.cpp) + sound.cpp + sound_filter.cpp) set_property(TARGET skeldal_sdl PROPERTY CXX_STANDARD 20) diff --git a/platform/sdl/sdl_context.cpp b/platform/sdl/sdl_context.cpp index b733d77..f462805 100644 --- a/platform/sdl/sdl_context.cpp +++ b/platform/sdl/sdl_context.cpp @@ -158,6 +158,10 @@ void SDLContext::init_video(const VideoConfig &config, const char *title) { aspect_x = config.aspect_x; aspect_y = config.aspect_y; _crt_filter = config.crt_filter ; + if (_crt_filter == CrtFilterType::none) { + _crt_filter = CrtFilterType::autoselect; + _enable_crt = false; + } _fullscreen_mode = config.fullscreen; @@ -396,13 +400,6 @@ void SDLContext::refresh_screen() { SDL_SetTextureAlphaMod(_visible_texture, 255); SDL_RenderCopy(_renderer.get(), _visible_texture, NULL, &winrc); } - if (winrc.h >= 720 && _crt_filter != CrtFilterType::none) { - if (!_crt_effect) { - SDL_Texture *txt; - generateCRTTexture(_renderer.get(), &txt, winrc.w, winrc.h, _crt_filter); - _crt_effect.reset(txt); - } - } for (const auto &sprite: _sprites) if (sprite.shown) { SDL_Rect rc = to_window_rect(winrc,sprite._rect); SDL_RenderCopy(_renderer.get(), sprite._txtr.get(), NULL, &rc); @@ -414,7 +411,14 @@ void SDLContext::refresh_screen() { recalc_rect.y = _mouse_rect.y - f.y; SDL_RenderCopy(_renderer.get(), _mouse.get(), NULL, &recalc_rect); } - SDL_RenderCopy(_renderer.get(), _crt_effect.get(), NULL, &winrc); + if (winrc.h >= 720 && _crt_filter != CrtFilterType::none && _enable_crt) { + if (!_crt_effect) { + SDL_Texture *txt; + generateCRTTexture(_renderer.get(), &txt, winrc.w, winrc.h, _crt_filter); + _crt_effect.reset(txt); + } + SDL_RenderCopy(_renderer.get(), _crt_effect.get(), NULL, &winrc); + } SDL_RenderPresent(_renderer.get()); } @@ -826,3 +830,16 @@ void SDLContext::unload_sprite(int sprite) { push_item(DisplayRequest::sprite_unload); push_item(sprite); } + +void SDLContext::enable_crt_filter(bool enable) +{ + std::lock_guard _(_mx); + _enable_crt = enable; + signal_push(); +} + +bool SDLContext::is_crt_enabled() const +{ + std::lock_guard _(_mx); + return _enable_crt; +} diff --git a/platform/sdl/sdl_context.h b/platform/sdl/sdl_context.h index 9e0d3aa..cdfbb7e 100644 --- a/platform/sdl/sdl_context.h +++ b/platform/sdl/sdl_context.h @@ -102,6 +102,8 @@ public: void hide_sprite(int sprite_id); void sprite_set_zindex(int sprite_id, int zindex); void unload_sprite(int sprite); + void enable_crt_filter(bool enable); + bool is_crt_enabled() const; protected: @@ -164,6 +166,7 @@ protected: int aspect_x = 4; int aspect_y = 3; CrtFilterType _crt_filter= CrtFilterType::autoselect; + bool _enable_crt = true; std::function _quit_callback; std::unique_ptr _window; diff --git a/platform/sdl/sound.cpp b/platform/sdl/sound.cpp index 6b1df9c..d59c495 100644 --- a/platform/sdl/sound.cpp +++ b/platform/sdl/sound.cpp @@ -14,9 +14,11 @@ static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len); static SoundMixer<2> sound_mixer; -static float master_volume = 1.0; +static float master_volume = 0.5; static float sound_effect_volume = 1.0; static float music_volume = 0.5; +static float bass_boost = 0; +static float treble_boost = 0; static float base_freq; bool swap_channels = false; static void empty_deleter(const void *) {} @@ -43,8 +45,7 @@ void game_sound_init_device(const INI_CONFIG_SECTION *audio_section) { auto r = get_sdl_global_context().init_audio(cfg, mixing_callback, nullptr); base_freq = r.freq; - - + sound_mixer.set_mix_freq(r.freq); } @@ -297,14 +298,17 @@ char set_snd_effect(AUDIO_PROPERTY funct,int data) { 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_GVOLUME: master_volume = data/512.0;update_music_volume();break; case SND_SWAP: swap_channels = !!data;break; + case SND_BASS: bass_boost = data/25.0;sound_mixer.set_bass(bass_boost);break; + case SND_TREBL: treble_boost = data/25.0;sound_mixer.set_treble(treble_boost);break; + case SND_OUTFILTER: get_sdl_global_context().enable_crt_filter(!!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; + if (funct == SND_PING|| funct == SND_GFX || funct == SND_MUSIC || funct == SND_GVOLUME || funct ==SND_BASS || funct == SND_TREBL||funct==SND_OUTFILTER) return 1; return 0; } int get_snd_effect(AUDIO_PROPERTY funct) { @@ -312,8 +316,11 @@ int get_snd_effect(AUDIO_PROPERTY 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_GVOLUME: return static_cast(master_volume * 512.0);break; + case SND_BASS: return static_cast(bass_boost * 25.0);break; + case SND_TREBL: return static_cast(treble_boost * 25.0);break; case SND_SWAP:return swap_channels?1:0; + case SND_OUTFILTER: return get_sdl_global_context().is_crt_enabled()?1:0;break; default: return 0; } } diff --git a/platform/sdl/sound_filter.cpp b/platform/sdl/sound_filter.cpp new file mode 100644 index 0000000..d376379 --- /dev/null +++ b/platform/sdl/sound_filter.cpp @@ -0,0 +1,104 @@ +#include +#include +#include "sound_filter.h" + +static constexpr float M_PI = 3.1415926535f; + +Biquad::Biquad() : b0(1), b1(0), b2(0), a1(0), a2(0), z1(0), z2(0) {} +void Biquad::setLowShelf(float gainDB, float cutoff, float sampleRate) { + const float A = std::pow(10.0f, gainDB / 40.0f); + const float omega = 2.0f * float(M_PI) * cutoff / sampleRate; + const float sn = std::sin(omega); + const float cs = std::cos(omega); + const float S = 1.0f; // shelf slope (1.0 is a typical choice) + const float alpha = sn / 2.0f * std::sqrt((A + 1.0f / A) * (1.0f / S - 1.0f) + 2.0f); + + const float b0_new = A * ((A + 1.0f) - (A - 1.0f) * cs + 2.0f * std::sqrt(A) * alpha); + const float b1_new = 2.0f * A * ((A - 1.0f) - (A + 1.0f) * cs); + const float b2_new = A * ((A + 1.0f) - (A - 1.0f) * cs - 2.0f * std::sqrt(A) * alpha); + const float a0_new = (A + 1.0f) + (A - 1.0f) * cs + 2.0f * std::sqrt(A) * alpha; + const float a1_new = -2.0f * ((A - 1.0f) + (A + 1.0f) * cs); + const float a2_new = (A + 1.0f) + (A - 1.0f) * cs - 2.0f * std::sqrt(A) * alpha; + + // Normalize coefficients so that a0 is 1 + b0 = b0_new / a0_new; + b1 = b1_new / a0_new; + b2 = b2_new / a0_new; + a1 = a1_new / a0_new; + a2 = a2_new / a0_new; + + // Reset filter state + z1 = z2 = 0.0f; +} + +void Biquad::setHighShelf(float gainDB, float cutoff, float sampleRate) { + const float A = std::pow(10.0f, gainDB / 40.0f); + const float omega = 2.0f * float(M_PI) * cutoff / sampleRate; + const float sn = std::sin(omega); + const float cs = std::cos(omega); + const float S = 1.0f; // shelf slope + const float alpha = sn / 2.0f * std::sqrt((A + 1.0f / A) * (1.0f / S - 1.0f) + 2.0f); + + const float b0_new = A * ((A + 1.0f) + (A - 1.0f) * cs + 2.0f * std::sqrt(A) * alpha); + const float b1_new = -2.0f * A * ((A - 1.0f) + (A + 1.0f) * cs); + const float b2_new = A * ((A + 1.0f) + (A - 1.0f) * cs - 2.0f * std::sqrt(A) * alpha); + const float a0_new = (A + 1.0f) - (A - 1.0f) * cs + 2.0f * std::sqrt(A) * alpha; + const float a1_new = 2.0f * ((A - 1.0f) - (A + 1.0f) * cs); + const float a2_new = (A + 1.0f) - (A - 1.0f) * cs - 2.0f * std::sqrt(A) * alpha; + + // Normalize coefficients + b0 = b0_new / a0_new; + b1 = b1_new / a0_new; + b2 = b2_new / a0_new; + a1 = a1_new / a0_new; + a2 = a2_new / a0_new; + + // Reset filter state + z1 = z2 = 0.0f; +} + +float Biquad::process(float in) { + float out = b0 * in + z1; + z1 = b1 * in - a1 * out + z2; + z2 = b2 * in - a2 * out; + return out; +} + +void Biquad::reset() { z1 = z2 = 0.0f; } + + +BassTrebleFilter::BassTrebleFilter(float sampleRate, float bassDB, float trebleDB) + : sampleRate(sampleRate), bassDB(bassDB), trebleDB(trebleDB) + { + // Choose cutoff frequencies (adjust these as desired) + bassCutoff = 200.0f; // e.g. boost/cut below 250 Hz + trebleCutoff = 2000.0f; // e.g. boost/cut above 4 kHz + + lowShelf.setLowShelf(bassDB, bassCutoff, sampleRate); + highShelf.setHighShelf(trebleDB, trebleCutoff, sampleRate); + } + +void BassTrebleFilter::setBass(float bassDBp) { + this->bassDB = bassDBp; + lowShelf.setLowShelf(bassDBp, bassCutoff, sampleRate); + } + + // Update treble gain (in dB) +void BassTrebleFilter::setTreble(float trebleDBp) { + this->trebleDB = trebleDBp; + highShelf.setHighShelf(trebleDBp, trebleCutoff, sampleRate); + } + +void BassTrebleFilter::setSampleFreq(float freq) { + this->sampleRate = freq; + lowShelf.setLowShelf(bassDB, bassCutoff, sampleRate); + highShelf.setHighShelf(trebleDB, trebleCutoff, sampleRate); + +} + + + // (Optional) Process a single sample: +float BassTrebleFilter::processSample(float sample) { + return highShelf.process(lowShelf.process(sample)); +} + diff --git a/platform/sdl/sound_filter.h b/platform/sdl/sound_filter.h new file mode 100644 index 0000000..f8297cf --- /dev/null +++ b/platform/sdl/sound_filter.h @@ -0,0 +1,72 @@ +#include +#include + +// Biquad filter class using Direct Form II Transposed +class Biquad { +public: + Biquad(); + + // Set coefficients for a low-shelf filter using RBJ's formulas. + // gainDB: boost/cut in dB, cutoff: shelf cutoff frequency in Hz, sampleRate: sampling rate in Hz. + void setLowShelf(float gainDB, float cutoff, float sampleRate); + + // Set coefficients for a high-shelf filter using RBJ's formulas. + void setHighShelf(float gainDB, float cutoff, float sampleRate); + + // Process a single sample (Direct Form II Transposed) + inline float process(float in); + // (Optional) Reset the filter state (e.g. when starting a new buffer) + void reset(); + +private: + float b0, b1, b2, a1, a2; + float z1, z2; +}; + + +// BassTrebleFilter: combines a low-shelf (bass) and a high-shelf (treble) filter. +class BassTrebleFilter { +public: + // sampleRate: sample frequency in Hz. + // bassDB: bass gain (in dB). Positive boosts bass, negative cuts. + // trebleDB: treble gain (in dB). Positive boosts treble, negative cuts. + BassTrebleFilter(float sampleRate=44100, float bassDB=0, float trebleDB=0); + // Update bass gain (in dB) + void setBass(float bassDB); + + // Update treble gain (in dB) + void setTreble(float trebleDB); + + void setSampleFreq(float freq); + + // Process a buffer of float samples (in-place). + // One common approach is to run the input through both filters. + // In many designs the filters are applied in cascade so that each only affects its target band. + void processBuffer(float* buffer, int numSamples); + + // Process a buffer of float samples (in-place). + // One common approach is to run the input through both filters. + // In many designs the filters are applied in cascade so that each only affects its target band. + template + void processBuffer(float* buffer, int numSamples) { + for (int i = 0; i < numSamples; i++) { + // You can choose to cascade the filters: + float processed = lowShelf.process(buffer[i*channels+channel_id]); + processed = highShelf.process(processed); + buffer[i*channels+channel_id] = processed; + } +} + + + // (Optional) Process a single sample: + float processSample(float sample); + + + +private: + float sampleRate; + float bassDB, trebleDB; + float bassCutoff, trebleCutoff; + Biquad lowShelf; + Biquad highShelf; +}; diff --git a/platform/sdl/sound_mixer.h b/platform/sdl/sound_mixer.h index 1045e4a..6060b33 100644 --- a/platform/sdl/sound_mixer.h +++ b/platform/sdl/sound_mixer.h @@ -1,10 +1,12 @@ #pragma once #include "wave_mixer.h" +#include "sound_filter.h" #include #include #include + template class SoundMixer { public: @@ -22,8 +24,43 @@ public: --i; } } + visit_all_filters([&](BassTrebleFilter &flt, auto idx){ + flt.processBuffer(buffer, samples_len); + }); + for (std::size_t i = 0; i < samples_len; ++i) { + for (int j = 0; j < channels; ++j) { + float f = buffer[i*channels+j] * clip_factor; + if (f > 1.0) { + clip_factor *= clip_factor / f; + f = 1.0; + } else if (f < -1.0) { + clip_factor *= -clip_factor / f; + f = -1.0; + } + buffer[i*channels+j] = f; + } + } ++_calls; _calls.notify_all(); + if (clip_factor<1) clip_factor*=1.001f; + } + + void set_mix_freq(int freq) { + visit_all_filters([&](BassTrebleFilter &flt, auto){ + flt.setSampleFreq(static_cast(freq)); + }); + } + + void set_bass(float val) { + visit_all_filters([&](BassTrebleFilter &flt, auto){ + flt.setBass(val); + }); + } + + void set_treble(float val) { + visit_all_filters([&](BassTrebleFilter &flt, auto){ + flt.setTreble(val); + }); } void wait_for_advance_write_pos() { @@ -103,10 +140,21 @@ protected: } } + template + void visit_all_filters_idx(Fn &&fn, std::integer_sequence) { + (fn(_filters[idx], std::integral_constant{}),...); + } + + + template + void visit_all_filters(Fn &&fn) { + visit_all_filters_idx(std::forward(fn), std::make_integer_sequence{}); + } std::mutex _mx; std::vector _tracks; std::atomic _calls = {0}; - + std::array _filters; + float clip_factor = 1.0; };