#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace psemek; static std::map const key_to_midi { {app::keycode::Z, 59}, {app::keycode::X, 60}, {app::keycode::C, 61}, {app::keycode::V, 62}, {app::keycode::B, 63}, {app::keycode::N, 64}, {app::keycode::M, 65}, {app::keycode::COMMA, 66}, {app::keycode::PERIOD, 67}, {app::keycode::SLASH, 68}, {app::keycode::A, 69}, {app::keycode::S, 70}, {app::keycode::D, 71}, {app::keycode::F, 72}, {app::keycode::G, 73}, {app::keycode::H, 74}, {app::keycode::J, 75}, {app::keycode::K, 76}, {app::keycode::L, 77}, {app::keycode::SEMICOLON, 78}, {app::keycode::APOSTROPHE, 79}, {app::keycode::Q, 80}, {app::keycode::W, 81}, {app::keycode::E, 82}, {app::keycode::R, 83}, {app::keycode::T, 84}, {app::keycode::Y, 85}, {app::keycode::U, 86}, {app::keycode::I, 87}, {app::keycode::O, 88}, {app::keycode::P, 89}, {app::keycode::LEFTBRACKET, 90}, {app::keycode::RIGHTBRACKET, 91}, }; static geom::interval 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::application_base { audio_app(options const &, context const &) { engine_ = audio::make_engine(); 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_event(app::key_event const & event) override { app::application_base::on_event(event); if (event.down && key_to_midi.contains(event.key)) { int midi = key_to_midi.at(event.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 (!event.down && key_to_midi.contains(event.key)) { int midi = key_to_midi.at(event.key); auto & ch = channels_[midi]; if (auto s = ch->stream()) ch->stream(audio::fade_out(s, 0.1f)); channels_.erase(midi); } if (event.down && event.key == app::keycode::SPACE) pause_control_->paused(!pause_control_->paused()); if (event.down && event.key == app::keycode::KP_PLUS) pitch_control_->pitch(std::pow(2.f, 1.f / 12.f)); if (event.down && event.key == app::keycode::KP_MINUS) pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f)); if (!event.down && event.key == app::keycode::KP_PLUS) pitch_control_->pitch(1.f); if (!event.down && event.key == app::keycode::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 = state().size[0] / 2.f; float y = state().size[1] / 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({state().size[0] / 2.f, state().size[1] - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts); { float x = state().size[0] - 200.f; float y = state().size[1] / 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{state().size[0], state().size[1]}.transform()); } ~audio_app() override { prof::dump(); } private: std::unique_ptr engine_; audio::mixer_ptr mixer_; std::shared_ptr volume_control_; std::shared_ptr pitch_control_; std::shared_ptr pause_control_; std::map channels_; float smooth_pitch_ = 0.f; util::clock<> clock_; float time_ = 0.f; gfx::painter painter_; }; namespace psemek::app { std::unique_ptr make_application_factory() { return default_application_factory({.name = "Audio example"}); } }