Skip to content

Instantly share code, notes, and snippets.

Created June 8, 2017 16:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/9f958afa08493e09ec51e71bfb1430be to your computer and use it in GitHub Desktop.
Save anonymous/9f958afa08493e09ec51e71bfb1430be to your computer and use it in GitHub Desktop.
#92 Method ringing simulator
/*
#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 &ampeg = *::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 &ampeg = *::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(&param, 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