stew

a monorepo of some sort
git clone git://git.nsmpr.xyz/stew.git
Log | Files | Refs

mubox.c (4685B)


      1 /*
      2 	Primitive audio synth
      3 
      4    - 6-voice polyphony, 1 noise channel, 1 kick channel
      5    - only triangle osc, only freq, vol and decay ctls
      6    - controlled via stdin
      7 */
      8 
      9 #include <u.h>
     10 #include <libc.h>
     11 #include <bio.h>
     12 #include <thread.h>
     13 
     14 #define MaxArgs 32
     15 #define MaxVoices 6
     16 #define BSize 441
     17 
     18 typedef struct Voice Voice;
     19 struct Voice {
     20 	double stime;
     21 	double volume;
     22 	double amplitude;
     23 	double decay;
     24 	double freq;
     25 	double bend;
     26 };
     27 
     28 enum {
     29 	VKick = MaxVoices,
     30 	VNoise = MaxVoices + 1,
     31 };
     32 
     33 Voice v[MaxVoices + 2], vv, *vp;
     34 double T, mix[2];
     35 Biobuf *in;
     36 int out;
     37 
     38 int realtime;
     39 
     40 /*
     41  * Frequencies for equal-tempered scale
     42  * from https://pages.mtu.edu/~suits/notefreqs.html
     43  */
     44 
     45 struct {
     46 	char *s;
     47 	double f;
     48 } notes[12] = {
     49 	{"C-", 261.63},
     50 	{"C#", 277.18},
     51 	{"D-", 293.66},
     52 	{"D#", 311.13},
     53 	{"E-", 329.63},
     54 	{"F-", 349.23},
     55 	{"F#", 369.99},
     56 	{"G-", 392.0 },
     57 	{"G#", 415.30},
     58 	{"A-", 440.0 },
     59 	{"A#", 466.16},
     60 	{"B-", 493.88},
     61 };
     62 
     63 double
     64 ms2decay(double x)
     65 {
     66 	if (x <= 0) return 1;
     67 	return 1/(x*44.1);
     68 }
     69 
     70 Voice *
     71 newvoice(double freq)
     72 {
     73 	static int n = 0;
     74 	n = (n + 1) % MaxVoices;
     75 	v[n] = vv;
     76 	v[n].amplitude = 1;
     77 	v[n].stime = T;
     78 	v[n].freq = freq;
     79 	return &v[n];
     80 }
     81 
     82 int
     83 note(char *s)
     84 {
     85 	int i, oct;
     86 	if ((s[2] < '0') || (s[2] > '9')) return -1;
     87 	oct = atoi(s+2) - 4;
     88 	for (i = 0; i < 12; i++) {
     89 		if (strncmp(s, notes[i].s, 2) == 0) {
     90 			double freq = notes[i].f;
     91 			while (oct < 0) {
     92 				freq = freq / 2;
     93 				oct++;
     94 			}
     95 			while (oct > 0) {
     96 				freq = freq * 2;
     97 				oct--;
     98 			}
     99 			vp = newvoice(freq);
    100 			return 0;
    101 		} 
    102 	}
    103 	return -1;
    104 }
    105 
    106 void
    107 cmd(char *s)
    108 {
    109 	double f;
    110 	if (note(s) != 0) switch(s[0]) {
    111 	/* noise channel commands */
    112 	case 'h':
    113 		v[VNoise].amplitude = 1;
    114 		v[VNoise].decay = ms2decay(25);
    115 		break;
    116 	case 'H':
    117 		v[VNoise].amplitude = 1;
    118 		v[VNoise].decay = ms2decay(100);
    119 		break;
    120 	case 'N':
    121 		f = atof(s+1);
    122 		v[VNoise].volume = f;
    123 		v[VNoise].amplitude = 1;
    124 		break;
    125 	case 'n':
    126 		f = atof(s+1);
    127 		v[VNoise].decay = ms2decay(f);
    128 		v[VNoise].amplitude = 1;
    129 		break;
    130 
    131 	/* kick channel commands */
    132 	case 'k':
    133 		v[VKick].stime = T;
    134 		v[VKick].freq = 220;
    135 		v[VKick].amplitude = 1;
    136 		break;
    137 	case 'K':
    138 		v[VKick].stime = T;
    139 		v[VKick].freq = 440;
    140 		v[VKick].amplitude = 1;
    141 		break;
    142 
    143 	/* voice channel commands */
    144 	case 'f': // freq
    145 		f = atof(s+1);
    146 		vp->freq = f;
    147 		vv.freq = f;
    148 		break;
    149 	case 'b': // bend
    150 		f = atof(s+1);
    151 		vp->bend = f;
    152 		vv.bend = f;
    153 		break;
    154 	case 'd': // decay;
    155 		f = atof(s+1);
    156 		vp->decay = ms2decay(f);
    157 		vv.decay = ms2decay(f);
    158 		break;
    159 	case 'v':
    160 		f = atof(s+1);
    161 		vp = newvoice(f);
    162 		break;
    163 	}
    164 }
    165 
    166 void
    167 threadread(void *)
    168 {
    169 	for (;;) {
    170 		char *s = Brdstr(in, '\n', 1);
    171 		if (s == nil) threadexitsall(nil);
    172 		char *args[MaxArgs];
    173 		int n = tokenize(s, args, MaxArgs);
    174 		int i;
    175 		for (i = 0; i < n; i++) cmd(args[i]);
    176 		free(s);
    177 	}
    178 }
    179 
    180 double
    181 tri(double T)
    182 {
    183 	static double env[5] = { 0.0, 1.0, 0.0, -1.0, 0.0 };
    184 	T = 4.0 * (T - floor(T));
    185 	int i = floor(T);
    186 	double X = T - i;
    187 	return env[i+1] * X + env[i] * (1 - X);
    188 }
    189 
    190 void
    191 voice(Voice *v)
    192 {
    193 	double x = tri((T - v->stime) * v->freq) * v->amplitude * v->volume;
    194 	mix[0] += x;
    195 	mix[1] += x;
    196 }
    197 
    198 void
    199 noise(void)
    200 {
    201 	mix[0] += (1 - frand() * 2) * v[VNoise].amplitude * v[VNoise].volume;
    202 	mix[1] += (1 - frand() * 2) * v[VNoise].amplitude * v[VNoise].volume;
    203 }
    204 
    205 void
    206 kick(void)
    207 {
    208 	double x = cos(2.0 * PI * (T - v[VKick].stime) * v[VKick].freq) *
    209 	  v[VKick].amplitude * v[VKick].volume;
    210 	mix[0] += x;
    211 	mix[1] += x;
    212 }
    213 
    214 void
    215 modvoice(Voice *v)
    216 {
    217 	v->amplitude -= v->decay;
    218 	v->freq *= v->bend;
    219 	if (v->amplitude < 0) v->amplitude = 0;
    220 	if (v->freq < 0) v->freq = 0;
    221 }
    222 
    223 void
    224 output(void)
    225 {
    226 	s16int buf[BSize * 2];
    227 	int i, j;
    228 	for (i = 0; i < BSize; i++) {
    229 		mix[0] = 0;
    230 		mix[1] = 0;
    231 		for (j = 0; j < MaxVoices; j++) voice(&v[j]);
    232 		noise();
    233 		kick();
    234 		for (j = 0; j < MaxVoices + 2; j++) modvoice(&v[j]);
    235 		buf[i * 2] = 0x7fff * (mix[0] / 8);
    236 		buf[i * 2 + 1] = 0x7fff * (mix[1] / 8);
    237 		T += 1.0/44100.0;
    238 	}
    239 	
    240 	write(out, buf, BSize * 4);
    241 }
    242 
    243 void
    244 usage(void)
    245 {
    246 	fprint(2, "usage: %s [-r]\n", argv0);
    247 	threadexitsall("usage");
    248 }
    249 
    250 void
    251 threadmain(int argc, char **argv)
    252 {
    253 	int i;
    254 	ARGBEGIN {
    255 	case 'r':
    256 		realtime = 1;
    257 		break;
    258 	default:
    259 		usage();
    260 	} ARGEND;
    261 	if (argc != 0) usage();
    262 	for (i = 0; i < MaxVoices; i++) {
    263 		v[i] = (Voice) {
    264 			0,
    265 			1,
    266 			0,
    267 			0,
    268 			0,
    269 			1,
    270 		};
    271 	}
    272 	v[VKick] = (Voice) {
    273 		0,
    274 		1,
    275 		0,
    276 		0.0001,
    277 		220,
    278 		0.9995,
    279 	};
    280 	v[VNoise] = (Voice) {
    281 		0,
    282 		1,
    283 		0,
    284 		0.01,
    285 		0,
    286 		0,
    287 	};
    288 	vv = (Voice) {
    289 		0,
    290 		1,
    291 		1,
    292 		ms2decay(500),
    293 		0,
    294 		1,
    295 	};
    296 
    297 	in = Bfdopen(0, OREAD);
    298 	out = 1;
    299 	vp = &v[0];
    300 	proccreate(threadread, nil, 1024 * 1024 * 64);
    301 
    302 	vlong t = nsec() / 1000000;
    303 	for (;;) {
    304 		output();
    305 		t += 10;
    306 		if (realtime != 0) sleep(t - (nsec() / 1000000));
    307 	}
    308 }