From 6c358a842baaf6d211a5c8c1717d815e7813ed96 Mon Sep 17 00:00:00 2001 From: JSDurand Date: Sun, 11 Jul 2021 18:45:38 +0800 Subject: First commit Now I have a kind of piano like instrument. A violin-like instrument is being constructed. --- "F\303\274r Elise/F\303\274r Elise notes.txt" | 100 ++++ cat/part of cat vibing.txt | 118 +++++ coffin/part of coffin dance.txt | 87 ++++ instrument.c | 390 +++++++++++++++ instrument.d | 1 + instrument.h | 33 ++ main.c | 588 ++++++++++++++++++++++ main.d | 29 ++ makefile | 96 ++++ marble machine/marble machine notes.txt | 5 + mix.c | 22 + mix.d | 1 + mix.h | 7 + notes.txt | 1 + parser.c | 694 ++++++++++++++++++++++++++ parser.d | 1 + parser.h | 48 ++ test.txt | 3 + turkish/turkish notes (archive).txt | 236 +++++++++ turkish/turkish notes.txt | 127 +++++ util.c | 80 +++ util.d | 1 + util.h | 126 +++++ 23 files changed, 2794 insertions(+) create mode 100644 "F\303\274r Elise/F\303\274r Elise notes.txt" create mode 100644 cat/part of cat vibing.txt create mode 100644 coffin/part of coffin dance.txt create mode 100644 instrument.c create mode 100644 instrument.d create mode 100644 instrument.h create mode 100644 main.c create mode 100644 main.d create mode 100644 makefile create mode 100644 marble machine/marble machine notes.txt create mode 100644 mix.c create mode 100644 mix.d create mode 100644 mix.h create mode 120000 notes.txt create mode 100644 parser.c create mode 100644 parser.d create mode 100644 parser.h create mode 100644 test.txt create mode 100644 turkish/turkish notes (archive).txt create mode 100644 turkish/turkish notes.txt create mode 100644 util.c create mode 100644 util.d create mode 100644 util.h diff --git "a/F\303\274r Elise/F\303\274r Elise notes.txt" "b/F\303\274r Elise/F\303\274r Elise notes.txt" new file mode 100644 index 0000000..cf05d9f --- /dev/null +++ "b/F\303\274r Elise/F\303\274r Elise notes.txt" @@ -0,0 +1,100 @@ +, +b 75 , +7 0.25 , s 2 +6 0.25 , +7 0.25 , +6 0.25 , +7 0.25 , +2 0.25 , +5 0.25 , +3 0.25 , +0 0.5 , -24 0.25 +s 0.25 , -17 0.25 +-9 0.25 , -12 0.25 +-5 0.25 , s 0.75 +0 0.25 , +# three bar lines +2 0.5 , -29 0.25 +s 0.25 , -17 0.25 +-5 0.25 , -13 0.25 +-1 0.25 , s 0.75 +2 0.25 , +# four bar lines +3 0.5 , -24 0.25 +s 0.25 , -17 0.25 +-5 0.25 , -12 0.25 +7 0.25 , s 0.75 +6 0.25 , +# five bar +7 0.25 , s 1.5 +6 0.25 , +7 0.25 , +2 0.25 , +5 0.25 , +3 0.25 , +# first line over +0 0.5 , -24 0.25 +s 0.25 , -17 0.25 + , -12 0.25 +-9 0.25 , s 0.75 +-5 0.25 , +0 0.25 , +# one bar +2 0.5 , -29 0.25 +s 0.25 , -17 0.25 +-9 0.25 , -13 0.25 +3 0.25 , s 0.75 +2 0.25 , +# two bars +0 1 , -24 0.25 + , -17 0.25 + , -12 0.25 + , s 0.25 +# repeat start +b 120 , +7 0.25 , s 2 +6 0.25 , +7 0.25 , +6 0.25 , +7 0.25 , +2 0.25 , +5 0.25 , +3 0.25 , +0 0.5 , -24 0.25 +s 0.25 , -17 0.25 +-9 0.25 , -12 0.25 +-5 0.25 , s 0.75 +0 0.25 , +# three bar lines +2 0.5 , -29 0.25 +s 0.25 , -17 0.25 +-5 0.25 , -13 0.25 +-1 0.25 , s 0.75 +2 0.25 , +# four bar lines +3 0.5 , -24 0.25 +s 0.25 , -17 0.25 +-5 0.25 , -12 0.25 +7 0.25 , s 0.75 +6 0.25 , +# five bar +7 0.25 , s 1.5 +6 0.25 , +7 0.25 , +2 0.25 , +5 0.25 , +3 0.25 , +# first line over +0 0.5 , -24 0.25 +s 0.25 , -17 0.25 + , -12 0.25 +-9 0.25 , s 0.75 +-5 0.25 , +0 0.25 , +# one bar +2 0.5 , -29 0.25 +s 0.25 , -17 0.25 +-9 0.25 , -13 0.25 +3 0.25 , s 0.75 +2 0.25 , +# repeat over diff --git a/cat/part of cat vibing.txt b/cat/part of cat vibing.txt new file mode 100644 index 0000000..6b234d0 --- /dev/null +++ b/cat/part of cat vibing.txt @@ -0,0 +1,118 @@ +-12 0.5 +-6 0.5 +-6 0.75 +-4 0.25 +-3 0.5 +-6 0.25 +-6 0.25 +-6 0.5 +-3 0.25 +-3 0.25 +-4 0.5 +-8 0.5 +-8 0.5 +-4 0.5 +-3 0.5 +-6 0.5 +-6 0.5 +-6 0.25 +-6 0.25 +-12 0.5 +-6 0.5 +-6 0.75 +-4 0.25 +-3 0.5 +-6 0.5 +-6 0.25 +-6 0.25 +-6 0.25 +-3 0.25 +1 0.5 +1 0.25 +-3 0.25 +-3 0.5 +-4 0.5 +-3 0.5 +-6 0.5 +-6 0.5 +-6 0.25 +-6 0.25 +1 0.5 +1 0.5 +-1 0.5 +-3 0.5 +-4 0.5 +-8 0.5 +-8 0.25 +-8 0.25 +-8 0.25 +-4 0.25 +-1 0.25 +-1 0.25 +-1 0.25 +-1 0.25 +-3 0.25 +-3 0.25 +-4 0.5 +-3 0.5 +-6 0.5 +-6 0.75 +-6 0.25 +1 0.5 +1 0.5 +-1 0.5 +-3 0.5 +-4 0.5 +-8 0.5 +-8 0.25 +-8 0.25 +-8 0.25 +-4 0.25 +-1 0.25 +-1 0.25 +-1 0.25 +-1 0.25 +-3 0.5 +-4 0.5 +-3 0.5 +-6 0.5 +-6 1 +-12 0.5 +-6 0.5 +-6 0.75 +-4 0.25 +-3 0.5 +-6 0.25 +-6 0.25 +-6 0.5 +-3 0.25 +-3 0.25 +-4 0.5 +-8 0.5 +-8 0.5 +-4 0.5 +-3 0.5 +-6 0.5 +-6 0.5 +-6 0.25 +-6 0.25 +-12 0.5 +-6 0.5 +-6 0.75 +-4 0.25 +-3 0.5 +-6 0.5 +-6 0.25 +-6 0.25 +-6 0.25 +-3 0.25 +1 0.5 +1 0.25 +-3 0.25 +-3 0.5 +-4 0.5 +-3 0.5 +-6 0.5 +-6 0.5 +-6 0.25 +-6 0.25 diff --git a/coffin/part of coffin dance.txt b/coffin/part of coffin dance.txt new file mode 100644 index 0000000..2fd051f --- /dev/null +++ b/coffin/part of coffin dance.txt @@ -0,0 +1,87 @@ +1 0.5 +0 0.5 +-1 0.5 +-3 0.5 +-2 1 +-2 0.5 +2 0.5 +1 1 +0 1 +-1 1 +-1 0.5 +-1 0.5 +1 1 +0 0.5 +-1 0.5 +-2 1 +-2 0.5 +7 0.5 +6 0.5 +7 0.5 +6 0.5 +7 0.5 +-2 1 +-2 0.5 +7 0.5 +6 0.5 +7 0.5 +6 0.5 +7 0.5 +0 0.5 +0 0.5 +0 0.5 +0 0.5 +2 0.5 +2 0.5 +2 0.5 +2 0.5 +1 0.5 +1 0.5 +1 0.5 +1 0.5 +4 0.5 +4 0.5 +4 0.5 +4 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +5 0.5 +1 0.5 +0 0.5 +-1 0.5 +-3 0.5 +-2 1 +-2 0.5 +2 0.5 +1 1 +0 1 +-1 1 +-1 0.5 +-1 0.5 +1 1 +0 0.5 +-1 0.5 +-2 1 +-2 0.5 +7 0.5 +6 0.5 +7 0.5 +6 0.5 +7 0.5 +-2 1 +-2 0.5 +7 0.5 +6 0.5 +7 0.5 +6 0.5 +7 0.5 + diff --git a/instrument.c b/instrument.c new file mode 100644 index 0000000..edb2f76 --- /dev/null +++ b/instrument.c @@ -0,0 +1,390 @@ +#include "instrument.h" +#include +#include + +/* A hard limit */ +#define REVERB_MAX 0 + +/* This is not exposed to other files, as this should be controlled by + individual instruments. */ +typedef struct { + float freq; + float amp; + unsigned char pred; /* if this is 0, then no frequency + modulation is present. */ +} FM; + +/* By similar reasoning, ADSR is not exposed either. */ +typedef struct { + float attack_p; + float decay_p; + float sustain_p; + float release_p; + float sustain_level; + unsigned char pred; /* If this is 0 then the instrument + does not adapt the ADSR model. */ +} ADSR; + +/* NOTE: The default method for an instrument to produce sounds is in + accordance with its FM, ADSR, and Fourier coefficients. But + individual instruments can override that default method by + providing their own functions. This way we would have more + flexibility. */ + +struct Instrument_s { + float *sin_coefs; /* coefficients of sin in the Fourier + expansion. */ + float *cos_coefs; /* coefficients of cos in the Fourier + expansion. */ + sound_t sound; /* the function to produce sounds. */ + merger_t merger; /* the function to merge waves. */ + float reverb; /* The percentage of reverberation. */ + FM fm; + ADSR adsr; +}; + +WaveFrag +make_sound(Instrument * in, Volume v, Hertz h, Seconds s) +{ + return in->sound(in, v, h, s); +} + +U_ATTR +WaveFrag +merge_waves(Instrument *in, WaveFrag *frags, LENT len, int step) +{ + return in->merger(in, frags, len, step); +} + +/* Now comes various instruments */ + +/* The first is piano */ + +UH_ATTR +static float +smooth_step(float t) +{ + if (t <= 0.0f) { + return 0.0f; + } else if (t >= 1.0f) { + return 1.0f; + } else { + return 6*pow(t, 5)-15*pow(t, 4)+10*pow(t, 3); + } +} + +UH_ATTR +static float +old_piano_sound_internal(Instrument *piano, float theta) +{ + return sin(theta)+1.869*sin(2.0f*theta)+0.042f*sin(3.0f*theta) + +0.022f*sin(4.0f*theta)+cos(theta); +} + +UH_ATTR +static float +piano_sound_internal(Instrument *piano, float theta) +{ + return (*(piano->sin_coefs) * sin(theta)+ + *(piano->sin_coefs+1) * sin(2.0f*theta)+ + *(piano->sin_coefs+2) * sin(3.0f*theta))* + exp(-0.004f*theta); +} + +UH_ATTR +WaveFrag +piano_sound(void * in, Volume v, Hertz h, Seconds s) +{ + Instrument *piano = (Instrument *)in; + + float step = (float) (h * 2 * M_PI) / SAMPLE_RATE; + LENT sample_num = (LENT) floor(SAMPLE_RATE * s); + + Wave w = MYALLOC(Pulse, sizeof(*w) * sample_num); + + float theta = 0.0f, temp = 0.0f; + + for (LENT i = 0; i < sample_num; i++, theta += step) { + temp = piano_sound_internal(piano, theta); + temp += temp*temp*temp; + *(w+i) = (float) v * piano_sound_internal(piano, temp); + } + + return (WaveFrag) { w, sample_num }; +} + +/* Just concatenates waves together. */ +UH_ATTR +WaveFrag +simple_merger(void *in, WaveFrag *frags, LENT len, int step) +{ + WaveFrag wf; + + LENT total_len = 0; + + for (LENT i = 0; i < len; i += step) + total_len += (frags+i)->n; + + wf.n = total_len; + wf.w = MYALLOC(Pulse, total_len); + + for (LENT i = 0, counter = 0, local = 0; + i < len && counter < total_len; + i += step) { + local = (frags+i)->n; + + for (LENT j = 0; j < local;) + *(wf.w+counter++) = *((frags+i)->w+j++); + + free((frags+i)->w); + } + + free(frags); + + return wf; +} + +Instrument *make_piano() +{ + Instrument *ins = malloc(sizeof *ins * 1); + + /* The piano's sound is simulated directly by some coefficients. */ + ins->fm.pred = 0; + ins->adsr.pred = 0; + ins->sin_coefs = malloc(sizeof(*(ins->sin_coefs)) * 3); + ins->cos_coefs = malloc(sizeof(*(ins->cos_coefs)) * 1); + ins->reverb = 0.0f; + + *(ins->sin_coefs) = 0.65f; + *(ins->sin_coefs+1) = 0.45f; + *(ins->sin_coefs+2) = 0.1f; + *(ins->cos_coefs) = 1; + ins->sound = piano_sound; + ins->merger = simple_merger; + + return ins; +} + +void +destroy_piano(Instrument *in) +{ + free(in->sin_coefs); + free(in->cos_coefs); + free(in); +} + +/* Then comes violin */ + +UH_ATTR +Pulse +violin_sound_internal(Instrument *in, float theta) +{ + /* return -1.0f*fmod(theta, 2.0f*M_PI)/M_PI + 1.0f; */ + /* return (*(in->sin_coefs)*sin(theta)+ + * *(in->sin_coefs+1)*sin(2.0f*theta)+ + * *(in->sin_coefs+2)*sin(3.0f*theta)+ + * *(in->sin_coefs+3)*sin(4.0f*theta)+ + * *(in->sin_coefs+4)*sin(5.0f*theta)); */ + Pulse result = 0.0f; + for (int i = 0; i < 26; i++) + result += *(in->sin_coefs+i) * sin((float) (i+1)*theta); + /* result = sin(theta); */ + + return result; +} + +UH_ATTR +WaveFrag +violin_sound(void * in, Volume v, Hertz h, Seconds s) +{ + Instrument *violin = (Instrument *)in; + + /* We produce a little more samples so that we can create the effect + of reverberation later on. */ + s *= (1.0f+violin->reverb); + + float step = (float) (h * 2 * M_PI) / SAMPLE_RATE; + LENT sample_num = (LENT) floor(SAMPLE_RATE * s); + float attack_step = 1.0f / (float) (sample_num * violin->adsr.attack_p); + float decay_step = 1.0f / (float) (sample_num * violin->adsr.decay_p); + float sustain_step = 1.0f / (float) (sample_num * violin->adsr.sustain_p); + float release_step = 1.0f / (float) (sample_num * violin->adsr.release_p); + + Wave w = MYALLOC(Pulse, sizeof(*w) * sample_num); + + float theta = 0.0f, temp = 0.0f, adsrcounter = 0.0f; + + unsigned char phase = 0, sustain_flag = 0; + + for (LENT i = 0; i < sample_num; i++, theta += step) { + temp = violin_sound_internal(violin, theta); + /* temp += temp*temp*temp; */ + /* *(w+i) = (float) v * temp * ((sustain_flag) ? SUSTAIN_LEVEL : adsrcounter); */ + *(w+i) = (float) v * temp; + + switch (phase) { + case 0: /* attack phase */ + adsrcounter += attack_step; + if (adsrcounter >= 1.0f) { + adsrcounter = 1.0f; + phase++; + } + break; + case 1: /* decay phase */ + adsrcounter -= decay_step; + if (adsrcounter <= SUSTAIN_LEVEL) { + adsrcounter = 0.0f; + sustain_flag = 1; + phase++; + } + break; + case 2: /* sustain phase */ + adsrcounter += sustain_step; + if (adsrcounter >= 1.0f) { + adsrcounter = SUSTAIN_LEVEL; + sustain_flag = 0; + phase++; + } + break; + default: /* release phase */ + adsrcounter -= release_step; + if (adsrcounter <= 0.0f) adsrcounter = 0.0f; + break; + } + } + + return (WaveFrag) { w, sample_num }; +} + +UH_ATTR +WaveFrag +violin_merger(void *in, WaveFrag *frags, LENT len, int step) +{ + Instrument *violin = (Instrument*) in; + + WaveFrag wf, local_last_wave = (WaveFrag) { NULL, 0 }; + WaveFrag local_current_wave = (WaveFrag) { NULL, 0 }; + WaveFrag local_mix_wave, local_mix_input[2]; + + LENT total_len = 0; + + float reverb_ratio = 1.0f / (1.0f + violin->reverb); + + for (LENT i = 0; i < len; i += step) + total_len += (LENT) ((frags+i)->n * reverb_ratio); + + wf.n = total_len; + wf.w = MYALLOC(Pulse, total_len); + + for (LENT i = 0, counter = 0, local = 0, local_last = 0; + i < len && counter < total_len; + i += step) { + /* The last fragment that we want to mix. */ + local_last = (!i) ? 0 : (frags+i-1)->n - local; + local = (LENT) ((frags+i)->n * reverb_ratio); + /* Make sure everything stays within bounds. */ + local_last = min(local_last, local); + local_last = min(local_last, REVERB_MAX); + + if (local_last && i) { + /* If we reach here then i > 0. */ + local_last_wave.n = local_last; + local_last_wave.w = realloc(local_last_wave.w, + sizeof(Pulse) * local_last); + + local_current_wave.n = local_last; + local_current_wave.w = realloc(local_current_wave.w, + sizeof(Pulse) * local_last); + + for (LENT j = 0; j < local_last; j++) { + *(local_last_wave.w+j) = + *((frags+i-1)->w+(frags+i-1)->n-local_last+j); + + *(local_current_wave.w+j) = *((frags+i)->w+j); + } + + local_mix_input[0] = local_last_wave; + local_mix_input[1] = local_current_wave; + + local_mix_wave = mix_waves(local_mix_input, 2); + + for (LENT j = 0; j < local_last;) + *(wf.w+counter++) = *(local_mix_wave.w+j++); + + free(local_mix_wave.w); + + for (LENT j = local_last; j < local;) + *(wf.w+counter++) = *((frags+i)->w+j++); + } else { + for (LENT j = 0; j < local;) + *(wf.w+counter++) = *((frags+i)->w+j++); + } + + free((frags+i)->w); + } + + free(local_last_wave.w); + free(local_current_wave.w); + + free(frags); + + return wf; +} + +UH_ATTR +Instrument * +make_violin() +{ + Instrument * violin= malloc(sizeof *violin); + + violin->fm.pred = 0; + violin->adsr.pred = 1; + + violin->reverb = 0.01f; + violin->adsr.attack_p = 0.55; + violin->adsr.release_p = 0.25; + violin->adsr.decay_p = 0.05; + violin->adsr.sustain_p = max(1.0f - (violin->adsr.attack_p+ + violin->adsr.decay_p+ + violin->adsr.release_p), + 0.0f); + violin->adsr.sustain_level = 0.9; + + double temp [] = { + 1.0, 0.399064778, 0.229404484, 0.151836061, + 0.196754229, 0.093742264, 0.060871957, + 0.138605419, 0.010535002, 0.071021868, + 0.029954614, 0.051299684, 0.055948288, + 0.066208224, 0.010067391, 0.00753679, + 0.008196947, 0.012955577, 0.007316738, + 0.006216476, 0.005116215, 0.006243983, + 0.002860679, 0.002558108, 0.0, 0.001650392 + }; + + violin->sin_coefs = malloc(sizeof(float)*26); + for (int i = 0; i < 26; i++) + *(violin->sin_coefs+i) = *(temp+i); + /* *(violin->sin_coefs) = 1.0f; + * *(violin->sin_coefs+1) = 0.41f; + * *(violin->sin_coefs+2) = 0.35f; + * *(violin->sin_coefs+3) = 0.39f; + * *(violin->sin_coefs+4) = 0.45f; + * *(violin->sin_coefs+5) = 0.35f; + * *(violin->sin_coefs+6) = 0.5f; + * *(violin->sin_coefs+7) = 0.4f; + * *(violin->sin_coefs+8) = 0.35f; + * *(violin->sin_coefs+9) = 0.1f; */ + + violin->sound = violin_sound; + violin->merger = simple_merger; + + return violin; +} + +UH_ATTR +void +destroy_violin(Instrument *in) +{ + free(in->sin_coefs); + free(in); +} diff --git a/instrument.d b/instrument.d new file mode 100644 index 0000000..3f8041b --- /dev/null +++ b/instrument.d @@ -0,0 +1 @@ +instrument.o instrument.d : instrument.c instrument.h util.h diff --git a/instrument.h b/instrument.h new file mode 100644 index 0000000..0ab12c9 --- /dev/null +++ b/instrument.h @@ -0,0 +1,33 @@ +#ifndef INSTRUMENT_H +#define INSTRUMENT_H +#include "util.h" + +struct Instrument_s; + +typedef struct Instrument_s Instrument; + +typedef WaveFrag (*sound_t)(void *, Volume v, Hertz h, Seconds s); +typedef WaveFrag (*merger_t)(void *, WaveFrag *, LENT, int); + +WaveFrag make_sound(Instrument * in, Volume v, Hertz h, Seconds s); + +WaveFrag piano_sound(void * in, Volume v, Hertz h, Seconds s); + +/* Need this so that we can let instruments control reverberation, + say. */ + +U_ATTR WaveFrag merge_waves(Instrument *in, WaveFrag *frags, LENT len, int step); + +/* Various instruments */ + +Instrument *make_piano(); + +void destroy_piano(Instrument *in); + +/* Violin */ + +Instrument * make_violin(); + +void destroy_violin(Instrument *in); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..43bf52b --- /dev/null +++ b/main.c @@ -0,0 +1,588 @@ +/* This is a program that can read in a text file and convert it to a + sound file. Since this is going to depend on ffmpeg's libraries, + otherwise I am only able to produce raw audio files, which are too + large and not very convenient to use, a makefile is included to + make the compiling process easier. */ + +/* include stuff */ +#include +#include +#include + +/* include FFMPEG stuff */ +#include + +#include +#include +#include +#include + +/* Self-Dependencies */ +#include "util.h" +#include "instrument.h" +#include "parser.h" + +/* #include */ + +/* helper functions */ + +/* FFMPEG helpers */ + +/* check that a given sample format is supported by the encoder */ +UNUSED +static int +check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) +{ + const enum AVSampleFormat *p = codec->sample_fmts; + + /* the array is terminated by -1 */ + for (int i = 0; *(p+i) != -1; i++) + fprintf(stderr, "supports %s\n", av_get_sample_fmt_name(*(p+i))); + + while (*p != AV_SAMPLE_FMT_NONE) { + if (*p == sample_fmt) + return 1; + p++; + } + return 0; +} + +__attribute__((__hot__, __unused__)) +static void +/* encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, + * FILE *output) */ +encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output) +{ + int ret = 0; + + /* send the frame for encoding */ + ret = avcodec_send_frame(ctx, frame); + if (ret < 0) { + fprintf(stderr, "Error sending the frame to the encoder\n"); + exit(1); + } + + /* read all the available output packets (in general there may be any + * number of them */ + while (ret >= 0) { + ret = avcodec_receive_packet(ctx, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return; + else if (ret < 0) { + fprintf(stderr, "Error encoding audio frame\n"); + exit(1); + } + + fwrite(pkt->data, 1, pkt->size, output); + /* av_write_frame(afc, pkt); */ + av_packet_unref(pkt); + } +} + +/* select layout with the highest channel count */ +UNUSED +static int +select_channel_layout(const AVCodec *codec) +{ + const uint64_t *p; + uint64_t best_ch_layout = 0; + int best_nb_channels = 0; + + if (!codec->channel_layouts) + return AV_CH_LAYOUT_STEREO; + + p = codec->channel_layouts; + while (*p) { + int nb_channels = av_get_channel_layout_nb_channels(*p); + + if (nb_channels > best_nb_channels) { + best_ch_layout = *p; + best_nb_channels = nb_channels; + } + p++; + } + return best_ch_layout; +} + +UHA_ATTR +static inline Pulse +square_wave(float counter) +{ + return (fmod(counter, 2*M_PI) < M_PI) ? 1 : -1; +} + +UHA_ATTR +static inline Pulse +synthesize_wave(float theta) +{ + + /* return -1.0f + fmod(theta, 2*M_PI)/M_PI; */ + + /* return sin(theta); */ + + /* return asin(sin(theta)) * 2/M_PI; */ + + /* return pow(theta/2*M_PI, 2); */ + + /* return sin(theta)+0.442f*sin(2.0f*theta)+0.315f*sin(3.0f*theta) + * +0.083f*sin(4.0f*theta)+cos(theta)+0.442f*cos(2.0f*theta); */ + + /* piano like */ + + /* return sin(theta)+1.869*sin(2.0f*theta)+0.042f*sin(3.0f*theta) + * +0.022f*sin(4.0f*theta)+cos(theta); */ + + /* excellent piano like */ + + return ((0.65f*sin(theta)+0.5f*sin(2.0f*theta))*exp(-0.004f*theta)); + +} + +/* This is replaced by make_sound function. */ +/* UH_ATTR + * static inline WaveFrag + * oscillator(Volume v, Hertz h, Seconds s, Instrument *ins) + * { + * + * return 0.0f; + * } */ + +UHA_ATTR +static inline float +frequency_modulation(float theta, Hertz h, float fm) +{ + return theta+FM_AMP*theta*sin(fm); +} + +UH_ATTR +static WaveFrag +hstow(Volume v, Hertz h, Seconds s) +{ + LENT sample_num = (LENT) floor(SAMPLE_RATE * s); + float attack_step = 1.0f / (float) (sample_num * ATTACK_P); + float decay_step = 1.0f / (float) (sample_num * DECAY_P); + float sustain_step = 1.0f / (float) (sample_num * SUSTAIN_P); + float release_step = 1.0f / (float) (sample_num * RELEASE_P); + + float step = (float) (h * 2 * M_PI) / SAMPLE_RATE; + + float fm_step = (float) ((FM_FREQUENCY)*2*M_PI) / SAMPLE_RATE; + + unsigned char phase = 0, sustain_flag = 0; + + Wave w = MYALLOC(Pulse, sizeof(*w) * sample_num); + + float adsrcounter = 0.0f, counter = 0.0f, fm_counter = 0.0f; + float theta = 0.0f, temp = 0.0f; + + for (LENT i = 0; i < sample_num; i++, counter += step, fm_counter += fm_step) { + /* theta = frequency_modulation(counter, (float) h*i/((float) SAMPLE_RATE), fm_counter); */ + theta=counter; + temp = synthesize_wave(theta); + temp += temp*temp*temp; + temp = synthesize_wave(temp); + temp += theta*exp(-2.0f*theta); + *(w+i) = (float) v * temp; + /* (synthesize_wave(temp)/\* + + * * 0.01*(2.0f*(float)rand()/(float) RAND_MAX - 1.0f) *\/) *//* * + * ((sustain_flag) ? SUSTAIN_LEVEL : adsrcounter) ;*/ + switch (phase) { + case 0: /* attack phase */ + adsrcounter += attack_step; + if (adsrcounter >= 1.0f) { + adsrcounter = 1.0f; + phase++; + } + break; + case 1: /* decay phase */ + adsrcounter -= decay_step; + if (adsrcounter <= SUSTAIN_LEVEL) { + adsrcounter = 0.0f; + sustain_flag = 1; + phase++; + } + break; + case 2: /* sustain phase */ + adsrcounter += sustain_step; + if (adsrcounter >= 1.0f) { + adsrcounter = SUSTAIN_LEVEL; + sustain_flag = 0; + phase++; + } + break; + default: /* release phase */ + adsrcounter -= release_step; + if (adsrcounter <= 0.0f) adsrcounter = 0.0f; + break; + } + } + + return (WaveFrag) { w, sample_num }; +} + +H_ATTR +static WaveFrag +sbtow(Volume v, Semitones st, Beats b) +{ + return hstow(v, stoh(st), (b * BEAT_DUR)); +} + +/* compose */ + +D_ATTR("play_sheet") +static WaveFrag +compose(Volume *vs, Semitones *sts, Beats *bs, LENT len) +{ + WaveFrag wf, *temp = MYALLOC(WaveFrag, len); + + LENT total_len = 0; + + for (LENT i = 0; i < len; i++) { + if (*(sts+i) >= -50) + *(temp+i) = sbtow(*(vs+i), *(sts+i), *(bs+i)); + else + *(temp+i) = sbtow(*(vs+i), *(sts+i), *(bs+i)); + total_len += (temp+i)->n; + } + + wf.n = total_len; + + wf.w = MYALLOC(Pulse, total_len); + + LENT counter = 0; + + for (int i = 0; i < len; i++) { + LENT local = (temp+i)->n; + + for (LENT j = 0; j < local; j++) + *(wf.w+counter++) = *((temp+i)->w+j); + + free((temp+i)->w); + } + + free(temp); + + return wf; +} + +/* Reading */ + +/* The file should contain an even number of numbers. For any natural + number N, the 2*N th number specifies the Semitone of the Nth note, + and the 2*N+1 number specifies the beats of the Nth note. */ + +U_ATTR +static void +read_sb(const char *str, int str_len, + int *len, Semitones **sts, Beats **bs) +{ + int str_counter = 0; + + float temp = 0; + + int semi_len = 0, b_len = 0; + Node_t semis = EMPTY_NODE, beats = EMPTY_NODE; + Node_t sn = EMPTY_NODE, bn = EMPTY_NODE; + Node_t *sp = NULL, *bp = NULL; + + LENT nth = 0; + + for (;str_counter < str_len; nth++) { + int number_chars_read = 0; + int result = sscanf(str+str_counter, "%f %n", &temp, &number_chars_read); + if (result < 1) break; + + str_counter += number_chars_read; + + if (nth % 2) { + /* odd */ + PUSH_NODE(b_len, bp, beats, temp, f); + } else { + /* even */ + PUSH_NODE(semi_len, sp, semis, temp, f); + } + } + + if (nth % 2) { + fprintf(stderr, "There should be an even number of floats in " + "the file, but found %lu.\n", + nth); + exit(1); + } + + *len = floor(nth/2.0f); + *sts = realloc(*sts, sizeof(float) * (*len)); + *bs = realloc(*bs, sizeof(float) * (*len)); + + for (int i = 0; i < *len; i++) { + POP_NODE(semi_len, temp, semis, sn, f); + *(*sts+*len-1-i) = temp; + + POP_NODE(b_len, temp, beats, bn, f); + *(*bs+*len-1-i) = temp; + } +} + +/* main */ + +int main(int argc, char **argv) +{ + const AVCodec *codec; + AVCodecContext *c = NULL; + /* AVOutputFormat *of; + * AVFormatContext *oc; */ + AVFrame *frame; + AVPacket *pkt; + int ret = 0; + float *samples; + + /* output format setup */ + + /* if (!(of = av_guess_format(NULL, DEFAULT_OUTPUT_NAME, "audio/aac"))) { + * fprintf(stderr, "cannot guess output format\n"); + * exit(1); + * } */ + + /* avformat_alloc_output_context2(&oc, of, NULL, NULL); */ + /* oc = avformat_alloc_context(); + * + * if (!oc) { + * fprintf(stderr, "cannot allocate output context\n"); + * exit(1); + * } */ + + /* oc->oformat = of; */ + /* oc->url = MYALLOC(char, sizeof(DEFAULT_OUTPUT_NAME)); + * for (int i = 0; i < sizeof(DEFAULT_OUTPUT_NAME); i++) + * oc->url[i] = DEFAULT_OUTPUT_NAME[i]; */ + + /* ret = avio_open(&oc->pb, DEFAULT_OUTPUT_NAME, AVIO_FLAG_READ_WRITE); + * + * if (ret<0) { + * fprintf(stderr, "cannot open %s\n", DEFAULT_OUTPUT_NAME); + * exit(1); + * } */ + + /* find the encoder */ + codec = avcodec_find_encoder(AV_CODEC_ID_MP3); + if (!codec) { + fprintf(stderr, "Codec not found\n"); + exit(1); + } + + /* set an option to place MOOV atom first. */ + + /* AVDictionary *opt = NULL; + * av_dict_set(&opt, "movflags", "faststart", 0); + * + * printf("of->audio_codec = %d\n", of->audio_codec); + * + * if (oc->oformat->flags & AVFMT_GLOBALHEADER) + * c->flags |= AVFMT_GLOBALHEADER; */ + + /* return 0; */ + + /* STD_BASE = (float) pow(2, 1.0f/12.0f); */ + + UNUSED int notes_len = 0; + + /* UNUSED Volume *vs = NULL; + * Semitones *sts = MYALLOC(Semitones, 1); + * Beats *bs = MYALLOC(Beats, 1); */ + + char *notes_content = MYALLOC(char, 500); + + LENT notes_str_len = 0; + + notes_str_len = (LENT) read_entire_file("notes.txt", ¬es_content); + +#ifdef DEBUG + fprintf(stderr, "notes content = %s\n", notes_content); +#endif + + PSheet psh = read_sheet(notes_content, notes_str_len); + + if (argc >= 2 && **(argv+1) == 'p') + print_sheet(psh); + +#ifdef DEBUG + fprintf(stderr, "after printing\n"); +#endif + + /* read_sb(notes_content, notes_str_len, ¬es_len, + * &sts, &bs); */ + +#ifdef DEBUG + fprintf(stderr, "notes len = %d\nfirst bs = %.2f\n", + notes_len, *bs); +#endif + + /* vs = MYALLOC(Volume, notes_len); + * for (int i = 0; i < notes_len;) *(vs+i++) = 0.3f; */ + + /* WaveFrag wf = compose(vs, sts, bs, notes_len); */ + WaveFrag wf = play_sheet(psh, 0.3f); + + destroy_sheet(psh); + + printf("wf n = %lu\n", wf.n); + + /* AVStream *os = avformat_new_stream(oc, NULL); */ + + c = avcodec_alloc_context3(codec); + if (!c) { + fprintf(stderr, "Could not allocate audio codec context\n"); + exit(1); + } + + /* check that the encoder supports FLTP pcm input */ + /* The P marks that this is "planar", i.e. the input is not + interleaved between channels. */ + c->sample_fmt = AV_SAMPLE_FMT_FLTP; + if (!check_sample_fmt(codec, c->sample_fmt)) { + fprintf(stderr, "Encoder does not support sample format %s", + av_get_sample_fmt_name(c->sample_fmt)); + exit(1); + } + + /* set other audio parameters supported by the encoder */ + c->sample_rate = (int) SAMPLE_RATE; + c->channel_layout = select_channel_layout(codec); + c->channels = av_get_channel_layout_nb_channels(c->channel_layout); + + /* this should be sizeof(float) * 8 * nb_channels * SAMPLE_RATE + + We divide by 1000 since the unit is KB.*/ + c->bit_rate = sizeof(float) * 8 * SAMPLE_RATE * c->channels / 1000; + + /* os->time_base = (AVRational) { 1, (int) SAMPLE_RATE }; */ + + /* open it */ + if (avcodec_open2(c, codec, NULL) < 0) { + fprintf(stderr, "Could not open codec\n"); + exit(1); + } + + /* /\* set stream data *\/ + * os->codecpar->sample_rate = SAMPLE_RATE; + * os->codecpar->format = c->sample_fmt; + * os->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + * os->codecpar->codec_id = of->audio_codec; + * os->codecpar->bit_rate = c->bit_rate; + * os->codecpar->channels = c->channels; + * os->codecpar->channel_layout = c->channel_layout; + * os->codecpar->frame_size = c->frame_size; + * + * if (avformat_write_header(oc, &opt) < 0) { + * fprintf(stderr, "cannot write header\n"); + * exit(1); + * } + * + * av_dump_format(oc, 0, DEFAULT_OUTPUT_NAME, 1); */ + + /* return 0; */ + + /* audio_st.st->time_base = (AVRational){ 1, c->sample_rate }; + * + * if (oc->oformat->flags & AVFMT_GLOBALHEADER) + * c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; */ + + /* av_dump_format(oc, 0, DEFAULT_OUTPUT_NAME, 1); */ + + /* ret = avio_open(&oc->pb, DEFAULT_OUTPUT_NAME, AVIO_FLAG_WRITE); + * if (ret < 0) { + * fprintf(stderr, "Could not open '%s': %s\n", DEFAULT_OUTPUT_NAME, + * av_err2str(ret)); + * return 1; + * } + * + * ret = avformat_write_header(oc, &opt); + * if (ret < 0) { + * fprintf(stderr, "Error occurred when opening output file: %s\n", + * av_err2str(ret)); + * return 1; + * } */ + + printf("sample rate = %d, channels = %d, bit rate = %lu\n" + "frame size = %d\n", + c->sample_rate, c->channels, (long) c->bit_rate, + c->frame_size); + /* return 0; */ + + FILE *file = fopen(DEFAULT_OUTPUT_NAME, "wb"); + if (!file) { + fprintf(stderr, "Could not open %s\n", DEFAULT_OUTPUT_NAME); + exit(1); + } + + /* packet for holding encoded output */ + pkt = av_packet_alloc(); + if (!pkt) { + fprintf(stderr, "could not allocate the packet\n"); + exit(1); + } + + /* pkt->stream_index = os->index; */ + + /* frame containing input raw audio */ + frame = av_frame_alloc(); + if (!frame) { + fprintf(stderr, "Could not allocate audio frame\n"); + exit(1); + } + + frame->nb_samples = c->frame_size; + frame->format = c->sample_fmt; + frame->channel_layout = c->channel_layout; + + /* allocate the data buffers */ + ret = av_frame_get_buffer(frame, 0); + if (ret < 0) { + fprintf(stderr, "Could not allocate audio data buffers\n"); + fprintf(stderr, "nbs = %d, format = %s, layout = %d\n", + frame->nb_samples, av_get_sample_fmt_name(frame->format), + av_get_channel_layout_nb_channels(frame->channel_layout)); + exit(1); + } + + /* write to the frame */ + + long frame_num = floor((float) wf.n/(float) c->frame_size) + 1; + long w_counter = 0; + + for (int i = 0; i < frame_num; i++, w_counter += c->frame_size) { + ret = av_frame_make_writable(frame); + if (ret < 0) { + fprintf(stderr, "cannot make frame writable\n"); + exit(1); + } + + for (int j = 0; j < c->channels; j++) { + samples = (float *)frame->data[j]; + + for (int k = 0; k < c->frame_size && k+w_counter < wf.n; k++) + samples[k] = *(wf.w+w_counter+k); + } + + encode(c, frame, pkt, file); + } + + /* fwrite(wf.w, sizeof(float), wf.n, file); */ + + /* flush the encoder */ + encode(c, NULL, pkt, file); + + /* free(vs); */ + /* free(sts); + * free(bs); */ + free(wf.w); + free(notes_content); + fclose(file); + + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&c); + /* av_dict_free(&opt); */ + + return 0; +} diff --git a/main.d b/main.d new file mode 100644 index 0000000..51047cb --- /dev/null +++ b/main.d @@ -0,0 +1,29 @@ +main.o main.d : main.c /usr/local/include/libavcodec/avcodec.h \ + /usr/local/include/libavutil/samplefmt.h \ + /usr/local/include/libavutil/avutil.h \ + /usr/local/include/libavutil/common.h \ + /usr/local/include/libavutil/attributes.h \ + /usr/local/include/libavutil/macros.h \ + /usr/local/include/libavutil/version.h \ + /usr/local/include/libavutil/avconfig.h \ + /usr/local/include/libavutil/mem.h \ + /usr/local/include/libavutil/error.h \ + /usr/local/include/libavutil/rational.h \ + /usr/local/include/libavutil/mathematics.h \ + /usr/local/include/libavutil/intfloat.h \ + /usr/local/include/libavutil/log.h \ + /usr/local/include/libavutil/pixfmt.h \ + /usr/local/include/libavutil/buffer.h \ + /usr/local/include/libavutil/cpu.h \ + /usr/local/include/libavutil/channel_layout.h \ + /usr/local/include/libavutil/dict.h \ + /usr/local/include/libavutil/frame.h \ + /usr/local/include/libavutil/hwcontext.h \ + /usr/local/include/libavcodec/bsf.h \ + /usr/local/include/libavcodec/codec_id.h \ + /usr/local/include/libavcodec/codec_par.h \ + /usr/local/include/libavcodec/packet.h \ + /usr/local/include/libavcodec/version.h \ + /usr/local/include/libavcodec/codec.h \ + /usr/local/include/libavcodec/codec_desc.h util.h instrument.h \ + parser.h diff --git a/makefile b/makefile new file mode 100644 index 0000000..fd11a83 --- /dev/null +++ b/makefile @@ -0,0 +1,96 @@ +CC=gcc +CPPFLAGS= -Wall -c # -I +LIBS= -Wall -O2 -L /usr/local/lib -lavutil -lavcodec -lavformat # -L /Users/durand/Desktop/Centre/A\ propos\ de\ programmes/C/player/lib -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -L /usr/local/lib -lavutil -lswscale -lavcodec +out=output.mp3 +SOURCES=main.c instrument.c util.c parser.c mix.c + +all: main TAGS + +%.d: %.c + @echo "Remaking makefiles..." + @set -e; rm -f $@; \ + $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ + @echo "Done" + +include $(SOURCES:.c=.d) + +TAGS: *.c *.h + etags $+ + +parser.o : parser.c parser.h util.h instrument.h mix.h + $(CC) $(CPPFLAGS) $< -o $@ + +main: $(SOURCES:.c=.o) + $(CC) $(LIBS) -o $@ $+ + +.PHONY: clean all play clear cat tur coffin + +clean: + @echo "Cleaning..." + -@rm -rf *.o main TAGS *.dYSM 2>/dev/null || true + @echo "Done." + +$(out): main + ./main + +# play: $(out) +# ffplay -f f32le -ar 44100 -showmode 1 -autoexit output.mkv + +play: $(out) + mpv $< + +tur: main + @echo "making turkish march..." + -@rm notes.txt + @ln -s turkish/turkish\ notes.txt notes.txt + @./main + @echo "Done." + +marble: main + @echo "making marble machine..." + -@rm notes.txt + @ln -s marble\ machine/marble\ machine\ notes.txt notes.txt + @./main + @echo "Done." + +cat: main + @echo "making cat vibing..." + -@rm notes.txt + @ln -s cat/part\ of\ cat\ vibing.txt notes.txt + @./main + @echo "Done." + +coffin: main + @echo "making coffin dance..." + -@rm notes.txt + @ln -s coffin/part\ of\ coffin\ dance.txt notes.txt + @./main + @echo "Done." + +elise: main + @echo "making Für Elise..." + -@rm notes.txt + @ln -s "Für Elise/Für Elise notes.txt" notes.txt + @./main + @echo "Done." + +single: main + @echo "making Single Notes..." + -@rm notes.txt + @ln -s "test.txt" notes.txt + @./main + @echo "Done." + + +clear: + @echo "Deleting $(out)..." + -@rm $(out) + @echo "Done." + +test-parser.o: parser.c parser.h util.h + $(CC) -Wall -c -DTEST -o $@ $< + +test: util.o test-parser.o instrument.o + $(CC) -Wall -O2 -o $@ $+ diff --git a/marble machine/marble machine notes.txt b/marble machine/marble machine notes.txt new file mode 100644 index 0000000..a56415f --- /dev/null +++ b/marble machine/marble machine notes.txt @@ -0,0 +1,5 @@ +, +7 1.5 , -17 +2 0.5 , (-14 -10 1) +2 1.5 , s +0 0.5 , (-14 -10 1) diff --git a/mix.c b/mix.c new file mode 100644 index 0000000..ae1c1ab --- /dev/null +++ b/mix.c @@ -0,0 +1,22 @@ +#include "mix.h" +#include +#include + +/* Mix different sounds together. For now this is just a simple + averaging function. But more sophostigated mixing techniques such + as filters and reverbs might get includede in the future. */ + +U_ATTR +Pulse +mix_sound(Wave w, LENT n) +{ + Pulse result = 0.0f;; + + for (LENT i = 0; i < n;) + result += *(w+i++); + + result /= (float) n; + + return result; +} + diff --git a/mix.d b/mix.d new file mode 100644 index 0000000..7f9f2f4 --- /dev/null +++ b/mix.d @@ -0,0 +1 @@ +mix.o mix.d : mix.c mix.h util.h diff --git a/mix.h b/mix.h new file mode 100644 index 0000000..9f11f95 --- /dev/null +++ b/mix.h @@ -0,0 +1,7 @@ +#ifndef MIX_H +#define MIX_H +#include "util.h" + +U_ATTR Pulse mix_sound(Wave w, LENT n); + +#endif diff --git a/notes.txt b/notes.txt new file mode 120000 index 0000000..0c7a840 --- /dev/null +++ b/notes.txt @@ -0,0 +1 @@ +Für Elise/Für Elise notes.txt \ No newline at end of file diff --git a/parser.c b/parser.c new file mode 100644 index 0000000..8d34121 --- /dev/null +++ b/parser.c @@ -0,0 +1,694 @@ +#include "parser.h" +#include "mix.h" +#include +#include +#include +#include + +/* NONE_TYPE is for empty notes. */ +/* Since different channels have different time specifications, an + empty line can only be parsed as an empty note. So we have to + explicitly mark a silent note. */ + +/* A note of type BEAT_DURATION_TYPE stores the new bpm in the secs + field. */ + +typedef enum { + NONE_TYPE, + NOTE_TYPE, + SILENT_TYPE, + INSTRUMENT_TYPE, + BEAT_DURATION_TYPE +} PType; + +struct PNote_s { + Semitones *tones; + Beats secs; + LENT len; + PType type; + + /* The instrument type, if it is an instrument changing note. */ + InstrumentTypes *name; +}; + +struct PUnit_s { + PNote *notes; /* the notes of channels */ + LENT len; /* the length of channels */ +}; + +/* Helper macro */ + +#define EMPTY_NOTE ((PNote) { NULL, 0.0f, 0, NONE_TYPE, NULL }) +#define EMPTY_UNIT ((PUnit) { NULL, 0 }) + +/* Helper function */ + +H_ATTR +unsigned char +is_empty_note(PNote *n) +{ + return n->type == NONE_TYPE; +} + +/* Destructor */ + +U_ATTR +void +destroy_sheet(PSheet sh) +{ + for (LENT i = 0; i < sh.len; i++) { + for (LENT j = 0; j < (sh.data+i)->len; j++) { + if (((sh.data+i)->notes+j)->tones) + free(((sh.data+i)->notes+j)->tones); + if (((sh.data+i)->notes+j)->name) + free(((sh.data+i)->notes+j)->name); + } + + free((sh.data+i)->notes); + } + + free(sh.data); +} + +/* For debugging */ + +U_ATTR +static void +print_note(const PNote note) +{ + switch (note.type) { + case NONE_TYPE: + fprintf(stderr, "an empty note\n"); + break; + case SILENT_TYPE: + fprintf(stderr, "a silent note with %.3f beats\n", + note.secs); + break; + case NOTE_TYPE: + fprintf(stderr, "a note with %.3f beats and %lu semitone%s\n", + note.secs, note.len, (note.len < 2) ? "" : "s"); + fprintf(stderr, "The semitone%s: ", + (note.len<2) ? " is" : "s are"); + for (LENT i = 0; i < note.len; i++) + fprintf(stderr, "%.2f%s", + *(note.tones+i), + (i==note.len-1) ? "" : ", "); + fprintf(stderr, "\n"); + break; + case INSTRUMENT_TYPE: + fprintf(stderr, "an instrument changing note\n"); + break; + case BEAT_DURATION_TYPE: + fprintf(stderr, "a note of the type that changes the bpm to " + "%.3f beats per minute.\n", + note.secs); + break; + } +} + +U_ATTR +static void +print_unit(const PUnit u) +{ + fprintf(stderr, "Printing a unit with %lu channel%s\n", + u.len, (u.len>1) ? "s" : ""); + for (LENT i = 0; i < u.len; i++) { +#ifdef DEBUG + fprintf(stderr, "The %lu-th note: ", i); + fprintf(stderr, "%p\n", (u.notes+i)); +#endif + print_note(*(u.notes+i)); + fprintf(stderr, "\n"); + } +} + +U_ATTR +void +print_sheet(const PSheet sh) +{ + for (LENT i = 0; i < sh.len;) print_unit(*(sh.data+i++)); +} + +/* The main read function. + + Warning: A valid sheet file should have a "channel specification" + in the first line. A "channel specification" line is a line that + has no numbers, but just semi-colons. If there are N channels, + then there should be N-1 semi-colons in the specification line. + What if there are no channels? Why read a sheet without + channels? + + If there are no specification lines in the beginning, then one + channel format will be assumed. But this is prone to errors, and + not recommended. */ + +U_ATTR +PSheet +read_sheet(const char *str, LENT len) +{ + PSheet result = { NULL, 0 }; + + Node_t stack = EMPTY_NODE, stack_next = EMPTY_NODE; + Node_t *stack_pointer = NULL; + + LENT counter = 0, increment = 0, line_count = 0; + + LENT stack_len = 0; + + char current = 0; + + float local = 0.0f; + + int channel_num = 0, num_chars_read = 0; + int num_channels = 0; + + unsigned char first_line = 1, note_used = 0; + + PUnit *unit_pointer = NULL; + PUnit current_unit = EMPTY_UNIT; + + PUnit *unit_stack = NULL; + int unit_stack_len = 0; + + /* A note cannot consist of more than 5 semitones. One more + semitone will be allocated so that it can store the time + information. */ + Semitones *st_stack = NULL; + int st_stack_len = 0; + + InstrumentTypes *ins_pointer = NULL; + + PNote temp_note = EMPTY_NOTE, *notes_pointer = NULL; + + Semitones *st_pointer = NULL; + + /* A note without time information will use the previous beats + information. A first note without time information will use 1.0f + as the beats. */ + + Beats *beats_channel_array = NULL; + + for (;counter < len;) { + current = *(str+counter); + + increment = 1; + + switch (current) { + /* 13 is also a newline character, it seems. */ + case 13: + case '\n': + if (!note_used) + free(current_unit.notes); + + if (first_line) { + num_channels = channel_num+1; + current_unit.len = num_channels; + beats_channel_array = MYALLOC(Beats, num_channels); + + for (int i = 0; i < num_channels; i++) { + *(beats_channel_array+i) = 1.0f; + } + + } else if (note_used) { + unit_pointer = MYALLOC(PUnit, 1); + *unit_pointer = current_unit; + PUSH_NODE(stack_len, stack_pointer, stack, + unit_pointer, v); + /* Since it is used, we set its pointer to NULL to prevent + accidental frees. */ + current_unit.notes = NULL; + } + + notes_pointer = MYALLOC(PNote, num_channels); + + for (int i = 0; i < num_channels;) + *(notes_pointer+i++) = EMPTY_NOTE; + + current_unit.notes = notes_pointer; + + channel_num = 0; + first_line = 0; + line_count++; + note_used = 0; + break; + case '#': + /* Comments */ + for (;*(str+counter) != '\n' && + *(str+counter) != 13;) counter++; + increment = 2; + counter -= 2; + break; + case ',': + note_used = 1; + channel_num++; + break; + default: + /* Limited support for a sheet without channel specifications. */ + + if (first_line) { + num_channels = (channel_num=0)+1; + current_unit.len = num_channels; + + notes_pointer = MYALLOC(PNote, num_channels); + beats_channel_array = MYALLOC(Beats, num_channels); + + for (int i = 0; i < num_channels; i++) { + *(notes_pointer+i) = EMPTY_NOTE; + *(beats_channel_array+i) = 1.0f; + } + + current_unit.notes = notes_pointer; + + first_line = 0; + } + + note_used = 1; + /* fprintf(stderr, "here the char is %c\n", *(str+counter)); */ + if (*(str+counter) == ' ') { + /* Just skip spaces so that they don't block us reading anchor + chars. */ + } else if (*(str+counter) == 'b') { + /* changing bpm note */ + + counter++; + + for (;*(str+counter) == ' ';) counter++; + + if (*(str+counter) != '\n' && + *(str+counter) != 13 && + sscanf(str+counter, "%f%n", + &local, &num_chars_read) == 1) { + temp_note = (PNote) { + NULL, local, + 0, BEAT_DURATION_TYPE, NULL + }; + + /* fprintf(stderr, "print a note\n"); + * print_note(temp_note); */ + + counter += num_chars_read; + } + *(current_unit.notes+channel_num) = temp_note; + } else if (*(str+counter) == 's') { + /* a silent note */ + counter++; + + /* skip all spaces that follow */ + for (;*(str+counter) == ' ';) counter++; + + if (*(str+counter) != '\n' && + *(str+counter) != 13 && + sscanf(str+counter, "%f%n", + &local, &num_chars_read) == 1) { + *(beats_channel_array+channel_num) = local; + temp_note = (PNote) { + NULL, local, + 1, SILENT_TYPE, NULL + }; + + increment = num_chars_read+1; + } else { + /* a note without time; use the previous beat instead. */ + temp_note = (PNote) { + NULL, *(beats_channel_array+channel_num), + 1, SILENT_TYPE, NULL + }; + } + +#ifdef DEBUG + fprintf(stderr, "print a note while reading\n"); + print_note(temp_note); + fprintf(stderr, "\n\n\n\n"); +#endif + + *(current_unit.notes+channel_num) = temp_note; + temp_note = EMPTY_NOTE; + /* subtract one from the increment so that we don't + unknowingly skip one character. */ + increment--; + } else if (sscanf(str+counter, "%f%n", + &local, &num_chars_read) == 1) { + /* a note consisting of one single semitone */ + st_pointer = MYALLOC(Semitones, 1); + *st_pointer = local; + counter += num_chars_read; + + /* skip all spaces that follow */ + for (;*(str+counter) == ' ';) counter++; + + + if (*(str+counter) != '\n' && + *(str+counter) != 13 && + sscanf(str+counter, "%f%n", + &local, &num_chars_read) == 1) { + *(beats_channel_array+channel_num) = local; + temp_note = (PNote) { + st_pointer, local, + 1, NOTE_TYPE, NULL + }; + + increment = num_chars_read+1; + } else { + /* a note without time; use the previous beat instead. */ + temp_note = (PNote) { + st_pointer, *(beats_channel_array+channel_num), + 1, NOTE_TYPE, NULL + }; + } + +#ifdef DEBUG + fprintf(stderr, "print a note while reading\n"); + print_note(temp_note); + fprintf(stderr, "\n\n\n\n"); +#endif + + *(current_unit.notes+channel_num) = temp_note; + temp_note = EMPTY_NOTE; + /* subtract one from the increment so that we don't + unknowingly skip one character. */ + increment--; + } else if (*(str+counter) == '(') { + /* a note consisting of multiple semitones */ + st_stack = MYALLOC(Semitones, 10); + st_stack_len = 0; + counter++; + + for (;sscanf(str+counter, "%f %n", &local, &num_chars_read) == 1; + counter += num_chars_read) { + *(st_stack+st_stack_len++) = local; + } + + /* Now skip to the closing parenthesis */ + for (;counter < len && *(str+counter) != ')';) counter++; + + if (st_stack_len>1) { + st_pointer = MYALLOC(Semitones, st_stack_len-1); + + for (int i = 0; i < st_stack_len-1; i++) + *(st_pointer+i) = *(st_stack+i); + + *(beats_channel_array+channel_num) = + *(st_stack+st_stack_len-1); + + temp_note = (PNote) { + st_pointer, *(st_stack+st_stack_len-1), + st_stack_len-1, NOTE_TYPE, NULL + }; + } else if (!st_stack_len) { + /* empty note */ + temp_note = EMPTY_NOTE; + } else { + /* one semitone note */ + st_pointer = MYALLOC(Semitones, 1); + *st_pointer = *st_stack; + + temp_note = (PNote) { + st_pointer, *(beats_channel_array+channel_num), + 1, NOTE_TYPE, NULL + }; + } + + free(st_stack); + + *(current_unit.notes+channel_num) = temp_note; + temp_note = EMPTY_NOTE; + } else if (*(str+counter) == 'i') { + /* an instrument changing unit */ + /* Just piano for now */ + ins_pointer = MYALLOC(InstrumentTypes, 1); + *ins_pointer = PIANO; + *(current_unit.notes+channel_num) = (PNote) { + NULL, 0, 0, INSTRUMENT_TYPE, ins_pointer + }; + } else { + /* invalid input */ + fprintf(stderr, "Invalid input in the sheet.\n" + "channel = %d, line = %lu\n", + channel_num, line_count); + } + + break; + } + + counter += increment; + } + + free(current_unit.notes); + free(beats_channel_array); + + /* Then fill the variables up with the data we have read. */ + + LENT total_len = stack_len; + + unit_stack = MYALLOC(PUnit, total_len); + + for (LENT i = 0; i < total_len; i++) { + POP_NODE(stack_len, unit_pointer, stack, stack_next, v); + *(unit_stack+total_len-1-(unit_stack_len++)) = + *unit_pointer; + free(unit_pointer); + } + + result.data = unit_stack; + result.len = total_len; + + return result; +} + +/* Get the number of note_type notes in a sheet. + + Also calculate the number of notes per channel and store in + nums_per_channel, which is assumed to be already allocated. */ + +U_ATTR +static LENT +sheet_note_num(const PSheet sh, LENT *nums_per_channel) +{ + LENT note_num = 0; + + for (LENT i = 0; i < (sh.data)->len;) + *(nums_per_channel+i++) = 0; + + for (LENT i = 0; i < sh.len; i++) { + PUnit pu = *(sh.data+i); + + for (LENT j = 0; j < pu.len; j++) + if ((pu.notes+j)->type == NOTE_TYPE || + (pu.notes+j)->type == SILENT_TYPE) { + note_num++; + *(nums_per_channel+j) += 1; + } + } + + return note_num; +} + +H_ATTR +static WaveFrag +silent_note(Beats b, float dur) +{ + WaveFrag w; + + LENT sample_num = (LENT) floor(SAMPLE_RATE * b * dur); + + w.w = MYALLOC(Pulse, sample_num); + w.n = sample_num; + + for (LENT i = 0; i < sample_num; i++) + *(w.w+i) = 0.0f; + + return w; +} + +U_ATTR +WaveFrag +play_sheet(const PSheet sh, const Volume v) +{ + WaveFrag wf = (WaveFrag) { NULL, 0 }; + + /* If there is not unit or if the channel number is zero then we + return an empty wave fragment. */ + + if (!(sh.len) || !(sh.data->len)) return wf; + + Instrument *piano = make_piano(); + Instrument *violin = make_violin(); + Instrument *inst = piano; + + /* For mixing sounds in notes */ + WaveFrag *war = NULL; + WaveFrag **temp = NULL; + LENT *channel_num_array = NULL; + /* For mixing semitones */ + WaveFrag *st_temp = MYALLOC(WaveFrag, 10); + + LENT total_len = 0, channel_num = sh.data->len; + + LENT *num_notes_per_channel = MYALLOC(LENT, channel_num); + + Beats dur = BEAT_DUR; + +#ifdef DEBUG + fprintf(stderr, "before finding num\n"); +#endif + + total_len = sheet_note_num(sh, num_notes_per_channel); + + /* for (LENT i = 0; i < sh.len;) + * total_len += (sh.data+i++)->len; */ + + /* Allocate spaces */ + + temp = MYALLOC(WaveFrag*, channel_num); + channel_num_array = MYALLOC(LENT, channel_num); + + for (LENT i = 0; i < channel_num; i++) { + +#ifdef DEBUG + fprintf(stderr, "num_notes_per_channel + %lu = %lu\n", + i, *(num_notes_per_channel+i)); +#endif + + *(temp+i) = MYALLOC(WaveFrag, *(num_notes_per_channel+i)); + } + + war = MYALLOC(WaveFrag, channel_num); + + for (LENT i = 0; i < channel_num;) *(channel_num_array+i++) = 0; + +#ifdef DEBUG + fprintf(stderr, "before for loop\n"); +#endif + + for (LENT i = 0; i < sh.len; i++) { + PUnit pu = *(sh.data+i); + + if (pu.len != channel_num) { + fprintf(stderr, "Every unit should have the same number of channels.\n" + "But unit %lu has %lu channels\n", + i, pu.len); + exit(1); + } + + for (LENT j = 0; j < channel_num; j++) { + PNote pn = *(pu.notes+j); + + if (pn.type != NOTE_TYPE && + pn.type != SILENT_TYPE && + pn.type != BEAT_DURATION_TYPE) continue; + + if (pn.len >= 10) { + fprintf(stderr, "A note cannot have more than 10 semitones together.\n"); + fprintf(stderr, "node %lu and number = %lu\n", j, pn.len); + exit(1); + } + +#ifdef DEBUG + fprintf(stderr, "i = %lu, j = %lu\n", i, j); +#endif + + switch (pn.type) { + case NOTE_TYPE: + for (LENT k = 0; k < pn.len; k++) { + *(st_temp+k) = make_sound(inst, v * ((k==1) ? 0.3f : 1.0f), + stoh(*(pn.tones+k)), pn.secs * dur); + } + break; + case BEAT_DURATION_TYPE: + dur = 60.0f / pn.secs; + break; + default: + /* SILENT_TYPE */ + *(st_temp) = silent_note(pn.secs, dur); + break; + } + +#ifdef DEBUG + fprintf(stderr, "after making sound\n"); + if (j==1) { + print_note(pn); + fprintf(stderr, "channel num + 1 = %lu\n", *(channel_num_array+1)); + } +#endif + + *(*(temp+j)+*(channel_num_array+j)) = + mix_waves(st_temp, pn.len); + +#ifdef DEBUG + fprintf(stderr, "after mixing\n"); +#endif + + *(channel_num_array+j) = *(channel_num_array+j) + 1; + /* *(temp + i*channel_num + j) = mix_waves(st_temp, pn.len); */ + + for (int k = 0; k < pn.len; k++) + free((st_temp+k)->w); + } + } + +#ifdef DEBUG + fprintf(stderr, "after for loop\n"); +#endif + + free(st_temp); + + /* Now all the waves are produced. We just need to merge and mix + them. */ + + /* Note: merge_waves free the waves in the input. */ + + for (LENT j = 0; j < channel_num; j++) + *(war+j) = merge_waves(inst, *(temp+j), + *(num_notes_per_channel+j), + 1); + free(temp); + free(num_notes_per_channel); + free(channel_num_array); + +#ifdef DEBUG + fprintf(stderr, "after merging\n"); +#endif + + wf = mix_waves(war, channel_num); + + for (LENT i = 0; i < channel_num;) free((war+i++)->w); + + free(war); + destroy_piano(piano); + destroy_violin(violin); + +#ifdef DEBUG + fprintf(stderr, "after mixing\n"); +#endif + + return wf; +} + +/* An embedded test function */ + +#ifdef TEST +int +main(int argc, char **argv) +{ + char *file_content = MYALLOC(char, 512); + + LENT len = read_entire_file("test.txt", &file_content); + + PSheet sh = read_sheet(file_content, len); + + print_sheet(sh); + + WaveFrag wf = play_sheet(sh, 0.3f); + + destroy_sheet(sh); + + printf("w.n = %lu\n", wf.n); + printf("some sound is %f\n", *(wf.w+10)); + + free(wf.w); + + free(file_content); + + return 0; +} +#endif diff --git a/parser.d b/parser.d new file mode 100644 index 0000000..0d896e2 --- /dev/null +++ b/parser.d @@ -0,0 +1 @@ +parser.o parser.d : parser.c parser.h util.h instrument.h mix.h diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..3454bd4 --- /dev/null +++ b/parser.h @@ -0,0 +1,48 @@ +#ifndef PARSER_H +#define PARSER_H +#include "util.h" +#include "instrument.h" + +/* Parse a complex sheet file so that it is easy to generate complex + sounds. */ + +/* A unit for the parser. This typically represents one note, but can + serve different purposes. In our format, a unit is given in one + line. */ + +struct PUnit_s; + +typedef struct PUnit_s PUnit; + +struct PNote_s; + +typedef struct PNote_s PNote; + +/* A sheet is just an array of units. But we need to know the length + of the array as well. */ + +typedef struct { + PUnit *data; + LENT len; +} PSheet; + +U_ATTR void print_sheet(const PSheet sh); + +/* Might be helpful, or not */ + +H_ATTR unsigned char is_empty_note(PNote *n); + +/* The function to read the sheet. */ + +U_ATTR PSheet read_sheet(const char *str, LENT len); + +/* Destructor */ + +U_ATTR void destroy_sheet(PSheet sh); + +/* Now play sheet */ + +U_ATTR WaveFrag play_sheet(const PSheet sh, const Volume v); + +#endif + diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..cd1b6af --- /dev/null +++ b/test.txt @@ -0,0 +1,3 @@ +3 2 +4 +5 diff --git a/turkish/turkish notes (archive).txt b/turkish/turkish notes (archive).txt new file mode 100644 index 0000000..539482e --- /dev/null +++ b/turkish/turkish notes (archive).txt @@ -0,0 +1,236 @@ +2 0.25 +0 0.25 +-1 0.25 +0 0.25 +3 0.5 +-100 0.5 +5 0.25 +3 0.25 +2 0.25 +3 0.25 +7 0.5 +-100 0.5 +8 0.25 +7 0.25 +6 0.25 +7 0.25 +14 0.25 +12 0.25 +11 0.25 +12 0.25 +14 0.25 +12 0.25 +10 0.25 +12 0.25 +15 1.0 +12 0.5 +15 0.5 +14 0.5 +12 0.5 +10 0.5 +12 0.5 +14 0.5 +12 0.5 +10 0.5 +12 0.5 +14 0.5 +12 0.5 +10 0.5 +12 0.5 +7 1 +2 0.25 +0 0.25 +-1 0.25 +0 0.25 +3 0.5 +-100 0.5 +5 0.25 +3 0.25 +2 0.25 +3 0.25 +7 0.5 +-100 0.5 +8 0.25 +7 0.25 +6 0.25 +7 0.25 +14 0.25 +12 0.25 +11 0.25 +12 0.25 +14 0.25 +12 0.25 +10 0.25 +12 0.25 +15 1.0 +12 0.5 +15 0.5 +14 0.5 +12 0.5 +10 0.5 +12 0.5 +14 0.5 +12 0.5 +10 0.5 +12 0.5 +14 0.5 +12 0.5 +10 0.5 +12 0.5 +7 1 +7 0.5 +8 0.5 +10 0.5 +10 0.5 +12 0.25 +10 0.25 +8 0.25 +7 0.25 +5 0.5 +5 0.5 +7 0.5 +8 0.5 +10 0.5 +10 0.5 +12 0.25 +10 0.25 +8 0.25 +7 0.25 +5 1 +3 0.5 +5 0.5 +7 0.5 +7 0.5 +8 0.25 +7 0.25 +5 0.25 +3 0.25 +2 0.5 +2 0.5 +3 0.5 +5 0.5 +7 0.5 +7 0.5 +8 0.25 +7 0.25 +5 0.25 +3 0.25 +2 1 +2 0.25 +0 0.25 +-2 0.25 +0 0.25 +3 0.5 +-100 0.5 +5 0.25 +3 0.25 +2 0.25 +3 0.25 +7 0.5 +-100 0.5 +8 0.25 +7 0.25 +6 0.25 +7 0.25 +14 0.25 +12 0.25 +11 0.25 +12 0.25 +14 0.25 +12 0.25 +10 0.25 +12 0.25 +15 1 +12 0.5 +14 0.5 +15 0.5 +14 0.5 +12 0.5 +11 0.5 +12 0.5 +7 0.5 +8 0.5 +5 0.5 +3 1 +2 0.75 +0 0.125 +2 0.125 +0 1 +7 0.5 +8 0.5 +10 0.5 +10 0.5 +12 0.25 +10 0.25 +8 0.25 +7 0.25 +5 0.5 +5 0.5 +7 0.5 +8 0.5 +10 0.5 +10 0.5 +12 0.25 +10 0.25 +8 0.25 +7 0.25 +5 1 +3 0.5 +5 0.5 +7 0.5 +7 0.5 +8 0.25 +7 0.25 +5 0.25 +3 0.25 +2 0.5 +2 0.5 +3 0.5 +5 0.5 +7 0.5 +7 0.5 +8 0.25 +7 0.25 +5 0.25 +3 0.25 +2 1 +2 0.25 +0 0.25 +-2 0.25 +0 0.25 +3 0.5 +-100 0.5 +5 0.25 +3 0.25 +2 0.25 +3 0.25 +7 0.5 +-100 0.5 +8 0.25 +7 0.25 +6 0.25 +7 0.25 +14 0.25 +12 0.25 +11 0.25 +12 0.25 +14 0.25 +12 0.25 +10 0.25 +12 0.25 +15 1 +12 0.5 +14 0.5 +15 0.5 +14 0.5 +12 0.5 +11 0.5 +12 0.5 +7 0.5 +8 0.5 +5 0.5 +3 1 +2 0.75 +0 0.125 +2 0.125 +0 1 diff --git a/turkish/turkish notes.txt b/turkish/turkish notes.txt new file mode 100644 index 0000000..548ec48 --- /dev/null +++ b/turkish/turkish notes.txt @@ -0,0 +1,127 @@ +, +2 0.25 , s +0 0.25 , -12 0.5 +-1 0.25 , (-9 -5 0.5) +0 0.25 , (-9 -5 0.5) +3 0.5 , (-9 -5 0.5) +-100 0.5 , -12 0.5 +5 0.25 , (-9 -5 0.5) +3 0.25 , (-9 -5 0.5) +2 0.25 , (-9 -5 0.5) +3 0.25 , -12 0.5 +7 0.5 , (-9 -5 0.5) +-100 0.5 , -12 0.5 +8 0.25 , (-9 -5 0.5) +7 0.25 , -12 0.5 +6 0.25 , (-9 -5 0.5) +7 0.25 , (-9 -5 0.5) +14 0.25 , (-9 -5 0.5) +12 0.25 , -17 0.5 +11 0.25 , (-5 -10 0.5) +12 0.25 , (-5 -10 0.5) +14 0.25 , (-5 -10 0.5) +12 0.25 , -17 0.5 +10 0.25 , (-5 -10 0.5) +12 0.25 , (-5 -10 0.5) +15 , (-5 -10 0.5) +12 0.5 , -17 0.5 +15 0.5 , (-5 -10 0.5) +10 0.01 , -10 0.5 +12 0.01 , -10 0.5 +14 0.48 , -17 +(12 9 0.5) , +(7 10 0.5) , +(8 12 0.5) , +10 0.01 , +12 0.01 , +14 0.48 , +(12 9 0.5) , +(7 10 0.5) , +(8 12 0.5) , +10 0.01 , +12 0.01 , +14 0.48 , +(12 9 0.5) , +(7 10 0.5) , +(8 12 0.5) , +7 , +2 0.25 , s +0 0.25 , -12 0.5 +-1 0.25 , (-9 -5 0.5) +0 0.25 , (-9 -5 0.5) +3 0.5 , (-9 -5 0.5) +-100 0.5 , -12 0.5 +5 0.25 , (-9 -5 0.5) +3 0.25 , (-9 -5 0.5) +2 0.25 , (-9 -5 0.5) +3 0.25 , -12 0.5 +7 0.5 , (-9 -5 0.5) +-100 0.5 , -12 0.5 +8 0.25 , (-9 -5 0.5) +7 0.25 , -12 0.5 +6 0.25 , (-9 -5 0.5) +7 0.25 , (-9 -5 0.5) +14 0.25 , (-9 -5 0.5) +12 0.25 , -17 0.5 +11 0.25 , (-5 -10 0.5) +12 0.25 , (-5 -10 0.5) +14 0.25 , (-5 -10 0.5) +12 0.25 , -17 0.5 +10 0.25 , (-5 -10 0.5) +12 0.25 , (-5 -10 0.5) +15 , (-5 -10 0.5) +12 0.5 , -17 0.5 +15 0.5 , (-5 -10 0.5) +10 0.01 , -10 0.5 +12 0.01 , -10 0.5 +14 0.48 , -17 +(12 9 0.5) , +(7 10 0.5) , +(8 12 0.5) , +10 0.01 , +12 0.01 , +14 0.48 , +(12 9 0.5) , +(7 10 0.5) , +(8 12 0.5) , +10 0.01 , +12 0.01 , +14 0.48 , +(12 9 0.5) , +(7 10 0.5) , +(8 12 0.5) , +7 , +(3 7 0.5) , s +(5 8 0.5) , -21 0.5 +(7 10 0.5) , -9 0.5 +(7 10 0.5) , -17 0.5 +12 0.25 , -5 0.5 +10 0.25 , -14 +8 0.25 , s +7 0.25 , +(2 5 0.5) , +(-2 5 0.5) , +(3 7 0.5) , +(5 8 0.5) , +# third line over +(7 10 0.5) , -21 0.5 +(7 10 0.5) , -9 0.5 +12 0.25 , -17 0.5 +10 0.25 , -5 0.5 +8 0.25 , -14 +7 0.25 , s +(2 5 0.5) , -24 0.5 +s 0.5 , -12 0.5 +(0 3 0.5) , -21 0.5 +(2 5 0.5) , -9 0.5 +(3 7 0.5) , -17 +(3 7 0.5) , s +8 0.25 , +7 0.25 , +5 0.25 , +3 0.25 , +(-1 2 0.5) , +(-5 2 0.5) , +(0 3 0.5) , +(2 5 0.5) , +# fourth line over diff --git a/util.c b/util.c new file mode 100644 index 0000000..c5921e5 --- /dev/null +++ b/util.c @@ -0,0 +1,80 @@ +#include "util.h" +#include +#include +#include + +UNUSED +long +read_entire_file (const char *file_name, char **str) +{ + long file_size = 0; + + FILE *file = fopen(file_name, "r"); + + if (!file) { + fprintf(stderr, "Cannot open file \"%s\": ", file_name); + perror(NULL); + return 0; + } + + fseek(file, 0, SEEK_END); + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + *str = (char*)realloc(*str, file_size+1); + *((*str)+file_size) = 0; + + fread(*str, sizeof(char), file_size, file); + + fclose(file); + return file_size; +} + +UC_ATTR +LENT +max(const LENT a, const LENT b) +{ + return (a < b) ? b : a; +} + +U_ATTR +LENT +min(LENT a, LENT b) +{ + return (a < b) ? a : b; +} + +U_ATTR +WaveFrag +mix_waves(WaveFrag *frags, LENT len) +{ + WaveFrag wf = (WaveFrag) { NULL, 0 }; + + LENT max_len = 0; + + Pulse p = 0.0f; + + for (LENT i = 0; i < len; i++) + max_len = max(max_len, (frags+i)->n); + + wf.n = max_len; + wf.w = MYALLOC(Pulse, max_len); + + for (LENT i = 0; i < max_len; i++) { + p = 0.0f; + + for (LENT j = 0; j < len; j++) + if (i < (frags+j)->n) p += *((frags+j)->w+i); + + *(wf.w+i) = p / (float) len; + } + + return wf; +} + +HC_ATTR +Hertz +stoh(const Semitones st) +{ + return STDP * pow(STD_BASE, st); +} diff --git a/util.d b/util.d new file mode 100644 index 0000000..329aa4d --- /dev/null +++ b/util.d @@ -0,0 +1 @@ +util.o util.d : util.c util.h diff --git a/util.h b/util.h new file mode 100644 index 0000000..700a8bf --- /dev/null +++ b/util.h @@ -0,0 +1,126 @@ +#ifndef UTIL_H +#define UTIL_H + +/* Mostly types, constants, and macros */ + +/* A simple singly linked list */ + +/* Push a node to the singly linked list. */ +#define PUSH_NODE(NUM_VAR, POINTER, CUR, VALUE, TYPE) { \ + (NUM_VAR)++; \ + if (NUM_VAR > 1) { \ + (POINTER) = MYALLOC(Node_t, 1); \ + *(POINTER) = (CUR); \ + (CUR) = (Node_t) { (Node_v) { .TYPE=(VALUE) }, (POINTER) }; \ + } else { \ + (CUR) = (Node_t) { (Node_v) { .TYPE=(VALUE) }, NULL }; \ + } \ + } + +/* Pop one node out of the singly linked list. */ +#define POP_NODE(NUM_VAR, X, CUR, NEXT, TYPE) { \ + (NUM_VAR)--; \ + X = CUR.value.TYPE; \ + if (CUR.next) { \ + NEXT = *(CUR.next); \ + free(CUR.next); \ + CUR = NEXT; \ + } \ + } + +/* An empty node. */ +#define EMPTY_NODE (Node_t) { (Node_v) { .f=0 }, NULL } + +typedef union { + float f; + void *v; /* very evil */ +} Node_v; + +typedef struct Node { + Node_v value; + struct Node *next; +} Node_t; + +/* macros */ + +#define MYALLOC(X, Y) ((X *) malloc(sizeof(X)*Y)) /* The most useful macro perhaps */ + +/* Constant macros */ + +#define SAMPLE_RATE 44100.0f +#define DEFAULT_OUTPUT_NAME "output.mp3" +#define STDP 440.0f +/* I don't want to use a global variable for this constant. */ +#define STD_BASE 1.059463094359295f +/* TODO: Set Volume, and other settings in the sheet. */ +/* These can now be changed in the sheet. */ +#define BPM 120.0f +#define BEAT_DUR (60.0f / BPM) + +/* Frequency modulation */ + +#define FM_FREQUENCY 2.0f +#define FM_AMP 0.02f + +/* For ADSR */ +#define ATTACK_P 0.01f +#define DECAY_P 0.495f +#define RELEASE_P 0.49f +#define SUSTAIN_P (1.0f-(ATTACK_P+DECAY_P+RELEASE_P)) +#define SUSTAIN_LEVEL 0.7f + +/* attribute macros */ + +#define AHC_ATTR __attribute__((__always_inline__, __hot__, __const__)) +#define AH_ATTR __attribute__((__always_inline__, __hot__)) +#define HC_ATTR __attribute__((__hot__, __const__)) +#define H_ATTR __attribute__((__hot__)) +#define UNUSED __attribute__((__unused__)) +#define U_ATTR UNUSED + +#define D_ATTR(X) __attribute__((__unused__, __deprecated__("This is deprecated.\n" \ + "Please use " X \ + " instead"))) + +#define UC_ATTR __attribute__((__unused__, __const__)) +#define UH_ATTR __attribute__((__unused__, __hot__)) +#define UHA_ATTR __attribute__((__unused__, __hot__, __always_inline__)) + +/* types */ + +typedef float Volume; +typedef float Samples; +typedef float Hertz; +typedef float Seconds; +typedef float Pulse; +typedef float Beats; +typedef float Semitones; +typedef Pulse *Wave; + +typedef unsigned long LENT; + +typedef struct { + Wave w; + LENT n; +} WaveFrag; + +/* supported instrument types */ + +typedef enum { + PIANO, + DRUMKICK +} InstrumentTypes; + +/* some helper functions */ + +U_ATTR LENT max(LENT a, LENT b); + +U_ATTR LENT min(LENT a, LENT b); + +UNUSED long read_entire_file(const char *file_name, char **str); + +U_ATTR WaveFrag mix_waves(WaveFrag *frags, LENT len); + +HC_ATTR Hertz stoh(const Semitones st); + +#endif -- cgit v1.2.3-18-g5258