diff --git a/examples/fibonacci_music_box.cpp b/examples/fibonacci_music_box.cpp new file mode 100644 index 00000000..f28dd288 --- /dev/null +++ b/examples/fibonacci_music_box.cpp @@ -0,0 +1,167 @@ +#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 + +// Inspired by https://www.youtube.com/watch?v=_aIf4WUCNZU + +using namespace psemek; + +std::vector>> fibonacci_cycles(int n) +{ + util::array cycle({n, n}, -1); + + std::vector>> result; + + for (int i = 0; i < n; ++i) + { + for (int j = 0; j < n; ++j) + { + if (cycle(i, j) != -1) + continue; + + int cycle_id = result.size(); + auto & current = result.emplace_back(); + + geom::point p{i, j}; + while (true) + { + current.push_back(p); + cycle(p[0], p[1]) = cycle_id; + p = {p[1], (p[0] + p[1]) % n}; + + if (p == geom::point{i, j}) + break; + } + } + } + + return result; +} + +std::vector major_scale() +{ + return {0, 2, 4, 5, 7, 9, 11}; +} + +std::vector minor_scale() +{ + return {0, 2, 3, 5, 7, 8, 10}; +} + +std::shared_ptr note(int i, float duration) +{ + return audio::fade_in(audio::fade_out(audio::karplus_strong(audio::midi_frequency(69 + i)), duration), duration / 16.f); +} + +std::shared_ptr generate(std::vector const & scale, std::vector> const & cycle, float speed) +{ + auto mixer = audio::make_mixer(); + + for (int i = 0; i < cycle.size(); ++i) + mixer->add(audio::concat({audio::truncate(audio::silence(), speed * i), note(scale[cycle[i][0] % scale.size()] + 12 * (cycle[i][0] / scale.size()), speed)})); + + return audio::record(mixer, speed * cycle.size()); +} + +struct fibonacci_music_box_app + : app::application_base +{ + fibonacci_music_box_app(options const &, context const &) + { + auto cycles = fibonacci_cycles(16); + + auto scale = minor_scale(); + float speed = 0.25f; + + log::info() << "Found cycles of length:"; + for (int i = 0; i < cycles.size(); ++i) + { + log::info() << " #" << i << ": " << cycles[i].size(); + cycles_.push_back(audio::loop(generate(scale, cycles[i], speed))); + } + + channels_.resize(cycles_.size()); + + engine_ = audio::make_engine(); + mixer_ = audio::make_mixer(); + + auto compressor = audio::compressor(mixer_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f)); + engine_->output()->stream(compressor); + + } + + void on_event(app::key_event const & event) override + { + app::application_base::on_event(event); + + if (event.down && event.key >= app::keycode::NUM_1 && event.key <= app::keycode::NUM_0) + { + int i; + if (event.key == app::keycode::NUM_0) + i = 0; + else + i = 1 + (int(event.key) - int(app::keycode::NUM_1)); + + log::info() << "Pressed " << i; + + if (i < channels_.size()) + { + if (channels_[i]) + { + channels_[i]->stop(); + channels_[i] = nullptr; + } + else + { + channels_[i] = mixer_->add(cycles_[i]); + } + } + } + } + + void update() override + {} + + void present() override + {} + +private: + std::unique_ptr engine_; + audio::mixer_ptr mixer_; + std::vector cycles_; + std::vector channels_; +}; + +namespace psemek::app +{ + + std::unique_ptr make_application_factory() + { + return default_application_factory({.name = "Fibonacci music box"}); + } + +}