/*
* Gregorio is a program that translates gabc files to GregorioTeX
* This file provides functions for writing gabc from Gregorio structures.
*
* Copyright (C) 2006-2018 The Gregorio Project (see CONTRIBUTORS.md)
*
* This file is part of Gregorio.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*
* This is a simple and easyly understandable output module. If you want to
* write a module, you can consider it as a model.
*/
#include "config.h"
#include
#include
#include /* for strchr */
#include "bool.h"
#include "characters.h"
#include "struct.h"
#include "unicode.h"
#include "messages.h"
#include "plugins.h"
#include "gabc.h"
typedef enum {
GABC_NORMAL,
GABC_AT_PROTRUSION_FACTOR,
GABC_IN_PROTRUSION_FACTOR
} gabc_write_state;
static gabc_write_state write_state;
static __inline char pitch_letter(const char height) {
char result = height + 'a' - LOWEST_PITCH;
if (result == 'o') {
return 'p';
}
return result;
}
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
static __inline void unsupported(const char *fn, const int line,
const char *type, const char *value)
{
gregorio_messagef(fn, VERBOSITY_ASSERTION, line, _("unsupported %s %s"),
type, value);
}
/* LCOV_EXCL_STOP */
/*
* Output one attribute, allowing for multi-line values
*/
static void gabc_write_str_attribute(FILE *f, const char *name,
const char *attr)
{
if (attr) {
fprintf(f, "%s: %s%s;\n", name, attr, strchr(attr, '\n') ? ";" : "");
}
}
/*
*
* Then we start the functions made to write the text of the syllable. See
* comments on struct.h and struct-utils.c to understand more deeply.
*
* This first function will be called each time we will encounter a
* gregorio_character which is the beginning of a style.
*
*/
static void gabc_write_begin(FILE *f, grestyle_style style)
{
switch (style) {
case ST_ITALIC:
fprintf(f, "");
break;
case ST_COLORED:
fprintf(f, "");
break;
case ST_SMALL_CAPS:
fprintf(f, "");
break;
case ST_BOLD:
fprintf(f, "");
break;
case ST_FORCED_CENTER:
fprintf(f, "{");
break;
case ST_TT:
fprintf(f, "");
break;
case ST_UNDERLINED:
fprintf(f, "");
break;
case ST_ELISION:
fprintf(f, "");
break;
case ST_PROTRUSION_FACTOR:
write_state = GABC_AT_PROTRUSION_FACTOR;
break;
case ST_INITIAL:
case ST_CENTER:
case ST_FIRST_WORD:
case ST_FIRST_SYLLABLE:
case ST_FIRST_SYLLABLE_INITIAL:
case ST_PROTRUSION:
/* nothing should be emitted for these */
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_begin", __LINE__, "style",
grestyle_style_to_string(style));
break;
/* LCOV_EXCL_STOP */
}
}
/*
*
* This function is about the same but for ends of styles.
*
*/
static void gabc_write_end(FILE *f, grestyle_style style)
{
switch (style) {
case ST_ITALIC:
fprintf(f, "
");
break;
case ST_COLORED:
fprintf(f, "");
break;
case ST_SMALL_CAPS:
fprintf(f, "");
break;
case ST_BOLD:
fprintf(f, "");
break;
case ST_FORCED_CENTER:
fprintf(f, "}");
break;
case ST_TT:
fprintf(f, "");
break;
case ST_UNDERLINED:
fprintf(f, "");
break;
case ST_ELISION:
fprintf(f, "");
break;
case ST_PROTRUSION_FACTOR:
if (write_state == GABC_IN_PROTRUSION_FACTOR) {
fprintf(f, ">");
}
break;
case ST_INITIAL:
case ST_CENTER:
case ST_FIRST_WORD:
case ST_FIRST_SYLLABLE:
case ST_FIRST_SYLLABLE_INITIAL:
case ST_PROTRUSION:
/* nothing should be emitted for these */
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_end", __LINE__, "style",
grestyle_style_to_string(style));
break;
/* LCOV_EXCL_STOP */
}
}
/*
*
* This function writes the special chars. As the specials chars are
* represented simply in gabc, this function is very simple, but for TeX output
* modules, this may be.. a little more difficult.
*
*/
static void gabc_write_special_char(FILE *f, const grewchar *first_char)
{
fprintf(f, "");
gregorio_print_unistring(f, first_char);
fprintf(f, "");
}
/*
*
* This functions writes verbatim output... but as the previous one it is very
* simple.
*
*/
static void gabc_write_verb(FILE *f, const grewchar *first_char)
{
if (write_state == GABC_AT_PROTRUSION_FACTOR) {
/* this is an auto protrusion, so ignore it */
write_state = GABC_NORMAL;
} else {
fprintf(f, "");
gregorio_print_unistring(f, first_char);
fprintf(f, "");
}
}
/*
*
* The function called when we will encounter a character. There may be other
* representations of the character (for example for Omega), so it is necessary
* to have such a function defined in each module.
*
*/
static void gabc_print_char(FILE *f, const grewchar to_print)
{
if (write_state == GABC_AT_PROTRUSION_FACTOR) {
write_state = GABC_IN_PROTRUSION_FACTOR;
if (to_print == 'd') {
fprintf(f, "");
break;
}
}
/*
*
* The function that writes a key change... quite simple.
*
*/
static void gabc_write_clef(FILE *f, gregorio_clef_info clef)
{
fprintf(f, "%c%s%d", clef.clef == CLEF_C? 'c' : 'f', clef.flatted? "b" : "",
clef.line);
if (clef.secondary_line) {
fprintf(f, "@%c%s%d", clef.secondary_clef == CLEF_C? 'c' : 'f',
clef.secondary_flatted? "b" : "", clef.secondary_line);
}
}
/*
*
* The function that writes spaces, called when we encounter one.
*
*/
static void gabc_write_space(FILE *f, gregorio_space type, char *factor,
bool next_is_space)
{
switch (type) {
case SP_NEUMATIC_CUT:
if (next_is_space) {
/* if the following is not a space, we omit this because the
* code always puts a "/" between elements unless there is some
* other space there */
fprintf (f, "/");
}
break;
case SP_LARGER_SPACE:
fprintf(f, "//");
break;
case SP_GLYPH_SPACE:
fprintf(f, " ");
break;
case SP_AD_HOC_SPACE:
fprintf(f, "/[%s]", factor);
break;
case SP_NEUMATIC_CUT_NB:
fprintf(f, "!/");
break;
case SP_LARGER_SPACE_NB:
fprintf(f, "!//");
break;
case SP_GLYPH_SPACE_NB:
fprintf(f, "! ");
break;
case SP_AD_HOC_SPACE_NB:
fprintf(f, "!/[%s]", factor);
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_space", __LINE__, "space type",
gregorio_space_to_string(type));
break;
/* LCOV_EXCL_STOP */
}
}
/*
*
* A function to write a bar.
*
*/
static void gabc_write_bar(FILE *f, gregorio_bar type)
{
switch (type) {
case B_VIRGULA:
fprintf(f, "`");
break;
case B_DIVISIO_MINIMA:
fprintf(f, ",");
break;
case B_DIVISIO_MINOR:
fprintf(f, ";");
break;
case B_DIVISIO_MAIOR:
fprintf(f, ":");
break;
case B_DIVISIO_FINALIS:
fprintf(f, "::");
break;
case B_DIVISIO_MINOR_D1:
fprintf(f, ";1");
break;
case B_DIVISIO_MINOR_D2:
fprintf(f, ";2");
break;
case B_DIVISIO_MINOR_D3:
fprintf(f, ";3");
break;
case B_DIVISIO_MINOR_D4:
fprintf(f, ";4");
break;
case B_DIVISIO_MINOR_D5:
fprintf(f, ";5");
break;
case B_DIVISIO_MINOR_D6:
fprintf(f, ";6");
break;
case B_DIVISIO_MINOR_D7:
fprintf(f, ";7");
break;
case B_DIVISIO_MINOR_D8:
fprintf(f, ";8");
break;
case B_VIRGULA_HIGH:
fprintf(f, "`0");
break;
case B_DIVISIO_MINIMA_HIGH:
fprintf(f, ",0");
break;
case B_DIVISIO_MAIOR_DOTTED:
fprintf(f, ":?");
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_bar", __LINE__, "bar type",
gregorio_bar_to_string(type));
break;
/* LCOV_EXCL_STOP */
}
}
/* writing the signs of a bar */
static void gabc_write_bar_signs(FILE *f, gregorio_sign type)
{
switch (type) {
case _V_EPISEMA:
fprintf(f, "'");
break;
case _V_EPISEMA_BAR_H_EPISEMA:
fprintf(f, "'_");
break;
case _BAR_H_EPISEMA:
fprintf(f, "_");
break;
case _NO_SIGN:
/* if there's no sign, don't emit anything */
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_bar_signs", __LINE__, "bar signs",
gregorio_sign_to_string(type));
break;
/* LCOV_EXCL_STOP */
}
}
static void gabc_hepisema(FILE *f, const char *prefix, bool connect,
grehepisema_size size)
{
fprintf(f, "_%s", prefix);
if (!connect) {
fprintf(f, "2");
}
switch (size) {
case H_SMALL_LEFT:
fprintf(f, "3");
break;
case H_SMALL_CENTRE:
fprintf(f, "4");
break;
case H_SMALL_RIGHT:
fprintf(f, "5");
break;
case H_NORMAL:
/* nothing to print */
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_hepisema", __LINE__, "hepisema size",
grehepisema_size_to_string(size));
break;
/* LCOV_EXCL_STOP */
}
}
static const char *vepisema_position(gregorio_note *note)
{
if (!note->v_episema_height) {
return "";
}
if (note->v_episema_height < note->u.note.pitch) {
return "0";
}
return "1";
}
static const char *mora_vposition(gregorio_note *note)
{
switch (note->mora_vposition) {
case VPOS_AUTO:
return "";
case VPOS_ABOVE:
return "1";
case VPOS_BELOW:
return "0";
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("mora_vposition", __LINE__, "vposition",
gregorio_vposition_to_string(note->mora_vposition));
return "";
/* LCOV_EXCL_STOP */
}
}
static void write_note_heuristics(FILE *f, gregorio_note *note) {
switch (note->high_ledger_specificity) {
case LEDGER_EXPLICIT:
fprintf(f, "[hl:%c]", note->high_ledger_line? '1' : '0');
break;
case LEDGER_EXPLICITLY_DRAWN:
fprintf(f, "[oll:%c]", note->high_ledger_line? '1' : '0');
break;
default:
break;
}
switch (note->low_ledger_specificity) {
case LEDGER_EXPLICIT:
fprintf(f, "[ll:%c]", note->low_ledger_line? '1' : '0');
break;
case LEDGER_EXPLICITLY_DRAWN:
fprintf(f, "[ull:%c]", note->low_ledger_line? '1' : '0');
break;
default:
break;
}
}
typedef struct glyph_context {
gregorio_syllable *syllable;
gregorio_element *element;
unsigned short he_adjustment_index[2];
} glyph_context;
/*
*
* The function that writes one gregorio_note.
*
*/
static void gabc_write_gregorio_note(FILE *f, gregorio_note *note,
const bool is_quadratum)
{
char shape;
gregorio_assert(note, gabc_write_gregorio_note, "call with NULL argument",
return);
gregorio_assert(note->type == GRE_NOTE, gabc_write_gregorio_note,
"call with argument which type is not GRE_NOTE", return);
shape = note->u.note.shape;
switch (shape) {
/* first we write the letters that determine the shapes */
case S_PUNCTUM:
if (is_quadratum) {
fprintf(f, "%cq", pitch_letter(note->u.note.pitch));
} else {
fprintf(f, "%c", pitch_letter(note->u.note.pitch));
}
break;
case S_PUNCTUM_INCLINATUM_ASCENDENS:
fprintf(f, "%c1", toupper((unsigned char)pitch_letter(note->u.note.pitch)));
break;
case S_PUNCTUM_INCLINATUM_DESCENDENS:
fprintf(f, "%c0", toupper((unsigned char)pitch_letter(note->u.note.pitch)));
break;
case S_PUNCTUM_INCLINATUM_DEMINUTUS:
if (note->next) {
fprintf(f, "%c~", toupper((unsigned char)pitch_letter(note->u.note.pitch)));
} else {
fprintf(f, "%c", toupper((unsigned char)pitch_letter(note->u.note.pitch)));
}
break;
case S_PUNCTUM_INCLINATUM_AUCTUS:
fprintf(f, "%c", toupper((unsigned char)pitch_letter(note->u.note.pitch)));
break;
case S_FLAT:
fprintf(f, "%cx", pitch_letter(note->u.note.pitch));
break;
case S_NATURAL:
fprintf(f, "%cy", pitch_letter(note->u.note.pitch));
break;
case S_SHARP:
fprintf(f, "%c#", pitch_letter(note->u.note.pitch));
break;
case S_VIRGA:
fprintf(f, "%cv", pitch_letter(note->u.note.pitch));
break;
case S_VIRGA_REVERSA:
fprintf(f, "%cV", pitch_letter(note->u.note.pitch));
break;
case S_ORISCUS_ASCENDENS:
fprintf(f, "%co1", pitch_letter(note->u.note.pitch));
break;
case S_ORISCUS_DESCENDENS:
fprintf(f, "%co0", pitch_letter(note->u.note.pitch));
break;
case S_ORISCUS_DEMINUTUS:
fprintf(f, "%co", pitch_letter(note->u.note.pitch));
/* Note: the DEMINUTUS is also in the liquescentia */
break;
case S_QUILISMA:
if (is_quadratum) {
fprintf(f, "%cW", pitch_letter(note->u.note.pitch));
} else {
fprintf(f, "%cw", pitch_letter(note->u.note.pitch));
}
break;
case S_LINEA:
fprintf(f, "%c=", pitch_letter(note->u.note.pitch));
break;
case S_LINEA_PUNCTUM:
fprintf(f, "%cR", pitch_letter(note->u.note.pitch));
break;
case S_ORISCUS_SCAPUS_ASCENDENS:
fprintf(f, "%cO1", pitch_letter(note->u.note.pitch));
break;
case S_ORISCUS_SCAPUS_DESCENDENS:
fprintf(f, "%cO0", pitch_letter(note->u.note.pitch));
break;
case S_STROPHA:
case S_STROPHA_AUCTA:
fprintf(f, "%cs", pitch_letter(note->u.note.pitch));
break;
default:
/* includes S_BIVIRGA, S_TRIVIRGA, S_DISTROPHA, and S_TRISTROPHA */
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_note", __LINE__, "shape",
gregorio_shape_to_string(shape));
fprintf(f, "%c", pitch_letter(note->u.note.pitch));
break;
/* LCOV_EXCL_STOP */
}
if (note->u.note.is_cavum) {
fprintf(f, "r");
}
switch (note->signs) {
case _PUNCTUM_MORA:
fprintf(f, ".%s", mora_vposition(note));
break;
case _AUCTUM_DUPLEX:
fprintf(f, "..");
break;
case _V_EPISEMA:
fprintf(f, "'%s", vepisema_position(note));
break;
case _V_EPISEMA_PUNCTUM_MORA:
fprintf(f, "'%s.%s", vepisema_position(note), mora_vposition(note));
break;
case _V_EPISEMA_AUCTUM_DUPLEX:
fprintf(f, "'%s..", vepisema_position(note));
break;
case _NO_SIGN:
/* if there's no sign, don't emit anything */
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_note", __LINE__, "shape signs",
gregorio_sign_to_string(note->signs));
break;
/* LCOV_EXCL_STOP */
}
switch (note->special_sign) {
case _ACCENTUS:
fprintf(f, "r1");
break;
case _ACCENTUS_REVERSUS:
fprintf(f, "r2");
break;
case _CIRCULUS:
fprintf(f, "r3");
break;
case _SEMI_CIRCULUS:
fprintf(f, "r4");
break;
case _SEMI_CIRCULUS_REVERSUS:
fprintf(f, "r5");
break;
case _MUSICA_FICTA_FLAT:
fprintf(f, "r6");
break;
case _MUSICA_FICTA_NATURAL:
fprintf(f, "r7");
break;
case _MUSICA_FICTA_SHARP:
fprintf(f, "r8");
break;
case _NO_SIGN:
/* if there's no sign, don't emit anything */
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_note", __LINE__, "special sign",
gregorio_sign_to_string(note->special_sign));
break;
/* LCOV_EXCL_STOP */
}
if (note->h_episema_above == HEPISEMA_AUTO
&& note->h_episema_below == HEPISEMA_AUTO) {
gabc_hepisema(f, "", note->h_episema_above_connect,
note->h_episema_above_size);
} else {
if (note->h_episema_below == HEPISEMA_FORCED) {
gabc_hepisema(f, "0", note->h_episema_below_connect,
note->h_episema_below_size);
}
if (note->h_episema_above == HEPISEMA_FORCED) {
gabc_hepisema(f, "1", note->h_episema_above_connect,
note->h_episema_above_size);
}
}
write_note_heuristics(f, note);
if (note->texverb) {
fprintf(f, "[nv:%s]", gregorio_texverb(note->texverb));
}
}
static void get_next_hepisema_adjustments(unsigned short *adjustment_index,
const gregorio_syllable *syllable, const gregorio_element *element,
const gregorio_glyph *glyph, const gregorio_note *note)
{
while (note) {
note = note->next;
if (!note) {
while (glyph) {
glyph = glyph->next;
if (!glyph) {
while (element) {
element = element->next;
if (!element) {
syllable = syllable->next_syllable;
if (syllable) {
element = syllable->elements[0];
}
}
if (element && element->type == GRE_ELEMENT) {
glyph = element->u.first_glyph;
break;
}
}
}
if (glyph && glyph->type == GRE_GLYPH) {
note = glyph->u.notes.first_note;
break;
}
}
}
if (note && note->type == GRE_NOTE) {
break;
}
}
if (note) {
adjustment_index[SO_OVER] = note->he_adjustment_index[SO_OVER];
adjustment_index[SO_UNDER] = note->he_adjustment_index[SO_UNDER];
} else {
adjustment_index[SO_OVER] = 0;
adjustment_index[SO_UNDER] = 0;
}
}
static __inline void emit_hepisema_adjustment(FILE *const f,
const gregorio_note *const note, const gregorio_sign_orientation index,
const char which, const bool open_brace)
{
gregorio_hepisema_adjustment *adj = gregorio_get_hepisema_adjustment(
note->he_adjustment_index[index]);
fprintf(f, "[%ch", which);
if (adj->vbasepos || adj->nudge) {
fputc(':', f);
switch (adj->vbasepos) {
case HVB_AUTO:
break;
case HVB_MIDDLE:
fputc('m', f);
break;
case HVB_O_LOW:
if (index == SO_OVER) {
fputc('l', f);
} else {
fprintf(f, "ol");
}
break;
case HVB_O_HIGH:
if (index == SO_OVER) {
fputc('h', f);
} else {
fprintf(f, "oh");
}
break;
case HVB_U_LOW:
if (index == SO_UNDER) {
fputc('l', f);
} else {
fprintf(f, "ul");
}
break;
case HVB_U_HIGH:
if (index == SO_UNDER) {
fputc('h', f);
} else {
fprintf(f, "uh");
}
break;
}
if (adj->nudge) {
fprintf(f, "%s", adj->nudge);
}
}
if (open_brace) {
fputc('{', f);
}
fputc(']', f);
}
static __inline void open_hepisema_adjustment(FILE *const f,
const gregorio_note *const note,
const unsigned short *const prev_adjustment_index,
const unsigned short *const next_adjustment_index,
const gregorio_sign_orientation index, const char which)
{
const unsigned short adjustment_index =
note->he_adjustment_index[index];
if (adjustment_index
&& adjustment_index != prev_adjustment_index[index]
&& adjustment_index == next_adjustment_index[index]) {
emit_hepisema_adjustment(f, note, index, which, true);
}
}
static __inline void close_hepisema_adjustment(FILE *const f,
const gregorio_note *const note,
const unsigned short *const prev_adjustment_index,
const unsigned short *const next_adjustment_index,
const gregorio_sign_orientation index, const char which)
{
const unsigned short adjustment_index =
note->he_adjustment_index[index];
if (adjustment_index) {
if (adjustment_index != next_adjustment_index[index]) {
if (adjustment_index == prev_adjustment_index[index]) {
fprintf(f, "[%ch}]", which);
} else {
emit_hepisema_adjustment(f, note, index, which, false);
}
}
}
}
/*
*
* The function that writes one glyph. If it is really a glyph (meaning not a
* space or an alteration), we just do like always, a loop on the notes and a
* call to the function that writes one note on each of them.
*
*/
static void gabc_write_gregorio_glyph(FILE *f, gregorio_glyph *glyph,
glyph_context *context)
{
unsigned short next_adjustment_index[2] = { 0, 0 };
gregorio_note *current_note;
gregorio_assert(glyph, gabc_write_gregorio_glyph, "call with NULL argument",
return);
switch (glyph->type) {
case GRE_TEXVERB_GLYPH:
if (glyph->texverb) {
fprintf(f, "[gv:%s]", gregorio_texverb(glyph->texverb));
}
break;
case GRE_SPACE:
if (glyph->next) {
switch (glyph->u.misc.unpitched.info.space) {
case SP_ZERO_WIDTH:
fprintf(f, "!");
break;
case SP_HALF_SPACE:
fprintf(f, "/0");
break;
case SP_INTERGLYPH_SPACE:
fprintf(f, "/!");
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_glyph", __LINE__, "space type",
gregorio_space_to_string(
glyph->u.misc.unpitched.info.space));
break;
/* LCOV_EXCL_STOP */
}
} else {
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
gregorio_fail(gabc_write_gregorio_glyph, "bad space");
/* LCOV_EXCL_STOP */
}
break;
case GRE_GLYPH:
if (is_initio_debilis(glyph->u.notes.liquescentia)) {
fprintf(f, "-");
} else if (is_fused(glyph->u.notes.liquescentia)) {
fprintf(f, "@");
}
current_note = glyph->u.notes.first_note;
while (current_note) {
get_next_hepisema_adjustments(next_adjustment_index,
context->syllable, context->element, glyph, current_note);
open_hepisema_adjustment(f, current_note,
context->he_adjustment_index, next_adjustment_index,
SO_OVER, 'o');
open_hepisema_adjustment(f, current_note,
context->he_adjustment_index, next_adjustment_index,
SO_UNDER, 'u');
/* third argument necessary for the special shape pes quadratum */
gabc_write_gregorio_note(f, current_note,
glyph->u.notes.glyph_type == G_PES_QUADRATUM
&& current_note == glyph->u.notes.first_note);
close_hepisema_adjustment(f, current_note,
context->he_adjustment_index, next_adjustment_index,
SO_OVER, 'o');
close_hepisema_adjustment(f, current_note,
context->he_adjustment_index, next_adjustment_index,
SO_UNDER, 'u');
context->he_adjustment_index[SO_OVER] =
current_note->he_adjustment_index[SO_OVER];
context->he_adjustment_index[SO_UNDER] =
current_note->he_adjustment_index[SO_UNDER];
current_note = current_note->next;
}
gabc_write_end_liquescentia(f, glyph->u.notes.liquescentia);
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_glyph", __LINE__, "glyph type",
gregorio_type_to_string(glyph->type));
break;
/* LCOV_EXCL_STOP */
}
}
/*
*
* To write an element, first we check the type of the element (if it is a bar,
* etc.), and if it is really an element, we make a loop on the list of glyphs
* inside the neume, and for each of them we call the function that will write
* one glyph.
*
*/
static void gabc_write_gregorio_element(FILE *f, gregorio_element *element,
glyph_context *context)
{
gregorio_glyph *current_glyph;
gregorio_assert(element, gabc_write_gregorio_element,
"call with NULL argument", return);
current_glyph = element->u.first_glyph;
switch (element->type) {
case GRE_ELEMENT:
while (current_glyph) {
gabc_write_gregorio_glyph(f, current_glyph, context);
current_glyph = current_glyph->next;
}
break;
case GRE_TEXVERB_ELEMENT:
if (element->texverb) {
fprintf(f, "[ev:%s]", gregorio_texverb(element->texverb));
}
break;
case GRE_ALT:
if (element->texverb) {
fprintf(f, "[alt:%s]", gregorio_texverb(element->texverb));
}
break;
case GRE_SPACE:
gabc_write_space(f, element->u.misc.unpitched.info.space,
element->u.misc.unpitched.info.ad_hoc_space_factor,
element->next && element->next->type == GRE_SPACE);
break;
case GRE_BAR:
gabc_write_bar(f, element->u.misc.unpitched.info.bar);
gabc_write_bar_signs(f, element->u.misc.unpitched.special_sign);
break;
case GRE_CLEF:
gabc_write_clef(f, element->u.misc.clef);
break;
case GRE_END_OF_LINE:
if (element->u.misc.unpitched.info.eol_ragged) {
fprintf(f, "Z");
} else {
fprintf(f, "z");
}
if (element->u.misc.unpitched.info.eol_forces_custos) {
fprintf(f, element->u.misc.unpitched.info.eol_forces_custos_on? "+"
: "-");
}
break;
case GRE_CUSTOS:
if (element->u.misc.pitched.force_pitch) {
fprintf(f, "%c+", pitch_letter(element->u.misc.pitched.pitch));
} else {
fprintf(f, "z0");
}
break;
case GRE_SUPPRESS_CUSTOS:
fprintf(f, "[nocustos]");
break;
case GRE_NLBA:
switch (element->u.misc.unpitched.info.nlba) {
case NLBA_BEGINNING:
fprintf(f, "");
break;
case NLBA_END:
fprintf(f, "");
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_element", __LINE__, "nlba type",
gregorio_nlba_to_string(element->u.misc.unpitched.info.nlba));
break;
/* LCOV_EXCL_STOP */
}
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
unsupported("gabc_write_gregorio_element", __LINE__, "element type",
gregorio_type_to_string(element->type));
break;
/* LCOV_EXCL_STOP */
}
}
/*
*
* Here is defined the function that will write the list of gregorio_elements.
* It is very simple: it makes a loop in which it calls a function that writes
* one element.
*
*/
static bool gabc_write_gregorio_elements(FILE *f, gregorio_element *element,
glyph_context *context)
{
bool linebreak_or_bar_in_element = false;
while (element) {
context->element = element;
gabc_write_gregorio_element(f, element, context);
/* we don't want a bar after an end of line */
if (element->type != GRE_END_OF_LINE
&& (element->type != GRE_SPACE
|| element->u.misc.unpitched.info.space == SP_NEUMATIC_CUT)
&& element->next && element->next->type == GRE_ELEMENT) {
fprintf(f, "/");
}
if (element->type == GRE_END_OF_LINE || element->type == GRE_BAR)
{
linebreak_or_bar_in_element = true;
}
element = element->next;
}
return linebreak_or_bar_in_element;
}
/*
*
* Here it goes, we are writing a gregorio_syllable.
*
*/
static void gabc_write_gregorio_syllable(FILE *f, gregorio_syllable *syllable,
glyph_context *context)
{
bool linebreak_or_bar_in_element;
gregorio_assert(syllable, gabc_write_gregorio_syllable,
"call with NULL argument", return);
write_state = GABC_NORMAL;
if (syllable->no_linebreak_area == NLBA_BEGINNING) {
fprintf(f, "");
}
if (syllable->euouae == EUOUAE_BEGINNING) {
fprintf(f, "");
}
if (syllable->clear) {
fprintf(f, "");
}
if (syllable->text) {
/* we call the magic function (defined in struct_utils.c), that will
* write our text. */
gregorio_write_text(WTP_NORMAL, syllable->text, f, &gabc_write_verb,
&gabc_print_char, &gabc_write_begin, &gabc_write_end,
&gabc_write_special_char);
}
if (syllable->translation) {
fprintf(f, "[");
gregorio_write_text(WTP_NORMAL, syllable->translation, f,
&gabc_write_verb, &gabc_print_char, &gabc_write_begin,
&gabc_write_end, &gabc_write_special_char);
fprintf(f, "]");
} else if (syllable->translation_type == TR_WITH_CENTER_END) {
fprintf(f, "[/]");
}
if (syllable->euouae == EUOUAE_END) {
fprintf(f, "");
}
if (syllable->no_linebreak_area == NLBA_END) {
fprintf(f, "");
}
fprintf(f, "(");
/* we write all the elements of the syllable. */
linebreak_or_bar_in_element = gabc_write_gregorio_elements(f, syllable->elements[0], context);
if (linebreak_or_bar_in_element)
{
fprintf(f, ")\n");
} else {
if (syllable->position == WORD_END
|| syllable->position == WORD_ONE_SYLLABLE
|| gregorio_is_only_special(syllable->elements[0]))
{
fprintf(f, ") ");
} else {
fprintf(f, ")");
}
}
}
/*
*
* This is the top function, the one called when we want to write a
* gregorio_score in gabc.
*
*/
void gabc_write_score(FILE *f, gregorio_score *score)
{
glyph_context context;
gregorio_syllable *syllable;
gregorio_header *header;
gregorio_assert(f, gabc_write_score, "call with NULL file", return);
context.he_adjustment_index[0] = 0;
context.he_adjustment_index[1] = 0;
for (header = score->headers; header; header = header->next) {
gabc_write_str_attribute(f, header->name, header->value);
}
/* And since the gabc is generated by this program, note this. */
fprintf(f, "generated-by: %s %s;\n", "gregorio", GREGORIO_VERSION);
gregorio_assert(score->number_of_voices == 1, gabc_write_score,
"gregorio_score seems to be empty", return);
fprintf(f, "%%%%\n");
/* at present we only allow for one clef at the start of the gabc */
if (score->first_voice_info) {
fprintf(f, "(");
gabc_write_clef(f, score->first_voice_info->initial_clef);
fprintf(f, ")");
}
syllable = score->first_syllable;
/* the we write every syllable */
while (syllable) {
context.syllable = syllable;
gabc_write_gregorio_syllable(f, syllable, &context);
syllable = syllable->next_syllable;
}
fprintf(f, "\n");
}
/* And that's it... not really hard isn't it? */