Created
August 31, 2014 09:03
-
-
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.
This file contains hidden or 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
// 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