May 29, 2025May 29 Hi everyone,I'm sharing this post in the Managing Complexity section because I believe this is an appropriate place to discuss constraint-based compositional systems such as the Cluster Engine.> ⚠️ Note: This post is not intended for beginners in Opusmodus. It assumes familiarity with advanced concepts such as pitch/rhythm domains, OMN, and generative constraint programming using external libraries like TOT by Torsten Anders.The example below shows how to generate a polyphonic duo using a set of constraints and automatic mapping of rhythm, pitch, and dynamics. It also showcases how the Cluster rules in TOT can serve as a solid starting point for creative exploration.Hope this inspires further discussion or variations!IntroductionThis post presents a study for two violins generated with the Cluster Engine by Örjan Sandred, now integrated into Opusmodus through the TOT library by Torsten Anders. This constraint-based generation engine was originally developed in PWGL, and later ported to SBCL by Örjan Sandred, Julien Vincenot, and Torsten Anders and finally ported to LispWorks and Opusmodus by Torsten Anders.In this Opusmodus version, the Cluster Engine offers a powerful system for rule-based musical generation, where voices are controlled through a set of structural and stylistic constraints.This example demonstrates how we can generate contrapuntal textures with internal coherence, avoiding direct repetitions, controlling harmonic intervals, and dynamically shaping rhythm and pitch domains.> 💡 Note: The constraint rules used in this example are based on the excellent TOT presets Cluster Rules provided in the library by Torsten Anders.ObjectiveTo automatically generate a musical section for two violins:- Using constrained pitch and rhythm domains.- Ensuring polyphonic independence between voices.- Mapping dynamic shapes to rhythmic density (with "traditional" Opusmodus procedure...)- Optionally adding rests for sparseness.SetupLoad the TOT system:(asdf:load-system :tot)Set the number of musical cells:(setf ncells 24)(setf *rnd-rest* nil)(setf *rnd-rest-percent* 0.25)DomainsRhythmic domains (encoded in OMN):(setf r-domain1 (omn-encode '((s s s s)(e e)(3q = =)(q)(h.)(h))))(setf r-domain2 (omn-encode r-domain1))Pitch domains (custom 7-note scale):(setf harmo (make-scale 'a4 7 :alt '(2 1)))(setf p-domain1 (mclist (pitch-to-midi harmo)))(setf p-domain2 (mclist (pitch-to-midi (pitch-transpose (rndn 1 -12 -7) harmo))))Cluster Engine GenerationUsing the Cluster Engine with TOT rules:(setf cluster-out(cr::cluster-enginencells(ce:rules->cluster(cr:no-direct-repetition :voices '(0 1))(cr:no-repetition :voices '(0 1) :window 3)(cr:set-intervals :voices '(0 1) :intervals '(1 2 3 4 6 7))(cr:resolve-skips :voices '(0 1) :skip-size 5)(cr:set-harmonic-intervals :voices '(0 1) :intervals '(3 4 5 7))(cr:min/max-interval :voices '(0 1) :max-interval 4 :min-interval 1)(cr:durations-control-intervals :voices '(0 1) :rel-factor 16 :acc-factor 16)(cr:no-parallels :voices '(0 1))(cr:no-voice-crossing :voices '(0 1)))'((3 4))(append(list r-domain1 p-domain1)(list r-domain2 p-domain2))))Processing the OutputExtract rhythm and pitch for both voices:(setf line1.len (first cluster-out))(setf line1.pitch (second cluster-out))(setf line2.len (third cluster-out))(setf line2.pitch (fourth cluster-out))(setf ts (car (fifth cluster-out)))Convert to OMN notation:(setf line1 (omn-to-time-signature (make-omn :pitch (midi-to-pitch line1.pitch) :length line1.len) ts))(setf line2 (omn-to-time-signature (make-omn :pitch (midi-to-pitch line2.pitch) :length line2.len) ts))Dynamic Shaping via Density Analysis(setf dyn1 (mclist (vector-to-velocity 'pp 'ff (density-analysis line1 :type :length))))(setf dyn2 (mclist (vector-to-velocity 'pp 'ff (density-analysis line2 :type :length))))(setf omn1.d (velocity-to-dynamic (omn-replace :velocity dyn1 line1)))(setf omn2.d (velocity-to-dynamic (omn-replace :velocity dyn2 line2)))Optional Random Rests(if *rnd-rest*(progn(setf omn1.d (rnd-rest *rnd-rest-percent* omn1.d))(setf omn2.d (rnd-rest *rnd-rest-percent* omn2.d))))Final Score(def-score EtudeClusterEngine(:title "Study Duo using Cluster Engine":composer "Stéphane Boussuge":copyright "Copyright © s.boussuge 2025":key-signature 'chromatic:time-signature '((1 1 1) 4):tempo 91:layout (bracket-group(violin1-layout 'violin1)(violin2-layout 'violin2)))(violin1 :omn omn1.d :channel 1 :sound 'gm :program 'violin :volume 100 :pan 54 :controllers (91 '(48)))(violin2 :omn omn2.d :channel 2 :sound 'gm :program 'violin :volume 100 :pan 74 :controllers (91 '(60))))ConclusionThis short algorithmic composition study demonstrates how the Cluster Engine, combined with a rich preset rule system (TOT), can help generate structured and coherent polyphonic music for multiple voices.By blending constraint programming, statistical dynamics, and stochastic variation (rests), we can explore highly controlled yet musical outcomes — ideal for modern compositional practice.AcknowledgmentsMany thanks to Örjan Sandred, Torsten Anders, and Julien Vincenot for their inspiring work in constraint-based composition and particularly to Torsten for the development of the TOT system for Opusmodus.Would love to hear feedback or see variations based on other rule combinations!StéphaneScore229-DuoVn_ClusterEng.opmo
May 29, 2025May 29 The "TOT" library is already included by default in Opusmodus ?I receive a message like this:<<automatic abort>>OM 4 > Error: Component :tot not found 1 (continue) Retry ASDF operation. 2 Retry ASDF operation after resetting the configuration. 3 Retry ASDF operation. 4 Retry ASDF operation after resetting the configuration. 5 (abort) Return to top loop level 0.Type :b for backtrace or :c <option number> to proceed.Type :bug-form "<subject>" for a bug report template or :? for other options.OM 5 : 1 > Thanks, Stephane. Looks good.
May 30, 2025May 30 Author 14 hours ago, Stephane Boussuge said:external libraries like TOT by Torsten Anders.Hi Julio,no, as I said into the post, the TOT library is not included in OM.Here's the link to Torsten's Github:GitHubtanders - OverviewI am a composer, researching and developing software for computer-aided composition. - tanders
May 30, 2025May 30 A very interesting post! Thank you for it.What might be very interesting is an audio recording as an additional means to evaluate this approach. It is a large undertaking to install this extension to Opusmodus. Hearing the result might be an incentive to users to take the plunge.
June 1, 2025Jun 1 Author On 5/30/2025 at 3:15 PM, RST said:What might be very interesting is an audio recording as an additional means to evaluate this approach.Here's one output. Duo d'étude du Cluster Engine [3] - Duo d'étude du Cluster Engine [3].mp3
June 3, 2025Jun 3 A general question:Has anyone successfully installed this software on a Windows system?IF so, could you please post some information about this here for general reference?I cannot figure out where to place the tot-master folder and/or tot.asd file so that asdf can parse it. So far I have tried a lot of approaches, but the documentation is not inclusive of how this might work on the Windows platform, at least as far as I can see so far.With thanks,Robert
June 4, 2025Jun 4 On 5/29/2025 at 3:19 PM, JulioHerrlein said:The "TOT" library is already included by default in Opusmodus ?I receive a message like this:<<automatic abort>>OM 4 >Error: Component :tot not found1 (continue) Retry ASDF operation.2 Retry ASDF operation after resetting the configuration.3 Retry ASDF operation.4 Retry ASDF operation after resetting the configuration.5 (abort) Return to top loop level 0.Type :b for backtrace or :c <option number> to proceed.Type :bug-form "<subject>" for a bug report template or :? for other options.OM 5 : 1 >Thanks, Stephane. Looks good.This is precisely my issue as well on Windows with Opusmodus 3. Is there any news of how to get this to work?
June 4, 2025Jun 4 On Windows 10 this is what I did to port the software to run the example:I created a directory in C: Users called common-lisp. And placed the various source directories from Torsten's GitHub, including dependent library cl-utilities. I also installed MiniZinc for completeness.asdf parses this directory by default on a Windows OS.
January 4Jan 4 Thank you Stephane for this outstanding, interesting demonstration!! Here's what I did to get this example to work in 2026 on Opusmodus 4. I installed the TOT library and prerequisites as described in the GitHub instructions. cl-utilities isn't found even though it's installed in ~/common-lisp with the other files, so I ran:(asdf:initialize-source-registry'(:source-registry(:tree "/Users/username/common-lisp/"):inherit-configuration))Then one of the files has to be converted because it isn't in UTF-8 format. In bash cd to ~/common-lisp/cluster-engine/sources, copy 05.rules-interface.lisp to 05.rules-interface.lisp.bak then:iconv -f WINDOWS-1252 -t UTF-8 05.rules-interface.lisp.bak > 05.rules-interface.lispNext the (setf cluster-out section of the example fails due to a missing function, so it needs to be aliased:(eval-when (:compile-toplevel :load-toplevel :execute)(when (and (find-package "CLUSTER-ENGINE")(find-symbol "FUNCTION-LAMBDA-LIST" "CLUSTER-ENGINE")(find-package "LISPWORKS")(fboundp 'lispworks:function-lambda-list))(setf (symbol-function (intern "FUNCTION-LAMBDA-LIST" "CLUSTER-ENGINE"))#'lispworks:function-lambda-list)))Next there was a clusterengine argument count mismatch. The new code to fix this is:(setf cluster-out(cluster-engine:clusterenginencellst ; rnd?nil ; debug?(ce:rules->cluster(cr:no-direct-repetition :voices '(0 1))(cr:no-repetition :voices '(0 1) :window 3)(cr:set-intervals :voices '(0 1) :intervals '(1 2 3 4 6 7))(cr:resolve-skips :voices '(0 1) :skip-size 5)(cr:set-harmonic-intervals :voices '(0 1) :intervals '(3 4 5 7))(cr:min/max-interval :voices '(0 1) :max-interval 4 :min-interval 1)(cr:durations-control-intervals :voices '(0 1) :rel-factor 16 :acc-factor 16)(cr:no-parallels :voices '(0 1))(cr:no-voice-crossing :voices '(0 1)))'((3 4))(append(list r-domain1 p-domain1)(list r-domain2 p-domain2))))After that it was smooth sailing!
January 4Jan 4 Author I think you didn't install the right part, please look carefully on the Torsten Github or alternatively you can put this common-lisp folder (for Mac) in your root folder to be sure you will have the right version of the cluster engine.File attached to this post.common-lisp.zip
January 4Jan 4 I tried this version but it doesn't include cl-utilities, and even when cl-utilities is installed the original example does not compile, so there must be other custom changes in the environment. Still, I'm enjoying experimenting with this library! I'm working on a 4 part fugue. It isn't easy to make it sound good.
January 4Jan 4 Author Here's an example code you probably can compile as a test because it works on my side:;;; Constraint Patterns Generations example.;;; Useful for couterpoint function or;;; any cells based workflow.;;; -------------------------------------------(asdf:load-system :tot);;; Domaines;; Rythme(setf r-domain (omn-encode '((s s s s)(e e)(q)(h))));; Notes(setf harmo (make-scale 'a3 14 :alt '(2 1)))(setf p-domain (mclist (pitch-to-midi harmo)));;; Cluster Gen(setf cluster-out (cr::cluster-engine ;; Number of variables in result 24 ;; Rules (ce:rules->cluster (cr:no-direct-repetition :voices '(0)) (cr:no-repetition :voices '(0) :window 3) (cr:set-intervals :voices '(0) :intervals '(1 2 3 4 6 7)) (cr:resolve-skips :voices '(0) :skip-size 5) (cr:min/max-interval :voices '(0) :max-interval 4 :min-interval 1) (cr:durations-control-intervals :voices '(0) :rel-factor 16 :acc-factor 16) ) ;; Meter '((3 4)) ;; Variable domains (list ;Voice 1 ;; Possible length values for 1st part (rhythmic domain) r-domain ;; Possible MIDI pitches for 1st part (pitch domain) p-domain )))(setf patterns (omn-to-time-signature (make-omn :pitch (midi-to-pitch (second cluster-out)) :length (first cluster-out) ) (third cluster-out)))
January 4Jan 4 I used your score as a starting point and added more voices and other features4 part fugue TOT library.opmo
January 5Jan 5 Author It’s beautiful, even though it’s not a fugue in the traditional sense, but it’s incredibly intriguing.The code is quite intriguing, and I intend to delve deeper into it when I have some time.Thank you very much for this very interesting example. I will definitely study it more closely soon.
January 6Jan 6 On 1/4/2026 at 9:25 PM, Stephane Boussuge said:Here's an example code you probably can compile as a test because it works on my side:;;; Constraint Patterns Generations example.;;; Useful for couterpoint function or;;; any cells based workflow.;;; -------------------------------------------(asdf:load-system :tot);;; Domaines;; Rythme(setf r-domain (omn-encode '((s s s s)(e e)(q)(h))));; Notes(setf harmo (make-scale 'a3 14 :alt '(2 1)))(setf p-domain (mclist (pitch-to-midi harmo)));;; Cluster Gen(setf cluster-out(cr::cluster-engine;; Number of variables in result24;; Rules(ce:rules->cluster(cr:no-direct-repetition :voices '(0))(cr:no-repetition :voices '(0) :window 3)(cr:set-intervals :voices '(0) :intervals '(1 2 3 4 6 7))(cr:resolve-skips :voices '(0) :skip-size 5)(cr:min/max-interval :voices '(0) :max-interval 4 :min-interval 1)(cr:durations-control-intervals :voices '(0) :rel-factor 16 :acc-factor 16));; Meter'((3 4));; Variable domains(list;Voice 1;; Possible length values for 1st part (rhythmic domain)r-domain;; Possible MIDI pitches for 1st part (pitch domain)p-domain)))(setf patterns (omn-to-time-signature(make-omn:pitch (midi-to-pitch (second cluster-out)):length (first cluster-out))(third cluster-out)))I'm having this kind of errors: Error: The symbol "R-PREDEFINE-METER" is not external in the CLUSTER-ENGINE package. 1 Edit the code where the error occurred 2 (continue) Use symbol "R-PREDEFINE-METER" anyway. 3 Try reading the next form. 4 Try compiling /Users/henryjuhani/common-lisp/tot/sources/constraints.lisp again. 5 Skip compiling /Users/henryjuhani/common-lisp/tot/sources/constraints.lisp. 6 Retry compiling#<asdf/lisp-action:cl-source-file "tot" "sources" "constraints">. 7 Continue, treating compiling#<asdf/lisp-action:cl-source-file "tot" "sources" "constraints"> as having been successful. 8 Retry ASDF operation. 9 Retry ASDF operation after resetting the configuration. 10 Retry ASDF operation. 11 Retry ASDF operation after resetting the configuration. 12 (abort) Return to top loop level 0.
January 6Jan 6 Try this version:(setf r-domain(omn-encode '((s s s s) (e e) (q) (h))))(setf harmo (make-scale 'a3 14 :alt '(2 1)))(setf p-domain (mclist (pitch-to-midi harmo)))(setf cluster-out(cluster-engine:clusterengine24tnil(ce:rules->cluster(cr:no-direct-repetition :voices '(0))(cr:no-repetition :voices '(0) :window 3)(cr:set-intervals :voices '(0) :intervals '(1 2 3 4 6 7))(cr:resolve-skips :voices '(0) :skip-size 5)(cr:min/max-interval :voices '(0) :max-interval 4 :min-interval 1)(cr:durations-control-intervals :voices '(0) :rel-factor 16 :acc-factor 16))(cluster-engine:metric-domain '(3 4) nil nil)(listr-domain ; variable 0: lengthsp-domain))) ; variable 1: pitches;; Convert to OMN(setf line1(omn-to-time-signature(make-omn :length (first cluster-out):pitch (midi-to-pitch (second cluster-out)))(third cluster-out)))
January 6Jan 6 The error "The symbol "R-PREDEFINE-METER" is not external in the CLUSTER-ENGINE package." does stop loading cluster-engine, so nothing else can be accomplished.
January 7Jan 7 Author Did you copy the entire folder I provided? I mean, not just the cluster-engine, but the entire folder with all its dependencies?
January 7Jan 7 Yes, I copied the entire folder. I also checked with git that everything is newest version.
January 7Jan 7 This is a message I received from Torsten:torstenandersReplied: 7 minutes agoDear Henry,nice to hear from you and thanks for getting in touch. Unfortunately, I have not used this software for many years now and have little time to dig into the details now. Anyway, here are some potential pointers that might help. The immediate error points to the fact that R-PREDEFINE-METER is called in the tot library assuming that it is an external symbol, i.e. as ce:r-predefine-meter and not ce::r-predefine-meter: https://github.com/tanders/tot/blob/fa2472bcef816e7f36866dd04b1529acf67ea646/sources/constraints.lisp#L539In the Cluster Engine package.lisp file, this symbol is indeed exportedhttps://github.com/tanders/cluster-engine/blob/835b74ad930b4b1bb4791d85fbf557e4efadf524/sources/package.lisp#L8However, this is only the case my special Cluster Engine optimisations git branch, in the master branch, this is still not the case: https://github.com/tanders/cluster-engine/blob/master/sources/package.lispMy tot library documentation points out that you should use that branch (https://github.com/tanders/tot/tree/master) and with the other branch you would also run into other issues when using Cluster Engine with Opusmodus.Unfortunately, I still did not have the time to merge my changes into the master branch. As I said, I have not been using this for quite some time (I have not been using Opusmodus for years because of other commitments).It might be that the ongoing Opusmodus development broke some code (I am still on Opusmodus version 3), but the other issue described above is more likely.Best,Torsten
January 7Jan 7 I tried the other branch (https://github.com/tanders/tot/tree/master) and, as Torsten predicted, I got another fatal error:Error: External format (:utf-8 :eol-style :lf) got error reading #<stream::ef-file-stream /Users/henryjuhani/common-lisp/cluster-engine/sources/05.rules-interface.lisp> (file-position 14062): second byte 78 of a sequence is not a continuation byte.
January 7Jan 7 I already posted the fix for this error in my previous post above, in addition to other solutions.
January 7Jan 7 I'm sure we're all happy to help, but if you are posting errors here instead of using Gemini, ChatGPT, and Claude, you're missing out on the best, fastest solutions. They're often wrong however if you persist you will find a solution using these tools, and you can find the answer independently.
Create an account or sign in to comment