/* 2 October 2023, by R. Harmsen Auxiliary program to convert output by pilaintv (see https://rudhar.com/musica/hwedwortehun/ia08.htm) to specs for Lilypond such that a score can be played in that tuning. The program only considers the relative intervals, so column IX, and ignores everything else. Recognition is on the separator --, so any input that contains that before the interval, can be used. The program tries to keep the note 'a' on concert pitch, so zero change relative to equidistant. (See NORMD) (17 July 2025: Changed that to note c instead of a.) The key or tonic can be specified in the command line (in lowercase, Dutch notenames, only naturals and sharps, no flats). Input is read from stdin. Should be exactly 12 notes for a chromatic scale. Improvement 9 October 2023: Instead of an interval relative to the previous note, the program now also accepts occasional absolute intervals, i.e. relative to the tonic (= first note of the scale). Any interval greater than about 267 cents (ratio 7:6) is automatically interpreted as such. 15 October 2023: The recently added calculation for a RatioSequence by default is done for all 12 (or more) notes of the chromatic scale. But now there's a command line option (-e followed by integers separated by spaces) to exclude some degrees for that calculation. The remaining ones then show the minimal ratio numbers for a chord. */ #include #include #include #include #include "../../rhmusicfuncs/rhmusicfuncs.h" #define NUMDGRE 14+1 #define LilylineElts ((int)(sizeof LilyLines / sizeof LilyLines[0])) /* I used to modify the scale such that the note A always remained at concert pitch, so had a bend of zero. As a result, the note C could have a bend, e.g. for a Zarlino type just intonation, that would be minus 6 cents. However Lilypond (in version 2.24.2 anyway) has a strange bug (which is how see it), that the bend specified for the note C is applied to it, but also again to the whole scale. See my examples in https://rudhar.com/musica/hwedwortehun/Vicnia/ . So C gets a bend of -12 instead of -6 cents, and all the other notes are also 6 cents lower than what I specified. As a result some notes got a bend of more than 25 cents, causing my program adhoc-midi to think the music contains quarter notes, although it doesn't. And that severely messed up the created ad hoc just intonation in some cases. So I changed the #define for NORMD, from 9 for note A, to 0 for note C, so C always remains at exactly 900 cents below concert pitch, i.e. at 261.625540 Hz. */ //#define NORMD 9 #define NORMD 0 #define ABSABOVE (7.0/6.0 + .00001) static char *Recog = "--"; static struct { struct IntvalRatio RelatIntv; double RelatCents; struct IntvalRatio AbsolIntv; double AbsolCents; LLI RatioSequence; int ExcludeForRatio; } wS[NUMDGRE]; static struct { char NoteName[4]; int LilyDegree; int EquiCents; double AbsolCents; } LilyLines[] = { {"c" , 0, 0, 0.0}, {"cis", 0, 0, 0.0}, {"d" , 1, 200, 0.0}, {"dis", 1, 200, 0.0}, {"e" , 2, 400, 0.0}, {"f" , 3, 500, 0.0}, {"fis", 3, 500, 0.0}, {"g" , 4, 700, 0.0}, {"gis", 4, 700, 0.0}, {"a" , 5, 900, 0.0}, {"ais", 5, 900, 0.0}, {"b" , 6, 1100, 0.0}, }; static int FindRatioSequence (int maxgrad); static void CheckExcludes (int argc, char **argv); #ifdef HTML static void WriteRationals (void); #endif int main (int argc, char **argv) { static char line[128]; int grado = 0; int maxgrad; char Tonica[4] = "c"; int TonicaIndex = 0; int ll, ws; double shift; CheckExcludes(argc, argv); while (grado < NUMDGRE && fgets(line, sizeof line, stdin)) { char *p = strstr(line, Recog); if (p) { p += strlen(Recog); sscanf(p, "%ld:%ld", &wS[grado].RelatIntv.numer, &wS[grado].RelatIntv.denom); grado++; } } maxgrad = grado - 1; wS[0].AbsolIntv.numer = wS[0].AbsolIntv.denom = 1; wS[0].AbsolCents = 0.0; for (grado = 1; grado < NUMDGRE && wS[grado].RelatIntv.denom > 0; grado++) { LLI thisGcd; if (wS[grado].RelatIntv.numer / (double)wS[grado].RelatIntv.denom < ABSABOVE) { wS[grado].AbsolIntv.numer = wS[grado-1].AbsolIntv.numer * wS[grado].RelatIntv.numer; wS[grado].AbsolIntv.denom = wS[grado-1].AbsolIntv.denom * wS[grado].RelatIntv.denom; thisGcd = gcd(wS[grado].AbsolIntv.numer, wS[grado].AbsolIntv.denom); wS[grado].AbsolIntv.numer /= thisGcd; wS[grado].AbsolIntv.denom /= thisGcd; } else { wS[grado].AbsolIntv.numer = wS[grado].RelatIntv.numer; wS[grado].AbsolIntv.denom = wS[grado].RelatIntv.denom; wS[grado].RelatIntv.numer = wS[grado].AbsolIntv.numer * wS[grado-1].AbsolIntv.denom; wS[grado].RelatIntv.denom = wS[grado].AbsolIntv.denom * wS[grado-1].AbsolIntv.numer; thisGcd = gcd(wS[grado].RelatIntv.numer, wS[grado].RelatIntv.denom); wS[grado].RelatIntv.numer /= thisGcd; wS[grado].RelatIntv.denom /= thisGcd; } wS[grado].AbsolCents = rats2cents(wS[grado].AbsolIntv.numer, wS[grado].AbsolIntv.denom); wS[grado].RelatCents = rats2cents(wS[grado].RelatIntv.numer, wS[grado].RelatIntv.denom); } FindRatioSequence(maxgrad); printf("%%{\n"); /* Lilypond block comment */ for (grado = 0; grado <= maxgrad; grado++) { printf("%2d %6.1f %6.0lld %4ld:%-4ld -- %4ld:%-4ld %6.1f\n", grado, wS[grado].AbsolCents, wS[grado].RatioSequence, wS[grado].AbsolIntv.numer, wS[grado].AbsolIntv.denom, wS[grado].RelatIntv.numer, wS[grado].RelatIntv.denom, wS[grado].RelatCents); } printf("%%}\n\n"); /* Lilypond block comment terminator */ if (argc > 1) { for (ll = 0; ll < LilylineElts; ll++) { if (strcmp(LilyLines[ll].NoteName, argv[1]) == 0) { strcpy(Tonica, argv[1]); TonicaIndex = ll; break; } } } for (ll = TonicaIndex, ws = 0; ll < LilylineElts + TonicaIndex; ll++, ws++) { int index = ll % LilylineElts; LilyLines[index].AbsolCents = wS[ws].AbsolCents; LilyLines[index].AbsolCents += LilyLines[TonicaIndex].EquiCents; if (LilyLines[index].AbsolCents > 1200 - 50) LilyLines[index].AbsolCents -= 1200; } if (LilyLines[0].AbsolCents > 300) LilyLines[0].AbsolCents -= 1200; /* Make sure that note a is never bent, remain 440 Hz or some octave of it. Or better: make sure C has no bend. */ shift = LilyLines[NORMD].AbsolCents - LilyLines[NORMD].EquiCents; fprintf(stderr, "Line=%3d, shift = %6.1f\n", __LINE__, shift); for (ll = 0; ll < LilylineElts; ll++) { LilyLines[ll].AbsolCents -= shift; } printf("justePitchNames = #`(\n"); for (ll = 0; ll < LilylineElts; ll++) { printf(" (%3.3s . ,(ly:make-pitch -1 %d %4.0f/200))\n", LilyLines[ll].NoteName, LilyLines[ll].LilyDegree, LilyLines[ll].AbsolCents - LilyLines[ll].EquiCents); if (strcmp(LilyLines[ll].NoteName + 1, "is") == 0) { char Enharmonic[4]; strcpy(Enharmonic, LilyLines[ll].NoteName); Enharmonic[0]++; Enharmonic[1] = 'e'; if (strcmp(Enharmonic, "hes") == 0) strcpy(Enharmonic, "as"); if (strcmp(Enharmonic, "ees") == 0 || strcmp(Enharmonic, "aes") == 0) strcpy(Enharmonic + 1, "s"); printf(" (%3.3s . ,(ly:make-pitch -1 %d %4.0f/200))\n", Enharmonic, LilyLines[ll].LilyDegree, LilyLines[ll].AbsolCents - LilyLines[ll].EquiCents); } if (strcmp(LilyLines[ll].NoteName, "f") == 0) printf(" (%3.3s . ,(ly:make-pitch -1 %d %4.0f/200))\n", "eis", LilyLines[ll].LilyDegree, LilyLines[ll].AbsolCents - LilyLines[ll].EquiCents); if (strcmp(LilyLines[ll].NoteName, "c") == 0) printf(" (%3.3s . ,(ly:make-pitch -1 %d %4.0f/200))\n", "bis", LilyLines[ll].LilyDegree, LilyLines[ll].AbsolCents - LilyLines[ll].EquiCents); } printf(")\n\n%%%% Set pitch names.\n#(set! language-pitch-names\n"); printf("\t(append language-pitch-names\n"); printf("\t\t(list `(justeNotos . ,justePitchNames))))\n\n\\language justeNotos\n\n"); #ifdef HTML WriteRationals(); #endif return 0; } static int FindRatioSequence (int maxgrad) { int grado; /* Least common multiple */ LLI lcm = wS[0].AbsolIntv.denom; LLI thisGcd; for (grado = 1; grado < maxgrad; grado++) { if (wS[grado].ExcludeForRatio) continue; thisGcd = gcd(wS[grado].AbsolIntv.denom, lcm); lcm = lcm * wS[grado].AbsolIntv.denom / thisGcd; } for (grado = 0; grado <= maxgrad; grado++) { wS[grado].RatioSequence = wS[grado].ExcludeForRatio ? 0 : wS[grado].AbsolIntv.numer * lcm / wS[grado].AbsolIntv.denom; } return 0; } static void CheckExcludes (int argc, char **argv) { if (argc > 1 && strcmp(argv[1], "-e") == 0) { for (argc -= 2, argv += 2; argc; argc--, argv++) { int index = atoi(argv[0]); if (index >= 0 && index < NUMDGRE) wS[index].ExcludeForRatio = 1; } } } #ifdef HTML #define NUMPAT (sizeof Patterns / sizeof Patterns[0]) struct PattStruct { char *name; short int Pattern[NUMDGRE]; }; static struct PattStruct Patterns[] = { /* 0 1 2 3 4 5 6 7 8 9 10 11 12 */ {"accordo minor", {0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0 } }, {"accordo major", {0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0 } }, {"accordo 7
dominante", {0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0 } }, {"accordo 7 minor", {0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0 } }, /* 0 1 2 3 4 5 6 7 8 9 10 11 12 */ {"a: scala eolian", {0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0 } }, {"b: scala locrian", {0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 } }, {"c: scala ionian", {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0 } }, {"d: scala dorian", {0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0 } }, {"e: scala phrygian", {0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0 } }, {"f: scala lydian", {0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0 } }, {"g: scala mixolydian", {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0 } }, {"a: scala eolian", {0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0 } }, /* 0 1 2 3 4 5 6 7 8 9 10 11 12 */ }; static void WriteRationals (void) { int pat, grado; printf("%%{\n\n"); for (pat = 0; pat < NUMPAT; pat++) { printf("\n\t\n\t", Patterns[pat].name); for (grado = 0; grado <= 12; grado++) wS[grado].ExcludeForRatio = Patterns[pat].Pattern[grado]; FindRatioSequence(12); for (grado = 0; grado <= 12; grado++) { printf(" ", wS[grado].RatioSequence); } printf("\n\n"); } printf("
%s%.0lld
\n%%}\n\n"); } #endif