Source code belongs to this explanation. Also available for download.

Codice fonte pertine a iste explanation. Anque disponibile pro discargar.


/*
   Written 16 May 1993, Ruud Harmsen

   Examine musical intervals made by frequency ratios between the
   first natural numbers, and from 12-, 24-, 31-, 53-, and 72-tone
   equidistant tuning systems.

   3 October 2020:
   Added Pythagorean intervals like 81/64, 32/27, 256/243 etc., by
   permuting factors 2 and 3.

   4 October 2020:
   More reliable algorithm, no longer assuming qsort will leave
   equal entries in the original order. A sequence number now
   guarantees that later entries (e.g. 6:4, 12:8) appear later
   than the basic ratio (e.g. 3:2), and so can easily and reliably
   be removed afterwards.

   :
   Published source and outputs to https://rudhar.com/musica/intonat/ .

   5 October 2020:
   Used realloc() instead of a fixed size array.

   6 October 2020:
   More identical code in a function, and add ratios 135:128,
   64:45 and 45:32.

   16 October 2020:
   Can now also generate C code (option -g), for use elsewhere
   in a display function (showintv.c). Also added some more
   intervals.

   24 October 2020:
   Added some more intervals, after examination of shifted Zarlino
   and Pythagoras scales.

   22 August 2023:
   Added 72-tone equidistant.

   25 August 2023:
   Added 54-tone equidistant. It seems this is used in Turkish music:
   multiples of a 'koma', which is 1/9 of a tone (two semitones).
   That makes (12/2)*9 = 54 such distances in the octave.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

/* 14 September 2023: 25 becomes 28 */
#define MAXFACT 28
/* Added 3 October 2020 */
#define MAXPYTH 13

struct interval
{
   int    seqnum;
   double cents;
   char   text[28];
};
struct interval *interv = NULL;

/* Number of filled rows, how many are already in the table. */
int fild_rows = 0;

static int cmp (const void *, const void *);
static void fill_octave_division (int div);
static void fill_pythagorean_ratios (void);
static void fill_pythagorean_ratios_2_3 (int factorA, int factorB);
static int addratio (
   int fild_rows_arg,
   int numerator, int denominator,
   struct interval **pp_interv);
static int addrow (
   int fild_rows_arg,
   struct interval *p_newrow,
   struct interval **pp_interv);

static void write_C_array (void);

int main (int argc, char **argv)
{
   int i, j;
   int only_53 = 0;
   int generate_C = 0;

   while (argc > 1)
   {
      if (strcmp(argv[1], "-53") == 0)
         only_53 = 1;
      else if (strcmp(argv[1], "-g") == 0)
         generate_C = 1;
      argc--, argv++;
   }

   for (i = 1;     i <= MAXFACT; i++)
   for (j = i + 1; j <= MAXFACT; j++)
   {
      fild_rows = addratio(fild_rows, j, i, &interv);
   }

   fill_octave_division(12);
   fill_octave_division(24);
   if (!only_53)
   {
      fill_octave_division(19);
      fill_octave_division(31);
      fill_octave_division(43);
      fill_octave_division(54);
      fill_octave_division(72);
   }
   fill_octave_division(53);

   fill_pythagorean_ratios();

   /* Some extra ratios, not covered by previous algorithms */
   fild_rows = addratio(fild_rows,    135,    128, &interv);
   fild_rows = addratio(fild_rows,     64,     45, &interv);
   fild_rows = addratio(fild_rows,     45,     32, &interv);
   fild_rows = addratio(fild_rows,     36,     25, &interv);
   fild_rows = addratio(fild_rows,     48,     25, &interv);

   /* Some additions as per
      https://en.wikipedia.org/wiki/Interval_(music)#Size_of_intervals_used_in_different_tuning_systems
      https://en.wikipedia.org/wiki/Five-limit_tuning#The_just_ratios
      https://en.wikipedia.org/wiki/Pythagorean_interval
      https://nl.wikipedia.org/wiki/Didymisch_komma
      https://en.wikipedia.org/wiki/Syntonic_comma
      etc. */
   fild_rows = addratio(fild_rows,     27,     25, &interv);
   fild_rows = addratio(fild_rows,     27,     20, &interv);
   fild_rows = addratio(fild_rows,     40,     27, &interv);
   fild_rows = addratio(fild_rows,     50,     27, &interv);
   fild_rows = addratio(fild_rows,     81,     80, &interv);
   /* 14 September 2023 */
   fild_rows = addratio(fild_rows,     88,     81, &interv);
   /*  5 November 2023 */
   fild_rows = addratio(fild_rows,     33,     20, &interv);

   /* Some additions after analysis of shifted Zarlino and
      Pythagoras scales, from self-written program zarlino.c */
   fild_rows = addratio(fild_rows,     32,     25, &interv);
   fild_rows = addratio(fild_rows,     75,     64, &interv);
   fild_rows = addratio(fild_rows,    225,    128, &interv);
   fild_rows = addratio(fild_rows,    256,    225, &interv);
   fild_rows = addratio(fild_rows,    256,    243, &interv);
   fild_rows = addratio(fild_rows,    512,    405, &interv);
   fild_rows = addratio(fild_rows,    675,    512, &interv);
   fild_rows = addratio(fild_rows,   2187,   2048, &interv);
   fild_rows = addratio(fild_rows,   6561,   4096, &interv);
   fild_rows = addratio(fild_rows,   8192,   6561, &interv);
   fild_rows = addratio(fild_rows,  19683,  16384, &interv);
   fild_rows = addratio(fild_rows,  59049,  32768, &interv);
   fild_rows = addratio(fild_rows,  65536,  59049, &interv);
   fild_rows = addratio(fild_rows, 177147, 131072, &interv);
   fild_rows = addratio(fild_rows, 262144, 177147, &interv);

   /* Sort the raw table on ascending number of cents,
      and sequence number */
   qsort((void *)interv, fild_rows, sizeof interv[0], cmp);

   /* Remove doublets, e.g. 3/2 and 6/4 both are a fifth,
      only the first one needs to be kept. */
   for (i = 1, j = 0; i < fild_rows; i++)
   {
      /* Round difference to 5 decimals, so spurious last bits
         cannot determine the sorting order. */
      long int centsdiff = 0.5 + (interv[i].cents - interv[j].cents) * 100000L;

      if (centsdiff == 0)
         /* Mark as doublet (first row is kept alive */
	 interv[i].text[0] = '\0';
      else
	 j = i;
   }

   /* Write the results to standard output */
   if (generate_C)
      write_C_array();
   else for (i = 0; i < fild_rows; i++)
   {
      /* Skip marked doublets, write everything else */
      if (interv[i].text[0])
      {
         fprintf(stdout, "%8.6f %8.3f %s\n",
            pow(2.0, interv[i].cents / 1200.0),
            interv[i].cents,
            interv[i].text);
      }
   }

   free(interv);

   return 0;
}

