Created
June 8, 2017 16:01
-
-
Save anonymous/9f958afa08493e09ec51e71bfb1430be to your computer and use it in GitHub Desktop.
#92 Method ringing simulator
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
/* | |
#92 Method ringing simulator - by /tech/ anon | |
g++ -g -O2 -std=c++11 -o bells bells.cc -lstk | |
*/ | |
#define PROGRAM_NAME "Method ringing simulator" | |
// The Synthesis Toolkit <https://ccrma.stanford.edu/software/stk/> | |
#include <stk/RtAudio.h> | |
#include <stk/TubeBell.h> | |
#include <stk/ADSR.h> | |
#include <stk/FreeVerb.h> | |
// | |
#include <cmath> | |
#include <vector> | |
#include <memory> | |
#include <iostream> | |
// | |
#include <mutex> | |
#include <condition_variable> | |
//============================================================================== | |
// Definitions | |
static RtAudio *rtaudio = nullptr; | |
static void create_rtaudio_stream(); | |
static unsigned sample_rate = 48000; | |
static unsigned buffer_size = 256; | |
static double freq2key(double f); | |
static double key2freq(double n); | |
static void bells_init(); | |
static void process_audio(float *left, float *right, unsigned nframes); | |
// Tubular Bells \m/ -- https://www.youtube.com/watch?v=gZq5huke3D8 | |
static unsigned numbells = 5; | |
static std::vector<std::unique_ptr<stk::TubeBell>> bells; | |
static std::vector<std::unique_ptr<stk::ADSR>> ampeg; | |
static std::unique_ptr<stk::FreeVerb> reverb; | |
static std::vector<double> bell_tones; | |
struct Ringer; | |
static std::unique_ptr<Ringer> ringer; | |
static float interval = 0.75; // between rings | |
static unsigned timecounter = 0; | |
static volatile bool finished = false; | |
static std::mutex mutex; | |
static std::condition_variable cond; | |
//============================================================================== | |
// Entry | |
int main() { | |
create_rtaudio_stream(); | |
bells_init(); | |
rtaudio->startStream(); | |
while (!finished) { | |
std::unique_lock<std::mutex> lock(::mutex); | |
cond.wait(lock); | |
} | |
return 0; | |
} | |
//============================================================================== | |
// The abstract ringing method | |
struct Ringer { | |
virtual ~Ringer() {} | |
virtual unsigned *step(unsigned &count) = 0; | |
}; | |
//============================================================================== | |
// Plain hunt method | |
struct RingerPH : Ringer { | |
int b[2], d[2]; | |
unsigned r[2]; | |
RingerPH() { | |
b[0] = 0; | |
b[1] = numbells - 1; | |
d[0] = +1; | |
d[1] = -1; | |
} | |
unsigned *step(unsigned &count) override { | |
count = 2; | |
r[0] = b[0]; | |
r[1] = b[1]; | |
const unsigned n = numbells; | |
for (int i = 0; i < 2; ++i) { | |
int c = b[i] + d[i]; | |
if (c < 0 || (unsigned)c >= n) | |
d[i] = -d[i]; | |
else | |
b[i] = c; | |
} | |
return r; | |
} | |
}; | |
//============================================================================== | |
// Grandsire method | |
struct RingerGS : Ringer { | |
int b[2], d[2]; | |
unsigned r[2]; | |
unsigned sn; // step number | |
RingerGS() { | |
if (numbells != 5) | |
throw std::runtime_error("Grandsire is a 5 bell method"); | |
b[0] = 0; | |
b[1] = 4; | |
d[0] = +1; | |
d[1] = -1; | |
sn = 0; | |
} | |
unsigned *step(unsigned &count) override { | |
count = 2; | |
r[0] = b[0]; | |
r[1] = b[1]; | |
const unsigned n = numbells; | |
for (unsigned i = 0; i < 2; ++i) { | |
int c = b[i] + d[i]; | |
if (c < 0 || (unsigned)c >= n) | |
d[i] = -d[i]; | |
else | |
b[i] = c; | |
} | |
sn = (sn + 1) % 30; | |
switch (sn) { | |
case 0: d[1] = -1; break; // start in rounds | |
case 1: d[1] = +1; break; // dodge 4/5 up | |
case 10: d[1] = 0; break; // make 3rds place | |
case 11: d[1] = -1; break; // - and turn back | |
case 21: d[1] = +1; break; // dodge 4/5 down | |
case 22: d[1] = -1; break; // | |
default: break; | |
} | |
return r; | |
} | |
}; | |
//============================================================================== | |
// Utility | |
static double freq2key(double f) { | |
return 12 * std::log2(f / 440) + 69; | |
} | |
static double key2freq(double n) { | |
return 440 * std::exp2((n - 69) / 12); | |
} | |
//============================================================================== | |
// Instrument initialization | |
static void bells_init() { | |
stk::Stk::setSampleRate(sample_rate); | |
::bells.resize(numbells); | |
::ampeg.resize(numbells); | |
for (unsigned i = 0, n = numbells; i < n; ++i) { | |
::bells[i].reset(new stk::TubeBell); | |
::ampeg[i].reset(new stk::ADSR); | |
} | |
reverb.reset(new stk::FreeVerb); | |
// tuning | |
::bell_tones.resize(numbells); | |
unsigned firstnote = 72; | |
unsigned note = firstnote; | |
for (unsigned i = 0; i < numbells; ++i) { | |
bell_tones[i] = key2freq(note); | |
printf("bell %u note %u freq %f\n", i + 1, note, key2freq(note)); | |
unsigned m = (note + 1) % 12; | |
++note; | |
if (m == 1 || m == 3 || | |
m == 6 || m == 8 || m == 10) // skip sharps | |
++note; | |
} | |
ringer.reset(new RingerGS); | |
} | |
//============================================================================== | |
// Instrument methods | |
static void play_bell(unsigned i) { | |
stk::TubeBell &bell = *::bells[i]; | |
stk::ADSR &eg = *::ampeg[i]; | |
bell.noteOn(::bell_tones[i], 1.0); | |
ampeg.keyOn(); | |
} | |
static void bells_tick() { | |
constexpr bool print_bells = false; // avoid this in audio loops | |
Ringer &ringer = *::ringer; | |
unsigned count {}; | |
unsigned *bells = ringer.step(count); | |
for (unsigned i = 0; i < count; ++i) { | |
if (print_bells) | |
printf(" %u", bells[i] + 1); | |
play_bell(bells[i]); | |
} | |
if (print_bells) | |
printf("\n"); | |
} | |
//============================================================================== | |
// Audio processing routine | |
static void process_audio(float *left, float *right, unsigned nframes) { | |
double fs = ::sample_rate; | |
double ts = 1.0 / fs; | |
for (unsigned i = 0; i < nframes; ++i) { | |
++timecounter; | |
float dt = ::timecounter * ts; | |
if (dt > ::interval) { | |
bells_tick(); | |
timecounter = 0; | |
} | |
} | |
for (unsigned i = 0; i < nframes; ++i) { | |
float bell_mix = 0; | |
for (unsigned b = 0, nb = numbells; b < nb; ++b) { | |
stk::TubeBell &bell = *::bells[b]; | |
stk::ADSR &eg = *::ampeg[b]; | |
bell_mix += bell.tick() * ampeg.tick(); | |
} | |
left[i] = right[i] = bell_mix; | |
} | |
stk::FreeVerb &reverb = *::reverb; | |
for (unsigned i = 0; i < nframes; ++i) { | |
reverb.tick(left[i], right[i]); | |
left[i] = reverb.lastOut(0); | |
right[i] = reverb.lastOut(1); | |
} | |
} | |
//============================================================================== | |
// Audio stream creation | |
static void create_rtaudio_stream() { | |
rtaudio = new RtAudio; | |
int dev = rtaudio->getDefaultOutputDevice(); | |
const RtAudio::DeviceInfo &info = rtaudio->getDeviceInfo(dev); | |
std::cerr << "Output to device " << dev << ": " << info.name << "\n"; | |
RtAudio::StreamParameters param; | |
param.deviceId = dev; | |
param.nChannels = 2; | |
RtAudio::StreamOptions options; | |
options.flags = RTAUDIO_NONINTERLEAVED | | |
RTAUDIO_SCHEDULE_REALTIME | | |
RTAUDIO_ALSA_USE_DEFAULT; | |
options.streamName = PROGRAM_NAME; | |
sample_rate = info.preferredSampleRate; | |
auto rtcallback = [](void *outbytes, void *, unsigned nframes, double time, | |
RtAudioStreamStatus, void *) -> int { | |
float *outframes = reinterpret_cast<float *>(outbytes); | |
::process_audio(outframes, outframes + nframes, nframes); | |
return 0; | |
}; | |
rtaudio->openStream(¶m, nullptr, RTAUDIO_FLOAT32, | |
sample_rate, &buffer_size, rtcallback, nullptr, &options); | |
sample_rate = rtaudio->getStreamSampleRate(); | |
std::cerr << "Sample rate: " << sample_rate << " Hz\n"; | |
std::cerr << "Buffer size: " << buffer_size << " frames\n"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment