/*
88888888
888888888888
88888888888888
8888888888888888
888888888888888888
888888 8888 888888
88888 88 88888
888888 8888 888888
88888888888888888888
88888888888888888888
8888888888888888888888
8888888888888888888888888888
88888888888888888888888888888888
88888888888888888888
888888888888888888888888
888888 8888888888 888888
888 8888 8888 888
888 888
OCTOBANANA
Licensed under the MIT License
Copyright (c) 2020 Brett Robinson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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 OR COPYRIGHT HOLDERS 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.
*/
#include "app/app.hh"
#include "app/filter.hh"
#include "app/util.hh"
#include "ob/string.hh"
#include "ob/term.hh"
#include "ob/prism.hh"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
template
T mean(std::vector const& v, std::size_t const size) {
return std::accumulate(v.begin(), v.begin() + size, static_cast(0)) / size;
}
template
T stdev(std::vector const& v, std::size_t const size, T const mean) {
std::vector dif (size);
std::transform(v.begin(), v.begin() + size, dif.begin(), [mean](auto const n) {return n - mean;});
return std::sqrt(std::inner_product(dif.begin(), dif.end(), dif.begin(), static_cast(0)) / size);
}
template
static std::enable_if_t::is_integer, bool>
almost_equal(T x, T y, int ulp = 2) {
return std::abs(x - y) <= std::numeric_limits::epsilon() * std::abs(x + y) * ulp || std::abs(x - y) < std::numeric_limits::min();
}
template
T clamp(T const val, T const min, T const max) {
assert(!(max < min));
if (val < min) {return min;}
if (val > max) {return max;}
return val;
}
template
T clampc(T const val, T const min, T const max) {
assert(!(max < min));
if (min == max) {return min;}
if (val < min) {
if ((min - val) > ((max - min) + 1)) {
return max - (((min - val) % ((max - min + 1))) - 1);
}
else {
return max - ((min - val) - 1);
}
}
if (val > max) {
if ((val - max) > ((max - min) + 1)) {
return min + (((val - max) % ((max - min + 1))) - 1);
}
else {
return min + ((val - max) - 1);
}
}
return val;
}
template
T clampcf(T const val, T const min, T const max) {
assert(!(max < min));
if (almost_equal(min, max)) {return min;}
if (val < min) {
if ((min - val) > ((max - min) + 1)) {
return max - (std::fmod((min - val), ((max - min + 1))) - 1);
}
else {
return max - ((min - val) - 1);
}
}
if (val > max) {
if ((val - max) > ((max - min) + 1)) {
return min + (std::fmod((val - max), ((max - min + 1))) - 1);
}
else {
return min + ((val - max) - 1);
}
}
return val;
}
template
constexpr T lerp(T const a, T const b, F const t) {
return (a + (t * (b - a)));
}
template
OB::Prism::HSLA lerp(OB::Prism::HSLA a, OB::Prism::HSLA b, T t) {
OB::Prism::HSLA c;
// clockwise distance between a and b
// auto d = b.h() - a.h();
// if (d == 0) {
// c = a;
// }
// else {
// c.h(clampc(lerp(0, clampc(b.h() - a.h(), 0, 359), t) + a.h(), 0, 359));
// }
// shortest distance between a and b
auto d = b.h() - a.h();
if (std::abs(d) > 180) {
if (d < 0) {
d += 360;
}
else {
d -= 360;
}
}
c.h(static_cast((a.h() + (t * d)) + 360) % 360);
if (a.h() > b.h()) {
t = 1.0 - t;
std::swap(a, b);
}
c.s(lerp(a.s(), b.s(), t));
c.l(lerp(a.l(), b.l(), t));
c.a(lerp(a.a(), b.a(), t));
return c;
}
template
T scale(T const val, T const in_min, T const in_max, T const out_min, T const out_max) {
assert(in_min <= in_max);
assert(out_min <= out_max);
return (out_min + (out_max - out_min) * ((val - in_min) / (in_max - in_min)));
}
template
T scale_log(T const val, T const in_min, T const in_max, T const out_min, T const out_max) {
assert(in_min <= in_max);
assert(out_min <= out_max);
auto const b = std::log(out_max / out_min) / (in_max - in_min);
auto const a = out_max / std::exp(b * in_max);
return a * std::exp(b * val);
}
class Note {
public:
Note(double const freq, std::size_t const scale = 12, double const a4 = 440.0) {
assert(freq > 1);
assert(scale == 12 || scale == 24);
_scale = scale;
int const tones {static_cast(std::round(std::log(freq / a4) / std::log(std::pow(2.0, 1.0 / static_cast(_scale)))) + (57 * (_scale / 12)))};
_tone = static_cast(tones % _scale);
_octave = static_cast(tones / _scale);
}
friend bool operator==(Note const& lhs, Note const& rhs);
friend bool operator!=(Note const& lhs, Note const& rhs);
std::string str() {
if (_scale == 12) {
return _semi_tones.at(_tone) + std::to_string(_octave);
}
return _quarter_tones.at(_tone) + std::to_string(_octave);
}
std::size_t scale() {
return _scale;
}
std::size_t tone() {
return _tone;
}
std::size_t octave() {
return _octave;
}
private:
std::size_t _scale {0};
std::size_t _tone {0};
std::size_t _octave {0};
inline static std::unordered_map const _semi_tones {
{ 0, "C"}, { 1, "C#"},
{ 2, "D"}, { 3, "D#"},
{ 4, "E"},
{ 5, "F"}, { 6, "F#"},
{ 7, "G"}, { 8, "G#"},
{ 9, "A"}, {10, "A#"},
{11, "B"},
};
inline static std::unordered_map const _quarter_tones {
{ 0, "C "}, { 1, "C+ "}, { 2, "C #"}, { 3, "C#+"},
{ 4, "D "}, { 5, "D+ "}, { 6, "D #"}, { 7, "D#+"},
{ 8, "E "}, { 9, "E+ "},
{10, "F "}, {11, "F+ "}, {12, "F #"}, {13, "F#+"},
{14, "G "}, {15, "G+ "}, {16, "G #"}, {17, "G#+"},
{18, "A "}, {19, "A+ "}, {20, "A #"}, {21, "A#+"},
{22, "B "}, {23, "B+ "},
};
};
bool operator==(Note const& lhs, Note const& rhs) {
return (lhs._scale == rhs._scale) && (lhs._tone == rhs._tone) && (lhs._octave == rhs._octave);
}
bool operator!=(Note const& lhs, Note const& rhs) {
return !(lhs == rhs);
}
App::App(OB::Parg const& pg) : _pg {pg} {
// prevent SFML from writing to std::cerr
sf::err().rdbuf(nullptr);
}
App::~App() {
}
void App::screen_init() {
std::cout
<< aec::cursor_hide
<< aec::screen_push
<< aec::cursor_hide
<< aec::screen_clear
<< aec::cursor_home
<< std::flush;
if (!_raw_output) {
_term_mode->set_raw();
}
}
void App::screen_deinit() {
std::cout
<< aec::nl
<< aec::screen_pop
<< aec::cursor_show
<< std::flush;
if (!_raw_output) {
_term_mode->set_cooked();
}
}
void App::await_tick() {
_tick_timer.stop();
_timer.expires_at(_tick_timer.end() + (_tick - (_tick_timer.time() % _tick)));
_timer.async_wait([&](auto ec) {
if (ec) {return;}
_tick_end = Clock::now();
_tick_timer.clear().start(_tick_end);
auto delta = std::chrono::duration_cast(_tick_end - _tick_begin);
_time += delta;
_tick_begin = _tick_end;
if (delta.count() > 0) {
_fps_actual = static_cast(std::round(1000.0 / std::chrono::duration_cast(delta).count()));
}
if (delta > _tick) {
int const dropped {static_cast((delta.count() / _tick.count())) - 1};
_fps_dropped += dropped;
}
on_tick(std::chrono::duration_cast(delta).count() / 1000.0);
await_tick();
});
}
void App::on_tick(double const dt) {
update(dt);
draw();
render();
}
void App::await_signal() {
_sig.on_signal({SIGINT, SIGTERM}, [&](auto const& ec, auto sig) {
// std::cerr << "\nEvent: " << Belle::Signal::str(sig) << "\n";
_io.stop();
});
_sig.on_signal(SIGWINCH, [&](auto const& ec, auto sig) {
// std::cerr << "\nEvent: " << Belle::Signal::str(sig) << "\n";
_sig.wait();
on_winch();
});
_sig.on_signal(SIGTSTP, [&](auto const& ec, auto sig) {
// std::cerr << "\nEvent: " << Belle::Signal::str(sig) << "\n";
_sig.wait();
_rec.stop();
_timer.cancel();
screen_deinit();
kill(getpid(), SIGSTOP);
});
_sig.on_signal(SIGCONT, [&](auto const& ec, auto sig) {
// std::cerr << "\nEvent: " << Belle::Signal::str(sig) << "\n";
_sig.wait();
screen_init();
on_winch();
_tick_timer.clear();
_tick_begin = Clock::now();
await_tick();
_rec.start();
});
_sig.wait();
}
void App::on_winch() {
if (!_fixed_size) {
OB::Term::size(_width, _height);
}
_win.size = {_width, _height};
_win.winch();
}
void App::await_read() {
_read.on_read([&](auto const& ctx) {
auto nctx = ctx;
std::visit([&](auto& e) {on_read(e);}, nctx);
});
_read.run();
}
bool App::on_read(Read::Null& ctx) {
return false;
}
bool App::on_read(Read::Mouse& ctx) {
ctx.pos.x -= 1;
ctx.pos.y = _height - ctx.pos.y;
return false;
}
bool App::on_read(Read::Key& ctx) {
// TODO add keybind to lock input
// add a timed prompt info string so that a message describing how to exit locked input mode can be shown on keypress when locked
// if (ctx.ch == OB::Term::ctrl_key(' ')) {
// _lock_input = !_lock_input;
// }
// if (_lock_input) {
// return true;
// }
{
_code.erase(_code.begin());
_code.emplace_back(std::make_pair(ctx.ch, _time));
if (_code.back().second - _code.front().second < 3000ms) {
if (
_code[0].first == Key::Up &&
_code[1].first == Key::Up &&
_code[2].first == Key::Down &&
_code[3].first == Key::Down &&
_code[4].first == Key::Left &&
_code[5].first == Key::Right &&
_code[6].first == Key::Left &&
_code[7].first == Key::Right &&
_code[8].first == 'b' &&
_code[9].first == 'a'
) {
_cfg.draw_peak = true;
_cfg.peak_reverse = true;
}
else if (
_code[0].first == Key::Up &&
_code[1].first == Key::Down &&
_code[2].first == Key::Left &&
_code[3].first == Key::Right &&
_code[4].first == Key::Down &&
_code[5].first == Key::Up &&
_code[6].first == Key::Right &&
_code[7].first == Key::Left &&
_code[8].first == '0' &&
_code[9].first == '8'
) {
{
_cfg.fps = 60;
_cfg.interval = (1000.0 / _cfg.fps) * 0.2;
_cfg.mono = false;
_cfg.size = 2048;
_cfg.sample_rate = 16000;
_cfg.low_pass = 2200;
_cfg.high_pass = 20;
_cfg.threshold_min = -54.0;
_cfg.threshold_max = -24.0;
_cfg.sort_log = false;
_cfg.octave_scale = 24;
_cfg.filter = Filter_Type::sg;
_cfg.filter_threshold = 0.10;
_cfg.filter_size = 3;
_cfg.overlay = false;
_cfg.bar_swap = false;
_cfg.block_flip = false;
_cfg.block_stack = true;
_cfg.block_reverse = false;
_cfg.block_vertical = true;
_cfg.block_width_dynamic = false;
_cfg.block_height_dynamic = true;
_cfg.block_height_full = true;
_cfg.block_height_linear = true;
_cfg.block_width = 1.0;
_cfg.block_height = 0.25;
_cfg.block_padding = 0;
_cfg.draw_freq = true;
_cfg.draw_freq_always = false;
_cfg.speed_freq_unique = true;
_cfg.speed_freq_up = 0.999999;
_cfg.speed_freq_down = 0.999999;
_cfg.draw_peak = false;
_cfg.draw_peak_always = false;
_cfg.speed_peak_unique = false;
_cfg.speed_peak_down = 0.20;
_cfg.peak_reverse = false;
_cfg.color = true;
_cfg.alpha = false;
_cfg.alpha_blend = 0.3;
_cfg.cl_shift = 0.0;
_cfg.cl_swap = false;
_cfg.cl_gradient_x = true;
_cfg.cl_gradient_y = true;
_cfg.style.bg = OB::Prism::Hex("1b1e24");
_cfg.style.freq = OB::Prism::Hex("4feae7");
_cfg.style.freq2 = OB::Prism::Hex("f34b7d");
_cfg.style.freq3 = _cfg.style.freq2;
_cfg.style.freq4 = _cfg.style.freq;
}
{
_style_base = Style{Style::Bit_24, Style::Null, _cfg.style.bg, _cfg.style.bg};
_win.style_base = _style_base;
_win.refresh();
_tick = static_cast(1000000000 / _cfg.fps);
if (_rec.recording()) {_rec.stop();}
_rec.process_interval(sf::milliseconds(_cfg.interval));
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
_rec.sample_rate(_cfg.sample_rate);
// _channel = clampc(_channel + 1, static_cast(Channel_Type::mono_mixed), static_cast(Channel_Type::size - 1));
if (_cfg.mono) {
_rec.channels(1);
}
else {
_rec.channels(2);
}
_rec.start();
}
}
}
}
switch (ctx.ch) {
case OB::Term::ctrl_key('c'): {
kill(getpid(), SIGINT);
return true;
}
case OB::Term::ctrl_key('z'): {
kill(getpid(), SIGTSTP);
return true;
}
case OB::Term::ctrl_key('l'): {
kill(getpid(), SIGWINCH);
return true;
}
case Key::Left: {
_overlay_index = clamp(static_cast(_overlay_index) - 1, 0, 64);
return true;
}
case Key::Right: {
_overlay_index = clamp(static_cast(_overlay_index) + 1, 0, 64);
return true;
}
case '?': {
auto const recording = _rec.recording();
if (recording) {
_rec.stop();
}
_timer.cancel();
screen_deinit();
std::system(("$(which less) -Ri '+/Key Bindings' <<'EOF'\n" + _pg.help() + "EOF").c_str());
screen_init();
on_winch();
_tick_timer.clear();
_tick_begin = Clock::now();
await_tick();
if (recording) {
_rec.start();
}
return true;
}
case Key::Backspace: {
_cfg = {};
{
_style_base = Style{Style::Bit_24, Style::Null, _cfg.style.bg, _cfg.style.bg};
_win.style_base = _style_base;
_win.refresh();
_tick = static_cast(1000000000 / _cfg.fps);
if (_rec.recording()) {_rec.stop();}
_rec.process_interval(sf::milliseconds(_cfg.interval));
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
_rec.sample_rate(_cfg.sample_rate);
// _channel = clampc(_channel + 1, static_cast(Channel_Type::mono_mixed), static_cast(Channel_Type::size - 1));
if (_cfg.mono) {
_rec.channels(1);
}
else {
_rec.channels(2);
}
_rec.start();
}
return true;
}
case 'q': {
_io.stop();
return true;
}
case 'w': {
_cfg.block_width = clampc(static_cast(_cfg.block_width) + 1, 1, 32);
return true;
}
case 'W': {
_cfg.block_width = clampc(static_cast(_cfg.block_width) - 1, 1, 32);
return true;
}
case 'e': {
_cfg.block_padding = clampc(static_cast(_cfg.block_padding) + 1, 0, 8);
return true;
}
case 'E': {
_cfg.block_padding = clampc(static_cast(_cfg.block_padding) - 1, 0, 8);
return true;
}
case 'r': {
_cfg.block_reverse = ! _cfg.block_reverse;
return true;
}
case 't': {
_cfg.bar_swap = ! _cfg.bar_swap;
return true;
}
case 'y': {
_cfg.sort_log = ! _cfg.sort_log;
return true;
}
case 'i': {
_cfg.fps = clamp(_cfg.fps + 2, 10, 60);
_tick = static_cast(1000000000 / _cfg.fps);
_cfg.interval = (1000.0 / _cfg.fps) * 0.2;
_rec.process_interval(sf::milliseconds(_cfg.interval));
return true;
}
case 'I': {
_cfg.fps = clamp(_cfg.fps - 2, 10, 60);
_tick = static_cast(1000000000 / _cfg.fps);
_cfg.interval = (1000.0 / _cfg.fps) * 0.2;
_rec.process_interval(sf::milliseconds(_cfg.interval));
return true;
}
case 'o': {
_cfg.overlay = ! _cfg.overlay;
return true;
}
case 'p': {
if (_rec.recording()) {
_rec.stop();
}
else {
_rec.start();
}
return true;
}
case 'a': {
_cfg.block_vertical = ! _cfg.block_vertical;
return true;
}
case 's': {
auto const recording = _rec.recording();
if (recording) {
_rec.stop();
}
_cfg.mono = ! _cfg.mono;
// _channel = clampc(_channel + 1, static_cast(Channel_Type::mono_mixed), static_cast(Channel_Type::size - 1));
if (_cfg.mono) {
_rec.channels(1);
}
else {
_rec.channels(2);
}
if (recording) {
_rec.start();
}
return true;
}
case 'd': {
_cfg.block_stack = ! _cfg.block_stack;
return true;
}
case 'f': {
_cfg.block_flip = ! _cfg.block_flip;
return true;
}
case 'g': {
_cfg.filter = clampc(_cfg.filter + 1, static_cast(Filter_Type::none), static_cast(Filter_Type::size - 1));
return true;
}
case 'G': {
_cfg.filter = clampc(_cfg.filter - 1, static_cast(Filter_Type::none), static_cast(Filter_Type::size - 1));
return true;
}
case 'h': {
_cfg.high_pass = static_cast(clamp(static_cast(_cfg.high_pass) + 20, 20, static_cast(_cfg.sample_rate) / 2));
if (static_cast(_cfg.high_pass) > static_cast(_cfg.low_pass)) {_cfg.low_pass = _cfg.high_pass;}
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
return true;
}
case 'H': {
_cfg.high_pass = static_cast(clamp(static_cast(_cfg.high_pass) - 20, 20, static_cast(_cfg.sample_rate) / 2));
if (static_cast(_cfg.high_pass) > static_cast(_cfg.low_pass)) {_cfg.low_pass = _cfg.high_pass;}
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
return true;
}
case 'j': {
_cfg.threshold_min = clamp(_cfg.threshold_min + 2.0, -120.0, 0.0);
if (_cfg.threshold_min > _cfg.threshold_max) {_cfg.threshold_max = _cfg.threshold_min;}
return true;
}
case 'J': {
_cfg.threshold_min = clamp(_cfg.threshold_min - 2.0, -120.0, 0.0);
return true;
}
case 'k': {
_cfg.threshold_max = clamp(_cfg.threshold_max - 2.0, -120.0, 0.0);
if (_cfg.threshold_max < _cfg.threshold_min) {_cfg.threshold_min = _cfg.threshold_max;}
return true;
}
case 'K': {
_cfg.threshold_max = clamp(_cfg.threshold_max + 2.0, -120.0, 0.0);
return true;
}
case 'l': {
_cfg.low_pass = static_cast(clamp(static_cast(_cfg.low_pass) - 20, 20, static_cast(_cfg.sample_rate) / 2));
if (static_cast(_cfg.low_pass) < static_cast(_cfg.high_pass)) {_cfg.high_pass = _cfg.low_pass;}
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
return true;
}
case 'L': {
_cfg.low_pass = static_cast(clamp(static_cast(_cfg.low_pass) + 20, 20, static_cast(_cfg.sample_rate) / 2));
if (static_cast(_cfg.low_pass) < static_cast(_cfg.high_pass)) {_cfg.high_pass = _cfg.low_pass;}
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
return true;
}
case 'z': {
_cfg.draw_freq = ! _cfg.draw_freq;
return true;
}
case 'Z': {
_cfg.draw_freq_always = ! _cfg.draw_freq_always;
return true;
}
case 'x': {
_cfg.draw_peak = ! _cfg.draw_peak;
return true;
}
case 'X': {
_cfg.draw_peak_always = ! _cfg.draw_peak_always;
return true;
}
case 'c': {
_cfg.color = ! _cfg.color;
if (_cfg.color) {
_style_base = Style{Style::Bit_24, Style::Null, _cfg.style.bg, _cfg.style.bg};
}
else {
_style_base = Style{Style::Default, Style::Null, {}, {}};
}
_win.style_base = _style_base;
_win.refresh();
return true;
}
case 'C': {
_cfg.alpha = ! _cfg.alpha;
return true;
}
case 'v': {
_cfg.style.freq = OB::Prism::HSLA{random_range(0, 359), static_cast(random_range(40, 100)), static_cast(random_range(20, 80)), 1.0};
_cfg.style.freq2 = OB::Prism::HSLA{clampc(_cfg.style.freq.h() + random_range(6, 120), 0, 359), static_cast(random_range(50, 100)), static_cast(random_range(30, 70)), 1.0};
// _cfg.style.freq2 = OB::Prism::HSLA{clampc(_cfg.style.freq.h() + random_range(30, 150), 0, 359), static_cast(random_range(40, 100)), static_cast(random_range(20, 80)), 1.0};
if (_cfg.style.freq.l() > _cfg.style.freq2.l()) {
std::swap(_cfg.style.freq, _cfg.style.freq2);
}
// TODO use 2 or 4 gradient colors
// maybe use fixed sat and lum
_cfg.style.freq3 = OB::Prism::HSLA{clampc(_cfg.style.freq2.h() + random_range(0, 30), 0, 359), static_cast(clampcf(_cfg.style.freq2.s() + random_range(0, 20), 0.f, 100.f)), static_cast(clampcf(_cfg.style.freq2.l() + random_range(0, 20), 0.f, 100.f)), 1.0};
_cfg.style.freq4 = OB::Prism::HSLA{clampc(_cfg.style.freq.h() + random_range(0, 30), 0, 359), static_cast(clampcf(_cfg.style.freq.s() + random_range(0, 20), 0.f, 100.f)), static_cast(clampcf(_cfg.style.freq.l() + random_range(0, 20), 0.f, 100.f)), 1.0};
// _cfg.style.freq3 = _cfg.style.freq2;
// _cfg.style.freq4 = _cfg.style.freq;
return true;
}
case 'V': {
_cfg.cl_shift = _cfg.cl_shift > 0.0 ? 0.0 : 250.0;
return true;
}
case 'b': {
_cfg.block_height_full = ! _cfg.block_height_full;
return true;
}
case 'n': {
_cfg.cl_gradient_x = ! _cfg.cl_gradient_x;
return true;
}
case 'N': {
_cfg.cl_gradient_y = ! _cfg.cl_gradient_y;
return true;
}
case 'm': {
swap_colors();
return true;
}
case 'M': {
_cfg.cl_swap = ! _cfg.cl_swap;
return true;
}
}
return false;
}
void App::swap_colors() {
std::swap(_cfg.style.freq, _cfg.style.freq2);
std::swap(_cfg.style.freq3, _cfg.style.freq4);
}
void App::shift_colors(double const dt) {
if (_cfg.color && _cfg.cl_shift > 0.0) {
_cl_delta += dt * 1000.0;
while (_cl_delta >= _cfg.cl_shift) {
_cl_delta -= _cfg.cl_shift;
_cfg.style.freq.h(clampc(static_cast(_cfg.style.freq.h()) - 1, 0, 359));
_cfg.style.freq2.h(clampc(static_cast(_cfg.style.freq2.h()) - 1, 0, 359));
_cfg.style.freq3.h(clampc(static_cast(_cfg.style.freq3.h()) - 1, 0, 359));
_cfg.style.freq4.h(clampc(static_cast(_cfg.style.freq4.h()) - 1, 0, 359));
}
}
}
std::size_t App::bar_calc_height(double const val, std::size_t height) const {
std::size_t res {0};
auto const nheight = (height * 8.0) - 1.0;
if (nheight < 0.0) {
return 0;
}
if (_cfg.block_height_linear) {
res = static_cast(std::trunc(scale(val, _cfg.threshold_min, _cfg.threshold_max, 0.0, nheight)));
}
else {
res = static_cast((std::trunc(scale_log(val, _cfg.threshold_min, _cfg.threshold_max, 1.0, nheight + 0.001)) - 1.0) * 2.0);
}
if (res > nheight) {
res = nheight;
}
return res;
}
void App::bar_calc_dimensions(Bars& bars) {
// calc bar width
// either a fixed size or dynamic based on output width
if (_cfg.block_width_dynamic) {
bars.bar_width = std::max(1ul, static_cast(std::trunc(bars.width * _cfg.block_width))); // _cfg.block_width 0.0<->1.0
}
else {
bars.bar_width = _cfg.block_width;
if (!_cfg.block_vertical) {
bars.bar_width = std::max(1ul, bars.bar_width / 2ul);
}
if (bars.bar_width >= bars.width) {bars.bar_width = bars.width;}
}
// calc bar height
// either a fixed size or dynamic based on output height
if (_cfg.block_height_dynamic) {
bars.bar_height = std::max(1ul, static_cast(std::trunc(bars.height * _cfg.block_height)));
}
else {
bars.bar_height = _cfg.block_height;
if (!_cfg.block_vertical) {
bars.bar_height = std::max(1ul, bars.bar_height * 2ul);
}
if (bars.bar_height >= bars.height) {bars.bar_height = bars.height;}
}
// calc number of bars to output
if (bars.width == 0) {
bars.size = 1;
}
else {
bars.size = std::max(1ul, (bars.width - (bars.margin_lhs + bars.margin_rhs)) / (bars.bar_width + bars.padding) + ((bars.width - (bars.margin_lhs + bars.margin_rhs)) % (bars.bar_width + bars.padding) ? 1ul : 0ul));
}
}
void App::bar_process(std::vector const& bins, Bars& bars) {
auto const bin_freq_res = _rec.sample_rate() / static_cast(_cfg.size);
auto const low = _rec.high_pass() + std::fmod(_rec.high_pass(), bin_freq_res);
_info.resize(bars.size);
if (bins.size()) {
if (_cfg.sort_log) {
std::size_t T {0};
for (std::size_t x = 0, p = 0, i = 0; x < bars.size; ++x) {
i = static_cast(std::trunc(scale_log(static_cast(x + 1), 1.0, static_cast(bars.size), 1.0, static_cast(bins.size())) + 0.001));
if (p >= i) {i = p + 1;}
if (i >= bins.size()) {i = bins.size() - 1;}
auto const begin = bins.begin() + p;
auto const end = bins.begin() + i;
// value is based on average of elements in the range
// double v = std::accumulate(begin, end, 0.0) / (i - p);
// bars.raw[x] = v;
// value is based on max element in the range
auto const ptr = std::max_element(begin, end);
bars.raw[x] = *ptr;
_info[x] = Info{bin_freq_res * static_cast(std::distance(bins.begin(), ptr)) + static_cast(std::trunc(low))};
T += i - p;
p = i;
}
}
else {
std::size_t T {0};
std::vector> raw;
for (std::size_t x = 0, p = 0, i = 0; p + 1 < bins.size(); ++x) {
Note const note {bin_freq_res * p + low, _cfg.octave_scale};
for (++i; i < bins.size(); ++i) {
if (note != Note(bin_freq_res * i + low, _cfg.octave_scale)) {
break;
}
}
if (p >= i) {i = p + 1;}
if (i >= bins.size()) {i = bins.size() - 1;}
auto const begin = bins.begin() + p;
auto const end = bins.begin() + i;
// value is based on average of elements in the range
// double v = std::accumulate(begin, end, 0.0) / (i - p);
// value is based on max element in the range
auto const ptr = std::max_element(begin, end);
raw.emplace_back(*ptr, bin_freq_res * static_cast(std::distance(bins.begin(), ptr)) + static_cast(std::trunc(low)));
T += i - p;
p = i;
}
// TODO make each octave as equally represented as possible
// depending on frequency resolution, low octaves may have considerably less notes than the higher octaves
// split bars by octave
// resample each octave to either 12 or 24 notes
// resample all bars to final size
auto const db_hz = Filter::resample(raw, bars.size, raw.size());
for (std::size_t i = 0; i < bars.size; ++i) {
auto const& e = db_hz[i];
bars.raw[i] = e.first;
_info[i] = Info{e.second};
}
}
for (auto& v : bars.raw) {
if ((!std::isfinite(v) && std::signbit(v)) || v < _cfg.threshold_min) {
v = _cfg.threshold_min;
}
else if ((!std::isfinite(v) && !std::signbit(v)) || v > _cfg.threshold_max) {
v = _cfg.threshold_max;
}
}
switch (_cfg.filter) {
case Filter_Type::sg: {
Filter::savitzky_golay(bars.raw, bars.size, _cfg.filter_size, 0.0);
break;
}
case Filter_Type::sgm: {
Filter::savitzky_golay(bars.raw, bars.size, _cfg.filter_size, std::abs(_cfg.threshold_max - _cfg.threshold_min) * _cfg.filter_threshold);
break;
}
default: {
break;
}
}
}
else {
bars.raw.assign(bars.size, _cfg.threshold_min);
}
}
void App::bar_movement(double const dt, Bars& bars) {
for (std::size_t i = 0; i < bars.size; ++i) {
if (bars.freq[i] < bars.raw[i]) {
if (_cfg.speed_freq_unique) {
bars.freq[i] = lerp(bars.freq[i], bars.raw[i], 1.0 - std::pow(1.0 - _cfg.speed_freq_up, dt));
}
else {
bars.freq[i] += std::min((std::abs(_cfg.threshold_max - _cfg.threshold_min) * _cfg.speed_freq_up) * dt, std::abs(bars.raw[i] - bars.freq[i]));
}
}
else if (bars.freq[i] > bars.raw[i]) {
if (_cfg.speed_freq_unique) {
bars.freq[i] = lerp(bars.freq[i], bars.raw[i], 1.0 - std::pow(1.0 - _cfg.speed_freq_down, dt));
}
else {
bars.freq[i] -= std::min((std::abs(_cfg.threshold_max - _cfg.threshold_min) * _cfg.speed_freq_down) * dt, std::abs(bars.freq[i] - bars.raw[i]));
}
}
if (bars.freq[i] - 0.1 < _cfg.threshold_min) {
bars.freq[i] = _cfg.threshold_min;
}
else if (bars.freq[i] > _cfg.threshold_max) {
bars.freq[i] = _cfg.threshold_max;
}
if (bars.peak[i] > bars.freq[i]) {
if (_cfg.speed_peak_unique) {
if (_cfg.peak_reverse) {
bars.peak[i] = lerp(bars.peak[i], _cfg.threshold_max, 1.0 - std::pow(1.0 - _cfg.speed_peak_down, dt));
}
else {
bars.peak[i] = lerp(bars.peak[i], bars.freq[i], 1.0 - std::pow(1.0 - _cfg.speed_peak_down, dt));
}
}
else {
if (_cfg.peak_reverse) {
bars.peak[i] += std::abs(_cfg.threshold_max - _cfg.threshold_min) * _cfg.speed_peak_down * dt;
}
else {
bars.peak[i] -= std::min((std::abs(_cfg.threshold_max - _cfg.threshold_min) * _cfg.speed_peak_down) * dt, std::abs(bars.peak[i] - bars.freq[i]));
}
}
}
else {
bars.peak[i] = bars.freq[i];
}
if (_cfg.peak_reverse) {
if (bars.peak[i] + 0.1 > _cfg.threshold_max) {
bars.peak[i] = _cfg.threshold_min;
}
}
if (bars.peak[i] - 0.1 < _cfg.threshold_min) {
bars.peak[i] = _cfg.threshold_min;
}
else if (bars.peak[i] > _cfg.threshold_max) {
bars.peak[i] = _cfg.threshold_max;
}
}
}
void App::update(double const dt) {
update_visualizer(dt);
}
void App::update_visualizer(double const dt) {
_rec.process();
_bars_left.margin_lhs = 0;
_bars_right.margin_lhs = 0;
_bars_left.margin_rhs = 0;
_bars_right.margin_rhs = 0;
if (_cfg.block_padding > 1 && !_cfg.block_vertical) {
_bars_left.padding = _cfg.block_padding / 2ul;
_bars_right.padding = _cfg.block_padding / 2ul;
}
else {
_bars_left.padding = _cfg.block_padding;
_bars_right.padding = _cfg.block_padding;
}
if (_cfg.mono) {
_bars_left.x = 0;
_bars_left.y = 0;
_bars_left.width = _width;
_bars_left.height = _height;
if (!_cfg.block_vertical) {std::swap(_bars_left.width, _bars_left.height);}
bar_calc_dimensions(_bars_left);
bar_process(_rec.buffer_left(), _bars_left);
bar_movement(dt, _bars_left);
}
else {
std::size_t width {_width};
std::size_t height {_height};
if (!_cfg.block_vertical) {std::swap(width, height);}
if (_cfg.block_stack) {
auto const height_half = height / 2;
auto const height_rem = height % 2;
_bars_left.width = width;
_bars_left.height = height_half + height_rem;
_bars_right.width = width;
_bars_right.height = height_half;
if (_cfg.block_vertical) {
_bars_left.x = 0;
_bars_left.y = height_half;
_bars_right.x = 0;
_bars_right.y = 0;
}
else {
_bars_left.x = 0;
_bars_left.y = 0;
_bars_right.x = 0;
_bars_right.y = height_half + height_rem;
}
bar_calc_dimensions(_bars_left);
bar_calc_dimensions(_bars_right);
}
else {
// stereo shared
auto const width_half = width / 2;
auto const width_rem = width % 2;
auto const padding_half = _bars_left.padding / 2;
auto const padding_rem = _bars_left.padding % 2;
_bars_left.width = width_half + width_rem;
_bars_left.height = height;
_bars_left.margin_lhs = padding_half + padding_rem;
_bars_right.width = width_half;
_bars_right.height = height;
_bars_right.margin_lhs = padding_half;
if (_cfg.block_vertical) {
_bars_left.x = 0;
_bars_left.y = 0;
_bars_right.x = width_half + width_rem;
_bars_right.y = 0;
}
else {
_bars_left.x = width_half + width_rem;
_bars_left.y = 0;
_bars_right.x = 0;
_bars_right.y = 0;
std::swap(_bars_left.width, _bars_right.width);
std::swap(_bars_left.margin_lhs, _bars_right.margin_lhs);
}
bar_calc_dimensions(_bars_left);
bar_calc_dimensions(_bars_right);
}
bar_process(_rec.buffer_left(), _bars_left);
bar_process(_rec.buffer_right(), _bars_right);
if (_cfg.bar_swap) {std::swap(_bars_left.raw, _bars_right.raw);}
bar_movement(dt, _bars_left);
bar_movement(dt, _bars_right);
}
shift_colors(dt);
}
void App::draw() {
draw_visualizer();
draw_overlay();
}
void App::draw_overlay() {
if (_cfg.overlay) {
if ((_cfg.mono || _cfg.block_stack) && _cfg.block_vertical && !_cfg.block_reverse) {
std::size_t index {0};
Pos pos {0, _height - 1};
for (auto const& info : _info) {
std::string const info_str {std::to_string(static_cast(std::trunc(info.freq))) + " " + Note{info.freq, _cfg.octave_scale}.str()};
for (auto const& e : info_str) {
if (_cfg.color) {
_win.buf(pos, Cell{1, Style{Style::Bit_24, 0, index % 2 ? OB::Prism::Hex("c0c0c0") : OB::Prism::Hex("f0f0f0"), _cfg.style.bg}, std::string(1, e)});
}
else {
_win.buf(pos, Cell{1, _style_default, std::string(1, e)});
}
if (pos.y == 0) {break;}
--pos.y;
}
pos.x += _bars_left.bar_width + _cfg.block_padding;
pos.y = _height - 1;
++index;
}
}
{
std::string title {
"frequency "s + std::to_string(_cfg.high_pass) + ":"s + std::to_string(_cfg.low_pass)
+ " | decibels "s + std::to_string(static_cast(_cfg.threshold_min)) + ":"s + std::to_string(static_cast(_cfg.threshold_max))
+ " | filter "s + std::to_string(_cfg.filter)
+ " | sort "s + (_cfg.sort_log ? "log"s : "note"s)
+ " | fps "s + std::to_string(_cfg.fps)
};
if (title.size() > _width) {
_overlay_index = std::min(_overlay_index, (title.size() - _width));
title = title.substr(_overlay_index, _width);
}
auto style = _cfg.color ? Style{Style::Bit_24, 0, OB::Prism::Hex("f0f0f0"), _cfg.style.bg} : Style{Style::Default, 0, {}, {}};
_win.buf.put(Pos{(_width / 2) - (title.size() / 2), 0}, Cell{1, style, title});
}
}
}
void App::draw_visualizer() {
if (_cfg.mono) {
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_left.x, _bars_left.y, _bars_left.width, _bars_left.height, _bars_left);
if (_cfg.cl_swap) {swap_colors();}
}
else {
if (_cfg.block_vertical) {
if (_cfg.block_stack) {
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_left.x, _bars_left.y, _bars_left.width, _bars_left.height, _bars_left);
if (_cfg.cl_swap) {swap_colors();}
_cfg.block_flip = !_cfg.block_flip;
draw_visualizer_impl(_bars_right.x, _bars_right.y, _bars_right.width, _bars_right.height, _bars_right);
_cfg.block_flip = !_cfg.block_flip;
}
else {
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_left.x, _bars_left.y, _bars_left.width, _bars_left.height, _bars_left, true);
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_right.x, _bars_right.y, _bars_right.width, _bars_right.height, _bars_right);
}
}
else {
if (_cfg.block_stack) {
_cfg.block_flip = !_cfg.block_flip;
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_left.x, _bars_left.y, _bars_left.width, _bars_left.height, _bars_left);
if (_cfg.cl_swap) {swap_colors();}
_cfg.block_flip = !_cfg.block_flip;
draw_visualizer_impl(_bars_right.x, _bars_right.y, _bars_right.width, _bars_right.height, _bars_right);
}
else {
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_left.x, _bars_left.y, _bars_left.width, _bars_left.height, _bars_left);
if (_cfg.cl_swap) {swap_colors();}
draw_visualizer_impl(_bars_right.x, _bars_right.y, _bars_right.width, _bars_right.height, _bars_right, true);
}
}
}
}
void App::draw_visualizer_impl(std::size_t x_begin, std::size_t y_begin, std::size_t width, std::size_t height, Bars& bars, bool const draw_reverse) {
if (!((_cfg.draw_freq || (_cfg.draw_freq && _cfg.draw_freq_always)) || (_cfg.draw_peak || (_cfg.draw_peak && _cfg.draw_peak_always)))) {
return;
}
// select vertical or horizontal character set
auto const& bar_char {_cfg.block_vertical ? _bar_vertical : _bar_horizontal};
// TODO improve alpha blending
// TODO fix corner color issue
// TODO use x color gradient along each block width
// TODO add stacked view but for mono channel
// TODO add single bar per channel mode
for (std::size_t x = 0; x < bars.size; ++x) {
auto const index = _cfg.block_reverse ? bars.size - 1 - x : x;
auto c = bars.freq[index];
auto p = bars.peak[index];
// calculate x position and bar width
// bar_width will be less than bars.bar_width if it partially overflows at the end or at the beginning if draw_reverse is true
int x_pos {0};
std::size_t bar_width {0};
if (draw_reverse) {
x_pos = width - bars.bar_width - bars.margin_lhs - ((x * bars.bar_width) + (x * bars.padding));
bar_width = x_pos + bars.bar_width > width ? width - x_pos : bars.bar_width;
if (x_pos < 0) {
bar_width += x_pos;
x_pos = 0;
}
}
else {
x_pos = bars.margin_lhs + (x * bars.bar_width) + (x * bars.padding);
bar_width = x_pos + bars.bar_width > width ? width - x_pos : bars.bar_width;
}
// draw bar
if (bar_width && ((_cfg.draw_freq && _cfg.draw_freq_always) || (_cfg.draw_freq && c > _cfg.threshold_min))) {
auto const vheight = bar_calc_height(c, height);
auto const bar_height = vheight / 8;
std::size_t const tip_index = (_cfg.block_flip ? 7 - (vheight % 8 ? vheight % 8 : 1) : vheight % 8);
bool y_bottom_drawn {false};
OB::Prism::HSLA init_color {_cfg.style.freq};
OB::Prism::HSLA init_color2 {_cfg.style.freq3};
if (_cfg.color) {
if (_cfg.cl_gradient_x && bars.size > 1 && _cfg.style.freq != _cfg.style.freq2) {
init_color = lerp(_cfg.style.freq, _cfg.style.freq2, static_cast(x) / static_cast(bars.size - 1));
if (_cfg.cl_gradient_y && _cfg.style.freq3 != _cfg.style.freq4) {
init_color2 = lerp(_cfg.style.freq3, _cfg.style.freq4, static_cast(x) / static_cast(bars.size - 1));
}
}
}
auto color = init_color;
// draw bar except for the top
for (std::size_t y = 0; y < bar_height; ++y) {
std::size_t const y_pos {_cfg.block_flip ? ((height - 1) - y) : y};
if (_cfg.color) {
if (_cfg.cl_gradient_y && height > 1) {
color = lerp(init_color, init_color2, static_cast(y) / static_cast(height - 1));
}
if (_cfg.alpha) {
auto const d = (static_cast(y) + _cfg.alpha_blend) / static_cast(height);
color.a(d >= _cfg.alpha_blend ? 255 : clamp(static_cast(std::round(((d / _cfg.alpha_blend) * 255.0) + 16.0)), 16, 255));
}
}
if (_cfg.block_height_full) {
// draw full
if (_cfg.color) {
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, Style{Style::Bit_24, 0, color, _cfg.style.bg}, bar_char[7]});
}
}
else {
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, _style_default, bar_char[7]});
}
}
}
else if (bar_height <= bars.bar_height || y >= bar_height - bars.bar_height) {
// draw fixed height
if (!y_bottom_drawn) {
y_bottom_drawn = true;
if (y == 0 && bars.bar_height > bar_height) {
// draw regular
if (_cfg.color) {
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, Style{Style::Bit_24, 0, color, _cfg.style.bg}, bar_char[7]});
}
}
else {
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, _style_default, bar_char[7]});
}
}
}
else {
// draw partial bottom
if (_cfg.color) {
OB::Prism::RGBA fg;
OB::Prism::RGBA bg;
if (_cfg.block_flip) {
fg = color;
bg = _cfg.style.bg;
}
else {
fg = _cfg.style.bg;
bg = color;
}
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, Style{Style::Bit_24, 0, fg, bg}, bar_char[tip_index]});
}
}
else {
auto const style_attr = _cfg.block_flip ? Style::Null : Style::Reverse;
auto style = _style_default;
style.attr = style_attr;
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, style, bar_char[tip_index]});
}
}
}
}
else {
// draw regular
if (_cfg.color) {
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, Style{Style::Bit_24, 0, color, _cfg.style.bg}, bar_char[7]});
}
}
else {
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, _style_default, bar_char[7]});
}
}
}
}
}
// draw partial top
{
if (_cfg.color) {
if (_cfg.cl_gradient_y) {
color = lerp(init_color, init_color2, static_cast(vheight) / static_cast(height * 8));
}
if (_cfg.alpha) {
auto const d = (static_cast(bar_height) + _cfg.alpha_blend) / static_cast(height);
color.a(d >= _cfg.alpha_blend ? 255 : clamp(static_cast(std::round(((d / _cfg.alpha_blend) * 255.0) + 16.0)), 16, 255));
}
}
std::size_t const y_pos {_cfg.block_flip ? (height - 1) - bar_height : bar_height};
if (_cfg.color) {
OB::Prism::RGBA fg;
OB::Prism::RGBA bg;
if (_cfg.block_flip) {
fg = _cfg.style.bg;
bg = color;
}
else {
fg = color;
bg = _cfg.style.bg;
}
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, Style{Style::Bit_24, 0, fg, bg}, bar_char[tip_index]});
}
}
else {
auto const style_attr = _cfg.block_flip ? Style::Reverse : Style::Null;
auto style = _style_default;
style.attr = style_attr;
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, style, bar_char[tip_index]});
}
}
}
}
// draw peak
if (bar_width && ((_cfg.draw_peak && _cfg.draw_peak_always) || (_cfg.draw_peak && p > _cfg.threshold_min))) {
if (p >= c) {
auto vheight = bar_calc_height(p, height);
auto bar_height = vheight / 8;
std::size_t const tip_index = (_cfg.block_flip ? 6 : 0);
std::size_t const y_pos {_cfg.block_flip ? (height - 1) - bar_height : bar_height};
if (y_begin + y_pos >= (_cfg.block_vertical ?_win.buf.size().y : _win.buf.size().x)) {continue;}
auto xp = x_begin + x_pos;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
auto const& cell = _win.buf.col(Pos{xp, yp});
// draw only if peak does not overlap bar
if (cell.text.empty() || cell.text == " ") {
if (_cfg.color) {
OB::Prism::HSLA init_color {_cfg.style.freq3};
OB::Prism::HSLA init_color2 {_cfg.style.freq};
if (_cfg.cl_gradient_x && bars.size > 1 && _cfg.style.freq != _cfg.style.freq2) {
init_color = lerp(_cfg.style.freq2, _cfg.style.freq, static_cast(x) / static_cast(bars.size - 1));
if (_cfg.cl_gradient_y && _cfg.style.freq3 != _cfg.style.freq4) {
init_color2 = lerp(_cfg.style.freq4, _cfg.style.freq3, static_cast(x) / static_cast(bars.size - 1));
}
}
OB::Prism::HSLA color {init_color};
if (_cfg.cl_gradient_y && height > 1) {
color = lerp(init_color, init_color2, static_cast(bar_height) / static_cast(height - 1));
}
if (_cfg.alpha) {
auto const d = (static_cast(bar_height) + _cfg.alpha_blend) / static_cast(height);
color.a(d >= _cfg.alpha_blend ? 255 : clamp(static_cast(std::round(((d / _cfg.alpha_blend) * 255.0) + 16.0)), 16, 255));
}
OB::Prism::RGBA fg;
OB::Prism::RGBA bg;
if (_cfg.block_flip) {
fg = _cfg.style.bg;
bg = color;
}
else {
fg = color;
bg = _cfg.style.bg;
}
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, Style{Style::Bit_24, 0, fg, bg}, bar_char[tip_index]});
}
}
else {
auto const style_attr = _cfg.block_flip ? Style::Reverse : Style::Null;
auto style = _style_default;
style.attr = style_attr;
for (std::size_t i = 0; i < bar_width; ++i) {
auto xp = x_begin + x_pos + i;
auto yp = y_begin + y_pos;
if (!_cfg.block_vertical) {std::swap(xp, yp);}
_win.buf(Pos{xp, yp}, Cell{1, style, bar_char[tip_index]});
}
}
}
}
}
}
}
void App::render() {
if (_raw_output) {
// _raw_file.open(_raw_filename, std::ios::out | std::ios::trunc);
// if (!_raw_file.is_open()) {throw std::runtime_error("open failed");}
_win.render_file(_raw_file);
// _raw_file.close();
}
else {
_win.render();
}
}
void App::run() {
await_signal();
if (!_raw_output) {
auto const is_term = Term::is_term(STDOUT_FILENO);
if (!is_term) {throw std::runtime_error("stdout is not a tty");}
}
if (! _rec.available()) {
throw std::runtime_error("recording device is unavailable");
}
if (_cfg.mono) {
_rec.channels(1);
}
else {
_rec.channels(2);
}
_rec.low_pass(_cfg.low_pass);
_rec.high_pass(_cfg.high_pass);
_rec.sample_rate(_cfg.sample_rate);
_rec.device(_rec.device_default());
// for (auto const& device : _rec.devices()) {
// if (OB::String::assert_rx(device, std::regex(".*monitor.*", std::regex::icase))) {
// _rec.device(device);
// break;
// }
// }
_rec.process_interval(sf::milliseconds(_cfg.interval));
auto const buf_size = _cfg.size / 2;
_bars_left.raw.assign(buf_size, _cfg.threshold_min);
_bars_left.freq.assign(buf_size, _cfg.threshold_min);
_bars_left.peak.assign(buf_size, _cfg.threshold_min);
_bars_right.raw.assign(buf_size, _cfg.threshold_min);
_bars_right.freq.assign(buf_size, _cfg.threshold_min);
_bars_right.peak.assign(buf_size, _cfg.threshold_min);
if (_cfg.color) {
_style_base = Style{Style::Bit_24, Style::Null, _cfg.style.bg, _cfg.style.bg};
}
else {
_style_base = Style{Style::Default, Style::Null, {}, {}};
}
_win.style_base = _style_base;
if (!_raw_output) {
_term_mode = std::make_unique();
}
screen_init();
on_winch();
if (!_raw_output) {
await_read();
}
_tick_begin = Clock::now();
await_tick();
_rec.start();
_io.run();
_rec.stop();
screen_deinit();
}