static int cmp (const void *a, const void *b)
{
   const struct interval *aa = (const struct interval *)a;
   const struct interval *bb = (const struct interval *)b;
   /* Round difference to 5 decimals, so spurious last bits
      cannot determine the sorting order. */
   long int centsdiff = 0.5 + (aa->cents - bb->cents) * 100000L;

   if (centsdiff)
   {
      if (aa->cents > bb->cents) return 1;
      if (aa->cents < bb->cents) return -1;
   }
   /* Equal intervals should remain in the original order
      of entry, so all but the first can be wiped. */
   return aa->seqnum - bb->seqnum;
}

static void fill_octave_division (int div)
{
   int i;

   for (i = 0; i <= div; i++)
   {
      struct interval newrow;

      newrow.cents = 1200.0 * i / (double)div;
      sprintf(newrow.text, "%6d/%-6d octave", i, div);
      /* Set sequence number, to guarantee that later additions
         stay after earlier ones, even after sorting. */
      newrow.seqnum = fild_rows;
      fild_rows = addrow(fild_rows, &newrow, &interv);
   }
}

/* Added the extra Pythagorean ratios on 3 October 2020. Yes,
   that's more than 27 years later, and still by the same
   programmer. I am Ruud Harmsen. */
static void fill_pythagorean_ratios (void)
{
   fill_pythagorean_ratios_2_3(3, 2);
   fill_pythagorean_ratios_2_3(2, 3);
}

static void fill_pythagorean_ratios_2_3 (int factorA, int factorB)
{
   int i , j;
   int numerator, denominator;
   double ratio;

   /* This adds things like 32/27, 256/243 etc. which would otherwise
      not be included, as the maximum factor MAXFACT is 25. */
   for (i = 1; i <= MAXPYTH; i++)
   for (j = 1; j <= MAXPYTH; j++)
   {
      struct interval newrow;

      numerator   = pow(factorA, i);
      denominator = pow(factorB, j);

      fild_rows = addratio(fild_rows, numerator, denominator, &interv);
   }
}

static int addratio (
   int fild_rows_arg,
   int numerator, int denominator,
   struct interval **pp_interv)
{
   double ratio = (double)numerator / (double)denominator;
   struct interval newrow;

   if (ratio < 1.0 || ratio > 2.0)
      return fild_rows;

   newrow.cents = log(ratio) / log(2.0) * 1200;
   sprintf(newrow.text, "%6d:%-6d ratio ", numerator, denominator);
   /* Set sequence number, to guarantee that later additions
      stay after earlier ones, even after sorting. */
   newrow.seqnum = fild_rows;

   return fild_rows_arg = addrow(fild_rows, &newrow, pp_interv);
}

static int addrow (
   int fild_rows_arg,
   struct interval *p_newrow,
   struct interval **pp_interv)
{
   struct interval *realloked =
      realloc(*pp_interv, (fild_rows_arg + 1) * sizeof (struct interval));

   if (realloked)
   {
      *pp_interv = realloked;
      /* Structure assign */
      (*pp_interv)[fild_rows_arg++] = *p_newrow;
   }
   else
   {
      /* Realloc failed. Silently ignore. Just don't put in
         that new row, and keep pointer and counter unchanged.
         Failure is so unlikely in practice, having only a few
         hundred entries, that error handling would be overkill.
       */ ;
   }
   return fild_rows_arg;
}

static void write_C_array (void)
{
   int i;

   fprintf(stdout, "/* Generated from intonat.c */\n");
   fprintf(stdout, "struct interv_struct\n");
   fprintf(stdout, "{\n");
   fprintf(stdout, "   double ratio;\n");
   fprintf(stdout, "   double cents;\n");
   fprintf(stdout, "   char   text[24];\n");
   fprintf(stdout, "} interv[] = {\n");

   for (i = 0; i < fild_rows; i++)
   {
      /* Skip marked doublets, write everything else */
      if (interv[i].text[0])
      {
         fprintf(stdout, "   { %8.6f, %11.6f, \"%s\" },\n",
            pow(2.0, interv[i].cents / 1200.0),
            interv[i].cents,
            interv[i].text);
      }
   }
   fprintf(stdout, "};\n");
   fprintf(stdout, "/* End of generated code from intonat.c */\n\n");
}