bass, treble, control crt filter in setup, fix lock multiaction

This commit is contained in:
Ondrej Novak 2025-02-18 10:32:37 +01:00
parent 05c1f952c4
commit e631f339dd
11 changed files with 279 additions and 23 deletions

View file

@ -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) { char ask_save_dialog(char *name_buffer, size_t name_size) {
const char *str_label = texty[98]; 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)); set_font(H_FBOLD,RGB555(31,31,31));
add_window(170,130,300,100,H_WINTXTR,3,20,20); add_window(170,130,300,100,H_WINTXTR,3,20,20);

View file

@ -739,10 +739,11 @@ void call_macro_ex(int side, int flags, int runatside) {
z = mrec.action_list; z = mrec.action_list;
if (z->general.flags & flags) { if (z->general.flags & flags) {
int jmp_to = -1; int jmp_to = -1;
char cancel_enabled = 1;
int stindex = z - first_macro; int stindex = z - first_macro;
char cancel = 0;
if (!z->general.once || !macro_state_block.states[stindex]) { if (!z->general.once || !macro_state_block.states[stindex]) {
macro_state_block.states[stindex] = 1; macro_state_block.states[stindex] = 1;
cancel = z->general.cancel;
count_actions++; count_actions++;
switch (z->general.action) { switch (z->general.action) {
default: default:
@ -789,14 +790,14 @@ void call_macro_ex(int side, int flags, int runatside) {
enter_shop(z->text.textindex); enter_shop(z->text.textindex);
break; break;
case MA_CLOCK: 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); z->clock.codenum);
break; break;
case MA_CACTN: case MA_CACTN:
cancel_action(z->cactn.sector, z->cactn.dir); cancel_action(z->cactn.sector, z->cactn.dir);
break; break;
case MA_LOCK: 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); z->lock.thieflevel, &z->lock);
break; break;
case MA_SWAPS: case MA_SWAPS:
@ -879,7 +880,7 @@ void call_macro_ex(int side, int flags, int runatside) {
if (jmp_to != -1) { if (jmp_to != -1) {
mrec = go_macro(runatside, jmp_to); mrec = go_macro(runatside, jmp_to);
program_counter = jmp_to; program_counter = jmp_to;
} else if (z->general.cancel && cancel_enabled) { } else if (cancel) {
break; break;
} else { } else {
program_counter++; program_counter++;

View file

@ -145,7 +145,7 @@ void new_setup()
int i; int i;
static int textxp[]={ 75, 75,435,435,434,535,535,535,434,434,434,434,434, 35,410,510}; 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 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); mix_back_sound(256000-16384);
memset(&ctl,0,sizeof(ctl)); memset(&ctl,0,sizeof(ctl));

View file

@ -802,7 +802,10 @@ void cti_texty(void)
exit(1); 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", "");
} }

View file

@ -4,5 +4,6 @@ add_library(skeldal_sdl
sdl_context.cpp sdl_context.cpp
BGraph2.cpp BGraph2.cpp
input.cpp input.cpp
sound.cpp) sound.cpp
sound_filter.cpp)
set_property(TARGET skeldal_sdl PROPERTY CXX_STANDARD 20) set_property(TARGET skeldal_sdl PROPERTY CXX_STANDARD 20)

View file

@ -158,6 +158,10 @@ void SDLContext::init_video(const VideoConfig &config, const char *title) {
aspect_x = config.aspect_x; aspect_x = config.aspect_x;
aspect_y = config.aspect_y; aspect_y = config.aspect_y;
_crt_filter = config.crt_filter ; _crt_filter = config.crt_filter ;
if (_crt_filter == CrtFilterType::none) {
_crt_filter = CrtFilterType::autoselect;
_enable_crt = false;
}
_fullscreen_mode = config.fullscreen; _fullscreen_mode = config.fullscreen;
@ -396,13 +400,6 @@ void SDLContext::refresh_screen() {
SDL_SetTextureAlphaMod(_visible_texture, 255); SDL_SetTextureAlphaMod(_visible_texture, 255);
SDL_RenderCopy(_renderer.get(), _visible_texture, NULL, &winrc); 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) { for (const auto &sprite: _sprites) if (sprite.shown) {
SDL_Rect rc = to_window_rect(winrc,sprite._rect); SDL_Rect rc = to_window_rect(winrc,sprite._rect);
SDL_RenderCopy(_renderer.get(), sprite._txtr.get(), NULL, &rc); 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; recalc_rect.y = _mouse_rect.y - f.y;
SDL_RenderCopy(_renderer.get(), _mouse.get(), NULL, &recalc_rect); 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()); SDL_RenderPresent(_renderer.get());
} }
@ -826,3 +830,16 @@ void SDLContext::unload_sprite(int sprite) {
push_item(DisplayRequest::sprite_unload); push_item(DisplayRequest::sprite_unload);
push_item(sprite); 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;
}

View file

@ -102,6 +102,8 @@ public:
void hide_sprite(int sprite_id); void hide_sprite(int sprite_id);
void sprite_set_zindex(int sprite_id, int zindex); void sprite_set_zindex(int sprite_id, int zindex);
void unload_sprite(int sprite); void unload_sprite(int sprite);
void enable_crt_filter(bool enable);
bool is_crt_enabled() const;
protected: protected:
@ -164,6 +166,7 @@ protected:
int aspect_x = 4; int aspect_x = 4;
int aspect_y = 3; int aspect_y = 3;
CrtFilterType _crt_filter= CrtFilterType::autoselect; CrtFilterType _crt_filter= CrtFilterType::autoselect;
bool _enable_crt = true;
std::function<void()> _quit_callback; std::function<void()> _quit_callback;
std::unique_ptr<SDL_Window, SDL_Deleter> _window; std::unique_ptr<SDL_Window, SDL_Deleter> _window;

View file

@ -14,9 +14,11 @@
static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len); static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len);
static SoundMixer<2> sound_mixer; 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 sound_effect_volume = 1.0;
static float music_volume = 0.5; static float music_volume = 0.5;
static float bass_boost = 0;
static float treble_boost = 0;
static float base_freq; static float base_freq;
bool swap_channels = false; bool swap_channels = false;
static void empty_deleter(const void *) {} 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); auto r = get_sdl_global_context().init_audio(cfg, mixing_callback, nullptr);
base_freq = r.freq; 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_PING: break;
case SND_GFX: sound_effect_volume = data/256.0;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_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_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; default: return 0;
} }
return 1; return 1;
} }
char check_snd_effect(AUDIO_PROPERTY funct) { 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; return 0;
} }
int get_snd_effect(AUDIO_PROPERTY funct) { 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_PING: return 1;
case SND_GFX: return static_cast<int>(sound_effect_volume * 256.0);break; case SND_GFX: return static_cast<int>(sound_effect_volume * 256.0);break;
case SND_MUSIC: return static_cast<int>(music_volume * 128.0);break; case SND_MUSIC: return static_cast<int>(music_volume * 128.0);break;
case SND_GVOLUME: return static_cast<int>(master_volume * 256.0);break; case SND_GVOLUME: return static_cast<int>(master_volume * 512.0);break;
case SND_BASS: return static_cast<int>(bass_boost * 25.0);break;
case SND_TREBL: return static_cast<int>(treble_boost * 25.0);break;
case SND_SWAP:return swap_channels?1:0; 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; default: return 0;
} }
} }

