-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[examples] Add an audio particle generator
- Loading branch information
Showing
2 changed files
with
158 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#include "AudioParticles.hpp" | ||
|
||
#include <score/tools/RecursiveWatch.hpp> | ||
|
||
#include <QDirIterator> | ||
namespace ao | ||
{ | ||
void AudioParticles::prepare(halp::setup info) | ||
{ | ||
this->rate = info.rate; | ||
// Initialization, this method will be called with buffer size, etc. | ||
m_sounds.clear(); | ||
m_sounds.reserve(1000); | ||
|
||
score::for_all_files(inputs.folder.value, [this](std::string_view v) { | ||
if(!v.ends_with(".wav")) | ||
return; | ||
|
||
const QString& path = QString::fromUtf8(v.data(), v.size()); | ||
|
||
auto dec = Media::AudioDecoder::decode_synchronous(path, this->rate); | ||
if(!dec) | ||
return; | ||
|
||
m_sounds.push_back(std::move(dec->second)); | ||
}); | ||
} | ||
|
||
std::optional<int> | ||
frame_in_interval(int start_frame, int end_frame, double rate, double freq) | ||
{ | ||
if(rate <= 0.) | ||
return std::nullopt; | ||
|
||
int64_t start_ns = 1e9 * start_frame / rate; | ||
int64_t end_ns = 1e9 * end_frame / rate; | ||
int64_t itv_ns = 1e9 / freq; | ||
auto start_div = std::div(start_ns, itv_ns); | ||
auto end_div = std::div(end_ns, itv_ns); | ||
if(end_div.quot > start_div.quot) | ||
{ | ||
return 1; | ||
} | ||
return std::nullopt; | ||
} | ||
void AudioParticles::operator()(const halp::tick_musical& t) | ||
{ | ||
if(this->outputs.audio.channels != this->inputs.channels.value) | ||
{ | ||
this->outputs.audio.request_channels(this->inputs.channels.value); | ||
return; | ||
} | ||
|
||
if(m_sounds.empty()) | ||
return; | ||
|
||
// FIXME range is not respected | ||
|
||
if(inputs.frequency > 0.) | ||
// Trigger new sounds | ||
if(frame_in_interval( | ||
t.position_in_frames, t.position_in_frames + t.frames, rate, | ||
1. / (1. - inputs.frequency))) | ||
{ | ||
if((1. - inputs.density) < std::exponential_distribution<float>()(m_rng)) | ||
m_playheads.push_back(Playhead{ | ||
0, uint16_t(unsigned(rand()) % m_sounds.size()), | ||
uint16_t(unsigned(rand()) % this->outputs.audio.channels)}); | ||
} | ||
|
||
for(auto& playhead : m_playheads) | ||
{ | ||
SCORE_ASSERT(m_sounds.size() > playhead.index); | ||
auto& sound = m_sounds[playhead.index]; | ||
int sound_frames = sound[0].size(); | ||
|
||
SCORE_ASSERT(outputs.audio.channels > playhead.channel); | ||
auto channel = outputs.audio.channel(playhead.channel, t.frames); | ||
|
||
if(sound_frames - playhead.frame > t.frames) | ||
for(int i = playhead.frame; i < playhead.frame + t.frames; i++) | ||
channel[i - playhead.frame] += sound[0][i]; | ||
else | ||
{ | ||
for(int i = playhead.frame; i < sound_frames; i++) | ||
channel[i - playhead.frame] += sound[0][i]; | ||
} | ||
playhead.frame += t.frames; | ||
} | ||
|
||
ossia::remove_erase_if(m_playheads, [this](const auto& playhead) { | ||
auto& sound = m_sounds[playhead.index]; | ||
return playhead.frame >= sound[0].size(); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#pragma once | ||
#include <Media/AudioDecoder.hpp> | ||
|
||
#include <QFile> | ||
|
||
#include <halp/audio.hpp> | ||
#include <halp/controls.hpp> | ||
#include <halp/mappers.hpp> | ||
#include <halp/meta.hpp> | ||
#include <rnd/include/rnd/random.hpp> | ||
namespace ao | ||
{ | ||
class AudioParticles | ||
{ | ||
public: | ||
halp_meta(name, "Audio particles") | ||
halp_meta(category, "Audio/Generator") | ||
halp_meta(c_name, "avnd_audio_particles") | ||
halp_meta(uuid, "e7f2b091-0de0-49cd-a581-d4087d901fbb") | ||
|
||
struct ins | ||
{ | ||
halp::lineedit<"Folder", ""> folder; | ||
halp::spinbox_i32<"Channels", halp::range{.min = 0., .max = 128, .init = 16}> | ||
channels; | ||
struct : halp::time_chooser<"Frequency", halp::range{0.001, 30., 0.2}> | ||
{ | ||
using mapper = halp::log_mapper<std::ratio<95, 100>>; | ||
} frequency; | ||
halp::knob_f32<"Density", halp::range{0.001, 1., 0.7}> density; | ||
} inputs; | ||
|
||
struct | ||
{ | ||
halp::variable_audio_bus<"Output", double> audio; | ||
} outputs; | ||
|
||
using setup = halp::setup; | ||
void prepare(halp::setup info); | ||
|
||
// Do our processing for N samples | ||
using tick = halp::tick_musical; | ||
|
||
// Defined in the .cpp | ||
void operator()(const halp::tick_musical& t); | ||
|
||
std::vector<Media::audio_array> m_sounds; | ||
|
||
struct Playhead | ||
{ | ||
int frame{}; | ||
uint16_t index{}; | ||
uint16_t channel{}; | ||
}; | ||
|
||
std::vector<Playhead> m_playheads; | ||
double rate{}; | ||
std::random_device m_rdev; | ||
rnd::pcg m_rng{m_rdev}; | ||
}; | ||
|
||
} |