Software » Cosy

Cosy Technical Details

Because I am a Ruby addict, Cosy is implemented in Ruby.

Here is a diagram showing the high-level structure of Cosy:

Cosy's syntax tries to be as compact as possible, so it can get a fairly complicated to parse. To make this project feasible, a wonderful library called Treetop is used to generate a parser for the language. Everything starts from a grammar specification document that is much more easily understood and maintained than a bunch of custom parsing code. This grammar is fed into Treetop to generate code that can parse the Cosy language. The generated code needs to be customized a little bit but still this approach is much easier than writing a parser from scratch.

Once we have a parser, we can input Cosy syntax, and the parser will transform that input into an internal representation (the parse tree) which I refer to as the sequence model. This is a hierarchical tree structure of nodes that capture the semantics of the Cosy syntax.

To give you a better idea, let's consider the following simple input:
(C4:mf:q D4 E4 F4)*3 G4:w

This is a measure of quarter ('q') notes in an ascending line C to F in the fourth octave, played moderately loud (mf means mezzo forte). The measure is repeated 3 times (*3) and followed by a whole ('w') note G.

The Cosy parser will transform that input into this tree structure. Each line is a node in the tree. Indentation indicates a lower level in the tree.
SequenceNode '(C4:mf:q D4 E4 F4)*3 G4:w' ModifiedNode '(C4:mf:q D4 E4 F4)*3' SequenceNode 'C4:mf:q D4 E4 F4' ChainNode 'C4:mf:q' NoteNode 'C4' VelocityNode 'mf' DurationNode 'q' NoteNode 'D4' NoteNode 'E4' NoteNode 'F4' BehaviorNode '*3' OperatorNode '*' IntNode '3' ChainNode 'G4:w' NoteNode 'G4' DurationNode 'w'

The Cosy sequencer traverses the sequence model one node at a time. Because the sequencer is completely separate (i.e. decoupled) from the model, it should be possible to run multiple sequencers on the same model simultaneously, to create rounds or fugal material. The sequencer mostly does a depth-first traversal, but special BehaviorNodes can modify the traversal path, for example by creating loops as in the example above. The sequencer often doesn't go all the way down to the bottom-most nodes. A simple "chain" node like 'C4:mf:q' will output all 3 values (the note, velocity, and duration) at the same time. This allows for a single event to consist of many parameters. Regardless of the exact traversal rules, at each step the sequencer will output the current node's data, which represents things like notes, chords, arbitrary text, or a snippet of Ruby to be evaluated during traversal.

The data outputted by the sequencer is still part of Cosy's internal representation. It is not directly usable. It is up to a separate renderer component to transform the sequencer output into the final output desired by the user. Various renderers can be plugged in, to support things like generating MIDI files or generating Max events when running inside Max/MSP. In the future I would like to support things like rendering a Csound score, and notation via LilyPond.

As mentioned above, BehaviorNodes can modify the traversal path. Cosy was initially conceived as a deterministic sequencing language, but as it evolves, non-deterministic (random) behaviors are being added to allow for more complex and unpredictable output. We always have full control of when and where things will become non-deterministic and can mix and match deterministic and non-deterministic behaviors throughout a sequence. In other words, controlled randomness.

More details to follow in the coming weeks.