Musical Constraint Satisfaction Problems in Strasheela

About this document
— Music Constraint Programming
intro
A Dummy Example
intro
With comments
Without comments
A Real Example
intro
Score Distribution Strategies
intro
Structuring complex CSPs
intro
Rule Applicators
intro
Deriving Information as Variable
intro
?? Pattern Constraints
intro
Constraining Inaccessible Score Contexts
intro
Modelling Soft Constraints
intro
Reified Constraints as Boolean Test
intro
Using plain test
wrong
good
More Information
intro

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.

— Music Constraint Programming

intro

The following sections introduce music constraint programming with Strasheela.

Please note that this part of the tutorial is still unfinished!

A Dummy Example

intro

This section is extremely simple: you could easily create its result without constraint programming. Nevertheless, because it is so simple this section helps you do understand the basic structure of a typical Strasheela section.

The constraint script for this section is a unary procedure (as was explained above in the section "A Simple Script"). The single argument of a Strasheela constraint script is the solution score. The script creates the solution score MyScore, consisting of a sequence container with three notes, where the domain of each notes pitch is the set {60, ..., 72} (i.e., the pitches of the octave above middle c — the pitchUnit is its default keynumber). Then the section applies the constraint that in every pair of two neighbouring notes, the pitch of the later note is higher.

This script is handed to a constraint solver. For convenience, Strasheela provides custom constraint solvers which resemble the standard Oz solvers, but which add special support for searching for Strasheela scores. The solver of this section is SDistro.searchOne. Like the standard solver SearchOne, SearchOne searches for a single solution, which is retured in a list. In contrast to SearchOne, however, SDistro.searchOne expects two arguments. The first argument is the script and the second argument is a specification of the search strategy (distribution strategy) to use. Here, this specification is just unit, that is, the default settings are used.

With comments

No information available.

/* This first version of the section comes with extensive inline comments. */

