commit 156fc6db49ca0e39887d3afd7ac2d85eba07f593
parent 1bcc4307d68af4470da5074afd80a7bd1933bdb1
Author: nibo <nibo@relim.de>
Date: Sat, 5 Oct 2024 10:21:25 +0200
Add 'transpose' directive
Diffstat:
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