/*
* Gregorio is a program that translates gabc files to GregorioTeX
* This file implements the score parser.
*
* Gregorio score determination from gabc utilities.
* Copyright (C) 2016-2019 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 .
*/
#include "config.h"
#include
#include
#include
#include
#include "bool.h"
#include "struct.h"
#include "struct_iter.h"
#include "gabc.h"
#include "gabc-score-determination.h"
#include "messages.h"
#include "support.h"
void gabc_suppress_extra_custos_at_linebreak(gregorio_score *score)
{
gregorio_syllable *syllable;
gregorio_element **custos = NULL;
for (syllable = score->first_syllable; syllable;
syllable = syllable->next_syllable) {
gregorio_element **element;
for (element = syllable->elements; element && *element;
element = &((*element)->next)) {
switch ((*element)->type) {
case GRE_CUSTOS:
if (!((*element)->u.misc.pitched.force_pitch)) {
/* save the encountered non-forced custos */
custos = element;
} else {
/* forget the (previous) custos */
custos = NULL;
}
break;
case GRE_CLEF:
case GRE_BAR:
/* remember the custos if only these appear before linebreak */
break;
case GRE_END_OF_LINE:
if (custos) {
/* suppress the custos when linebreak follows */
gregorio_free_one_element(custos);
}
/* fall through */
default:
/* forget the custos */
custos = NULL;
break;
}
}
}
}
void gabc_fix_custos_pitches(gregorio_score *score_to_check)
{
gregorio_syllable *current_syllable;
gregorio_element *current_element;
gregorio_element *custos_element;
int newkey;
int current_key;
if (!score_to_check || !score_to_check->first_syllable
|| !score_to_check->first_voice_info) {
return;
}
current_key = gregorio_calculate_new_key(
score_to_check->first_voice_info->initial_clef);
for (current_syllable = score_to_check->first_syllable; current_syllable;
current_syllable = current_syllable->next_syllable) {
for (current_element = (current_syllable->elements)[0]; current_element;
current_element = current_element->next) {
if (current_element->type == GRE_CLEF) {
newkey = gregorio_calculate_new_key(
current_element->u.misc.clef);
current_element->u.misc.clef.pitch_difference =
(signed char) newkey - (signed char) current_key;
current_key = newkey;
}
}
}
custos_element = NULL;
for (current_syllable = score_to_check->first_syllable; current_syllable;
current_syllable = current_syllable->next_syllable) {
for (current_element = (current_syllable->elements)[0]; current_element;
current_element = current_element->next) {
switch (current_element->type) {
case GRE_CUSTOS:
if (current_element->u.misc.pitched.force_pitch) {
/* forget about the preceding custos if a forced one is
* encountered */
custos_element = NULL;
} else {
/* the pitch is not forced, so it may need to be adjusted */
custos_element = current_element;
custos_element->u.misc.pitched.pitch =
gregorio_determine_next_pitch(current_syllable,
current_element, NULL, NULL);
}
break;
case GRE_ELEMENT:
/* if it's an element, forget any preceding custos */
custos_element = NULL;
break;
case GRE_CLEF:
if (custos_element) {
/* adjust the preceding custos for the clef */
custos_element->u.misc.pitched.pitch =
gregorio_adjust_pitch_into_staff(score_to_check,
custos_element->u.misc.pitched.pitch
- current_element->u.misc.clef.pitch_difference);
}
break;
default:
/* to prevent the warning */
break;
}
}
}
}
/*
* A function that checks the score integrity.
*/
bool gabc_check_score_integrity(gregorio_score *score_to_check)
{
bool good = true;
gregorio_assert(score_to_check, check_score_integrity, "score is NULL",
return false);
if (score_to_check->first_syllable
&& score_to_check->first_syllable->elements
&& *(score_to_check->first_syllable->elements)) {
gregorio_character *ch;
if ((score_to_check->first_syllable->elements)[0]->type
== GRE_END_OF_LINE) {
gregorio_message(
"line break is not supported on the first syllable",
"check_score_integrity", VERBOSITY_ERROR, 0);
good = false;
}
if (gregorio_get_clef_change(score_to_check->first_syllable)) {
gregorio_message(
"clef change is not supported on the first syllable",
"check_score_integrity", VERBOSITY_ERROR, 0);
good = false;
}
/* check first syllable for elision at the beginning */
for (ch = score_to_check->first_syllable->text; ch;
ch = ch->next_character) {
if (ch->is_character) {
break;
} else if (ch->cos.s.style == ST_VERBATIM
|| ch->cos.s.style == ST_SPECIAL_CHAR) {
break;
} else if (ch->cos.s.style == ST_ELISION) {
gregorio_message(
_("score initial may not be in an elision"),
"check_score_integrity", VERBOSITY_ERROR, 0);
break;
}
}
}
return good;
}
/*
* Another function to be improved: this one checks the validity of the voice_infos.
*/
bool gabc_check_infos_integrity(gregorio_score *score_to_check)
{
if (!score_to_check->name) {
gregorio_message(_("no name specified, put `name:...;' at the "
"beginning of the file, can be dangerous with some output "
"formats"), "det_score", VERBOSITY_WARNING, 0);
}
return true;
}
static void set_oriscus_descending(const gregorio_note_iter_position *const p,
void *const ignored __attribute__((unused)))
{
switch(p->note->u.note.shape) {
case S_ORISCUS_UNDETERMINED:
p->note->u.note.shape = S_ORISCUS_DESCENDENS;
break;
case S_ORISCUS_SCAPUS_UNDETERMINED:
p->note->u.note.shape = S_ORISCUS_SCAPUS_DESCENDENS;
gregorio_assert_only(p->glyph->u.notes.glyph_type
!= G_PES_DESCENDENS_ORISCUS, set_oriscus_descending,
"glyph type should not be G_PES_DESCENDENS_ORISCUS");
if (p->glyph->u.notes.glyph_type == G_PES_ASCENDENS_ORISCUS) {
p->glyph->u.notes.glyph_type = G_PES_DESCENDENS_ORISCUS;
}
break;
default:
break;
}
}
static void set_oriscus_ascending(const gregorio_note_iter_position *const p,
void *const ignored __attribute__((unused)))
{
switch(p->note->u.note.shape) {
case S_ORISCUS_UNDETERMINED:
p->note->u.note.shape = S_ORISCUS_ASCENDENS;
break;
case S_ORISCUS_SCAPUS_UNDETERMINED:
p->note->u.note.shape = S_ORISCUS_SCAPUS_ASCENDENS;
gregorio_assert_only(p->glyph->u.notes.glyph_type
!= G_PES_DESCENDENS_ORISCUS, set_oriscus_ascending,
"glyph type should not be G_PES_DESCENDENS_ORISCUS");
break;
default:
break;
}
}
/* data must be (gregorio_note_iter_position *) */
static void oriscus_orientation_visit(
const gregorio_note_iter_position *const p, void *const data)
{
gregorio_note *const note = p->note;
gregorio_note_iter_position *const oriscus =
(gregorio_note_iter_position *const)data;
if (oriscus->note && note->u.note.pitch != oriscus->note->u.note.pitch) {
if (note->u.note.pitch <= oriscus->note->u.note.pitch) {
/* descending (or undetermined oriscus in unison) */
gregorio_from_note_to_note(oriscus, p, false,
set_oriscus_descending, NULL, GRESTRUCT_NONE, NULL);
} else {
/* ascending */
gregorio_from_note_to_note(oriscus, p, false,
set_oriscus_ascending, NULL, GRESTRUCT_NONE, NULL);
}
oriscus->syllable = NULL,
oriscus->element = NULL,
oriscus->note = NULL;
oriscus->glyph = NULL;
}
if (!oriscus->note) {
switch (note->u.note.shape) {
case S_ORISCUS_UNDETERMINED:
case S_ORISCUS_SCAPUS_UNDETERMINED:
*oriscus = *p;
break;
default:
break;
}
}
}
void gabc_determine_oriscus_orientation(const gregorio_score *const score)
{
gregorio_note_iter_position oriscus = {
/* .syllable = */ NULL,
/* .element = */ NULL,
/* .glyph = */ NULL,
/* .note = */ NULL
};
gregorio_for_each_note(score, oriscus_orientation_visit, NULL,
GRESTRUCT_NONE, &oriscus);
if (oriscus.note) {
gregorio_from_note_to_note(&oriscus, NULL, true,
set_oriscus_descending, NULL, GRESTRUCT_NONE, NULL);
}
}
typedef struct {
gregorio_note_iter_position first, previous;
gregorio_shape orientation;
int running;
int count;
bool unison;
} punctum_inclinatum_vars;
/* data must be (gregorio_shape *) */
static void set_shape(const gregorio_note_iter_position *const p,
void *const data)
{
p->note->u.note.shape = *((gregorio_shape *)data);
}
static __inline void set_punctum_inclinatum_orientation(
punctum_inclinatum_vars *const v)
{
if (v->orientation == S_UNDETERMINED) {
if (v->unison && (v->count > 1 || (v->count == 1 && v->running == 0))) {
v->orientation = S_PUNCTUM_INCLINATUM_STANS;
} else if (v->running > 0) {
v->orientation = S_PUNCTUM_INCLINATUM_ASCENDENS;
} else {
v->orientation = S_PUNCTUM_INCLINATUM_DESCENDENS;
}
}
}
static __inline void finalize_punctum_inclinatum_orientation(
punctum_inclinatum_vars *const v)
{
if (v->first.note) {
set_punctum_inclinatum_orientation(v);
gregorio_assert_only(v->orientation != S_UNDETERMINED,
punctum_inclinatum_orientation_end_item,
"orientation should not be S_UNDETERMINED");
gregorio_from_note_to_note(&v->first, &v->previous, true, set_shape,
NULL, GRESTRUCT_NONE, &v->orientation);
}
v->first.syllable = NULL;
v->first.element = NULL;
v->first.glyph = NULL;
v->first.note = NULL;
v->unison = true;
}
/* data must be (punctum_inclinatum_vars *) */
static void punctum_inclinatum_orientation_visit(
const gregorio_note_iter_position *const p, void *const data)
{
const gregorio_shape shape = p->note->u.note.shape;
punctum_inclinatum_vars *const v = (punctum_inclinatum_vars *)data;
if (shape == S_PUNCTUM_INCLINATUM_UNDETERMINED) {
if (v->orientation) {
p->note->u.note.shape = v->orientation;
} else {
/* any cases not covered here will not change running */
if (v->previous.note
&& (v->first.note || v->previous.syllable == p->syllable)) {
if (v->previous.note->u.note.pitch
< p->note->u.note.pitch) {
++ v->running;
if (v->count > 0) {
v->unison = false;
}
} else if (v->previous.note->u.note.pitch
> p->note->u.note.pitch) {
-- v->running;
if (v->count > 0) {
v->unison = false;
}
}
}
if (!v->first.note) {
v->first = *p;
}
}
++ v->count;
} else { /* non-inclinatum or determined inclinatum */
bool is_punctum_inclinatum;
/* shape can't be S_PUNCTUM_INCLINATUM_UNDETERMINED here */
switch (shape) {
case S_PUNCTUM_INCLINATUM_ASCENDENS:
v->orientation = S_PUNCTUM_INCLINATUM_ASCENDENS;
is_punctum_inclinatum = true;
break;
case S_PUNCTUM_INCLINATUM_STANS:
v->orientation = S_PUNCTUM_INCLINATUM_STANS;
is_punctum_inclinatum = true;
break;
case S_PUNCTUM_INCLINATUM_DEMINUTUS:
case S_PUNCTUM_INCLINATUM_AUCTUS:
v->unison = false;
/* fall through */
case S_PUNCTUM_INCLINATUM_DESCENDENS:
v->orientation = S_PUNCTUM_INCLINATUM_DESCENDENS;
is_punctum_inclinatum = true;
break;
default:
is_punctum_inclinatum = false;
break;
}
if (v->first.note) {
if (!is_punctum_inclinatum) {
/* if v->first.note is not null,
* then v->previous.note is not null */
if (v->previous.note->u.note.pitch
< p->note->u.note.pitch
&& v->previous.syllable == p->syllable) {
++ v->running;
} else if (v->previous.note->u.note.pitch
> p->note->u.note.pitch) {
-- v->running;
}
}
finalize_punctum_inclinatum_orientation(v);
}
if (is_punctum_inclinatum) {
++ v->count;
/* and leave orientation alone */
} else {
v->running = 0;
v->count = 0;
v->orientation = S_UNDETERMINED;
}
}
v->previous = *p;
}
/* data must be (punctum_inclinatum_vars *) */
static void punctum_inclinatum_orientation_end_item(
const gregorio_note_iter_position *const p __attribute__((__unused__)),
const gregorio_note_iter_item_type item_type,
void *const data)
{
punctum_inclinatum_vars *const v = (punctum_inclinatum_vars *)data;
gregorio_assert_only(item_type == GRESTRUCT_SYLLABLE,
punctum_inclinatum_orientation_end_item,
"item type should be GRESTRUCT_SYLLABLE");
finalize_punctum_inclinatum_orientation(v);
v->count = 0;
}
void gabc_determine_punctum_inclinatum_orientation(
const gregorio_score *const score)
{
punctum_inclinatum_vars v = {
/* .first = */ {
/* .syllable = */ NULL,
/* .element = */ NULL,
/* .glyph = */ NULL,
/* .note = */ NULL
},
/* .previous = */ {
/* .syllable = */ NULL,
/* .element = */ NULL,
/* .glyph = */ NULL,
/* .note = */ NULL
},
/* .orientation = */ S_UNDETERMINED, /* because it's 0 */
/* .running = */ 0,
/* .count = */ 0,
/* .unison = */ true,
};
gregorio_for_each_note(score, punctum_inclinatum_orientation_visit,
punctum_inclinatum_orientation_end_item, GRESTRUCT_SYLLABLE, &v);
if (v.first.note) {
set_punctum_inclinatum_orientation(&v);
gregorio_from_note_to_note(&v.first, &v.previous, true, set_shape, NULL,
GRESTRUCT_NONE, &v.orientation);
}
}
typedef struct note_stack {
gregorio_note *note;
struct note_stack *prev;
} note_stack;
static void note_stack_push(note_stack **const stack, gregorio_note *note) {
note_stack *item = gregorio_malloc(sizeof(note_stack));
item->note = note;
item->prev = *stack;
*stack = item;
}
static gregorio_note *note_stack_pop(note_stack **const stack) {
note_stack *item = *stack;
if (item) {
gregorio_note *note = item->note;
note_stack *prev = item->prev;
free(item);
*stack = prev;
return note;
}
return NULL;
}
static void note_stack_clear(note_stack **const stack) {
note_stack *item = *stack;
while (item) {
note_stack *prev = item->prev;
free(item);
item = prev;
}
*stack = NULL;
}
typedef struct {
note_stack *high, *low;
gregorio_note *prev_note;
signed char high_ledger_line_pitch;
bool running_high, running_low;
} ledger_line_vars;
static __inline void clear_ledger_line_vars(ledger_line_vars *const v) {
note_stack_clear(&v->high);
note_stack_clear(&v->low);
v->prev_note = NULL;
v->running_high = false;
v->running_low = false;
}
static __inline void adjust_ledger(const gregorio_note_iter_position *const p,
const gregorio_ledger_specificity specificity, bool ledger_line,
note_stack **const stack, bool *const running, gregorio_note *prev_note,
const signed char high_ledger_line_pitch,
bool (*extend_ledger)(gregorio_note *, const gregorio_note *,
const gregorio_note *, signed char))
{
if (specificity & LEDGER_DRAWN) {
if (ledger_line) {
gregorio_note *after = p->note;
gregorio_note *note;
/* process from this ledger backwards */
while ((note = note_stack_pop(stack))) {
if (!extend_ledger(note, NULL, after, high_ledger_line_pitch)) {
/* ledger has ended */
break;
}
after = note;
}
*running = true;
} else {
*running = false;
}
note_stack_clear(stack);
} else {
if (*running) {
if (!extend_ledger(p->note, prev_note, NULL,
high_ledger_line_pitch)) {
/* ledger has ended */
note_stack_push(stack, p->note);
*running = false;
}
/* else stack should be empty, keep it that way */
} else {
note_stack_push(stack, p->note);
}
}
}
static bool extend_high_ledger(gregorio_note *const note,
const gregorio_note *const note_before,
const gregorio_note *const note_after,
const signed char high_ledger_line_pitch)
{
bool extend = false;
if (note_before) {
extend = note_before->u.note.pitch < note->u.note.pitch
|| (note_before->u.note.pitch > high_ledger_line_pitch
&& note->u.note.pitch < high_ledger_line_pitch);
} else if (note_after) {
extend = note_after->u.note.pitch < note->u.note.pitch
|| (note_after->u.note.pitch > high_ledger_line_pitch
&& note->u.note.pitch < high_ledger_line_pitch);
}
if (extend) {
note->high_ledger_line = true;
note->high_ledger_specificity = LEDGER_DRAWN;
}
return extend;
}
static bool extend_low_ledger(gregorio_note *const note,
const gregorio_note *const note_before,
const gregorio_note *const note_after,
const signed char high_ledger_line_pitch __attribute__((__unused__)))
{
bool extend = false;
if (note_before) {
extend = note_before->u.note.pitch < note->u.note.pitch
|| (note_before->u.note.pitch > LOW_LEDGER_LINE_PITCH
&& note->u.note.pitch < LOW_LEDGER_LINE_PITCH);
} else if (note_after) {
extend = note_after->u.note.pitch < note->u.note.pitch
|| (note_after->u.note.pitch > LOW_LEDGER_LINE_PITCH
&& note->u.note.pitch < LOW_LEDGER_LINE_PITCH);
}
if (extend) {
note->low_ledger_line = true;
note->low_ledger_specificity = LEDGER_DRAWN;
}
return extend;
}
/* data must be (ledger_line_vars *) */
static void ledger_line_visit(const gregorio_note_iter_position *const p,
void *const data)
{
ledger_line_vars *const v = (ledger_line_vars *)data;
adjust_ledger(p, p->note->high_ledger_specificity, p->note->high_ledger_line,
&v->high, &v->running_high, v->prev_note, v->high_ledger_line_pitch,
&extend_high_ledger);
adjust_ledger(p, p->note->low_ledger_specificity, p->note->low_ledger_line,
&v->low, &v->running_low, v->prev_note, v->high_ledger_line_pitch,
&extend_low_ledger);
v->prev_note = p->note;
}
/* data must be (ledger_line_vars *) */
static void ledger_line_end_item(
const gregorio_note_iter_position *const p __attribute__((__unused__)),
const gregorio_note_iter_item_type item_type, void *const data)
{
if (item_type == GRESTRUCT_ELEMENT) {
clear_ledger_line_vars((ledger_line_vars *)data);
}
}
void gabc_determine_ledger_lines(const gregorio_score *const score)
{
ledger_line_vars v;
memset(&v, 0, sizeof v);
v.high_ledger_line_pitch = score->high_ledger_line_pitch;
gregorio_for_each_note(score, ledger_line_visit, ledger_line_end_item,
GRESTRUCT_ELEMENT, &v);
/* stacks should be cleared by ledger_line_end_item */
}