lorid

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

commit 156fc6db49ca0e39887d3afd7ac2d85eba07f593
parent 1bcc4307d68af4470da5074afd80a7bd1933bdb1
Author: nibo <nibo@relim.de>
Date:   Sat,  5 Oct 2024 10:21:25 +0200

Add 'transpose' directive

Diffstat:
Mchordpro.c | 162++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mchordpro.h | 1+
Mconfig.h | 6++++++
Mout_pdf.c | 2--
Mtodo | 3++-
5 files changed, 162 insertions(+), 12 deletions(-)

diff --git a/chordpro.c b/chordpro.c @@ -4,6 +4,9 @@ #include <stdint.h> #include <string.h> #include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <math.h> #include "chordpro.h" #include "config.h" #include "util.h" @@ -44,6 +47,10 @@ static const char *font_directives[] = { "tocfont", "tocsize", "toccolour", */ }; +static const char *chord_directives[] = { + "transpose", /* "define", "chord", */ NULL +}; + static const char *output_directives[] = { "new_page", "np", "column_break", "colb", NULL }; @@ -151,6 +158,8 @@ static const char *chord_extensions_minor[] = { static enum SongFragmentType g_current_ftype = SF_TEXT; static enum SongFragmentType g_prev_ftype = SF_TEXT; static struct Config *g_config = NULL; +static int *g_transpose_history = NULL; +static int *g_transpose = NULL; #ifdef DEBUG @@ -1343,6 +1352,101 @@ static struct ChoMetadata *cho_metadata_split(const char *directive_value) } } +static bool transposition_parse(const char *str, int *transpose) +{ + long i; + char *endptr; + i = strtol(str, &endptr, 10); + if (str == endptr) { + return false; + } + if (i == LONG_MIN || i == LONG_MAX) { + return false; + } + if (i > INT_MAX) { + return false; + } + *transpose = (int)i; + return true; +} + +static char *transposition_calc_chord_root(int index, enum NoteType type) +{ + int transpose = *g_transpose; + if (transpose == 0) { + switch (type) { + case NT_NOTE: + return strdup(g_config->output->notes[index]->note); + case NT_SHARP: + return strdup(g_config->output->notes[index]->sharp); + case NT_FLAT: + return strdup(g_config->output->notes[index]->flat); + } + } + int new_index = index; + enum NoteType note_type = type; + int i; + if (transpose > 0) { + for (i = 0; i<transpose; i++) { + switch (note_type) { + case NT_NOTE: + switch (new_index) { + case 2: + new_index++; + break; + case 6: + new_index = 0; + break; + default: + note_type = NT_SHARP; + } + break; + case NT_SHARP: + new_index++; + note_type = NT_NOTE; + break; + case NT_FLAT: + note_type = NT_NOTE; + break; + } + } + } else { + for (i=transpose; i<0; i++) { + switch (note_type) { + case NT_NOTE: + switch (new_index) { + case 0: + new_index = 6; + break; + case 3: + new_index--; + break; + default: + note_type = NT_FLAT; + } + break; + case NT_SHARP: + note_type = NT_NOTE; + break; + case NT_FLAT: + new_index--; + note_type = NT_NOTE; + break; + } + } + } + switch (note_type) { + case NT_NOTE: + return strdup(g_config->output->notes[new_index]->note); + case NT_SHARP: + return strdup(g_config->output->notes[new_index]->sharp); + case NT_FLAT: + return strdup(g_config->output->notes[new_index]->flat); + } + fprintf(stderr, "ERR: Invalid NoteType '%d'.\n", note_type); + return NULL; +} + static struct ChoChord *cho_chord_new(void) { struct ChoChord *chord = malloc(sizeof(struct ChoChord)); @@ -1390,27 +1494,37 @@ static int cho_chord_root_parse(const char *str, struct ChoChord *chord) const char *note = NULL; const char *sharp = NULL; const char *flat = NULL; - const char *out_note = NULL; - const char *out_sharp = NULL; - const char *out_flat = NULL; + char *transposed_root; int i; for (i = 0; i<7; i++) { sharp = g_config->parser->notes[i]->sharp; - out_sharp = g_config->output->notes[i]->sharp; if (sharp && str_starts_with(str, sharp)) { - chord->root = strdup(out_sharp); + transposed_root = transposition_calc_chord_root(i, NT_SHARP); + if (!transposed_root) { + fprintf(stderr, "transposition_calc_chord_root failed.\n"); + return 0; + } + chord->root = transposed_root; return strlen(sharp); } flat = g_config->parser->notes[i]->flat; - out_flat = g_config->output->notes[i]->flat; if (flat && str_starts_with(str, flat)) { - chord->root = strdup(out_flat); + transposed_root = transposition_calc_chord_root(i, NT_FLAT); + if (!transposed_root) { + fprintf(stderr, "transposition_calc_chord_root failed.\n"); + return 0; + } + chord->root = transposed_root; return strlen(flat); } note = g_config->parser->notes[i]->note; - out_note = g_config->output->notes[i]->note; if (str_starts_with(str, note)) { - chord->root = strdup(out_note); + transposed_root = transposition_calc_chord_root(i, NT_NOTE); + if (!transposed_root) { + fprintf(stderr, "transposition_calc_chord_root failed.\n"); + return 0; + } + chord->root = transposed_root; return strlen(note); } } @@ -2002,6 +2116,10 @@ static struct ChoDirective *cho_directive_parse(const char *name) directive->ftype = SF_TITLE; goto END; } + if (strcmp(chord_directives[0], name) == 0) { + directive->dtype = DT_CHORD; + goto END; + } if ( strcmp(output_directives[0], name) == 0 || strcmp(output_directives[1], name) == 0 @@ -2065,7 +2183,13 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) int atv = 0; int ann = 0; int chord_pos; + int transpose; + int th = 0; size_t read; + g_transpose_history = realloc(g_transpose_history, (th+1) * sizeof(int *)); + g_transpose_history[th] = 0; + g_transpose = &g_transpose_history[th]; + th++; enum AttrValueSyntax avs = AVS_NO; struct ChoDirective *directive = NULL; struct ChoMetadata *metadata = NULL; @@ -2284,6 +2408,11 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) return NULL; } break; + case DT_CHORD: + /* INFO: The only chord directive is currently 'transpose' */ + g_transpose--; + th--; + break; case DT_OUTPUT: if (directive->btype != BT_EMPTY) { songs[so]->sections[se]->lines[li]->btype = directive->btype; @@ -2459,6 +2588,18 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) } free(dir_value); break; + case DT_CHORD: + /* INFO: The only chord directive is currently 'transpose' */ + if (!transposition_parse(directive_value, &transpose)) { + fprintf(stderr, "transposition_parse failed.\n"); + fprintf(stderr, "ERR: Directive 'transpose' has an invalid value.\n"); + return NULL; + } + g_transpose_history = realloc(g_transpose_history, (th+1) * sizeof(int *)); + g_transpose_history[th] = g_transpose_history[th-1] + transpose; + g_transpose = &g_transpose_history[th]; + th++; + break; case DT_OUTPUT: fprintf(stderr, "ERR: Directive '%s' can't have a value.\n", directive_name); return NULL; @@ -2856,6 +2997,7 @@ struct ChoSong **cho_songs_parse(FILE *fp, struct Config *config) so++; songs = realloc(songs, (so+1) * sizeof(struct ChoSong *)); songs[so] = NULL; + free(g_transpose_history); bool exist_title = false; for (so = 0; songs[so]; so++) { for (m = 0; songs[so]->metadata[m]; m++) { @@ -2893,6 +3035,8 @@ static const char *cho_debug_the_dtype(enum DirectiveType dtype) return "DT_PREAMBLE"; case DT_FONT: return "DT_FONT"; + case DT_CHORD: + return "DT_CHORD"; case DT_OUTPUT: return "DT_OUTPUT"; case DT_CUSTOM: diff --git a/chordpro.h b/chordpro.h @@ -145,6 +145,7 @@ enum DirectiveType { DT_FORMATTING, DT_PREAMBLE, DT_FONT, + DT_CHORD, DT_OUTPUT, DT_CUSTOM }; diff --git a/config.h b/config.h @@ -27,6 +27,12 @@ struct Note { char *flat; }; +enum NoteType { + NT_NOTE, + NT_SHARP, + NT_FLAT +}; + struct ConfigChords { enum NamingSystem system; enum ParseMode mode; diff --git a/out_pdf.c b/out_pdf.c @@ -1074,9 +1074,7 @@ static struct Text **text_create(struct ChoSong **songs, struct Config *config) } text = realloc(text, (t+1) * sizeof(struct Text *)); text[t] = NULL; - struct TextLineMetadata *me; for (m = 0; m<tlm; m++) { - me = lines_metadata[m]; free(lines_metadata[m]); } free(lines_metadata); diff --git a/todo b/todo @@ -5,13 +5,14 @@ metadata directives %{blabla} in lyrics, chords and annotations conditional metadata directives + don't forget key, key_actual, key_from chords transpose define chords chord diagrams strict and relaxed parsing makes no difference!? make parser bulletproof - should it just parse valid input correctly and crash on invalid input? + try to detect invalid input as much as possible parse environment directive value when: label="Verse 1" # pdf output