This functor defines abstractions for constraining the musical form. The fundamental idea here is the motif prototype concept. A motif prototype is a Strasheela score object (e.g., a container containing a few notes) which serves as a blueprint for motif instances in the final score. The motif prototype is usually fully determined (although this is not necessary, see MakeScript documentation). From this prototype, an extended script is created using the procedure MakeScript. This extended script expects some arguments and returns a motif instance which is similar to the prototype. For example, the rhythmical structure may be the same in the prototype and all motif instances, the actual pitches differ, but the pitch contour is also the same. In which way the prototype and the motif instances are similar is defined by the user, see the documentation of MakeScript for details.
In general, an extended script is a function which expects some arguments and returns a score object to which constraints are applied. With the help of GUtils.extendedScriptToScript, an extended script can be directly given to the Strasheela solvers exported by SDistro. Alternatively, the exteneded script can be used inside a script to create a part of the final score. See the examples for both such uses.
Strasheela defines multiple motif models. These are the differences between two motif models, the present one amd the one defined in contributions/anders/Motif.
Advantages of the prototype-based motif model: The charateristic features of motifs are conveniently defined in the model by giving examples (the prototypes). In the [older] motif model, these features are defined in a more abstract way (although an extension of the older model might also allow defining these features by way of examples). Moreover, different motif instances in the same CSP can differ in their score topology in the prototype-based model (e.g., a melodic motif might be expressed as a sequential container with notes, whereas a chordal motif might additionally use simultaneous containers). This is not possible in the [older] motif model. In general, a hierarchically nested score is more easily defined in the prototype-based model. Also, additional score information (e.g., sound synthesis details such as continuos controllers or timing functions, both expressable by fenvs) which are shared by all motif instances are added conveniently in this model.
Advantages of the other motif model (variation motif model): Most importantly, the motif identity is constrainable in the variation motif model. For example, in this model it can be specified by constraints which motif identity can follow which identity (e.g., whether A B A is permitted or not). In the prototype-based model, the arrangement of motifs in the score must be determined in the problem definition and cannot be constrained. Also, this motif model distinguishes between the identity the variation of a motif (the variation can be constrained as well, independently of the motif identity). Multiple variation scripts can be created from a single prototype in the prototype-based model, but formally these variations are indistinguishable from unrelated motif scripts (in practise, you can name them in a way that you recognise which motif scripts are related). Making an distinction between motif identity and variation by distinct FD variables (as in the variation model) is unnecessary in this model, as the arrangement of motifs in the score cannot be constrained anyway.
In summany, the prototype-based motif model allows more easily for direct control of the resulting musical form (e.g., the form may be composed 'by hand', or created by some deterministic algorithmic composition technique). The variation motif model, on the other hand, is suited for complex CSPs where also the musical form is constrained besides other musical aspects (e.g., complex forms such as inventios or fugues).
Functor
PrototypeMotif ("PrototypeMotif.oz")
Import
- FD
- GUtils at "x-ozlib://anders/strasheela/source/GeneralUtils.ozf"
- LUtils at "x-ozlib://anders/strasheela/source/ListUtils.ozf"
- Score at "x-ozlib://anders/strasheela/source/ScoreCore.ozf"
- SMapping at "x-ozlib://anders/strasheela/source/ScoreMapping.ozf"
- Pattern at "x-ozlib://anders/strasheela/Pattern/Pattern.ozf"
Export
Define
proc{MakeScript MyProto Args MyScript}
MakeScript returns a (sub)-CSP for creating motif instances similar to a given prototype. MakeScript expects a prototype motif MyProto (a score object, usually a container with other score objects) and some optional arguments. MakeScript returns an extended script, that is a binary procedure with the interface {MyScript Args MyScore} (for details see GUtils.extendedScriptToScript).
The returned script expects the following optional arguments. 'initScore' (defaults to false) specifies whether the resulting score is fully initialised implicitly. Ignore this argument if the returned motif instance is nested into other containers and that way only a part of a score, but set it to true if the returned motif instance is the full score (e.g., for testing). For further details on score initialisation see Score.initScore.
In addition, the returned script expects all arguments of its top-level score object (e.g., a script for a temporal object expects the optional arguments startTime and timeUnit). Further arguments can be defined explicitly with the MakeScript argument 'scriptArgs' (see below). The optional MakeScript arguments are the following.
'unset': this argument specifies which variables in the resulted motif instance are not shared with the prototype. This argument expects a list of pairs TestI#AttributesI, where TestI is a Boolean function or method and AttributesI is either a single atom naming an object attribute or a list of such atoms. The attributes must either bind a parameter or directly a variable (e.g., the note attributes 'pitch' and 'duration' bind parameters). For every score item (e.g., note or container) for which a Test returns true, the corresponding attribute variables (e.g., parameter values) are unset and independent of the prototype. The following example unsets all note pitches.
unset:[isNote#pitch]
The parameters start and end times are always implicitly unset. In case it is required that all motif instances start exactly at the start time of the prototype, constrain the startTime values of the motif instances and the prototype explicitly to the same value (see argument 'prototypeDependencies').
Note that only parameter values which are unset are unique to a motif instance. All non-unset variables are shared by the prototype and all motif instances of this prototype.
The prototype can have undetermined variables. In that case the prototype must be defined within the (top-level) script so that all variables are in local spaces (variables in the top-level space, i.e. outside a script, block the solver).
'prototypeDependencies': this argument defines constraints between 'unset' variables of the resulting motif instance and the prototype. The argument expects a list of pairs TestI#ConstraintI. TestI is a Boolean function or method. ConstraintI is a procedure with the interface {$ MyPrototype MyInstance} which expects the motif protoype and the motif instance returned by the script as arguments. The following dummy example dependency constrains all motif instance pitches to be higher than their corresponding prototype pitch.
prototypeDependencies: [isNote#proc {$ Proto Inst}
{Proto getPitch($)} <: {Inst getPitch($)}
end]
NB: 'prototypeDependencies' (currently) requires that the protytype and the motif instance have the same score topology, i.e. method collect with given Test must return corresponding objects (if the protytype and the motif instance differ in the number of score objects, then score objects can only be removed at the end -- score objects returned by collect for both prototype and motif instance must still correspond).
'constraints': this argument defines additional constraints applied to the resulting motif instance. It expects a list of pairs TestI # ConstraintI. TestI is a Boolean function or method. ConstraintI is a procedure with the interface {$ MyInstance} which expects the motif instance. The following example constraints the domain of all notes in the motif.
constraints: [isNote#proc {$ N} {N getPitch($)} = {FD.int 60#72} end]
'scriptArgs': this argument specifies additional arguments of the returned script. It expects a record whose features are the additional script arguments. The values at these features are either a procedure ConstraintI or a pair ConstraintI # DefaultI. ConstraintI is a procedure with the interface {$ MyMotif Argument}. This procedure is applied to the motif instance and the script argument specified at its feature. Optional script arguments are defined by additionally providing DefaultI, a default argument value. The following 'scriptArgs' example specifies a pitch domain for all notes contained in the motif with the default domain 60#72.
scriptArgs: unit(pitchDomain: proc {$ MyMotif Dom}
{ForAll {MyMotif collect($ test:isNote)}
proc {$ N} {N getPitch($)} = {FD.int Dom.1} end}
end # dom(60#72))
'constructors': this argument (a record of classes or unary functions) makes it possible to overwrite constructors used when the motif instance is created with the function Score.makeScore2. By default, the classes of the prototype are specified as constructors, but this default can be overwritten for specific score objects with this argument. Example: lets assume the prototype contains notes which are instances of the class Score.note. The motif instance can overwrite this class with the note class HS.score.note by specifying the following. In that case, only the (no unset) note parameters defined by the note objects in the prototype are copied, but additional constraints can be applied explicitly to the parameters of the new class (e.g., via a special constructor function instead of the class, by using the 'scriptArgs', etc.)
constructors: unit(note:HS.score.note)
NB: Be careful with variables as default script arguments, they would be shared by all motif instances. If you need independent variables as script arguments, then wrap them in a function argument (e.g., fun {$} {FD.decl} end) which would be called inside the procedure ConstraintI.
'motifTest': this is an optional output argment. It binds its value to a Boolean function which returns true for motif instances created with MyScript.
Note that MakeScript internally uses toInitRecord. Therefore, all present restrictions of toInitRecord apply: getInitInfo must be defined correctly for all classes and only tree-form score topologies are supported.
fun{IsMotif X}
Returns true for any motif instance created by a script which was created by MakeScript.
fun{IsInMotif X Test}
Is X contained in a motif (directly or indirectly) and is Test true? Test is a boolean binary function with the interface {$ X MyMotif}.
proc{NestedScript MyTextScore Args MyScript}
NestedScript returns a (sub)-CSP for creating nested motif instances which consist in multiple sub-motifs. For an example see PrototypeMotif/testing/PrototypeMotif-test.oz.
NestedScript expects a score MyTextScore and some optional arguments. MyTextScore is a textual score (a record) which specifies the motif score topology. MyTextScore typically contains motif instance declations created with MakeScript or other NestedScript calls. Note that NestedScript expects a score in textual format, in contrast to MakeScript which expects a score object. MakeScript returns an extended script, that is a binary procedure with the interface {MyScript Args MyScore} (for details see GUtils.extendedScriptToScript).
The returned script expects the following optional arguments. The argument 'initScore' (defaults to false) specifies whether the resulting score is fully initialised implicitly. Ignore this argument if the returned motif instance is nested into other containers and that way only a part of a score, but set it to true if the returned motif instance is the full score (e.g., for testing). For further details on score initialisation see Score.initScore.
Arbitrary arguments can be handed directly to the creation of nested score objects, including to nested motifs by the argument 'nestedArgs'. The intended score objects are identified by info tags. 'nestedArgs' expects a list of pairs ID#Args where ID is a complete info tag (i.e. a record, not just its label) and Args is the record of arguments for this score object. ID can also be a list of info tags, which allows to specify multiple score objects for the same arguments. Handing over arguments this way does not only work for score objects explicitly contained in MyTextScore, but also for deeper nested sub-sub-motifs. For non-motif score objects, however, it only works if they are explicitly contained in MyTextScore. Note that 'nestedArgs' arguments overwrite the respective arguments of the matching score objects. The exception are info tags, which are appended. In case of complex score with several info tags consider using a record like id(ID) in order to avoid clashes of info record labels.
In addition, the returned script expects all arguments of its top-level score object (e.g., a script for a temporal object expects the optional arguments startTime and timeUnit). Further arguments can be defined explicitly with the MakeScript argument 'scriptArgs' (see below). The optional MakeScript arguments are the following.
'constructors': a record of score constructors (unary functions or classes). These constructors are very much like the second argument of Score.makeScore. However, they must expect the additional (init method) argument 'nestedArgs', which is used to recursively pass arguments to inner score objects and motifs (see above).
'constraints': this argument defines additional constraints applied to the resulting nested motif instance. It expects a list of pairs TestI # ConstraintI. TestI is a Boolean function or method. ConstraintI is a procedure with the interface {$ MyInstance} which expects the motif instance. The following example constraints the domain of all notes in the nested motif.
constraints: [isNote#proc {$ N} {N getPitch($)} = {FD.int 60#72} end]
'scriptArgs': this argument specifies additional arguments of the returned script. It expects a record whose features are the additional script arguments. The values at these features are either a procedure ConstraintI or a pair ConstraintI # DefaultI. ConstraintI is a procedure with the interface {$ MyMotif Argument}. This procedure is applied to the motif instance and the script argument specified at its feature. Optional script arguments are defined by additionally providing DefaultI, a default argument value. The following 'scriptArgs' example specifies a pitch domain for all notes contained in the nested motif with the default domain 60#72.
pitchDomain: proc {$ MyMotif Dom}
{ForAll {MyMotif collect($ test:isNote)}
proc {$ N} {N getPitch($)} = {FD.int Dom.1} end}
end # dom(60#72)
NB: Be careful with variables as default script arguments, they would be shared by all motif instances. If you need independent variables as script arguments, then wrap them in a function argument (e.g., fun {$} {FD.decl} end) which would be called inside the procedure ConstraintI.
'motifTest': this is an optional output argment. It binds its value to a Boolean function which returns true for motif instances created with MyScript.
proc{ChoiceScript Scripts ChoiceArgs MyScript}
Expects Scripts, a record of extended scripts, and returns a new extended script by which one of the scripts in Scripts can be selected. The new script expects all arguments of the scripts in Scripts and an additional optional argument. The name of this additional argument is set by the ChoiceArgs feature choiceArg. ChoiceArgs default is
unit(choiceArg:choose)
The additional argument itself defaults to the first script in Scripts (first of its arity).
It is recommended that all scripts in Scripts expect the same arguments, so that the arguments of the returned script don't depend on the selected script.
fun{UnifyDependency Fn}
[convenience definition] Function for defining a dependency between a motif instance and a prototype. The unary function Fn is applied to the prototype and the motif instance and the results are unified. The following example is a pair given to the MakeScript argument prototypeDependencies which constrains that the prototype and the motif instance feature the same pitch contour.
prototypeDependencies:
[isContainer#{PM.unifyDependency
fun {$ X} {Pattern.contour {X map($ getPitch test:isNote)}} end}]
fun{GetFirstNote MyMotif}
[convenience definition] Expects a motif instance and returns the first note contained, regardless of nesting depth (returns the first note returned by the collect method).
proc{GetHighestPitch MyMotif MaxPitch}
[convenience definition] Expects a motif instance and returns the pitch of the highest motif note (regardless of nesting depth).
End