psemek/examples/audio.cpp

241 lines
5.8 KiB
C++

#include <psemek/audio/engine.hpp>
#include <psemek/audio/wave/silence.hpp>
#include <psemek/audio/wave/sine.hpp>
#include <psemek/audio/wave/sawtooth.hpp>
#include <psemek/audio/wave/square.hpp>
#include <psemek/audio/wave/triangle.hpp>
#include <psemek/audio/wave/karplus_strong.hpp>
#include <psemek/audio/effect/volume.hpp>
#include <psemek/audio/effect/fade_in.hpp>
#include <psemek/audio/effect/fade_out.hpp>
#include <psemek/audio/effect/compressor.hpp>
#include <psemek/audio/effect/pause.hpp>
#include <psemek/audio/combine/loop.hpp>
#include <psemek/audio/effect/pitch.hpp>
#include <psemek/audio/effect/distortion.hpp>
#include <psemek/audio/combine/duplicate.hpp>
#include <psemek/audio/combine/stereo.hpp>
#include <psemek/audio/combine/mixer.hpp>
#include <psemek/audio/midi.hpp>
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/geom/constants.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/io/file_stream.hpp>
#include <map>
using namespace psemek;
static std::map<SDL_Keycode, int> const key_to_midi
{
{SDLK_z, 59},
{SDLK_x, 60},
{SDLK_c, 61},
{SDLK_v, 62},
{SDLK_b, 63},
{SDLK_n, 64},
{SDLK_m, 65},
{SDLK_COMMA, 66},
{SDLK_PERIOD, 67},
{SDLK_SLASH, 68},
{SDLK_a, 69},
{SDLK_s, 70},
{SDLK_d, 71},
{SDLK_f, 72},
{SDLK_g, 73},
{SDLK_h, 74},
{SDLK_j, 75},
{SDLK_k, 76},
{SDLK_l, 77},
{SDLK_SEMICOLON, 78},
{SDLK_QUOTE, 79},
{SDLK_q, 80},
{SDLK_w, 81},
{SDLK_e, 82},
{SDLK_r, 83},
{SDLK_t, 84},
{SDLK_y, 85},
{SDLK_u, 86},
{SDLK_i, 87},
{SDLK_o, 88},
{SDLK_p, 89},
{SDLK_LEFTBRACKET, 90},
{SDLK_RIGHTBRACKET, 91},
};
static geom::interval<int> const key_rows[3] = {
{59, 68},
{69, 79},
{80, 91},
};
static std::string_view const midi_name[12] = {
"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#",
};
struct audio_app
: app::app
{
audio_app()
: app::app("Audio example")
{
mixer_ = audio::make_mixer();
volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f);
pitch_control_ = audio::pitch(volume_control_, 1.f, 0.025f);
auto compressor = audio::compressor(pitch_control_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f));
pause_control_ = audio::pause(compressor, false, 0.01f);
engine_.output()->stream(pause_control_);
}
void on_key_down(SDL_Keycode key) override
{
app::app::on_key_down(key);
if (key_to_midi.contains(key))
{
int midi = key_to_midi.at(key);
if (!channels_.contains(midi))
{
auto tone = audio::karplus_strong(audio::midi_frequency(midi));
tone = audio::distortion(std::move(tone), 4.f);
channels_[midi] = mixer_->add(audio::fade_in(tone, 0.005f));
}
}
if (key == SDLK_SPACE)
{
pause_control_->paused(!pause_control_->paused());
}
if (key == SDLK_KP_PLUS)
pitch_control_->pitch(std::pow(2.f, 1.f / 12.f));
if (key == SDLK_KP_MINUS)
pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f));
}
void on_key_up(SDL_Keycode key) override
{
app::app::on_key_up(key);
if (key_to_midi.contains(key))
{
int midi = key_to_midi.at(key);
auto & ch = channels_[midi];
if (auto s = ch->stream())
ch->stream(audio::fade_out(s, 0.1f));
channels_.erase(midi);
}
if (key == SDLK_KP_PLUS)
pitch_control_->pitch(1.f);
if (key == SDLK_KP_MINUS)
pitch_control_->pitch(1.f);
}
void update() override
{
float const dt = clock_.restart().count();
time_ += dt;
smooth_pitch_ += (12.f * std::log2(pitch_control_->pitch()) - smooth_pitch_) * (1.f - std::exp(- 20.f * dt));
float volume = std::sin(time_);
volume_control_->gain_left(0.5f + 0.5f * volume);
volume_control_->gain_right(0.5f - 0.5f * volume);
}
void present() override
{
gl::ClearColor(0.8f, 0.8f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
float size = 64.f;
float margin = 4.f;
float border = 8.f;
float center = width() / 2.f;
float y = height() / 2.f + size * std::size(key_rows) / 2.f;
gfx::painter::text_options opts;
opts.scale = 2.f;
opts.c = {0, 0, 0, 255};
opts.x = gfx::painter::x_align::center;
opts.y = gfx::painter::y_align::center;
for (auto const & row : key_rows)
{
y -= size;
float x = center - (row.max - row.min + 1) * size / 2.f;
for (int i = 0; i <= row.max - row.min; ++i)
{
gfx::color_rgba color{0, 0, 0, 255};
if (channels_.contains(row.min + i))
color = {255, 127, 0, 255};
painter_.rect({{{x + margin, x + size - margin}, {y + margin, y + size - margin}}}, color);
color = {255, 255, 255, 255};
painter_.rect({{{x + border, x + size - border}, {y + border, y + size - border}}}, color);
painter_.text({x + size / 2.f, y + size / 2.f}, midi_name[(row.min + i + 3) % 12], opts);
x += size;
}
}
opts.scale = 4.f;
opts.c = pause_control_->paused() ? gfx::color_rgba{255, 0, 0, 255} : gfx::color_rgba{0, 127, 0, 255};
painter_.text({width() / 2.f, height() - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts);
{
float x = width() - 200.f;
float y = height() / 2.f;
float w = 4.f;
float h = 64.f;
float s = 16.f;
float r = y - h * smooth_pitch_;
painter_.rect({{{x - w, x + w}, {y - h, y + h}}}, {0, 0, 0, 255});
painter_.rect({{{x - s, x + s}, {r - w, r + w}}}, {0, 0, 255, 255});
}
painter_.render(geom::window_camera{width(), height()}.transform());
}
void on_scene_exit() override
{
prof::dump();
}
private:
audio::engine engine_;
audio::mixer_ptr mixer_;
std::shared_ptr<audio::volume_control_stereo> volume_control_;
std::shared_ptr<audio::pitch_control> pitch_control_;
std::shared_ptr<audio::pause_control> pause_control_;
std::map<int, audio::channel_ptr> channels_;
float smooth_pitch_ = 0.f;
util::clock<> clock_;
float time_ = 0.f;
gfx::painter painter_;
};
int main()
{
return app::main<audio_app>();
}