Add fibonacci music box example

This commit is contained in:
Nikita Lisitsa 2023-08-22 14:53:37 +03:00
parent 63008d62ff
commit d1975dd917

View file

@ -0,0 +1,167 @@
#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/effect/truncate.hpp>
#include <psemek/audio/combine/concat.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/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/log/log.hpp>
#include <psemek/util/array.hpp>
#include <unordered_map>
// Inspired by https://www.youtube.com/watch?v=_aIf4WUCNZU
using namespace psemek;
std::vector<std::vector<geom::point<int, 2>>> fibonacci_cycles(int n)
{
util::array<int, 2> cycle({n, n}, -1);
std::vector<std::vector<geom::point<int, 2>>> 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<int> major_scale()
{
return {0, 2, 4, 5, 7, 9, 11};
}
std::vector<int> minor_scale()
{
return {0, 2, 3, 5, 7, 8, 10};
}
std::shared_ptr<audio::stream> 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<audio::track> generate(std::vector<int> const & scale, std::vector<geom::point<int, 2>> 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<audio::engine> engine_;
audio::mixer_ptr mixer_;
std::vector<audio::stream_ptr> cycles_;
std::vector<audio::channel_ptr> channels_;
};
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<fibonacci_music_box_app>({.name = "Fibonacci music box"});
}
}