summaryrefslogtreecommitdiff
path: root/parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'parser.c')
-rw-r--r--parser.c694
1 files changed, 694 insertions, 0 deletions
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