View file

@ -0,0 +1,104 @@
#include <cmath>
#include <vector>
#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));
}

View file

@ -0,0 +1,72 @@
#include <cmath>
#include <vector>
// 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<int channels, int channel_id>
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;
};

View file

@ -1,10 +1,12 @@
#pragma once #pragma once
#include "wave_mixer.h" #include "wave_mixer.h"
#include "sound_filter.h"
#include <algorithm> #include <algorithm>
#include <optional> #include <optional>
#include <vector> #include <vector>
template<int channels> template<int channels>
class SoundMixer { class SoundMixer {
public: public:
@ -22,8 +24,43 @@ public:
--i; --i;
} }
} }
visit_all_filters([&](BassTrebleFilter &flt, auto idx){
flt.processBuffer<channels, idx.value>(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;
_calls.notify_all(); _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<float>(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() { void wait_for_advance_write_pos() {
@ -103,10 +140,21 @@ protected:
} }
} }
template<typename Fn, int ... idx>
void visit_all_filters_idx(Fn &&fn, std::integer_sequence<int, idx...>) {
(fn(_filters[idx], std::integral_constant<int, idx>{}),...);
}
template<typename Fn>
void visit_all_filters(Fn &&fn) {
visit_all_filters_idx(std::forward<Fn>(fn), std::make_integer_sequence<int, channels>{});
}
std::mutex _mx; std::mutex _mx;
std::vector<Track> _tracks; std::vector<Track> _tracks;
std::atomic<unsigned int> _calls = {0}; std::atomic<unsigned int> _calls = {0};
std::array<BassTrebleFilter, channels> _filters;
float clip_factor = 1.0;
}; };