Skip to content

Instantly share code, notes, and snippets.

@cjxgm
Created August 31, 2014 09:03
Show Gist options
  • Save cjxgm/2b4e7c5c2eec037c3594 to your computer and use it in GitHub Desktop.
Save cjxgm/2b4e7c5c2eec037c3594 to your computer and use it in GitHub Desktop.
a simple module in-code tracker, utilizing the bnd time code, no floating point arithmetic, full of integral type tricks.
// clang++ -o bnd_play bnd_play.cc -std=gnu++1y -O3 -march=native -Wall -Wextra -lpulse-simple
#include <vector>
#include <algorithm>
#include <initializer_list>
#include <stdexcept>
#include <cstdio>
#include <pulse/simple.h>
struct pitch
{
enum class kind { mute, freq, end };
pitch(int pitch_code)
{
if (!pitch_code) {
k = kind::mute;
return;
}
if (pitch_code == 0xFFFF) {
k = kind::end;
return;
}
k = kind::freq;
octave = (pitch_code >> 8) & 0xFF;
major = (pitch_code >> 4) & 0b0111;
minor = (pitch_code >> 0) & 0b0001;
// frequency from C-4 to B-4, multiplied by 100, and then rounded
static int freq_table[12] = {
26163, 27718, 29366, 31113, 32963, 34923,
36999, 39200, 41530, 44000, 46616, 49388,
};
// major scale
static int scale_table[8] = {
0,
0, 2, 4, 5, 7, 9, 11
};
int relative_pitch = scale_table[major] + minor;
freq = freq_table[relative_pitch]; // relative frequency
if (octave > 4) freq <<= octave-4; // several ovtaves higher
else if (octave < 4) freq >>= 4-octave; // several octaves lower
// scale down and round the result
if (freq % 100 >= 50) freq += 100;
freq /= 100;
}
bool is_mute() const { return (k == kind::mute); }
bool is_freq() const { return (k == kind::freq); }
bool is_end () const { return (k == kind::end ); }
int frequency() const { return freq ; }
private:
kind k;
int octave;
int major;
int minor;
int freq;
};
struct bnd
{
bnd(int beat, int num, int den)
: beat{beat}, num{num}, den{den} {};
// multiply and divide
int md(int m, int d) const { return beat*m/d + num*m/den/d; }
// to sample count
int sample(int srate, int bpm) const { return md(srate*60, bpm); }
bool operator<(bnd const& rhs) const
{
return ((beat*den+num)*rhs.den < (rhs.beat*rhs.den+rhs.num)*den);
}
private:
int beat;
int num;
int den;
};
struct note_data
{
pitch p;
bnd at;
unsigned char vol;
note_data(pitch p, bnd at, unsigned char vol=100)
: p{p}, at{at}, vol{vol} {}
};
using buffer_type = std::vector<unsigned char>;
unsigned char sample_volume(unsigned char x, unsigned char v)
{
int ox = int(x) - 0x80;
ox = ox * v / 0xFF;
return ox + 0x80;
}
unsigned char sample_mix(unsigned char a, unsigned char b)
{
int oa = int(a) - 0x80;
int ob = int(b) - 0x80;
int result = oa + ob;
if (result > 0xFF-0x80) return 0xFF;
if (result < 0x00-0x80) return 0x00;
return result + 0x80;
}
struct track
{
using buffer_type = ::buffer_type;
using buffer_ref = buffer_type &;
using init_list = std::initializer_list<note_data>;
using list_type = std::vector<note_data>;
using iterator = list_type::iterator;
track(init_list il) : notes(il)
{
std::sort(notes.begin(), notes.end(),
[](note_data const& a, note_data const& b) {
return (a.at < b.at);
});
it = notes.begin();
end = notes.end ();
}
// return true to remove this track
bool fill(buffer_ref buffer, int srate, int bpm)
{
for (auto& x: buffer) {
if (it != end && sample == it->at.sample(srate, bpm)) {
last_note = *it;
pos = 0;
++it;
}
if (last_note.p.is_freq()) {
auto freq = last_note.p.frequency();
auto t = srate / freq;
auto p = pos++ % t;
if ((p<<1) < t) x = sample_volume(0xFF, last_note.vol);
else x = sample_volume(0x00, last_note.vol);
}
else x = 0x80;
sample++;
}
return last_note.p.is_end();
}
private:
list_type notes;
iterator it;
iterator end;
note_data last_note = { 0x0000, { 0, 0, 1 } };
int sample = 0;
int pos = 0;
};
struct tracks : std::vector<track>
{
using super_type = std::vector<track>;
using init_list = std::initializer_list<track>;
using buffer_type = ::buffer_type;
using buffer_ref = buffer_type &;
tracks(init_list il) : super_type(il) {}
// return true for continue
bool fill(buffer_ref buffer, int srate, int bpm)
{
buffer_type acc(buffer.size(), 0x80);
auto it = std::remove_if(begin(), end(),
[&buffer, &acc, srate, bpm](track& trk) {
auto done = trk.fill(buffer, srate, bpm);
std::transform(buffer.begin(), buffer.end(),
acc.begin(), acc.begin(),
sample_mix);
return done;
});
erase(it, end());
std::swap(acc, buffer);
return (!empty());
}
};
static track t1 = {
{ 0x0330, { 0, 0, 4 }, 0x7F },
{ 0x0340, { 0, 2, 4 }, 0x7F },
{ 0x0350, { 1, 0, 4 }, 0x7F },
{ 0x0310, { 1, 3, 4 }, 0x7F },
{ 0x0360, { 2, 0, 4 }, 0x7F },
{ 0x0000, { 2, 1, 4 }, 0x7F },
{ 0x0350, { 2, 2, 4 }, 0x7F },
{ 0x0341, { 2, 3, 4 }, 0x7F },
{ 0x0350, { 3, 0, 4 }, 0x7F },
{ 0xFFFF, { 4, 0, 4 }, 0x7F },
};
static track t2 = {
{ 0x0430, { 0, 2, 8 }, 0x40 },
{ 0x0430, { 0, 3, 8 }, 0x40 },
{ 0x0000, { 0, 4, 8 }, 0x40 },
{ 0x0440, { 0, 6, 8 }, 0x40 },
{ 0x0440, { 0, 7, 8 }, 0x40 },
{ 0xFFFF, { 1, 0, 4 }, 0x40 },
};
static track t3 = {
{ 0x0200, { 0, 0, 6 }, 0x60 },
{ 0x0250, { 0, 1, 6 }, 0x60 },
{ 0x0200, { 0, 2, 6 }, 0x60 },
{ 0x0220, { 0, 3, 6 }, 0x60 },
{ 0x0200, { 0, 4, 6 }, 0x60 },
{ 0x0210, { 0, 5, 6 }, 0x60 },
{ 0xFFFF, { 1, 0, 4 }, 0x60 },
};
static tracks trks{t1, t2, t3};
static unsigned int srate = 48000;
static int bpm = 72;
int main()
{
::pa_sample_spec ss = {
.format = PA_SAMPLE_U8,
.rate = ::srate,
.channels = 1,
};
auto pa = ::pa_simple_new(
NULL, // default server
"bnd_play", // app name
PA_STREAM_PLAYBACK, // direction: playback
NULL, // default device
"playback", // stream name
&ss, // sample spec
NULL, // default channel map
NULL, // default buffering
NULL); // error code storage
if (!pa) throw std::runtime_error{"unable to initialize pulseaudio."};
buffer_type buf(1024);
while (trks.fill(buf, ::srate, ::bpm))
::pa_simple_write(pa, buf.data(), buf.size(), NULL);
::pa_simple_drain(pa, NULL);
::pa_simple_free(pa);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment