%% %% This file contains the Oz code for all examples of the Strasheela tutorial for direct evalulation %% %% The following file presents some basic ideas of Strasheela %% usage. The musical examples aim to be simple for the sake of %% clarity ;-) %% %% The user of Strasheela must make himself familiar %% with the Oz programming language (www.mozart-oz.org). The textbook %% 'Concepts, Techniques, and Models of Computer Programming ' by %% Peter Van Roy and Seif Haridi (see %% www.info.ucl.ac.be/people/PVR/book.html) is a very good starting %% point. Other useful tutorials to get into Strasheela are the %% 'Tutorial of Oz' %% (www.mozart-oz.org/documentation/tutorial/index.html) and 'Finite %% Domain Constraint Programming in Oz. A Tutorial' %% (www.mozart-oz.org/documentation/fdt/index.html). %% %% Have fun with Strasheela! %% Torsten Anders (www.torsten-anders.de) %% !! TODO: replace: AllIntervalSeries %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Example: all-distance series, series represented as a list of %%% pitches. To better understand the output, the list of distances %%% between neighbouring pitches is shown as well. declare proc {AllDistanceSeries Solution} N = 12 % Solution series length N1 = N-1 Pitches Intervals % Vars for series and intervals in Solution = unit(pitches:Pitches intervals:Intervals) Pitches = {FD.list N 0#N1} % List of FD vars in [0,N-1] Intervals = {FD.list N1 1#N1} for Pitch1 in {List.take Pitches N1} % butlast of Pitches Pitch2 in Pitches.2 % tail of Pitches Interval in Intervals do {FD.distance Pitch1 Pitch2 '=:' Interval} end {FD.distinctD Pitches} % no pitch class repetition {FD.distinctD Intervals} % no (abs) interval repetition %% Specify search strategy {FD.distribute ff Pitches} end {Browse {SearchOne AllDistanceSeries}} {ExploreOne AllDistanceSeries} % {Explorer.all AllDistanceSeries} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %% To represent more complex music than a scale we need a score %% representation. %% Generate a simple score and output it. Score.makeScore expects a %% literal score representation consisting of records and returns a %% score object. Each record in the literal score representation is an %% init method to create a score object (which optionally contains %% other score objects), i.e. score object attributes can be set by %% specifying init record features. declare MyScore = {Score.makeScore seq(items: [note(duration: 4 pitch: 60 amplitude: 80) note(duration: 4 pitch: 64 amplitude: 80) note(duration: 4 pitch: 67 amplitude: 80)] startTime:0 %% duration 1 corresponds to 1/4 beats, i.e. duration %% one is a 1/16th note. timeUnit:beats(4)) unit} %% There are various ways to look at scores %% Look at the object without inspecting it. {Browse MyScore} %% !! TODO %% toInitRecord %% toFullRecord %% output into MIDI, Csound and Lily (bare and with args) %% make generator classes/functions explicit %% To inspect the score nesting and parameter values (see %% toPPrintRecord doc for additional args) {Browse {MyScore toPPrintRecord($)}} %% To see all score object attributes (see toFullRecord doc for %% additional args) {Browse {MyScore toFullRecord($)}} %% To transform the object back into an init record (e.g. for %% archiving purposes after a successful search). {Browse {MyScore toInitRecord($)}} %% To listing to the score (you need to have Csound and a sound file player installed and some Strasheela environment variables set correctly). See the doc for more arguments. {Out.renderAndPlayCsound MyScore unit} %% To view the score in common music notation (you need to have lilypond and a PDF viewer installed and some Strasheela environment variables set correctly). See the doc for more arguments. {Out.renderAndShowLilypond MyScore unit} %% Many unset score parameters are often implicitly bound to FD %% integers (for details on the init defaults see the class %% definitions in ScoreCore.oz). Such parameters can be searched for in a script. declare MyScore = {Score.makeScore seq(items: [note note note]) unit} {Browse {MyScore toPPrintRecord($)}} %% However, it is often sensible to set some parameters directly by %% hand. This is most often true for the startTime of the full score %% -- leaving the startTime unset would greatly increase the size of %% the search space without any further use. %% %% For output into various formats it is also necessary to set the %% timeUnit (the timeUnits of all objects are unified). See the doc of %% the class TimeParameter for details. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %% Example: all-interval series, series represented by a score (a %% sequence of notes). declare fun {AllDistanceSeries1 N} proc {$ MyScore} N1 = N-1 Pitches Intervals in MyScore = {Score.makeScore seq(items: {LUtils.collectN N fun {$} note(duration: 4 pitch: {FD.int 60#60+N1} amplitude: 80) end} startTime: 0 timeUnit:beats(4)) unit} %% explicit param units (for output): all timing param units are %% unified with each other. Pitches = {MyScore map($ getPitch test:isNote)} Intervals = {FD.list N1 1#N1} for Pitch1 in {List.take Pitches N1} % butlast of Pitches Pitch2 in Pitches.2 % tail of Pitches Interval in Intervals do {FD.distance Pitch1 Pitch2 '=:' Interval} end {FD.distinctD Pitches} % no pitch class repetition {FD.distinctD Intervals} % no (abs) interval repetition %% Specify search strategy {FD.distribute ff Pitches} end end %% after evaluating {Init.addExplorerOuts} (e.g. in '#/.ozrc'), the Oz %% explorer supports score output into a few formats by simply %% clicking on solution notes. Select the desired output format in the %% explorer menu Nodes:Information Action. {Explorer.one {AllDistanceSeries1 4}} {Explorer.all {AllDistanceSeries1 8}} {Explorer.one {AllDistanceSeries1 12}} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% We can apply some complex constraint expressions directly to the %% score. However, a more general notion is to encapsulate a %% constraint expression into a compositional rule (implemented by a %% procedure) which then can be applied one or more times. %% %% Example: choral melody with simple voice leading rule. Example %% introduces the notion of a context: a voice leading rule makes a note %% and its previous note interdependend. declare local %% MIDI pitch domain reduction: only 'wite keys' (c major) proc {InCMajor MyNote} {List.forAll [1 3 6 8 10] % list of 'black' pitch classes (c=0) proc {$ BlackKey} {FD.modI {MyNote getPitch($)} 12} \=: BlackKey end} end %% Determine the pitch of the first and last note of MyVoice proc {StartAndEndWithFundamental MyVoice} Notes = {MyVoice getItems($)} in {Notes.1 getPitch($)} = 60 {{List.last Notes} getPitch($)} = 60 end %% voice leading: only intervals up to a fifth, no pitch repetition proc {NoBigJump Pitch1 Pitch2} %% all intervals between minor second and fourth are allowed {FD.distance Pitch1 Pitch2 '>=:' 1} {FD.distance Pitch1 Pitch2 '=<:' 5} end in proc {ChoralMelody MyMelody} N = 9 in MyMelody = {Score.makeScore seq(items: {LUtils.collectN N fun {$} %% predetermined and constant note durations note(duration: 4 pitch: {FD.int 53#72} amplitude: 80) end} startTime: 0 timeUnit:beats(4)) unit} %% %% Apply compositional rules: %% %% rule on melody {StartAndEndWithFundamental MyMelody} %% rule on single notes {MyMelody forAll(test: isNote InCMajor)} %% rule on pitch pair {Pattern.for2Neighbours {MyMelody mapItems($ getPitch)} NoBigJump} end end %% SDistro.exploreOne simplifies definition of a CSP with a score a solution: the score distribution strategy is not part of the script but given to SDistro.exploreOne. {SDistro.exploreOne ChoralMelody unit(order:size value:random)} {OS.srand 11} % the random seed can be set to 'select' a solution %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% ?? end tutorial here? %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% Extend the previous example to simple two-voice counterpoint %% declare local %% MIDI pitch domain reduction: only 'wite keys' (c major) proc {InCMajor MyNote} {List.forAll [1 3 6 8 10] % list of 'black' pitch classes (c=0) proc {$ BlackKey} {FD.modI {MyNote getPitch($)} 12} \=: BlackKey end} end %% Determine the pitch of the first and last note of MyVoice proc {StartAndEndWithFundamental MyVoice} Notes = {MyVoice getItems($)} in {Notes.1 getPitch($)} = 60 {{List.last Notes} getPitch($)} = 60 end %% voice leading: only intervals up to a fifth, no pitch repetition proc {NoBigJump Pitch1 Pitch2} %% all intervals between minor second and fourth are allowed {FD.distance Pitch1 Pitch2 '>=:' 1} {FD.distance Pitch1 Pitch2 '=<:' 5} end % harmony: only consonants proc {NoDissonance [Note1 Note2]} Consonance = {FD.int [3 4 7 8 9 12 15 16]} in %% Note1 is bass note {Note1 getPitch($)} + Consonance =: {Note2 getPitch($)} end in proc {SimpleCounterpoint MyScore} N = 9 Voice1 Voice2 in MyScore = {Score.makeScore sim(items: {LUtils.collectN 2 fun {$} seq(items: {LUtils.collectN N fun {$} note(duration: 1 pitch: {FD.int 53#72} amplitude: 80) end}) end} startTime: 0 timeUnit:beats) unit} Voice1 = {MyScore getItems($)}.1 Voice2 = {Nth {MyScore getItems($)} 2} %% %% Apply compositional rules: %% %% rule on single notes {MyScore forAll(test: isNote InCMajor)} %% rule on pitch pair {Pattern.for2Neighbours {Voice1 mapItems($ getPitch)} NoBigJump} {Pattern.for2Neighbours {Voice2 mapItems($ getPitch)} NoBigJump} %% rule on notes of Voice1 and their simultaneous notes %% %% NB: SMapping.forContexts only works for a predetermined context %% (here, the context of simultaneous notes is determined %% because the rhythmic structure is determined in the CSP def.) {SMapping.forContexts {Voice1 getItems($)} fun {$ X} X | {X getSimultaneousItems($ test:isNote)} end NoDissonance} %% rule on first (bass) voice {StartAndEndWithFundamental Voice1} end end {SDistro.exploreOne SimpleCounterpoint unit(order:size value:random)} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %% simple rhythmic rule: %% %% * predefine allowed rythmic values: 4, 2, 1 %% %% * Either repeat duration or double or halve it. %% %% * Start and end are predefined to be max. duration (4) %% %% NB: most rule definitions in the following examples apply a different 'style': instead of called the rules with a rule context (i.e. the set of score objects the rule constraints) -- see rules NoDissonance and NoBigJump and their application above -- the context is partly accessed within the rule definition (e.g. by accessing the simultaneous or precedessing note). %% %% %% simple monophonic and purely rhythmic example %% declare local proc {StartAndEndWithLongest Note} C = {Note getTemporalAspect($)} in if {Note isFirstItem($ C)} orelse {Note isLastItem($ C)} then {Note getDuration($)} = 4 end end proc {SlowRhythmChanges Note} C = {Note getTemporalAspect($)} in if {Note hasPredecessor($ C)} then Dur1 = {{Note getPredecessor($ C)} getDuration($)} Dur2 = {Note getDuration($)} HalveDur1 in %{FD.decl HalveDur1} {FD.times HalveDur1 2 Dur1} {FD.times HalveDur1 {FD.int [1 2 4]} Dur2} end end in proc {SmoothRhythm MyScore} N = 9 in MyScore = {Score.makeScore seq(items: {LUtils.collectN N fun {$} note(duration: {FD.int [1 2 4]} pitch: 60 amplitude: 80) end} startTime: 0 timeUnit:beats) unit} %% %% Apply compositional rules: %% {MyScore forAll(test: isNote proc {$ Note} {StartAndEndWithLongest Note} {SlowRhythmChanges Note} end)} %% search strategy (i.e. distribution strategy) {FD.distribute {SDistro.makeFDDistribution unit(order:size value:random)} {MyScore collect($ test:isParameter)}} end end {ExploreOne SmoothRhythm} %%% %%% Two parallel rhythmic sequences %%% declare local Durations = [1 2 4] proc {StartAndEndWithLongest Note} C = {Note getTemporalAspect($)} in if {Note isFirstItem($ C)} orelse {Note isLastItem($ C)} then {Note getDuration($)} = {List.last Durations} end end proc {SlowRhythmChanges Note} C = {Note getTemporalAspect($)} in if {Note hasPredecessor($ C)} then Dur1 = {{Note getPredecessor($ C)} getDuration($)} Dur2 = {Note getDuration($)} HalveDur1 in %{FD.decl HalveDur1} {FD.times HalveDur1 2 Dur1} {FD.times HalveDur1 {FD.int [1 2 4]} Dur2} end end in proc {SmoothRhythm2 MyScore} N = 17 EndTime in %{FD.decl EndTime} MyScore = {Score.makeScore sim(items: [seq(items: {LUtils.collectN N fun {$} note(duration: {FD.int Durations} pitch: 48 amplitude: 80) end} offsetTime:0 endTime:EndTime) seq(items: {LUtils.collectN N fun {$} note(duration: {FD.int Durations} pitch: 70 amplitude: 80) end} offsetTime:{List.last Durations} endTime:EndTime)] startTime:0 timeUnit:beats) unit} %% %% Apply compositional rules: %% {MyScore forAll(test: isNote proc {$ Note} {StartAndEndWithLongest Note} {SlowRhythmChanges Note} end)} %% search strategy (i.e. distribution strategy) {FD.distribute {SDistro.makeFDDistribution unit(order:size value:random)} {MyScore collect($ test:isParameter)}} end end {ExploreOne SmoothRhythm2} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %% %% To define IsSimultaneous with free rhythmic structure in an %% efficient and sound way, I need to go into reified constraints %% declare local Durations = [1 2 4] proc {StartAndEndWithLongest Note} C = {Note getTemporalAspect($)} in if {Note isFirstItem($ C)} orelse {Note isLastItem($ C)} then {Note getDuration($)} = {List.last Durations} end end proc {SlowRhythmChanges Note} C = {Note getTemporalAspect($)} in if {Note hasPredecessor($ C)} then Dur1 = {{Note getPredecessor($ C)} getDuration($)} Dur2 = {Note getDuration($)} HalveDur1 in %{FD.decl HalveDur1} {FD.times HalveDur1 2 Dur1} {FD.times HalveDur1 {FD.int [1 2 4]} Dur2} end end %% MIDI pitch domain reduction: only 'wite keys' (c major) proc {InCMajor Note} {List.forAll [1 3 6 8 10] % list of 'black' pitch classes (c=0) proc {$ BlackKey} {FD.modI {Note getPitch($)} 12} \=: BlackKey end} end proc {StartAndEndWithFundamental Note} C = {Note getTemporalAspect($)} in if {Note isFirstItem($ C)} orelse {Note isLastItem($ C)} then {Note getPitch($)} = 60 end end %% voice leading: only intervals up to a fifth, no pitch repetition %% (context dependent constraint -- getPredecessor -- but this %% context is predetermined by predetermined hierarchic structure) proc {NoBigJump Note} C = {Note getTemporalAspect($)} in if {Note hasPredecessor($ C)} then Pitch1 = {{Note getPredecessor($ C)} getPitch($)} Pitch2 = {Note getPitch($)} in %% all intervals between minor second and fourth are allowed {FD.distance Pitch1 Pitch2 '>:' 0} {FD.distance Pitch1 Pitch2 '<:' 6} end end %% harmony: only consonants proc {NoDissonance Note1} OtherVoiceNotes = {{Note1 find($ fun {$ X} {X hasThisInfo($ voice2)} end mode:graph)} getItems($)} Pitch1 = {Note1 getPitch($)} in {ForAll OtherVoiceNotes proc {$ Note2} Pitch2 = {Note2 getPitch($)} Consonance in %% !! Consonance does not necessarily get determined: the %% solution diamond in the explorer are light green Consonance = {FD.int [3 4 7 8 9 12 15 16]} {FD.impl {Note1 isSimultaneousItemR($ Note2)} ( Pitch1 + Consonance =: Pitch2 ) 1} end} end in proc {RhythmicCounterpoint MyScore} N=17 EndTime in MyScore = {Score.makeScore sim(items: [seq(info:voice1 items: {LUtils.collectN N fun {$} note(duration: {FD.int Durations} pitch: {FD.int 53#72} amplitude: 80) end} offsetTime:0 endTime:EndTime) seq(info:voice2 items: {LUtils.collectN N fun {$} note(duration: {FD.int Durations} pitch: {FD.int 53#72} amplitude: 80) end} offsetTime:{List.last Durations} endTime:EndTime)] startTime: 0 timeUnit:beats) unit} %% %% Apply compositional rules: %% %% rules for al notes {MyScore forAll(test: isNote proc {$ Note} {InCMajor Note} {NoBigJump Note} {StartAndEndWithLongest Note} {SlowRhythmChanges Note} end)} %% rules for notes of first voice {{MyScore find($ fun {$ X} {X hasThisInfo($ voice1)} end)} forAll(test: isNote proc {$ Note} {StartAndEndWithFundamental Note} {NoDissonance Note} end)} %% search strategy (i.e. distribution strategy) {FD.distribute {SDistro.makeFDDistribution unit(order:startTime value:random)} {MyScore collect($ test:fun {$ X} {X isParameter($)} andthen {Not {X isTimePoint($)}} andthen {Not {{X getItem($)} isContainer($)}} end)}} end end {ExploreOne RhythmicCounterpoint} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Canon %%% declare local Durations = [1 2 4] proc {StartAndEndWithLongest Note} C = {Note getTemporalAspect($)} in if {Note isFirstItem($ C)} orelse {Note isLastItem($ C)} then {Note getDuration($)} = {List.last Durations} end end proc {SlowRhythmChanges Note} C = {Note getTemporalAspect($)} in if {Note hasPredecessor($ C)} then Dur1 = {{Note getPredecessor($ C)} getDuration($)} Dur2 = {Note getDuration($)} HalveDur1 in %{FD.decl HalveDur1} {FD.times HalveDur1 2 Dur1} {FD.times HalveDur1 {FD.int [1 2 4]} Dur2} end end %% MIDI pitch domain reduction: only 'wite keys' (c major) proc {InCMajor Note} {List.forAll [1 3 6 8 10] % list of 'black' pitch classes (c=0) proc {$ BlackKey} {FD.modI {Note getPitch($)} 12} \=: BlackKey end} end proc {StartAndEndWithFundamental Note} C = {Note getTemporalAspect($)} in if {Note isFirstItem($ C)} orelse {Note isLastItem($ C)} then {Note getPitch($)} = 60 end end %% voice leading: only intervals up to a fifth, no pitch repetition %% (context dependent constraint -- getPredecessor -- but this %% context is predetermined by predetermined hierarchic structure) proc {NoBigJump Note} C = {Note getTemporalAspect($)} in if {Note hasPredecessor($ C)} then Pitch1 = {{Note getPredecessor($ C)} getPitch($)} Pitch2 = {Note getPitch($)} in %% all intervals between minor second and fourth are allowed {FD.distance Pitch1 Pitch2 '>:' 0} {FD.distance Pitch1 Pitch2 '<:' 6} end end %% harmony: only consonants proc {NoDissonance Note1 Voice2} Voice2Notes = {Voice2 getItems($)} Pitch1 = {Note1 getPitch($)} in {ForAll Voice2Notes proc {$ Note2} Pitch2 = {Note2 getPitch($)} Consonance in %% !! Consonance does not necessarily get determined: the %% solution diamond in the explorer are light green Consonance = {FD.int [3 4 7 8 9 12 15 16]} {FD.impl {Note1 isSimultaneousItemR($ Note2)} ( Pitch1 + Consonance =: Pitch2 ) 1} end} end in proc {SimpleCanon MyScore} fun {GetVoice MyScore ScoreName} {MyScore find($ fun {$ X} {X hasThisInfo($ ScoreName)} end)} end EndTime Voice1 Voice2 in MyScore = {Score.makeScore sim(items: [seq(info:voice1 items: {LUtils.collectN 17 fun {$} note(duration: {FD.int Durations} pitch: {FD.int 53#67} amplitude: 80) end} offsetTime:0 endTime:EndTime) seq(info:voice2 items: {LUtils.collectN 15 fun {$} note(duration: {FD.int Durations} pitch: {FD.int 53#72} amplitude: 80) end} offsetTime:{List.last Durations}*2 endTime:EndTime)] startTime: 0 timeUnit:beats) unit} Voice1 = {GetVoice MyScore voice1} Voice2 = {GetVoice MyScore voice2} %% %% Apply compositional rules: %% %% rules for al notes {MyScore forAll(test: isNote proc {$ Note} {InCMajor Note} {NoBigJump Note} {StartAndEndWithLongest Note} {SlowRhythmChanges Note} end)} %% rules for notes of first voice {Voice1 forAll(test: isNote proc {$ Note} {StartAndEndWithFundamental Note} {NoDissonance Note Voice2} end)} %% The first 12 notes of each voice form a canon in a fifth for Note1 in {List.take {Voice1 getItems($)} 12} Note2 in {List.take {Voice2 getItems($)} 12} do {Note1 getPitch($)} + 7 =: {Note2 getPitch($)} {Note1 getDuration($)} =: {Note2 getDuration($)} end %% search strategy (i.e. distribution strategy) {FD.distribute {SDistro.makeFDDistribution unit(order:startTime value:random)} {MyScore collect($ test:fun {$ X} {X isParameter($)} andthen {Not {X isTimePoint($)}} andthen {Not {{X getItem($)} isContainer($)}} end)}} end end {ExploreOne SimpleCanon}