local
   MyScore =
   %% SDistro.searchOne is the constraint solver
   {SDistro.searchOne
    %% the constraint script: a procedure returning a score object
    proc {$ MyScore}
       %% Create score object: three notes in sequence
       MyScore = {Score.makeScore
                  seq(items:[note(duration:4
                                  pitch:{FD.int 60#72}
                                  amplitude:64)
                             note(duration:4
                                  pitch:{FD.int 60#72}
                                  amplitude:64)
                             note(duration:4
                                  pitch:{FD.int 60#72}
                                  amplitude:64)]
                      startTime:0
                      timeUnit:beats(4))
                  unit}
       %% Constraint application: note pitches are increasing
       %%
       %% The higher-order procedure Pattern.for2Neighbours expects a
       %% list, and applies a binary procedure to every pair of
       %% neighbouring list elements (there exists also a procedure
       %% Pattern.forNeighbours of N neigbouring elements, and
       %% Pattern.map2Neighbours which expects a function and collects
       %% the results in a list).
       {Pattern.for2Neighbours {MyScore getItems($)}
        proc {$ Note1 Note2}
           {Note1 getPitch($)} <: {Note2 getPitch($)}
        end}
    end
    %% The distribution strategy specification, now outside the
    %% constraint script. unit means the default distribution
    %% strategy.
    unit
    %% The solver SDistro.searchOne returns a list with the first
    %% solution, we take this solution with .1
   }.1
in
   %% Output solution object to Csound score and call Csound
   {Out.renderAndPlayCsound MyScore
    unit(file:{Tk.return tk_getSaveFile}
         scoDir:nil soundDir:nil)}
end

Without comments

No information available.

/* This section is essentially the same as the previous one, but more
terse. All comments are stripped out, and the notes in the score are
not all explicitly noted. Instead, a list with all pitch variables is
created: FD.list returns a list of FD integers (try changing the
number of notes and the pitch variable domains..). This list is then
transformed into note declarations using Map (Map was explained in the
section "Higher-Order Programming"). */

local
   Sol = {SDistro.searchOne
          proc {$ MyScore}
             Pitches = {FD.list 3 60#72}
          in
             MyScore = {Score.makeScore
                        seq(items:{Map Pitches
                                   fun {$ Pitch}
                                      note(duration:4
                                           pitch:Pitch
                                           amplitude:64)
                                   end}
                            startTime:0
                            timeUnit:beats(4))
                        unit}
             {Pattern.for2Neighbours Pitches
              proc {$ P1 P2} P1 <: P2 end}
          end
          unit}.1
in
   {Out.renderAndPlayCsound Sol
    unit(file:{Tk.return tk_getSaveFile}
         scoDir:nil soundDir:nil)}
end

A Real Example

intro

[Note: unfinished]

The Explorer has been extended for outputting Strasheela scores into various formats.

Do settings for different program path names as explained before

Note for developer: output dir is always default (ie. /tmp) and this cannot be changed in settings GUI yet

{SDistro.exploreOne
 proc {$ MyScore}
    NoteNo = 11
    Pitches = {FD.list NoteNo 55#79}
    MaxPitch = {Pattern.max Pitches}
    MinPitch = {Pattern.min Pitches}
 in
    %% create score: NoteNo notes in sequence
    MyScore = {Score.makeScore
               seq(items:{Map Pitches
                          fun {$ Pitch}
                             note(duration:2
                                  pitch:Pitch
                                  amplitude:64)
                          end}
                   startTime:0
                   timeUnit:beats(4))
               unit}
    %% Interval between max and min pitch is major seventh
    {FD.distance MaxPitch MinPitch '=:' 11}
    %% Melodic intervals are minor/major second, minor/major third, or tritone
    for Pitch1 in Pitches
       Pitch2 in Pitches.2
    do
       Interval = {FD.int [1 2 3 4 6]}
    in
       {FD.distance Pitch1 Pitch2 '=:' Interval}
    end
    %% neither the first nor the last pitch are the max or min
    Pitches.1 \=: MaxPitch
    Pitches.1 \=: MinPitch
    {List.last Pitches} \=: MaxPitch
    {List.last Pitches} \=: MinPitch
 end
 unit(value:random)}

Score Distribution Strategies

intro

Show how to use various predefined score distribution strategies.

One of Strasheela's major strength is that it allows you to define your own score distribution strategies — optimised for your specific musical CSP. Although the definition of score distribution strategies is beyond this tutorial, it is already discussed in detail in the thesis "Composing Music by Composing Rules: Design and Usage of a Generic Music Constraint System", Chap. 7 (http://strasheela.sourceforge.net/documents/TorstenAnders-PhDThesis.pdf). This chapter also points to further relevant Oz documentation.

Structuring complex CSPs

intro

Decomposing a CSP into modular definitions which create parts of the score and define compositional rules.

Rule Applicators

intro

Defining and Applying Abstracted Rules ..

Deriving Information as Variable

intro

Creating variables on the fly..

Also show Memoization?

?? Pattern Constraints

intro

No information available.

Constraining Inaccessible Score Contexts

intro

two ways: delayed constraint application and reified constraint

third way: redefine your CSP

Modelling Soft Constraints

intro

No information available.

Reified Constraints as Boolean Test

intro

Test: find simultaneous notes. Problem: temporal structure is only partially determined.

Using plain test

No information available.


%% Using the plain Boolean test isSimultaneousItem (i.e., a method returning either true or false) blocks until the temporal structure is fully determined

local
Note1
MyScore = {Score.makeScore
           sim(items:[seq(items:[note(duration:2)
                                 note(duration:1 handle:Note1)
                                 note])
                      seq(items:[note(duration:1)
                                 note(duration:2)
                                 note])
                      seq(items:[note
                                 note
                                 note])]
               startTime:0
               timeUnit:beats(4))
           unit}
in
%% blocks
{Browse {MyScore filter($ fun {$ X}
                             {X isNote($)} andthen
                             X \= Note1 andthen % ignore Note1
                             {Note1 isSimultaneousItem($ X)}
                          end)}}
end

wrong

No information available.


%% Wrong approach: first testing whether items are determined before using isSimultaneousItem does not block, but notes which are potentially simultaneous are ignored

local
Note1
MyScore = {Score.makeScore
           sim(items:[seq(items:[note(duration:2)
                                 note(duration:1 handle:Note1)
                                 note])
                      seq(items:[note(duration:1)
                                 note(duration:2)
                                 note])
                      seq(items:[note
                                 note
                                 note])]
               startTime:0
               timeUnit:beats(4))
           unit}
in
{Browse {MyScore filter($ fun {$ X}
                             {X isNote($)} andthen
                             X \= Note1 andthen % ignore Note1
                             {IsDet {X getStartTime($)}} andthen
                             {IsDet {X getEndTime($)}} andthen
                             {Note1 isSimultaneousItem($ X)}
                          end)}}
end


good

No information available.


%% Suitable approach: using reified constraint together with an equality check as a Boolean test

local
Note1 Note2 Seq1
MyScore = {Score.makeScore
           sim(items:[seq(items:[note(duration:2)
                                 note(duration:1 handle:Note1)
                                 note])
                      seq(items:[note(duration:1)
                                 note(duration:2)
                                 note])
                      seq(items:[note(handle:Note2)
                                 note
                                 note]
                         handle:Seq1)]
               startTime:0
               timeUnit:beats(4))
           unit}
in
%% blocks until all potentially simultaneous notes are determined or
%% are known to be not simultaneous
{Browse {MyScore filter($ fun {$ X}
                             {X isNote($)} andthen
                             X \= Note1 andthen % ignore Note1
                             {Note1 isSimultaneousItemR($ X)} == 1
                          end)}}
%% wait for three seconds
{Delay 3000}
%%
%% Two cases: either there are sim notes in third voice or not
{Note2 getDuration($)} = 4
% {Seq1 getDuration($)} = 1
end

More Information

intro

This tutorial provided an overview of important Strasheela concepts and demonstrated them with many sections. It concludes with references to further Strasheela documentation.

The fundamental concepts and the design of Strasheela is detailed in my thesis "Composing Music by Composing Rules: Design and Usage of a Generic Music Constraint System" (http://strasheela.sourceforge.net/documents/TorstenAnders-PhDThesis.pdf). This text also compares Strasheela carefully with other similar systems.

The sections in this tutorial are all rather short and explain only specific matters. More elaborated sections can be found at strasheela/doc/StrasheelaExamples.html.

This tutorial gave an overview of various Strasheela functionality. The Strasheela reference documentation at strasheela/doc/StrasheelaReference.html (hopefully) explains every Strasheela construct (procedure, class, ...) in detail. In addition, the usage of most Strasheela constructs is also demonstrated in several test cases in Strasheela's test files. The test files for the Strasheela core are situated in strasheela/testing/.

The reference documentation and test files for various Strasheela extensions are (usually) found in the directory of the respective extension. The writing of such an extension is demonstrated in a contribution template at strasheela/contributions/ExtensionTemplate/. This template also explains how to write your own Strasheela classes in strasheela/contributions/ExtensionTemplate/source/ClassDefinitionDemo.oz

In case you have specific questions, please consider contacting the strasheela-users mailing list (https://lists.sourceforge.net/lists/listinfo/strasheela-users).

Finally, if all else fails, the Strasheela source itself can serve as documentation. I really tried to make the code readable for other users as well...

Have fun with Strasheela!

Torsten Anders