#title Strasheela's Music Representation #author Torsten Anders and Graham Percival * About this document This file was automatically generated from the interactive Strasheela tutorial. Some aspects of the text only make sense in the original interactive tutorial application (e.g., buttons indicated to press, and positions specified on the screen), and not in this version of the text. * -- Strasheela's Music Representation ** Introduction The following sections introduce various aspects of Strasheela's music representation. The representation is then later used for defining musical constraint satisfaction problems. *Please note that this part of the tutorial is still unfinished!* * Score Creation ** Introduction This section creates a Strasheela score consisting of a single note. The score is specified textually, and this specification is then transformed into a score object instance. - the textual representation is easier for human interaction -- we can read and write music in textual form relatively easily. For example, note(pitch:60 duration:4) - the score object is easier for computer interaction. For example, there are various operations available for outputting a score into other formats (e.g. Csound, LilyPond, MIDI). ** Inspect score object This subsection shows how to create a simple score and examine it in the Inspector The Inspector shows where the O says that this is an object, and Note is the name of its class. To look "inside" a score object, activate the context menu (typically right-click, but on a mac you must middle-click). Select 'Filter' -> 'Show Textual Score'. This shows the score in the textual form as entered in this example. If you cannot activate the context menu (for example, having a mac laptop with only one mouse button), you can still see some info in the Inspector by adding `{... toInitRecord($)}'. WARNING: the Inspector may appear behind this window. local TextualScore = note(startTime:0 duration:1000 timeUnit:milliseconds pitch:60 amplitude:64) %% second argument to Score.makeScore explained later ScoreInstance = {Score.makeScore TextualScore unit} in {Inspect ScoreInstance} %% if difficulties arise with the Context Menu {Inspect {ScoreInstance toInitRecord($)}} end ** More Inspector features There are a few other useful features of the Inspector. When you select a Filter, the background color behind the score changed to indicate that we are now 'looking into' something. You can 'go back' to the original view by the context menu entry 'unmap' (you have to right-click on the top-level label note). The Inspector also provides different views on the score via the other 'Filter' context menu entries. These provide more information on the internal structure of a score object. For example, 'Show Score Hierarchy Recursively' reveals that even a plain note is internally represented hiearchically: the note parameters (e.g. duration, pitch) are represented by their own objects. WARNING: the Inspector may appear behind this window. local TextualScore = note(startTime:0 duration:1000 timeUnit:milliseconds pitch:60 amplitude:64) %% second argument to Score.makeScore explained later ScoreInstance = {Score.makeScore TextualScore unit} in {Inspect ScoreInstance} end * Output Formats ** Introduction This section creates a Strasheela score consisting of three notes and demonstrates various methods of outputting the music. Please note that the shell (or Dos) where you started this tutorial provides feedback. It confirms the writing of the LilyPond or Csound score file, shows the call to the helper applications, etc. When you are using Strasheela from within the OPI, the Emacs buffer 'Oz Emulator' shows this information. ** Output LilyPond The first section outputs the music to a PDF file using LilyPond. The {MyScore wait} requires Strasheela to wait until all score information not explicitly specified (e.g. the start time of the notes) is propagated in the background. Remember that Oz is a concurrent programming languages: propagation of such score information happens concurrently 'behind the scene'. The wait method blocks until all parameter values in the score are determined. We will not need it later in actual CSPs. WARNING: you must specify (via the `Settings...' menu entry) where Strasheela can find lilypond, convert-ly (part of LilyPond), and a PDF viewer. local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:1500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) MyScore = {Score.makeScore TextualScore unit} in {MyScore wait} {Out.renderAndShowLilypond MyScore unit(file:{Tk.return tk_getSaveFile} dir:nil)} end ** Output MusicXML, via Fomus This second section outputs to a MusicXML file (using Fomus), which can be opened, for section, by software like Finale and Sibelius. Fomus supports outputting into further formats and supports tweaking the MusicXML output specifically for Finale or Sibelius usage: just set the appropriate Fomus flag in the section below (see the Fomus documentation for the supported flags). Please note that for running these sections you must specify (via 'Settings...' menu entry) where Strasheela can find the applications lilypond, convert-ly (an application part of Lilypond), a PDF file viewer, and Fomus. WARNING: you must specify (via the `Settings...' menu entry) where Strasheela can find Fomus. local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:1500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) MyScore = {Score.makeScore TextualScore unit} in {MyScore wait} {Out.renderFomus MyScore unit(file:{Tk.return tk_getSaveFile} dir:nil flags:['-x'])} end ** Output MIDI file This section saves a MIDI file with the procedure `Out.renderMidiFile'. The filename is given directly as "/tmp/test.midi" (the .midi is added automatically). You may change this by editing the argument csvDir and midiDir (the directory) and file (the file name without extension). WARNING: Windows users will have to input a directory path like "C:\\Dokumente und Einstellungen\\" (German settings). WARNING: you must specify (via the `Settings...' menu entry) where Strasheela can find a MIDI player. local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:1500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) MyScore = {Score.makeScore TextualScore unit} in {MyScore wait} {Out.midi.renderAndPlayMidiFile MyScore unit(file:'test' midiDir:'/tmp/' csvDir:'/tmp/')} end ** Output Csound file (no sound) This section saves a Csound score file with the procedure `Out.outputCsoundScore'. The filename is given directly as "/tmp/test.sco" (the .sco is added automatically). You may change this by editing the argument scoDir (the directory) and file (the file name without extension). Windows users will have to input a directory path like "C:\\Dokumente und Einstellungen\\" (German settings..). Please have a look at the content of the resulting Csound score file (on UNIX, you may just uncomment the last line of the first section). You will notice that the Csound p-fields p2 and p3 (i.e. the second and third columns) correspond to the notes' offset time and duration, now measured in Csound beats (note the Csound tempo specification above, which can be changed with the procedure Init.setTempo, but defaults to 60.0). Moreover, p4 is the amplitude (measure as midi velocity value in the section and now normalised in 0-1) and p5 is the pitch (measured as midi keynumber). In strasheela/goodies/csound you will find a demo Csound orc file which works with these settings. This file is used in the second and third section. WARNING: you must specify (via the `Settings...' menu entry) where Strasheela can find CSound. local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:1500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) MyScore = {Score.makeScore TextualScore unit} in {MyScore wait} {Out.outputCsoundScore MyScore unit(scoDir:"/tmp/" file:"test")} % UNIX: show content of resulting test.sco at terminal (uncomment next line) % {Out.exec 'cat' ["/tmp/test.sco"]} end ** Output Csound file (with sound) This section additionally calls Csound in the background and open the resulting sound file with an editor with the procedure `Out.renderAndPlayCsound'. There is one new file variable: soundDir (the directory for the resulting sound file). WARNING: you must specify (via the `Settings...' menu entry) where Strasheela can find CSound. local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:1500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) MyScore = {Score.makeScore TextualScore unit} in {Init.setTempo 80.0} {MyScore wait} {Out.renderAndPlayCsound MyScore unit(file:"test" scoDir:"/tmp/" soundDir:"/tmp/")} end ** Output CSound file (file dialog) In this example, the output file is specified with a file dialog for convenience. Most directory arguments of output procedures (e.g., scoDir and soundDir in Out.renderAndPlayCsound) can be omitted; their default value are directories set in the Strasheela environment (cf. the _ozrc file in the Strasheela directory for an section how to set these default directories). WARNING: you must specify (via the `Settings...' menu entry) where Strasheela can find CSound. local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:1500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) MyScore = {Score.makeScore TextualScore unit} in {Init.setTempo 40.0} {MyScore wait} %% Specify file with file dialog (full path without extension) {Out.renderAndPlayCsound MyScore unit(file:{Tk.return tk_getSaveFile} scoDir:nil soundDir:nil)} end * Music Containers ** Introduction The following section introduces the overall construction and organization of Strasheela's music representation. ** Containers This example introduces Strasheela containers: notes are organized into sequential containers (seq) and simultaneous containers (sim). In the previous section, the timeUnit was set to milliseconds. Here, it is set to beats(4), which means that the duration 4 indicates a quarter-note (a beat) and consequently duration 1 is a sixteenth-note. In general, all musical parameters support some unit of measurement which indicates how the numeric parameter value is interpreted (in particular for output into export formats). For example, the pitchUnit defaults to (MIDI) 'keynumber', but can also be set to midicent, frequency, or et72 (i. e. equal temperament with 72 steps per octave). These various parameter units of measurements are supported, because Strasheela parameter values are (at least presently) always integers. Whereas the timeUnit must be set only once in the score, other units can be set individually for every score object. The timeUnit is handled differently, because Strasheela implicitly applies some constraints to all temporal parameters (e.g., the temporal structure in sequential and simultaneous containers is enforced by constraints), and these constraints require a consistent timeUnit for all parameters. local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:2 pitch:64 amplitude:64) note(duration:2 pitch:65 amplitude:64) note(duration:4 pitch:67 amplitude:64) note(duration:4 pitch:62 amplitude:64)]) seq(items:[sim(items:[note(duration:8 pitch:48 amplitude:64) note(duration:8 pitch:55 amplitude:64)]) sim(items:[note(duration:4 pitch:50 amplitude:64) note(duration:4 pitch:54 amplitude:64)])])] startTime:0 timeUnit:beats(4)) unit} in {MyScore wait} {Out.midi.renderAndPlayMidiFile MyScore unit(file:'test')} %Uncomment if desired: % {Out.renderAndShowLilypond MyScore % unit(file:{Tk.return tk_getSaveFile} % dir:nil)} end * Specifying and Accessing Basic Score Object Information ** intro Various information about score objects can be stored and retrieved from Strasheela's music representation. This information is used for formulating music theory models. For example, a musical CSP may specify a score where all note pitch variables are initialised to certain domains, and neighbouring melodic notes are then accessed and constrained. The textual music representation allows for the convenient definition of complex scores, as already shown by the previous sections. The interface defined for Strasheela score objects, on the other hand, simplifies the access to score information. As shown before, the textual representation is transformed into a score object with the function Score.makeScore. Vice versa, a score object can be transformed into its textual representation (see below for an section). Using the textual representation and Score.makeScore is strongly recommended for creating Strasheela score objects, because it hides low-level details of the score object creation process. This section introduces the textual music representation format in more detail. In general, a textual score is a (often nested) Oz record. Record labels correspond with score classes, and record features are initialisation arguments for the class creation. This was already demonstrated in previous sections. In the following, specific aspects are introduced in more detail by sections. For additional information and sections of the textual representation please see the Score.makeScore reference documentation (strasheela/doc/api/node6.html#entity225) and and the Score.makeScore sections in strasheela/testing/ScoreCore-test.oz. The section also demonstrates the retrieval of various basic information. Basic information includes access to object parameter values (e.g. the pitch of a note), checking the type (class) of an object (e.g. checking whether object X is a note), checking the identity of two objects, and exploring the hierarchic structure of a score. Later sections will show how you can access more complex information. The naming of many methods and functions accessing basic information follows common conventions. Such convensions are briefly summarised here. The name of accessor methods usually start with 'get' as in getSomething (e.g., getDuration, getPitch, getContainers). Type-checking methods usually start with 'is' (e.g., isNote, isPause). Methods which check a has-a relation often start with 'has' (hasSuccessor, hasThisInfo). Converters start often with 'to' or contain 'to' in their name (e.g. toInitRecord, but also Int.toFloat). The name of many constructors starts with 'make' (Score.makeScore, Score.makeClass). ** Setting/accessing a parameter value No information available. /* A parameter value (e.g. the duration of a note, or of a simultaneous container) is usually specified in the textual score with the name of the parameter as a record feature (e.g. duration), and accessed with a method get (e.g. getDuration). The following section initialises and accesses the duration of a simultaneous container. */ local MySim = {Score.makeScore sim(duration:3) unit} in {Inspect {MySim getDuration($)}} end ** Default parameter values No information available. /* The textual music representation specifies initialisation arguments for score object to create (e.g., sets the pitch of a note, or the duration of a sequential container). Most of these initialisation arguments are optional, and Strasheela defines a default value for these initialisation arguments. For example, parameter values such as durations and pitches default to a constrained variable. This section creates a single note and specifies no initialisation arguments at all. You can inspect the note's parameters and their default values (mostly undetermined constrained variables). In the Inspector, use the note's context menu: 'Filter' -> 'Show Score Hierarchy Recursively'. An undetermined parameter value is accessed the same way as a determined parameter. For example, a note's undetermined pitch is accessed with the method getPitch. Accessing undetermined parameter values is important for constraining these values. */ local MyNote = {Score.makeScore note unit} in {Inspect pitch # {MyNote getPitch($)}} {Inspect note # MyNote} end ** Type checking (1) No information available. /* The type (class) of Strasheela objects can be checked. Methods such as isNote or isPause return either true or false. This information can be useful, for section, to decide to which object a constraint is applied and which objects are skipped. */ local MyNote = {Score.makeScore note unit} in %% MyNote is a note but note a pause {Inspect isNote # {MyNote isNote($)}} {Inspect isPause # {MyNote isPause($)}} end ** Type checking (2) No information available. /* Strasheela defines a hierarchy of types (class hierarchy). For section, see strasheela/doc/api/class25.html for the superclasses of the note class. Consequently, an object returns true for the typechecker of its class and all its superclasses. This is useful, for section, for being able to apply a constraint to all notes and pauses but not to the containers these objects contained in (possibly deeply nested). Strasheela uses the following terminolody. Every Strasheela object is a ScoreObject. Strasheela distinguishes between Parameters (e.g. the pitch of a note), Elements (e.g. notes and pauses), and Containers (e.g. simultaneous and sequential containers). An umbrella term (superclass) for Elements and Containers is Items. For an even more fine-grained terminology please refer to the reference documentation. The list function Map was introduced in section "Higher-Order Programming". The function Record.map is the same as map, but for records. */ local MyNote = {Score.makeScore note unit} MyPause = {Score.makeScore pause unit} MySim = {Score.makeScore sim unit} MyObjects = unit(note:MyNote pause:MyPause sim:MySim) in %% Both MyNote and MyPause are elements, and all three objects are items. {Inspect isElement # {Record.map MyObjects fun {$ X} {X isElement($)} end}} {Inspect isItem # {Record.map MyObjects fun {$ X} {X isItem($)} end}} end ** Identity test No information available. /* Sometimes we want to check the identity of score objects, for section, to prevent that constraints between identical objects are applied. The identity of score objects can be tested with the == operator (and its opposite, the \= operator). Please note that the operator == checks for the identity of objects, not for their equality. */ local Note1 = {Score.makeScore note unit} Note2 = {Score.makeScore note unit} in {Inspect identicalObjects # (Note1 == Note1)} {Inspect differentObjects # (Note1 == Note2)} end ** Info tags (1) No information available. /* It is often useful to add various additional information to certain score objects (e.g. for naming a container representing a voice, or for tagging notes with a specific purpose in the music). It is convenient to have a general info attribute for all such information, instead of defining some special attribute for each information tag which might occur. Information tags can be specified via the info argument for a score object. The method hasThisInfo returns true for a specific info tag, in the case the score object was tagged with it. */ local MyNote = {Score.makeScore note(info:myTag) unit} in {Inspect {MyNote hasThisInfo($ myTag)}} end ** Info tags (2) No information available. /* A score object get be given multiple information tags at the info argument, and further info tags can be added later with the method addInfo. All these tags can be checked with the method hasThisInfo, as shown above. The method getInfo returns a list of all information tags of a given object. */ local MyNote = {Score.makeScore note(info:[hi there]) unit} in {MyNote addInfo(test)} {Inspect {MyNote getInfo($)}} end ** The handle argument No information available. /* The handle argument makes it easy to directly access some object within a nested score. The argument expects a variable. After score creation, this variable is bound to the score object to which the handle argument was given. In this section, the first and the second note in a sequential container are bound to the variables Note1 and Note2 using their handle argument (the method toInitRecord returns the textual representation of a score item). Watch the different note pitches to confirm them. Every score object in the textual representation supports the handle argument. */ local Note1 Note2 MyScore = {Score.makeScore seq(items:[note(handle:Note1 duration:2 pitch:60 amplitude:64) note(handle:Note2 duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {Inspect note1 # {Note1 toInitRecord($)}} {Inspect note2 # {Note2 toInitRecord($)}} end ** Contained objects No information available. /* The method getItems returns the items contained in a container. Similarily, the containers of an item are accessed with the method getContainers. This section makes again use of the handle argument (see above). */ local MyNote MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:60 amplitude:64) note(handle:MyNote duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {Inspect items # {MyScore getItems($)}} {Inspect containers # {MyNote getContainers($)}} end ** Positional information (1) No information available. /* Sometimes it is useful to access positionally related information. For example, you may want to access the successor of a note in a voice in order to constrain the melodic interval between these two notes. A Strasheela item can be contained in multiple containers (e.g. a temporal container and a container expressing the motific or harmonic structure). See the documentation of Score.makeScore for details. This approach may be seldomly used because it is often too much restrictive (see my Thesis Sec. 5.4.3.3 for a discussion). Nevertheless, the access to positionally related information must take into account that a score object may be contained in multiple containers. Therefore, positional accessor methods expect a specification which container they must refers to. For example, the container may be given as an argument or a method tailored for a specific container type such as getTemporalSuccessor is used (an item can only be contained in a single temporal container at maximum). */ local MyNote TextualScore = sim(items:[note(duration:2 pitch:60 amplitude:64) note(handle:MyNote duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) %% container can be acccessed from MyNote with getTemporalContainer _/*Ignore container*/ = {Score.makeScore TextualScore unit} MyContainer = {MyNote getTemporalContainer($)} in {Inspect position # {MyNote getPosition($ MyContainer)}} {Inspect successor # {{MyNote getSuccessor($ MyContainer)} toInitRecord($)}} end ** Positional information (2) No information available. /* The hierarchy of temporal items must be a tree. Consequently, if you are interested in positional information from this hierarchy, then you do not need to specify a container. Instead, use specialised methods such as getTemporalSuccessor or getTemporalPredecessor. Please note that getTemporalSuccessor returns the positional successor in a TemporalContainer (not necessarily the temporal successor!). */ local MyNote TextualScore = sim(items:[note(duration:2 pitch:60 amplitude:64) note(handle:MyNote duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) _/*Ignore container*/ = {Score.makeScore TextualScore unit} in {Inspect {{MyNote getTemporalSuccessor($)} toInitRecord($)}} end ** The temporal parameters No information available. /* All temporal objects (e.g., notes, and temporal containers like the sequential and the simultaneous container) define the three temporal parameters startTime, duration and endTime (they also define a temporal parameter offsetTime, discussed below). Three parameters are initially constrained to the obvious relation. StartTime + Duration = EndTime Consequently, you can choose which parameter(s) you want to specify during the initialisation, and all three parameters will always be consistent. You even can leave all three parameters unspecified (as we saw above), and can only further constrain their value and thus define a rhythmical CSP. The following section demonstrates this by specifying the startTime and the endTime of a note. You can examine all three parameters with the Inspector (Show Score Hierarchy Recursively). */ local MyNote = {Score.makeScore note(startTime:2 endTime:5 timeUnit: beats) unit} in {Inspect MyNote} end ** The offsetTime parameter (1) No information available. /* All temporal score objects (e.g., notes, simultaneous and sequential containers) support the parameter offsetTime. The meaning of this parameter depends on the class of the temporal container in which an object is contained in. For objects in a simultaneous container, the offsetTime delays the start of its contained objects with respect to the startTime of this container. The Csound section shown previously is reproduced here. */ local TextualScore = sim(items:[note(offsetTime:0 duration:1000 pitch:60 amplitude:64) note(offsetTime:500 duration:1000 pitch:62 amplitude:64) note(offsetTime:2000 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) ScoreInstance = {Score.makeScore TextualScore unit} in {Init.setTempo 40.0} {ScoreInstance wait} {Out.renderAndPlayCsound ScoreInstance unit(file:{Tk.return tk_getSaveFile} scoDir:nil soundDir:nil)} end ** The offsetTime parameter (2) No information available. /* For objects in a sequential container, the offsetTime specifies a pause between the object and its preceeding score object in this container. he offsetTime parameter defaults to 0 (this is the only parameter in the Strasheela core which defaults to a determined value). Please note that (like all FD integers) the offsetTime can *not* be negative (i.e., it cannot be used for expressing an overlap of objects in a sequential container). */ local TextualScore = seq(items:[note(duration:1000 pitch:60 amplitude:64) note(offsetTime:1500 duration:1000 pitch:62 amplitude:64) note(offsetTime:500 duration:1000 pitch:64 amplitude:64)] startTime:0 timeUnit:milliseconds) ScoreInstance = {Score.makeScore TextualScore unit} in {Init.setTempo 40.0} {ScoreInstance wait} {Out.renderAndPlayCsound ScoreInstance unit(file:{Tk.return tk_getSaveFile} scoDir:nil soundDir:nil)} end ** Incrementally creating scores (1) No information available. /* The function Score.makeScore creates a fully initialised score. The hierarchic structure of such a fully initialised score is fixed (at least for a CSP it should be regarded as fixed). Sometimes, however, it is convenient to create a score incrementally by creating parts independently and combining them later. One option is, that you simply create independent textual scores and combine them later. */ local Voice1 = seq(info:voice1 items:[note(duration:4 pitch:59 amplitude:64) note(duration:4 pitch:60 amplitude:64)]) Voice2 = seq(info:voice2 items:[note(duration:4 pitch:67 amplitude:64) note(duration:4 pitch:67 amplitude:64)]) MyScore = {Score.makeScore sim(items:[Voice1 Voice2] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore toInitRecord($)}} end ** Incrementally creating scores (2) No information available. /* In certain cases, you want to create your score incrementally, but you need access to the score objects (i.e. not only their textual representation). You could use the approach shown in the previous section and additionally use the handle argument introduced before. With that approach, the score object is available after the full score is created. Another option is to use the function Score.makeScore2 instead of Score.makeScore. Score.makeScore2 immediately outputs a score which is not yet fully initialised and can still be inserted in other Strasheela containers. Such partially initialised scores are later fully initialised simply by using these score objects within a call of Score.makeScore (or by explitictly calling it with the procedure Score.initScore). This section only reproduces the previous section with Score.makeScore2. A later section below will show a use of Score.makeScore2 which cannot be reproduced with independent textual score objects nor with the handle argument. */ local Voice1 = {Score.makeScore2 seq(info:voice1 items:[note(duration:4 pitch:59 amplitude:64) note(duration:4 pitch:60 amplitude:64)]) unit} Voice2 = {Score.makeScore2 seq(info:voice2 items:[note(duration:4 pitch:67 amplitude:64) note(duration:67 pitch:55 amplitude:64)]) unit} MyScore = {Score.makeScore sim(items:[Voice1 Voice2] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore toInitRecord($)}} end ** Using extension classes No information available. /* A Strasheela music representation created with Score.makeScore consists of objects, that is class instances. Per default, Score.makeScore uses the classes defined by the Strasheela core -- as did all sections so far. Strasheela extensions, however, often define their own classes which extend the classes of the Strasheela core. For example, Strasheela's harmony model -- an extension provided in the strasheela/contribtion folder -- provides the class HS.score.note (the harmony extention -- i.e. its functor -- is usually bound to the variable HS). The class HS.score.note extends the standard note class Score.note by the notion of pitch classes (among many other things). This section shows how you can specify which classes Score.makeScore should use when creating a score. The clases are specified in the second argument of Score.makeScore (which so far was alway only unit). This argument expects a record whose features match the score object labels in the textual music representation, and whose values are the classes which should be used for objects with this label. Every Strasheela score class defines a method init, which is called internally when an instance of this class is created (cf. Strasheela's reference documentation for a init method definition of various classes). The feature/value-pairs of an object's textual representation correspond to these init method arguments. For example, the init method of the class HS.score.note expects the arguments of the standard note class Score.note (e.g., duration and pitch) with additional arguments (e.g., pitchClass). The textual representation output of this sections shows further arguments, which are irrelevant here. */ local MyNote = {Score.makeScore note(startTime:0 duration:4 pitchClass:7 pitch:{FD.int 48#72}) unit(note:HS.score.note)} in {Inspect {MyNote toInitRecord($)}} end ** Using creator functions No information available. /* You can customise further the meaning of a textual music representation. Score object labels in the textual representation can also be mapped to creator functions via the second argument of Score.makeScore (instead of classes). Using this feature, a single score object in the textual music representation can express a complex subscore. In this section, the meaning of the textual score object with the label mySection is defined by the creator function MakeMySection. Such a function expects the record (namely the fully textual score object) and returns a score object (not fully initialised, i.e., created with Score.makeScore2 as described above). */ local fun {MakeMySection Args} {Score.makeScore2 seq(items:{LUtils.collectN Args.n fun {$} note(duration:{FD.int 1#8}) end} duration:Args.dur) unit} end MyScore = {Score.makeScore sim(items:[mySection(n:2 dur:4) mySection(n:4 dur:4)] startTime:0 timeUnit:beats) unit(mySection:MakeMySection sim:Score.simultaneous)} in {Inspect {MyScore toInitRecord($)}} end ** Saving a score object as textual score No information available. /* A score object can be transformed 'back' into its textual representation, for section, to hand-edit the score directly. This works even for score objects which are not fully determined, as the section below demonstrates (its pitches are variables). The procedure Out.saveScore saves a given score object as Oz code into a text file, and the function Out.loadScore loads the (possibly edited) score from a file and returns a score object. */ local MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:{FD.int 60#67} amplitude:64) note(duration:1 pitch:{FD.int 60#67} amplitude:64) note(duration:3 pitch:{FD.int 60#67} amplitude:64)] startTime:0 timeUnit:beats) unit} in {Out.saveScore MyScore unit(file:{Tk.return tk_getSaveFile} %% use file name as given by GUI dir:nil extension:nil)} end %% Look at the textual score to compare with the original {Inspect {Out.loadScore unit(file:{Tk.return tk_getOpenFile} dir:nil extension:nil)}} ** Finding init Documentation No information available. /* The arguments of score objects in the textual score representation are the arguments for the init method of their class. So, the init method documentation also serves as documentation fo the textual score representation. However, many init method arguments are inherited from superclasses and it can be hard to find which superclass defines and documents them. In such a situation, you can ask the score object itself. The method getInitArgSources returns the classes which define an init arguments and the method getInitArgDefaults returns their default values (_ indicates no default value). */ % the values at the record features are the classes which define this argument {Browse initArgs # {{Score.makeScore note unit} getInitArgSources($)}} {Browse initArgDefaults # {{Score.makeScore note unit} getInitArgDefaults($)}} * Higher-Order Accessors ** intro Strasheela provides various means for accessing information about multiple score objects contained in a container. Many of these means are higher-order methods, that is, the user specifies the desired information with a method or function as argument (see the introduction to higher order programming previously in this tutorial). The section shows shows how a contained can be queried about its directly contained score items (all containers, notes and pauses belong to the superclass item -- in contrast to score parameters). You will notice a consistent naming scheme of these methods. Their name usually end in 'Items' (as in mapItems). ** filterItems No information available. /* The method filterItems returns all items directly contained in a container for which a given unary function or method returns true. Try using a Boolean method (e.g., isNote). */ local MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:60 amplitude:64) note(duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore filterItems($ fun {$ MyNote} {MyNote getDuration($)} < 4 end)}} end ** findItem No information available. /* The method findItem returns the first item for which a test (method or function) returns true. */ local MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:60 amplitude:64) note(duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore findItem($ fun {$ MyNote} {MyNote getDuration($)} < 4 end)}} end ** mapItems No information available. /* The method mapItems applies a unary function or method to every object which fulfills a test. Here, the pitchs of all notes in MyScore are collected with the method getPitch. Try and replace getPitch with another accessor method introduced before (e.g., getDuration). Also, try and replace the test (e.g., using isNote), or leave the test out altogether. */ local MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:60 amplitude:64) note(duration:2 pitch:62 amplitude:64) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore mapItems($ getPitch test: fun {$ MyNote} {MyNote getDuration($)} < 4 end)}} end ** forAllItems No information available. /* The method forAllItems is very similar to mapItems. forAllItems applies a unary procedure or method to every object which fulfills a test -- in contrast to mapItems this procedure/method returns no value (cf. the Oz procedures Map and ForAll introduced before). */ local MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:60 amplitude:64) pause(duration:2) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {MyScore forAllItems(proc {$ X} {Inspect X} end test: isNote)} end ** countItems No information available. /* The method countItems returns the number of score objects directly contained in a container which fulfill a given test. This test can be a method or a function (try isNote, isPause or isItem instead). */ local MyScore = {Score.makeScore seq(items:[note(duration:2 pitch:60 amplitude:64) pause(duration:2) note(duration:4 pitch:64 amplitude:64)] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore countItems($ test:fun {$ X} {X getDuration($)} < 4 end)}} end * Higher-Order Accessors for Indirectly Contained Objects ** intro The previous section discussed methods for accessing (information on) directly contained score objects. This sections shows methods which traverse the whole score hierarchy for accessing information. For example, the method collect returns a list of directly and indirectly contained score objects. These methods support a few additional arguments. You saw the argument test already in the section before. The argument 'level' expects an integer which limits the depth the method decends during its traversal (its default is all, i.e., full recursive traversal). The argument 'mode' specifies whether only the subtree below the given object is traversed (mode: tree) or whether the full score graph is traversed (mode: graph). ** collect (1) No information available. /* When no further arguments are specified, the method collect returns all objects directly or indirectly contained in MyScore, even the containers and parameters. It does not, however, include MyScore itself in the output. */ local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:1 pitch:60) note(duration:2 pitch:60)]) seq(items:[note(duration:2 pitch:64) note(duration:1 pitch:64)])] startTime:0) unit} in {Inspect {MyScore collect($)}} end ** collect (2) No information available. /* With the the argument test, you filter which objects are collected. Again, this argument expects either a Boolean function or method. This section returns all notes whose duration is > 1.*/ local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:1 pitch:60) note(duration:2 pitch:60)]) seq(items:[note(duration:2 pitch:64) note(duration:1 pitch:64)])] startTime:0) unit} in {Inspect {MyScore collect($ test:fun {$ X} {X isNote($)} andthen {X getDuration($)} > 1 end)}} end ** collect (3) No information available. /* This section also filters score objects with the test argument of collect. Here, all sequential objects in MyScore are contained. */ local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:1 pitch:60) note(duration:2 pitch:60)]) seq(items:[note(duration:2 pitch:64) note(duration:1 pitch:64)])] startTime:0) unit} in {Inspect {MyScore collect($ test:isSequential)}} end ** collect (4) No information available. /* This section collects all items (i.e. notes and containers) directly contained in MyScore (level 1). Try increasing the level to 2. Please note that the level only specifies the nesting of items, but not parameters (i.e., there is no third nesting level in this section). Thus, if you also allow for parameters in the output (by removing the test isItem), then the parameters from the items of the levels specified are included. */ local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:1 pitch:60) note(duration:2 pitch:60)]) seq(items:[note(duration:2 pitch:64) note(duration:1 pitch:64)])] startTime:0) unit} in {Inspect {MyScore collect($ level:1 test:isItem)}} end ** collect (5) No information available. /* This section demonstrates the graph traversal of collect, which considers not only score items contained in a given container, but also containers in which a given item is contained in. This section collects all items in the score (argument test:isItem) which are either contained in the container MyItem or in which MyItem is contained (argument mode:graph). Please note, however, that MyItem itself is not included: as you already have access to it you can easily add it to the list returned (how?). MyItem is again bound with the handle argument, see above. */ local MyItem _ /* Ignore */ = {Score.makeScore sim(items:[seq(items:[note(duration:1 pitch:59) note(duration:2 pitch:60)]) seq(handle:MyItem items:[note(duration:2 pitch:67) note(duration:1 pitch:67)])] startTime:0) unit} in {Inspect {MyItem collect($ mode:graph test:isItem)}} end ** map No information available. /* The method map is like mapItems, but recursively traverses a score hierarchy. Like the method collect, it supports the arguments test, level, and mode. There also exists a method forAll, which corresponds to forAllItems but works with score hierarchies. */ local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:1 pitch:59) note(duration:2 pitch:60)]) seq(items:[note(duration:2 pitch:67) note(duration:1 pitch:67)])] startTime:0) unit} in {Inspect {MyScore map($ getPitch test:isNote)}} end ** find No information available. /* The method find is like the method findItems, but recursively traverses a score hierarchy. This section returns the first simultaneous object which directly contains only notes. Again, find also supports the arguments test, level, and mode. Moreover, there exists a method filter which corresponds to filterItems. */ local MyScore = {Score.makeScore sim(items:[seq(items:[note(duration:2 pitch:64 amplitude:64) note(duration:2 pitch:65 amplitude:64) note(duration:4 pitch:67 amplitude:64) note(duration:4 pitch:62 amplitude:64)]) seq(items:[sim(items:[note(duration:8 pitch:48 amplitude:64) note(duration:8 pitch:55 amplitude:64)]) sim(items:[note(duration:4 pitch:50 amplitude:64) note(duration:4 pitch:54 amplitude:64)])])] startTime:0 timeUnit:beats(4)) unit} in {Inspect {MyScore find($ fun {$ X} {X isSimultaneous($)} andthen {All {X getItems($)} fun {$ MyItem} {MyItem isNote($)} end} end)}} end ** Simultaneous items No information available. /* The previous sections demonstrated various generic means for accessing information. In addition, Strasheela provides predefined accessors for more specific information. For example, the method getSimultaneousItems returns all score in the whole score items which are simultaneous to a given item (i.e. overlap in time). Like many methods shown before, getSimultaneousItems also supports an argument test. The score topology of this section is a simultaneous container, containing two sequential containers which in turn contain notes. Such a topology can express polyphony where multiple voices run in parallel. MyNote is the first note of the upper voice (accessed with the handle argument). Two notes of the lower voice sound simultaneous with MyNote, and these are returned by getSimultaneousItems (check their textual representation to confirm they are correct). The procedure Wait delays the inspecting until the notes are found. Try what happens if you use another function/method as test for getSimultaneousItems. */ local MyNote _ /*IgnoreScore*/ = {Score.makeScore sim(items:[seq(items:[note(handle:MyNote duration:2 pitch:72) note(duration:1 pitch:71) note(duration:3 pitch:72)]) seq(items:[note(duration:1 pitch:60) note(duration:2 pitch:62) note(duration:3 pitch:64)]) ] startTime:0) unit} Result = {MyNote getSimultaneousItems($ test:isNote)} in {Wait Result} {Inspect Result} end ** User-defined accessors No information available. /* Strasheela provides access to various information, but chances are that some accessor for your particular requirements is not available yet. However, Strasheela makes it easy for you to define your own. As a demonstration, this section defines an accessor similar to getSimultaneousItems used in the previous section. This section accesses the relevant information using generic accessors such as the method filter. Moreover, it encapsulates our new accessors in functions, so the accessors are modular and can be easily reused later. More specifically, two functions are defined. The function IsSimultaneousItem expects two score items and checks whether their temporal position overlaps. The function GetSimultaneousNotes expects a single score item, retrieves its top-level temporal container and then filters out all score objects directly or indirectly contained within this container which meet a specific condition. The condition is a conjunction of three tests: (i) a selected object Y must not be the argument X given to GetSimultaneousNotes, (ii) Y must be a note, and (iii) the two objects X and Y must be simultaneous -- which is tested with the function IsSimultaneousItem defined before. Finally, the function IsSimultaneousItem is used in an section virtually idential to the previous section. */ local fun {IsSimultaneousItem X Y} StartX = {X getStartTime($)} StartY = {Y getStartTime($)} EndX = {X getEndTime($)} EndY = {Y getEndTime($)} in (StartX < EndY) andthen (StartY < EndX) end fun {GetSimultaneousNotes X} TopLevel = {X getTopLevels($ test:Score.isTemporalContainer)}.1 in {TopLevel filter($ fun {$ Y} %% thread because IsSimultaneousItem would block %% when temporal params of X or Y are undetermined thread Y \= X andthen {Y isNote($)} andthen {IsSimultaneousItem X Y} end end)} end MyNote _ /*IgnoreScore*/ = {Score.makeScore sim(items:[seq(items:[note(handle:MyNote duration:2 pitch:72) note(duration:1 pitch:71) note(duration:3 pitch:72)]) seq(items:[note(duration:1 pitch:60) note(duration:2 pitch:62) note(duration:3 pitch:64)]) ] startTime:0) unit} Result = {GetSimultaneousNotes MyNote} in {Wait Result} {Inspect Result} end * Customising Output to Export Formats ** intro Strasheela's music representation has been designed to make the definition of new output formats and the extension of existing formats relatively simple without necessarily touching the original Strasheela code. This section demonstrates the definition of Csound and Liliput output for a different temperament. The temperament is expressed in the score by a new pitch unit et31, denoting 31-tone equal-temperament. For more information on this interesting temperament visit http://www.tonalsoft.com/enc/number/31edo.aspx or http://en.wikipedia.org/wiki/31_equal_temperament. Please note that the output for these sections again requires the correct settings for Csound, a sound file editor, lilypond, convert-ly (an application part of Lilypond), and a PDF file viewer (via 'Settings...' menu entry). ** Output Csound Score No information available. /* This section defines Csound score file format export from scratch. The 31-tone equal-temperament pitch value is transformed into a MIDI pitch float in the Csound score. This definition is simplified by procedures predefined in Strasheela such as Out.scoreToEvents and Out.writeToFile (see the Strasheela reference for more details on these procedures). A test demonstrates this definition. */ local %% Returns true if X is a note whose pitch unit is et31 fun {IsEt31Note X} {X isNote($)} andthen {X getPitchUnit($)} == et31 end %% Transforms MyScore into a list of VS in Csound note syntax fun {ScoreToCsoundNotes MyScore} %% Out.scoreToEvents expects a list of clauses %% Test#Transformation: when the function Test matches, then the %% function Transformation is called with the matching score %% object. {Out.scoreToEvents MyScore [%% transform note with pitch unit et31 into Csound note of the %% format [i1 StartTime Duration Amplitude Pitch], where %% Amplitude is in the interval [0,1] and Pitch is a MIDI %% number float. This format corresponds with the default %% Csound orc provided by Strasheela. IsEt31Note#fun {$ MyNote} [{Out.listToVS [i1 {MyNote getStartTimeInSeconds($)} {MyNote getDurationInSeconds($)} %% transform MIDI velocity into interval [0,1] {IntToFloat {MyNote getAmplitude($)}} / 128.0 %% transform et31 to MIDI float {IntToFloat {MyNote getPitch($)}} * 12.0 / 31.0] " "}] end %% raise error for every other event in MyScore isScoreObject#fun {$ X} raise unsupported(X) end end] %% only output fully determined events with a duration > 0, ignore %% everything else unit(test:fun {$ X} {X isEvent($)} andthen {X isDet($)} andthen ({X getDuration($)} > 0) end)} end %% Output MyScore as Csound score file at Path proc {OutputCsoundEt31 MyScore Path} Extension = ".sco" in {Out.writeToFile {Out.listToVS {ScoreToCsoundNotes MyScore} "\n"} Path#Extension} end %% A testscore: a simple cadence C-min, F-min, G-maj, C-min %% pitch notation: 31 * Octave + ET 31 pitch class MyScore = {Score.makeScore sim(items:[seq(items:[note(duration: 4 pitch:31*5 + 18 pitchUnit:et31 amplitude:64) % MIDI velo note(duration: 4 pitch:31*5 + 21 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 18 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 18 pitchUnit:et31 amplitude:64)]) seq(items:[note(duration: 4 pitch:31*5 + 8 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 13 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 5 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 8 pitchUnit:et31 amplitude:64)]) seq(items:[note(duration: 4 pitch:31*5 + 0 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 0 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*4 + 28 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 0 pitchUnit:et31 amplitude:64)])] startTime:0 timeUnit:beats(4)) unit} Path = {Tk.return tk_getSaveFile} in {MyScore wait} %% output Csound score file to Path {OutputCsoundEt31 MyScore Path} {Out.callCsound unit(file:Path %% use Path as is scoDir:nil soundDir:nil)} {Out.playSound unit(file:Path %% use Path as is scoDir:nil soundDir:nil)} end ** Output Lilypond No information available. /* This section extends Strasheela's lilypond export to support 31-tone equal temperament. The pitch notation uses a mapping from et31 pitch classes to notated pitches as shown at http://www.tonalsoft.com/enc/number/31edo.aspx. Please note that the notation of this temperament correctly supports enharmonic notation (in contrast to Strasheela's default Lilypond output which only supports sharps). Moreover, interval transpositions in this temperament result in the correct enharmonic spelling up to two sharps and flats. Again, a test demonstrates this definition (only flats are used in the test for simplicity). */ local fun {IsEt31Note X} {X isNote($)} andthen {X getPitchUnit($)} == et31 end %% This 31-tone equal temperament pitch class mapping follows %% http://www.tonalsoft.com/enc/number/31edo.aspx LilyEt31PCs = pcs(c deses cis des cisis d eses 'dis' es disis e fes eis f geses fis ges fisis g aeses gis aes gisis a beses ais bes aisis b ces bis) LilyOctaves = octs(",,,," ",,," ",," "," "" "'" "''" "'''" "''''") %% Transform a Pitch (an int) into the corresponding Lily code (a VS) fun {ET31PitchToLily MyPitch} PC = {Int.'mod' MyPitch 31} + 1 Oct = {Int.'div' MyPitch 31} + 1 in LilyEt31PCs.PC # LilyOctaves.Oct end %% Expects a Strasheela note object and returns the corresponding %% Lilypond code (a VS). For simplicity, this transformation does not %% support any expessions (e.g. fingering marks, or articulation %% marks). fun {NoteEt31ToLily MyNote} Rhythms = {Out.lilyMakeRhythms {MyNote getDurationParameter($)}} in %% if MyNote is shorter than 64th then skip it (Out.lilyMakeRhythms %% then returns nil) if Rhythms == nil then '' else Pitch = {ET31PitchToLily {MyNote getPitch($)}} FirstNote = Pitch#Rhythms.1 in %% handle tied notes if {Length Rhythms} == 1 %% no tied notes then FirstNote %% all values in Rhythm.2 are tied to predecessor else FirstNote#{Out.listToVS {Map Rhythms.2 fun {$ R} " ~ "#Pitch#R end} " "} end end end %% A testscore: a simple cadence C-min, F-min, G-maj, C-min %% pitch notation: 31 * Octave + ET 31 pitch class MyScore = {Score.makeScore sim(items:[seq(items:[note(duration: 4 pitch:31*5 + 18 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 21 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 18 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 18 pitchUnit:et31 amplitude:64)]) seq(items:[note(duration: 4 pitch:31*5 + 8 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 13 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 5 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 8 pitchUnit:et31 amplitude:64)]) seq(items:[note(duration: 4 pitch:31*5 + 0 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 0 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*4 + 28 pitchUnit:et31 amplitude:64) note(duration: 4 pitch:31*5 + 0 pitchUnit:et31 amplitude:64)])] startTime:0 timeUnit:beats(4) ) unit} in {MyScore wait} %% Strasheela's standard Lilypond output procedure %% Out.renderAndShowLilypond provides an argument clauses which %% expects a list of clauses Test#Transformation. When the function %% Test matches an object, then the function Transformation is called %% with the matching score object to generate Lilypond %% code. Otherwise, the score object is processed by the default %% clauses of Out.renderAndShowLilypond. {Out.renderAndShowLilypond MyScore unit(file:"test" clauses:[IsEt31Note#NoteEt31ToLily])} end * to be sorted ** Implicit pattern matching in Strasheela The following function GetPitch expects a record as argument which must match the record note(pitch:Pitch ...). The variable Pitch is implicitly declared and bound to the value at the feature 'pitch' of the record given as argument to the function. Please note that the record in the header of the function GetPitch is not even complete but contains three dots (...) to indicate that further record features are possible. local fun {GetPitch note(pitch:Pitch ...)} Pitch end in {Inspect {GetPitch note(duration:4 pitch:60)}} end