Previous: Sample Module Code, Up: Sample Module Code


    Copyright (C) 2009, 2010, 2011  David Psenicka
    This file is part of FOMUS.

    FOMUS 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.

    FOMUS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    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 <>. */


   runs after the `divide' module and "unsplits" notes to make them easier to read.  the results are a bit
   unconventional and appropriate for music involving complex rhythms and/or many notes beginning/ending on offbeats. */

// ---------------------------------------- headers
#include <cassert>
#include <algorithm>
#include <vector>
#include <boost/ptr_container/ptr_deque.hpp>

#include <fomus/module.h> // include the API for FOMUS modules
#include <fomus/modutil.h> // includ the API for various utility functions
#include <fomus/ifacedumb.h> // include the API for the built-in `dumb' engine

namespace untie   // tuck away things that don't need to be exported

// ---------------------------------------- setting ids
int dur_range_id, untie_id;

// ---------------------------------------- data structures
/* stores a note object along with a flag indicating whether or not to remove the note. */
struct note {
    module_noteobj n;
    bool remove; // remove this note?
    note(module_noteobj n):n(n), remove(false) {}
    /* in all modules note objects must be given assignments in the order than they were received from
       `module_nextnote()'.  each module has one or more special assign functions that it can use (this module uses
       `divide_assign_unsplit()'.  if no assignment is to be made on a note object then `module_skipassign()' must be
       called on it.  once an assignment has been made, the note object is free for access by other threads.  at this
       point it cannot be used in calls to `module_peeknextnote()', though it can be passed to other functions such as
       `module_time()'. */
    void assign() {
        /* `divide_assign_unsplit()' removes the note object that is passed to it and adjusts the duration of the note
           that is tied to its left.  actions on assignments that involve adding or removing notes are always deferred
           until after the module has finished its processing. */
        if (remove) divide_assign_unsplit(n); // special "unsplit" assign function for divide modules
        else module_skipassign(n); // skip assignment for this note

// ---------------------------------------- typedefs
typedef std::vector<note> notes_vector;
/* boost's `ptr_deque' stores pointers to objects and automatically deletes them when they are erased or the container
   is cleared. */
typedef boost::ptr_deque<notes_vector> chords_deque;

// ---------------------------------------- auxiliary functions
/* processes a chunk of note objects that have been organized into chords and identified as a "group" that belongs
   together.  `chords' is a deque filled with vectors (representing chords, which are filled with note objects).  `upto'
   is an iterator marking one past the last chord in the deque to be processed.  `remove' flags on all appropriate notes
   are set, then all assignments are made and chords are popped from the front of the deque. */
void process(chords_deque& chords, chords_deque::iterator upto)
    for (chords_deque::iterator i(chords.begin()); i != upto; ++i) { // i = a chord
        /* check to see if `untie' setting is turned on for this note */
        assert(module_setting_val(i->front().n, untie_id).type == module_int); // we're expecing an integer (boolean)
        if (!module_setting_ival(i->front().n, untie_id)) continue;
        /* get the `untie-dur-range' setting value. */
        assert(module_setting_val(i->front().n, dur_range_id).type == module_list); // we're expecting this to be a list of length 2
        struct module_list range = module_setting_val(i->front().n, dur_range_id).val.l; // get `untie-dur-range' setting value from note
        assert(range.n == 2);
        /* iterate through all spans of two or more tied chords, starting with the largest span and working towards the
           smallest. */
        for (chords_deque::iterator j(upto - 1); j != i; --j) { // j = a chord (iterating backwards from upto - 1 to i + 1)
            /* find the total duration of all tied notes, adjusting to compensate for any tuplets that are present */
            fomus_rat dur = module_beatstoadjdur(i->front().n, module_endtime(j->front().n) - module_time(i->front().n), -1);
            if (dur < range.vals[0]) break; // if span is too small, break out of loop and continue to next i
            if (dur > range.vals[1]) continue; // if span is too large, continue to smaller span
            if (isexpof2(dur)) { // only untie chords that span a valid note duration
                /* found a group of two or more chords that can be untied.  iterate through all note objects to the
                   right of the first tied chord and set their `remove' flags to true. */
                for (chords_deque::iterator k(i + 1), ke(j + 1); k != ke; ++k) {
                    notes_vector& chord = *k;
                    for (notes_vector::iterator l(chord.begin()); l != chord.end(); ++l) l->remove = true; // set `remove' flag
                i = j; // jump to next chord before continuing with next i iteration
    /* send assignments to FOMUS in the order that data was received and delete everything we just processed. */
    while (chords.begin() != upto) {
        notes_vector& chord = chords.front();
        for (notes_vector::iterator i(chord.begin()); i != chord.end(); ++i) i->assign(); // do assignments
        chords.pop_front(); // delete the chord

/* test for the beginning or end of a tuplet at any nested tuplet level. */
bool tupbound_right(module_noteobj n)
    /* we need to loop through all possible nested levels to determine if there is a tuplet right boundary somewhere. */
    for (int l = 0; ; ++l) { // l = nested tuplet level
        if (module_tuplet(n, l) == (fomus_int)0) return false; // no more nested tuplets at this level
        if (module_tupletend(n, l)) return true;
bool tupbound_left(module_noteobj n)
    for (int l = 0; ; ++l) {
        if (module_tuplet(n, l) == (fomus_int)0) return false;
        if (module_tupletbegin(n, l)) return true;

// ---------------------------------------- main functions
/* this is the main entry point for the module.  the `dumb' engine simply passes control over to this function.  note
   objects are received from FOMUS and organized into chords.  when a rest or an untied note or tuplet begin/end is
   encountered, all chords collected so far are considered as a "group" and sent to the `process()' function above for
   processing.  `fom' is a handle to the entire score instance created by the user (and is rarely needed).  `moddata' is
   a pointer to a data structure created by `module_newdata()' below.  modules should only read/write to their own data
   structures and not use global variables since multiple threads may be running concurrently. */
extern "C" void run(FOMUS fom, void* moddata)
    fomus_rat t1 = {-1, 1}; // t = onset time of last note (used to separate notes into chords)
    fomus_rat t2 = {-1, 1}; // t = onset time of last note (used to separate notes into chords)
    chords_deque chords; // deque of chords
    bool ready = false; // ready to process?
    bool norighttie = false; // encountered note with no right tie?
    /* loop, calling `module_nextnote()' repeatedly and finishing when FOMUS returns NULL instead of a note object. */
    while (true) {
        module_noteobj n = module_nextnote(); // get the next note
        if (!n) { // if module_nextnote returns 0 then we're done
            process(chords, chords.end()); // process remaining notes
        /* if note skips over a rest then we have a point where all the notes we've collected can be grouped and
           processed.  so process everything up to this point and continue looping. */
        if (module_time(n) > t2) {
            process(chords, chords.end());
            ready = norighttie = false; // reset flags
        /* if the note belongs to a new chord, then create a new chord and stick it onto the deque. */
        if (module_time(n) > t1) { // if note belongs in new chord then create it
            chords.push_back(new notes_vector); // push new chord onto deque
            /* if we've encountered notes with no right tie, then we're ready to process. */
            if (norighttie) ready = true;
            norighttie = false;
            t1 = module_time(n); // update t1
        t2 = std::max(t2, module_endtime(n));
        /* push the note onto the last chord in the deque. */
        assert(!chords.empty()); // chords should have at least one chord at this point
        chords.back().push_back(note(n)); // insert note into chord
        /* if this note has no right tie or is at a tuplet boundary on the right side, then soon we're ready to process.
           set `norighttie' so that we know this when we get to the next chord. */
        if (!module_istiedright(n) || tupbound_right(n)) norighttie = true;
        /* if we're ready to process (or have no left tie or are at a tuplet boundary on the left side) then do it. */
        if (ready || !module_istiedleft(n) || tupbound_left(n)) {
            process(chords, chords.end() - 1); // process everything up to the last chord
            ready = false;

/* this callback function must return an error string if an error has occurred.  the string is printed as part of an
   error message to the user and indicates to FOMUS that this module has failed.  since this module should never fail
   doesn't need to report errors, it always returns NULL. */
extern "C" const char* err(void* moddata)
    return 0;   // this module doesn't report errors

// ---------------------------------------- setting values/functions
/* data used by `module_get_setting()' below. */
const char* dur_range_type = "(number>=0 number>=0)";
int dur_range_isvalid(const struct module_value val)
    return module_valid_listofnums(val,
                                   2, 2, // the list must have a minimum and maximum size of 2
                                   module_makeval((fomus_int)0), module_incl, // value must be >= 0
                                   module_makeval((fomus_int)0), module_nobound, // value has no upper bound
                                   0, // no function to validate separate list elements
                                   dur_range_type); // type string is passed for error messages


using namespace untie;

// ---------------------------------------- callback functions
/* FOMUS expects to find these callback functions in every module that it loads.  the declarations are in module.h. */

// info
/* these return the long name, author and documentation strings.  the short name is the filename of the module
   itself. */
const char* module_longname()
    return "Untie";
const char* module_author()
    return "(fomus)";
const char* module_doc()
    return "Unties notes to make complex notation easier to read.";

// initialization
/* `module_init()' is called once when the module is first loaded.  `module_free()' is called once when it is
   unloaded. */
void module_init() {}
void module_free() {}
/* `module_ready()' is called once after all modules have been loaded and queried for settings.  this function can be
   used to confirm any dependencies that it might have (by looking for settings in other modules) and store setting ids
   into global variables for fast access. */
void module_ready() {}
/* this callback is used only during initialization and freeing.  it should return a string if something has failed
   (e.g., if the `module_ready()' function didn't find the settings or dependencies that this module needs). */
const char* module_initerr()
    return 0;   // initialization never fails
/* once the module returns a data structure with `module_newdata()', this error function is called instead. */
const char* module_err(void* data)
    return 0;
/* this is called during scheduling and tells FOMUS if two separate module instances can be considered equivalent.  this
   is necessary because FOMUS needs to create multiple instances and do a lot of juggling around if the user decides to
   activates different modules in different sections of the score.  two instances might not be equivalent, for example,
   if the user specifies the `bfsearch' engine in one instance and the `dynprog' engine in the other or the user has
   specified a different modules for determining the distance between notes (in any of these cases, the algorithm
   changes and this module behaves differently).  any of the `module_setting' functions can be called on the module
   objects passed to this function. */
int module_sameinst(module_obj a, module_obj b)
    return true;

/* the module type determines the purpose of the algorithm, when it gets scheduled, and which other modules it will get
   grouped with (and run in sequence with). */
enum module_type module_type()
    return module_moddivide;
/* the iteration type tells FOMUS what notes to send this module. we only need to operate on one measure at a time, and
   only want to process notes (ignoring rests and grace notes) that are in the same voice. */
int module_itertype()
    return module_bymeas | module_byvoice | module_norests | module_nograce;
/* the priority value forces this module to run either before or after other modules of the same type.  the default
   priority that most of FOMUS's modules are set to is 0.  1 forces this module to run immediately after FOMUS's
   `divide' module, which does all of the complicated splitting and dividing of notes.  when modules have the same
   priority, it is up to the user to determine the order that they run in. */
int module_priority()
    return 1;

/* these functions are called for each module instance and are responsible for creating and destroying any data
   structures that are needed.  the module's code should only read/write to separate data structures since multiple
   instances may run simultaneously in separate threads. */
void* module_newdata(FOMUS f)
    return 0;
void module_freedata(void* dat) {}

// engine
/* every module has to be driven by an engine.  the default built-in `dumb' module simply passes complete control over
   and waits until the module is finished. */
const char* module_engine(void*)
    return "dumb";
/* since some engines are interchangeable (like `bfsearch' and `dynprog'), FOMUS also needs to know a unique engine
   "interface id" so that it can match the right engines with the right modules.  the header files for FOMUS's engines
   store this value in the `ENGINE_INTERFACEID' macro. */
int module_engine_iface()
/* the module must fill a data structure (defined in the engine's header file) with information the engine needs to
   drive the module.  for the `dumb' interface not much information is required.  the engine needs a pointer to data
   created by `module_newdata()', the address of the function it calls to pass control over to the module, and the
   address of an error function it calls once after the module has finished. */
void module_fill_iface(void* moddata, void* iface)
    ((dumb_iface*)iface)->moddata = moddata;
    ((dumb_iface*)iface)->run = run;
    /* usually this function and `module_err()' should do the same thing.  `module_err()' is called by FOMUS while `err'
       is called by the engine.  both should check if an error has occurred and return an appropriate error string or
       NULL. */
    ((dumb_iface*)iface)->err = err;

// ---------------------------------------- settings
/* this callback is used to query for all settings that this module uses.  the module might want to use settings from
   other modules, in which case it should use `module_ready()' to look for them and find their ids.  FOMUS calls the
   function multiple times, incrementing `n' by 1 each time until the function returns 0.  `set' is a pointer to a
   pre-initialized data structure that must be filled with information on a setting.  `id' is an id value for the
   setting which should be stored in a global variable for quick access later. */
int module_get_setting(int n, struct module_setting* set, int id)
    switch (n) {
    case 0: { // first setting
        set->name = "untie-dur-range"; // set setting name
        set->type = module_list_nums; // set value type
        set->descdoc = // set documentation string
            "A list of two values specifying the minimum and maximum durations an \"untied\" note is allowed to span.  "
            "FOMUS unties notes that fall within this range to make the notation easier to read.  "
            "The results are a bit unconventional and appropriate for scores with complex rhythms and many notes beginning or ending on offbeats.  "
            "Change this setting to influence which notes FOMUS considers for untying."
        set->typedoc = dur_range_type; // set type documentation string
        set->uselevel = 2; // set use level
        set->loc = module_locnote; // set location where setting may appear
        set->valid = dur_range_isvalid; // set function to test validity of setting value
        module_setval_list(&set->val, 2); // set default value to list of length 2
        struct module_list &x = set->val.val.l;
        module_setval_rat(x.vals + 0, module_makerat(1, 2)); // set first value in list
        module_setval_rat(x.vals + 1, module_makerat(3, 2)); // set second value in list
        dur_range_id = id; // save id in global variable
    case 1: { // second setting
        set->name = "untie"; // set setting name
        set->type = module_bool; // set value type
        set->descdoc = // set documentation string
            "Whether or not FOMUS unties notes that can be combined into a single note.  "
            "FOMUS unties notes that fall within this range to make the notation easier to read.  "
            "The results are a bit unconventional and appropriate for scores with complex rhythms and many notes beginning or ending on offbeats."
        // (use default `typedoc')
        set->uselevel = 2; // set use level
        set->loc = module_locnote; // set location where setting may appear
        // (use default `valid')
        module_setval_int(&set->val, 0); // set default value false (0)
        untie_id = id; // save id in global variable
        return 0; // return 0 when finished
    return 1; // return 1 when there are more settings

Figure 11.1: