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. --- parser.c | 694 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 694 insertions(+) create mode 100644 parser.c (limited to 'parser.c') 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 -- cgit v1.2.3-18-g5258