lorid

convert chordpro to pdf
git clone git://git.relim.de/lorid.git
Log | Files | Refs | README | LICENSE

commit b0e41e3f6bfe7a4033153c633a7e7f719978c806
Author: nibo <nibo@relim.de>
Date:   Sun, 26 May 2024 17:33:14 +0200

Initial commit

Diffstat:
AMakefile | 2++
Achordpro.c | 606+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Achordpro.h | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alorid.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 774 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,2 @@ +all: + $(CC) -Wall -Wextra -O2 chordpro.c lorid.c -o lorid diff --git a/chordpro.c b/chordpro.c @@ -0,0 +1,606 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include "chordpro.h" + +char *string_remove_leading_whitespace(const char *str) +{ + int i = 0; + while (str[i] == ' ' || str[i] == '\t') { + i++; + } + return strdup(&str[i]); +} + + +static const char *environment_directives[] = { + "start_of_chorus", "soc", "end_of_chorus", "eoc", "chorus", + "start_of_verse", "sov", "end_of_verse", "eov", + "start_of_bridge", "sob", "end_of_bridge", "eob", + "start_of_tab", "sot", "end_of_tab", "eot", + "start_of_grid", "sog", "end_of_grid", "eog", NULL +}; + +static const char *metadata_directives[] = { + "title", "sorttitle", "subtitle", + "artist", "composer", "lyricist", + "copyright", "album", "year", "key", + "time", "tempo", "duration", "capo", + "meta", NULL +}; + +static const char *formatting_directives[] = { + "comment", "c", "highlight", "comment_italic", "ci", + "comment_box", "cb", "image", NULL +}; + +static const char *preamble_directives[] = { + "new_song", "ns", NULL +}; + +static const char *the_state(enum State state) +{ + switch (state) { + case STATE_LYRICS: + return "STATE_LYRICS"; + case STATE_DIRECTIVE_NAME: + return "STATE_DIRECTIVE_NAME"; + case STATE_DIRECTIVE_VALUE: + return "STATE_DIRECTIVE_VALUE"; + case STATE_CHORD: + return "STATE_CHORD"; + } + return ""; +} + +static const char *dtype(enum DirectiveType dtype) +{ + switch (dtype) { + case DT_ENVIRONMENT: + return "DT_ENVIRONMENT"; + case DT_METADATA: + return "DT_METADATA"; + case DT_FORMATTING: + return "DT_FORMATTING"; + case DT_PREAMBLE: + return "DT_PREAMBLE"; + case DT_CUSTOM: + return "DT_CUSTOM"; + } + return ""; +} + +const char *the_stype(enum SectionType stype) +{ + switch (stype) { + case ST_NOTHING: + return "ST_NOTHING"; + case ST_CHORUS: + return "ST_CHORUS"; + case ST_VERSE: + return "ST_VERSE"; + case ST_BRIDGE: + return "ST_BRIDGE"; + case ST_TAB: + return "ST_TAB"; + case ST_GRID: + return "ST_GRID"; + case ST_NEWSONG: + return "ST_NEWSONG"; + } + return ""; +} + +static const char *pos(enum Position pos) +{ + switch (pos) { + case POS_NOTHING: + return "POS_NOTHING"; + case POS_BEGIN: + return "POS_BEGIN"; + case POS_END: + return "POS_END"; + } + return ""; +} + +const char *the_style(enum LineStyle style) +{ + switch (style) { + case LS_NORMAL: + return "LS_NORMAL"; + case LS_GREY_BACKGROUND: + return "LS_GREY_BACKGROUND"; + case LS_ITALIC: + return "LS_ITALIC"; + case LS_BOX: + return "LS_BOX"; + } + return ""; +} + +struct ChoMetadata *cho_metadata_new(void) +{ + struct ChoMetadata *meta = malloc(sizeof(struct ChoMetadata)); + meta->name = NULL; + meta->value = NULL; + return meta; +} + +struct ChoMetadata *cho_metadata_split(const char *directive_value) +{ + struct ChoMetadata *meta = cho_metadata_new(); + char *value = string_remove_leading_whitespace(directive_value); + int i = 0; + int n = 0; + int v = 0; + bool is_name = true; + while (value[i] != 0) { + if (value[i] == ' ') { + meta->name = realloc(meta->name, (n+1) * sizeof(char)); + meta->name[n] = 0; + is_name = false; + } else { + if (is_name) { + meta->name = realloc(meta->name, (n+1) * sizeof(char)); + meta->name[n] = value[i]; + n++; + } else { + meta->value = realloc(meta->value, (v+1) * sizeof(char)); + meta->value[v] = value[i]; + v++; + } + } + i++; + } + meta->value = realloc(meta->value, (v+1) * sizeof(char)); + meta->value[v] = 0; + free(value); + if (v > 1) { + return meta; + } else { + free(meta->name); + free(meta->value); + free(meta); + fprintf(stderr, "INFO: Failed to parse directive 'meta'.\n"); + return NULL; + } +} + +struct ChoChord *cho_chord_new(void) +{ + struct ChoChord *chord = malloc(sizeof(struct ChoChord)); + chord->index_in_lyrics = -1; + chord->chord = NULL; + return chord; +} + +struct ChoLine *cho_line_new(void) +{ + struct ChoLine *line = malloc(sizeof(struct ChoLine)); + line->style = LS_NORMAL; + line->chords = NULL; + line->lyrics = NULL; + return line; +} + +struct ChoSection *cho_section_new(void) +{ + struct ChoSection *section = malloc(sizeof(struct ChoSection)); + section->type = ST_NOTHING; + section->name = NULL; + section->lines = NULL; + return section; +} + +struct ChoSong *cho_song_new(void) +{ + struct ChoSong *song = malloc(sizeof(struct ChoSong)); + song->metadata = NULL; + song->sections = NULL; + return song; +} + +void cho_song_free(struct ChoSong *song) +{ + int i = 0; + int k = 0; + int c = 0; + while (song->metadata[i] != NULL) { + free(song->metadata[i]->name); + free(song->metadata[i]->value); + free(song->metadata[i]); + i++; + } + free(song->metadata); + i = 0; + while (song->sections[i] != NULL) { + free(song->sections[i]->name); + while (song->sections[i]->lines[k] != NULL) { + free(song->sections[i]->lines[k]->lyrics); + while (song->sections[i]->lines[k]->chords[c] != NULL) { + free(song->sections[i]->lines[k]->chords[c]->chord); + free(song->sections[i]->lines[k]->chords[c]); + c++; + } + free(song->sections[i]->lines[k]->chords); + free(song->sections[i]->lines[k]); + c = 0; + k++; + } + free(song->sections[i]->lines); + free(song->sections[i]); + k = 0; + i++; + } + free(song->sections); + free(song); +} + +struct ChoDirective *directive_parse(const char *name) +{ + struct ChoDirective *directive = malloc(sizeof(struct ChoDirective)); + directive->style = LS_NORMAL; + int i = 0; + if ( + strcmp(name, environment_directives[0]) == 0 || + strcmp(name, environment_directives[1]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_BEGIN; + directive->stype = ST_CHORUS; + goto END; + } else if ( + strcmp(name, environment_directives[2]) == 0 || + strcmp(name, environment_directives[3]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_CHORUS; + goto END; + } else if ( + strcmp(name, environment_directives[5]) == 0 || + strcmp(name, environment_directives[6]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_BEGIN; + directive->stype = ST_VERSE; + goto END; + } else if ( + strcmp(name, environment_directives[7]) == 0 || + strcmp(name, environment_directives[8]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_VERSE; + goto END; + } else if ( + strcmp(name, environment_directives[9]) == 0 || + strcmp(name, environment_directives[10]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_BEGIN; + directive->stype = ST_BRIDGE; + goto END; + } else if ( + strcmp(name, environment_directives[11]) == 0 || + strcmp(name, environment_directives[12]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_BRIDGE; + goto END; + } else if ( + strcmp(name, environment_directives[13]) == 0 || + strcmp(name, environment_directives[14]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_BEGIN; + directive->stype = ST_TAB; + goto END; + } else if ( + strcmp(name, environment_directives[15]) == 0 || + strcmp(name, environment_directives[16]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_TAB; + goto END; + } else if ( + strcmp(name, environment_directives[17]) == 0 || + strcmp(name, environment_directives[18]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_BEGIN; + directive->stype = ST_GRID; + goto END; + } else if ( + strcmp(name, environment_directives[19]) == 0 || + strcmp(name, environment_directives[20]) == 0 + ) { + directive->dtype = DT_ENVIRONMENT; + directive->position = POS_END; + directive->stype = ST_GRID; + goto END; + } + while (metadata_directives[i] != NULL) { + if (strcmp(metadata_directives[i], name) == 0) { + directive->dtype = DT_METADATA; + directive->stype = ST_NOTHING; + directive->position = POS_NOTHING; + goto END; + } + i++; + } + i = 0; + if ( + strcmp(formatting_directives[0], name) == 0 || + strcmp(formatting_directives[1], name) == 0 || + strcmp(formatting_directives[2], name) == 0 + ) { + directive->style = LS_GREY_BACKGROUND; + directive->dtype = DT_FORMATTING; + directive->stype = ST_NOTHING; + directive->position = POS_NOTHING; + goto END; + } else if ( + strcmp(formatting_directives[3], name) == 0 || + strcmp(formatting_directives[4], name) == 0 + ) { + directive->style = LS_ITALIC; + directive->dtype = DT_FORMATTING; + directive->stype = ST_NOTHING; + directive->position = POS_NOTHING; + goto END; + } else if ( + strcmp(formatting_directives[5], name) == 0 || + strcmp(formatting_directives[4], name) == 0 + ) { + directive->style = LS_BOX; + directive->dtype = DT_FORMATTING; + directive->stype = ST_NOTHING; + directive->position = POS_NOTHING; + goto END; + } + while (preamble_directives[i] != NULL) { + if (strcmp(preamble_directives[i], name) == 0) { + directive->dtype = DT_PREAMBLE; + directive->stype = ST_NEWSONG; + directive->position = POS_NOTHING; + goto END; + } + i++; + } + directive->dtype = DT_CUSTOM; + directive->stype = ST_NOTHING; + directive->position = POS_NOTHING; +END: + return directive; +} + +struct ChoSong *cho_parse(FILE *fp) +{ + struct ChoSong *song = cho_song_new(); + int s = 0; + song->sections = malloc((s+1) * sizeof(struct ChoSection *)); + song->sections[s] = cho_section_new(); + int li = 0; + song->sections[s]->lines = realloc(song->sections[s]->lines, (li+1) * sizeof(struct ChoLine *)); + song->sections[s]->lines[li] = cho_line_new(); + int dn = 0; + int dv = 0; + int ch = 0; + int c = 0; + int m = 0; + int ly = 0; + char buf; + size_t read; + char directive_name[16]; + char directive_value[512]; + char chord[15]; + enum State state = STATE_LYRICS; + struct ChoDirective *directive = NULL; + struct ChoMetadata *metadata = NULL; + while (feof(fp) == 0) { + read = fread(&buf, 1, 1, fp); + if (read == 1) { + // printf("state: %s, buf: %c\n", the_state(state), buf); + switch (state) { + case STATE_LYRICS: + if (buf == '{') { + state = STATE_DIRECTIVE_NAME; + break; + } + if (buf == '[') { + state = STATE_CHORD; + break; + } + if (buf == '\n') { + song->sections[s]->lines[li]->lyrics = realloc(song->sections[s]->lines[li]->lyrics, (ly+1) * sizeof(char)); + song->sections[s]->lines[li]->lyrics[ly] = 0; + ly = 0; + song->sections[s]->lines[li]->chords = realloc(song->sections[s]->lines[li]->chords, (c+1) * sizeof(struct ChoChord *)); + song->sections[s]->lines[li]->chords[c] = NULL; + c = 0; + li++; + song->sections[s]->lines = realloc(song->sections[s]->lines, (li+1) * sizeof(struct ChoLine *)); + song->sections[s]->lines[li] = cho_line_new(); + break; + } + song->sections[s]->lines[li]->lyrics = realloc(song->sections[s]->lines[li]->lyrics, (ly+1) * sizeof(char)); + song->sections[s]->lines[li]->lyrics[ly] = buf; + ly++; + break; + case STATE_DIRECTIVE_NAME: + if (buf == '}') { + directive_name[dn] = 0; + dn = 0; + directive = directive_parse(directive_name); + /* printf( + "directive: '%s'\ndtype: %s, stype: %s, position: %s\n", + directive_name, dtype(directive->dtype), the_stype(directive->stype), pos(directive->position) + ); */ + switch (directive->dtype) { + case DT_ENVIRONMENT: + switch (directive->position) { + case POS_BEGIN: + free(song->sections[s]->lines[li]); + song->sections[s]->lines[li] = NULL; + s++; + song->sections = realloc(song->sections, (s+1) * sizeof(struct ChoSection *)); + song->sections[s] = cho_section_new(); + song->sections[s]->type = directive->stype; + li = 0; + song->sections[s]->lines = malloc(sizeof(struct ChoLine *)); + song->sections[s]->lines[li] = cho_line_new(); + break; + case POS_END: + if (directive->stype == song->sections[s]->type) { + free(song->sections[s]->lines[li]); + song->sections[s]->lines[li] = NULL; + s++; + song->sections = realloc(song->sections, (s+1) * sizeof(struct ChoSection *)); + song->sections[s] = cho_section_new(); + li = 0; + song->sections[s]->lines = malloc(sizeof(struct ChoLine *)); + song->sections[s]->lines[li] = cho_line_new(); + } + break; + } + break; + case DT_METADATA: + fprintf(stderr, "INFO: Metadata directive '%s' has no value.\n", directive_name); + song->metadata = realloc(song->metadata, (m+1) * sizeof(struct ChoMetadata *)); + song->metadata[m] = cho_metadata_new(); + song->metadata[m]->name = strdup(directive_name); + m++; + memset(directive_name, 0, strlen(directive_name)); + break; + case DT_FORMATTING: + fprintf(stderr, "INFO: Formatting directive '%s' has no value.\n", directive_name); + break; + case DT_PREAMBLE: + break; + case DT_CUSTOM: + break; + } + free(directive); + directive = NULL; + state = STATE_LYRICS; + break; + } + if (buf == ':') { + directive_name[dn] = 0; + dn = 0; + state = STATE_DIRECTIVE_VALUE; + break; + } + directive_name[dn] = buf; + dn++; + break; + case STATE_DIRECTIVE_VALUE: + if (buf == '}') { + directive_value[dv] = 0; + dv = 0; + directive = directive_parse(directive_name); + /* printf( + "directive: '%s'\ndtype: %s, stype: %s, position: %s\n", + directive_name, dtype(directive->dtype), the_stype(directive->stype), pos(directive->position) + ); */ + switch (directive->dtype) { + case DT_ENVIRONMENT: + switch (directive->position) { + case POS_BEGIN: + free(song->sections[s]->lines[li]); + song->sections[s]->lines[li] = NULL; + s++; + song->sections = realloc(song->sections, (s+1) * sizeof(struct ChoSection *)); + song->sections[s] = cho_section_new(); + song->sections[s]->type = directive->stype; + song->sections[s]->name = string_remove_leading_whitespace(directive_value); + li = 0; + song->sections[s]->lines = malloc(sizeof(struct ChoLine *)); + song->sections[s]->lines[li] = cho_line_new(); + break; + case POS_END: + if (directive->stype == song->sections[s]->type) { + free(song->sections[s]->lines[li]); + song->sections[s]->lines[li] = NULL; + s++; + song->sections = realloc(song->sections, (s+1) * sizeof(struct ChoSection *)); + song->sections[s] = cho_section_new(); + li = 0; + song->sections[s]->lines = malloc(sizeof(struct ChoLine *)); + song->sections[s]->lines[li] = cho_line_new(); + } + break; + } + break; + case DT_METADATA: + if (strcmp(directive_name, "meta") == 0) { + metadata = cho_metadata_split(directive_value); + if (metadata != NULL) { + song->metadata = realloc(song->metadata, (m+1) * sizeof(struct ChoMetadata *)); + song->metadata[m] = metadata; + m++; + } + } else { + song->metadata = realloc(song->metadata, (m+1) * sizeof(struct ChoMetadata *)); + song->metadata[m] = cho_metadata_new(); + song->metadata[m]->name = strdup(directive_name); + song->metadata[m]->value = string_remove_leading_whitespace(directive_value); + m++; + } + break; + case DT_FORMATTING: + song->sections[s]->lines[li]->style = directive->style; + song->sections[s]->lines[li]->lyrics = string_remove_leading_whitespace(directive_value); + ly = strlen(song->sections[s]->lines[li]->lyrics); + break; + case DT_PREAMBLE: + break; + case DT_CUSTOM: + break; + } + memset(directive_value, 0, strlen(directive_value)); + free(directive); + directive = NULL; + state = STATE_LYRICS; + break; + } + directive_value[dv] = buf; + dv++; + break; + case STATE_CHORD: + if (buf == ']') { + chord[ch] = 0; + ch = 0; + song->sections[s]->lines[li]->chords = realloc(song->sections[s]->lines[li]->chords, (c+1) * sizeof(struct ChoChord *)); + song->sections[s]->lines[li]->chords[c] = cho_chord_new(); + song->sections[s]->lines[li]->chords[c]->index_in_lyrics = ly; + song->sections[s]->lines[li]->chords[c]->chord = strdup(chord); + memset(chord, 0, strlen(chord)); + c++; + state = STATE_LYRICS; + break; + } + chord[ch] = buf; + ch++; + break; + } + } else if (ferror(fp) != 0) { + fprintf(stderr, "fread failed.\n"); + return NULL; + } + } + free(song->sections[s]->lines[li]); + song->sections[s]->lines[li] = NULL; + song->metadata = realloc(song->metadata, (m+1) * sizeof(struct ChoMetadata *)); + song->metadata[m] = NULL; + s++; + song->sections = realloc(song->sections, (s+1) * sizeof(struct ChoSection *)); + song->sections[s] = NULL; + return song; +} + diff --git a/chordpro.h b/chordpro.h @@ -0,0 +1,77 @@ +enum State { + STATE_LYRICS, + STATE_DIRECTIVE_NAME, + STATE_DIRECTIVE_VALUE, + STATE_CHORD +}; + +enum DirectiveType { + DT_ENVIRONMENT, + DT_METADATA, + DT_FORMATTING, + DT_PREAMBLE, + DT_CUSTOM +}; + +enum SectionType { + ST_NEWSONG, + ST_NOTHING, + ST_CHORUS, + ST_VERSE, + ST_BRIDGE, + ST_TAB, + ST_GRID +}; + +enum Position { + POS_NOTHING, + POS_BEGIN, + POS_END +}; + +enum LineStyle { + LS_NORMAL, + LS_GREY_BACKGROUND, + LS_ITALIC, + LS_BOX +}; + +struct ChoDirective { + enum DirectiveType dtype; + enum SectionType stype; + enum Position position; + enum LineStyle style; +}; + +struct ChoMetadata { + char *name; + char *value; +}; + +struct ChoChord { + int index_in_lyrics; + char *chord; +}; + +struct ChoLine { + enum LineStyle style; + struct ChoChord **chords; + char *lyrics; +}; + +struct ChoSection { + enum SectionType type; + char *name; + struct ChoLine **lines; +}; + +struct ChoSong { + struct ChoMetadata **metadata; + struct ChoSection **sections; +}; + +struct ChoSong *cho_parse(FILE *fp); +void cho_song_free(struct ChoSong *song); +char *string_remove_leading_whitespace(const char *str); +const char *the_stype(enum SectionType stype); +const char *the_style(enum LineStyle style); diff --git a/lorid.c b/lorid.c @@ -0,0 +1,89 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include "chordpro.h" + +void print_hex(const char *str) +{ + int i = 0; + while (str[i] != 0) { + printf("%02X ", str[i]); + i++; + } + putchar('\n'); +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Provide a file.\n"); + return 1; + } + FILE *fp = fopen(argv[1], "r"); + if (fp == NULL) { + fprintf(stderr, "fopen failed.\n"); + return 1; + } + struct ChoSong *song = cho_parse(fp); + if (song == NULL) { + fprintf(stderr, "chordpro_parse failed.\n"); + return 1; + } + /*int m = 0;*/ + /*printf("---- BEGIN METADATA ----\n");*/ + /*while (song->metadata[m] != NULL) {*/ + /* if (song->metadata[m]->name) {*/ + /* printf("name: %s", song->metadata[m]->name);*/ + /* }*/ + /* if (song->metadata[m]->value) {*/ + /* printf(", value: %s\n", song->metadata[m]->value);*/ + /* } else {*/ + /* printf("\n");*/ + /* }*/ + /* m++;*/ + /*}*/ + /*printf("---- END METADATA ------\n");*/ + char *lyrics; + struct ChoChord **chords; + enum SectionType stype; + enum LineStyle style; + int s = 0; + int li = 0; + int c = 0; + while (song->sections[s] != NULL) { + printf("---- BEGIN SECTION ----\n"); + stype = song->sections[s]->type; + printf("stype: %s\n", the_stype(stype)); + if (song->sections[s]->name) { + printf("name: %s\n", song->sections[s]->name); + } + while (song->sections[s]->lines[li] != NULL) { + /*chords = song->sections[s]->lines[li]->chords;*/ + /*while (chords[c] != NULL) {*/ + /* printf("index: %d, chord: %s | ", chords[c]->index_in_lyrics, chords[c]->chord);*/ + /* c++;*/ + /*}*/ + /*printf("\n");*/ + /*c = 0;*/ + /*lyrics = song->sections[s]->lines[li]->lyrics;*/ + /*if (strlen(lyrics) > 0) {*/ + /* printf("lyrics: %s\n", lyrics);*/ + /* style = song->sections[s]->lines[li]->style;*/ + /* printf("style: %s\n", the_style(style));*/ + /*}*/ + style = song->sections[s]->lines[li]->style; + lyrics = song->sections[s]->lines[li]->lyrics; + if (strlen(lyrics) > 0) { + printf("style %s, line %s\n", the_style(style), lyrics); + } + li++; + } + li = 0; + s++; + printf("---- END SECTION ------\n"); + } + cho_song_free(song); + fclose(fp); + return 0; +}