diff options
author | JSDurand <mmemmew@gmail.com> | 2021-07-11 18:45:38 +0800 |
---|---|---|
committer | JSDurand <mmemmew@gmail.com> | 2021-07-11 18:45:38 +0800 |
commit | 6c358a842baaf6d211a5c8c1717d815e7813ed96 (patch) | |
tree | c78122d49e75a540a4f6054b224a90738b554606 |
First commit
Now I have a kind of piano like instrument. A violin-like instrument
is being constructed.
-rw-r--r-- | Für Elise/Für Elise notes.txt | 100 | ||||
-rw-r--r-- | cat/part of cat vibing.txt | 118 | ||||
-rw-r--r-- | coffin/part of coffin dance.txt | 87 | ||||
-rw-r--r-- | instrument.c | 390 | ||||
-rw-r--r-- | instrument.d | 1 | ||||
-rw-r--r-- | instrument.h | 33 | ||||
-rw-r--r-- | main.c | 588 | ||||
-rw-r--r-- | main.d | 29 | ||||
-rw-r--r-- | makefile | 96 | ||||
-rw-r--r-- | marble machine/marble machine notes.txt | 5 | ||||
-rw-r--r-- | mix.c | 22 | ||||
-rw-r--r-- | mix.d | 1 | ||||
-rw-r--r-- | mix.h | 7 | ||||
l--------- | notes.txt | 1 | ||||
-rw-r--r-- | parser.c | 694 | ||||
-rw-r--r-- | parser.d | 1 | ||||
-rw-r--r-- | parser.h | 48 | ||||
-rw-r--r-- | test.txt | 3 | ||||
-rw-r--r-- | turkish/turkish notes (archive).txt | 236 | ||||
-rw-r--r-- | turkish/turkish notes.txt | 127 | ||||
-rw-r--r-- | util.c | 80 | ||||
-rw-r--r-- | util.d | 1 | ||||
-rw-r--r-- | util.h | 126 |
23 files changed, 2794 insertions, 0 deletions
diff --git a/Für Elise/Für Elise notes.txt b/Für Elise/Für Elise notes.txt new file mode 100644 index 0000000..cf05d9f --- /dev/null +++ b/Für Elise/Für 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 <math.h> +#include <stdlib.h> + +/* 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 @@ -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 <stdlib.h> +#include <stdio.h> +#include <math.h> + +/* include FFMPEG stuff */ +#include <libavcodec/avcodec.h> + +#include <libavutil/channel_layout.h> +#include <libavutil/common.h> +#include <libavutil/frame.h> +#include <libavutil/samplefmt.h> + +/* Self-Dependencies */ +#include "util.h" +#include "instrument.h" +#include "parser.h" + +/* #include <libavformat/avformat.h> */ + +/* 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; +} @@ -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) @@ -0,0 +1,22 @@ +#include "mix.h" +#include <stdio.h> +#include <stdlib.h> + +/* 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; +} + @@ -0,0 +1 @@ +mix.o mix.d : mix.c mix.h util.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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +/* 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 @@ -0,0 +1,80 @@ +#include "util.h" +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +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); +} @@ -0,0 +1 @@ +util.o util.d : util.c 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 |