mirror of
https://github.com/ondra-novak/gates_of_skeldal.git
synced 2025-07-05 14:10:27 -04:00
bass, treble, control crt filter in setup, fix lock multiaction
This commit is contained in:
parent
05c1f952c4
commit
e631f339dd
11 changed files with 279 additions and 23 deletions
|
@ -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);
|
||||||
|
|
|
@ -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++;
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
104
platform/sdl/sound_filter.cpp
Normal file
104
platform/sdl/sound_filter.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
|
72
platform/sdl/sound_filter.h
Normal file
72
platform/sdl/sound_filter.h
Normal 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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue