LMUSe version 0.5c
3/19/98
david sharp
dsharp@interport.net
To LMUSe intro / download page
c o n t e n t s
An L-system starts in generation 0 with a simple string of symbols, and for each subsequent generation, symbols are replaced by new symbols or strings of symbols according to a set of rules. The idea is easiest to see by example. Say our starting string, (called the 'axiom'), is just
A
The replacement rules show how each symbol in the current generation should be replaced. Two simple rules (for example) are
A=B
B=BA
According to the first rule, every 'A' in the current generation
should be replaced by a 'B', and each 'B'
in the current generation will be replaced with 'BA' in
the next generation.
generation 0: A (the axiom)
generation 1: B (the A was replaced with B )
generation 2: BA (the B of generation 1 was replaced with BA)
generation 3: BAB (the B of gen 2 replaced with BA and the A replaced with B)
generation 4: BABBA
Note how the replacements for each generation occur in 'parallel'. We will call the number of generations the "recursion level". When the symbols are interpreted as graphics commands, reading from left to right, the resulting pictures can get very complex. In most L-system implementations the symbol 'F' is interpreted as a command to "draw a line". Because of the recursive way the production string is made, L-systems are a concise way to generate the 'self-similar' nature of fractals.
The purpose of LMUSe is to interpret the resulting symbol strings as musical directions. For example, 'F' is interpreted as "play the note". See 'Symbols'. LMUSe starts with a 'rule file' which contains an axiom string, a set of replacement rules, a default recursion level, and another parameter the 'angle'.
#=== Sample Rules file ====
5 #recursion level
10 #angle
AB #axiom
A=+FB # first rule
B=AF # second rule
#===========================
In LMUSe, '#' is a special 'comment' symbol. Everything on a line after a '#' is ignored. The rules are applied to the axiom to make a 'production string' (the first generation). The rules are then applied to that production string in turn, making a new production string (the second generation), which is run through the rules, the process repeated "recursion level" number of times. The result at the end is just called the "production string".
By itself, the production string is just a string of symbols. The production string is then 'interpreted'. That is, the symbols are mapped to drawing and/or music playing commands. In LMUSE, the musical commands are in the form of MIDI messages.
Context
Sensitivity and Stochastic Rules
Rules like A=AB, and X=FB^&F,
are 'context-free' because the symbol to be replaced (on the left
of the '=') is replaced by the right side of the '='
no matter what other symbols are around. 'Context-sensitive' rules
are rules whose application depends on the surrounding symbols. In LMUSE,
context- sensitivity of a rule is with '>' or '<'
directly after the symbol to be replaced.
For examples:
A>B=AB # "if B is to the immediate right of A
replace the A with AB".
A<F=FFAB # "if F is to the left of A, replace
the A with FFAB"
In LMUSE rules, the symbol to be replaced is always the first symbol in the rule's line. If the above pair of rules were in the same rules file and both conditions were true, (for example if the production contained the sequence "FAB") only the first rule would be actually applied. This is because once a rule is applied to a symbol, all following rules for that symbol are ignored. Also, "A is to the left of B" is True if all that separates A and B is a number or a number in parentheses, or, of course, if A is directly adjacent to B.
To combine both '<', and '>' in a single rule condition, the two conditions must be in that order:
A<B>C=X
would match BAC and replace the A with X.
LMUSe also implements 'stochastic rules'.
A(.5)=BBA
will replace A with BBA only 50% of the time. The pair of rules:
A(.5)=BBA
A=ABA
will replace A with BBA 50% of the time, otherwise (the other 50% of the time) A is replaced with AAB. The order of these two rules is important. Reversing them:
A=ABA
A(.5)=BBA
will always replace A with ABA. This is because the first (A=ABA) is not a stochastic rule (no probability in parentheses) and since it comes first, it is applied. Once a rule is applied to a symbol, the rest of the rules for that symbol are skipped, so the second line has no effect at all.
To have three stochastic rules each with a probability of 1/3, you would write:
A(.33)=BBA
A(.5)=ABA
A=BABB
The way it works is, the probability in parentheses is the probability that the rule will be applied if LMUSE gets to that rule, and the rules are looked at sequentially. That is, there is a .33 probability that the first rule will be applied. If it isn't applied (probability .67), then the second rule is attempted. Half the time the second rule is attempted, it is applied. That is 1/2 of .67 =.335 probability that the second rule will be applied. And finally, if and only if neither of the first two are applied, the last rule is applied. Again, once a rule is applied to a symbol, following rules are skipped.
To combine a stochastic condition with a context-sensitive condition, the stochastic condition must come at the end:
F<A>B(.5)=FF
Any combination not in the right order will match up to, but not including, the first condition that is recognized as out of order.
F(.5)<A>B=FF
would test for the probablility ("toss the coin") but then ignore the two context conditions.
Stochastic rules have their random effect as the production string is being built. Once the production string is done you would have to do another 'Make' to see any difference due to the randomness. LMUSe transformation rules can contain another type of randomness. The '~' (tilde) changes, randomly, the current state's direction. And '~(x)' changes the direction randomly up to a maximum of x degrees. This random change occurs at the time of interpretation and will give (probably) different results every time the production string is interpreted. To avoid differences between interpretations, you can do 'ReMap' instead of 'Remake'.
Most of this list was 'cut and pasted' from Laurens Lapre's lparser.txt
t(x) | transpose up (+x ) or down (-x ) by x semitones |
t | transpose 0 |
d(x) | multiply note durations by x |
d | multiply note durations by 1.0 (cancels d(x)) |
v(x) | multiply velocities (volume) by x |
v | multiply velocities by 1.0 (cancels v(x)) |
*(x) | write to MIDI channel x |
* | write to MIDI channel 0 |
also see the T(x), D(x) , and V(x) commands under Stack commands.
F | draw full length forward
play |
F(x) | draw x length forward
play |
Z | draw half length forward
play |
Z(x) | draw x length forward
play |
f | move forward full length, draw within {}
play within {}, otherwise rest |
f(x) | move forward x, draw within {}
play within {}, otherwise rest |
z | move forward half length, draw if within {}
play within {}, otherwise rest |
z(x) | move forward x, draw if within {}
play within {}, otherwise rest |
g | move forward full length
rest |
g(x) | move forward x
rest |
. | don't move |
[ | push current state but not event time |
] | pop current state but not event time |
{ | push current state and time |
{x | push current state and time, writing to MIDI channel x |
} | pop current state and time |
\ | push just the time |
\x | push just time, writing to MIDI channel x |
/ | pop just the time. |
only use '/' to pop a time that was 'pushed' with '\' | |
T(x) | (*) push pitch transposition by x onto the transpose stack |
D | (*) pop transposition from the transpose stack |
D(x) | (*) push duration multiplier x onto the duration multiplier stack |
D | (*) pop duration multiplier from the duration multiplier stack |
V(x) | (*) push velocity multiplier x onto the velocity multiplier stack |
V | (*) pop velocity multiplier from the velocity multiplier stack |
* The default is for the transpose stack and the duration and velocity multiplier stacks to be disabled. Enable them from the "Map dialog". |
" | increment length (times 1.1) |
' | decrement length (times 1/1.1) |
"(x) '(x) |
multiply length with x |
; | increment angle (times 1.1) |
: | decrement angle (times 1/1.1) |
:(x) ;(x) |
multiply angle with x |
? | increment thickness (times 1.4) |
! | decrement thickness (times 1/1.4) |
?(x) !(x) |
multiply thickness with x |
Color / timbre-instrument commands
c | increment color index
increments instrument program number |
c(x) | set color index to x
sets instrument program number to x |
@ | end of rules (optional) |
The format for rule files follows the format of Lparser. Each rule file must be organized in this order, line by line:
recursion level
angle (in degrees)
thickness (optional in LMUSe, required by Lparser)
axiom
transformation rule 1
transformation rule 2
more rules, one to a line
...
@ (end of rules marker, optional in LMUSe, required by Lparser)
In addition, the file can contain comments. Anything after "#" on a line is ignored by LMUSe.
An example:
# this whole line is ignored
4 # default recursion level
25 # basic angle of 25 degrees
50 # thickness is 50% of length
A # the axiom
A=+B^FA # rule 1
B=BBt(12)F # rule 2
@ # end
View is for choosing the viewing plane of the drawing.
If Draw is unchecked, LMUSe will not attempt a drawing.
The "Scale" button is for choosing a musical scale into which the generated notes are "forced".
How the notes are forced into a scale is determined with the scale function button. (see Help/Scales)
Multiply durations slider is for stretching or compressing all musical event times. Multiplying by a number greater than 1.0 stretches out the notes, while multiplying durations by a number less than 1.0 compresses all the notes. Similar to changing tempo .
"Use Transpose stack" enables "T(x) ... T" transpose stack.
"Factor stacks" enables the "D(x) ... D" and "V(x) ... V" duration and velocity multiplier stacks.
If you aren't using these constructs in a set of transformation rules, it is safest to turn this off (unchecked).
'Play' starts the music at the beginning.
Clicking on 'Pause' stops the music until you click on 'Pause' again.
'Save Midi' saves the piece as a standard midi file (format 1).
'New' and 'Exit' are functionally identical. Either one stops the playing and sends you back to the production screen.
The note display shows pitch by height in the box, and duration of a note by length of its drawn line. At the bottom of the box where the notes are displayed is a 'ticker' which shows you where in time the piece is playing. If you move the mouse cursor into this area and click the mouse, the piece will jump to that time and start playing from there.
You can choose a musical scale in the Map dialog box under "Scale". The scales are described below.
'User' choice is for defining your own scale. If you pick 'User', you are given a box where you type in some scale steps and the scale you type in will be used. The scale you type in should start with '1' (lowest note of the scale) and thereafter each following number denotes the next higher note in the scale, in 'half tones'. Put spaces or commas between different scale notes. For example:
1 3 5 6 8 10 12
would make a major scale.
The scales in the list are defined, either by convention or by me, as:
twelve tone | 1 2 3 4 5 6 7 8 9 10 11 12 | c c# d d# e f f# g g# a a# b |
Major | 1 3 5 6 8 10 12 | c d e f g a b |
Penta 1 | 1 5 8 10 11 | c e g a a# |
minor | 1 3 4 6 8 9 12 | c d d# f g g# b |
blues 1 | 1 3 4 5 8 9 10 11 | c d d# e g g# a a# |
whole tone | 1 3 5 7 9 11 | c d e f# g# a# |
diminished | 1 2 4 5 7 8 10 11 | c c# d# e f# g a a# |
Scale Function
Below the button for choosing a scale is the 'scale function button.
LMUSE gets note pitches from some L-system state variable, as chosen in
the 'connect' box in the Map dialog. These variables are actually numbers.
If you want to use a scale, those numbers need to end up being notes in
the scale. LMUSE lets you choose between two (three) ways to do this.
The 'slide to' setting finds the lowest note greater than or equal to the generated number. It finds the next note by going 'up' the scale until reaches the number or greater. It uses semi-tones to step up.
'steps' uses the generated number to count directly up the scale. That is, it uses the scale itself to step.
'constant' is just a way to get a constant pitch rather than a scale.
LMUSe rules files (*.l,*.ls,*.lm) are generally compatible with Laurens Lapre's Lparser *.LS files. The LMUSe parser was designed to interpret the large number of LParser files available. On the other hand, while LParser can make beautiful graphics, LMUSE can't. The drawing that LMUSe does while interpreting is intended mainly as a 'progress indicator'. LMUSE is for generating music and it is just amazing that some of the LParser files do make nice music when fed to LMUSE.
I have tried to keep the file extensions of the examples consistent. A file with just an 'L' extension is an LMUSe rules file which does not contain a 'thickness' (the third non-comment in Lparser files is the starting thickness of lines as a percent of their length), or if it contains stochastic rules (a la A(.5)=ABB) or context sensitive rules (e.g. A<F=BF). If I believe a file is compatible with Lparser (for example, contains a thickness and no context sensitieve or stochastic rules), it has the 'ls' extension. A file that has been mutated by LMUSe (saved after 'mutating') is given the 'lm' extension. 'lm' files which were originally 'ls' files before mutation should be compatible with Lparser, but I haven't checked all of them.
Certain of the symbols are interpreted differently in the two programs. For example 't(x)' in LMUSE transposes the following notes by x semitones. In LParser, t(x) is interpreted as a 'tropism' or gravity influence. This interpretation of 't(x)' is completely ignored by LMUSE. In LMUSE, '{' and '}' are mostly for creating parallel in time (polyphonic) musical lines. Their use in LParser is for making polygons like leaves, flower petals, etc). LMUSE does not know 'polygons' at all and so the generated graphics often look different from what you might expect from seeing the same file interpreted by LParser.
Another popular program for doing L-systems is Fractint. Fractint L-system files (*.l) are 2-dimensional. To use them with LMUSe requires some easy adaptations to the Fractint file. First, a Fractint 'L' file can contain many sets of L-system rules. The different L-system rule sets have to be isolated in their own file to be used in LMUSe. Fractint L-system rules, parameters and axioms are also enclosed in curly brackets. You have to erase those. Plus you have to get rid of the labels like 'Angle' and Axiom'. and insert a recursion level on the first line. Also, the comment character in LMUSE is '#' while in the Fractint .L files it is ';'. And the angle in the Fractint files is a division of 360 degrees rather than the number of degrees itself. One other major difference in symbols is that @nnn in Fractint systems is the command to multiply line segment lengths by nnn, while the corresponding LMUSE command is "(nnn). (a double quotation mark followed by the multiplier).
Also note that Fractint's interpretation of .L files is case insensitive (e.g. F'='f'). LMUSe is quite sensitive to case. (e.g. 'F' and 'f' are interpreted differently).
There are a number of other differences which I guess I am not prepared to detail.
For example (grabbed from fractint.l):
Koch1 { ; Adrian Mariano ; from The Fractal Geometry of Nature by Mandelbrot
Angle 6
Axiom F--F--F F=F+F--F+F }
would become (koch1.ls):
# Adrian Mariano
# from The Fractal Geometry of Nature by Mandelbrot
3 # recursion level
60 # 60 degrees = 360 degrees/ 6
F--F--F # Axiom
F=F+F--F+F
Most of the more symmetric L-systems make static/boring music. One thing that 'helps' such files is changing the basic angle. Naturally this will 'screw up' the picture, but often gives the music more life.
Running out of production string space:
The default limit for production string space is 2 megabytes. You can
increase this limit on the command line with the parameter "-sXXXX"
where "XXXX" is the number of kilobytes of string space to allocate.
For example, "-s4000" will allocate room for 4 megabyte production
strings. Be aware, however, that LMUSE actually allocates for two strings
of this size. That is, -s4000 will actually allocate for two 4 megabyte
strings. Since LMUSE can use DPMI virtual memory, allocating large amounts
should not be a problem except for speed concerns.
Running out of stack space:
If you are finding that the stack space is often too small, let me
know. Running out of stack space is most likely to occur when trying to
run a mutation. The real remedy is to increase the stack space by recompiling
LMUSE with a larger stack size. (The current size is 2K). A workaround
to try is to reduce the recursion level you are attempting, if the recursion
level is not critical to your project.
Arithmetic errors:
A mutated set of rules sometimes causes an arithmetic error during interpretation.
Mainly because of the irregularity of these errors, I haven't figured this
one out. The only "option" LMUSE currently gives you is to quit.
On the other hand, if LMUSE actually crashes due to an arithmetic/floating
point error, please send me details.
The drawing runs off the screen:
When Re-interpret(Remap) is run and a new view is chosen, LMUSE uses
its previous view parameters. That is, Remap does not figure the screen
limits of the new drawing. The "solution" is to use "Remake
and Re- Interpret" instead, even though it does take longer.
The drawing does not resemble the LParser rendering:
There are a number of LParser's drawing symbols which are interpreted differently
(or not at all) by LMUSE. Probably most important (i.e. having the most
dramatic effect on the drawings) is that LParser polygons, "{....}",
are not supported by LMUSE. In a minimal attempt at compatiblility, LMUSE
takes a stab at drawing the polygons by treating them as normal (non-polygon)
drawing commands, but actually does a pretty poor job. Also, while LMUSe
does keep track of line 'thickness' (you can map note parameters to it),
line thickness is completely ignored in the drawing. The excuse I am currently
going with is "the drawing should be treated as a progress indicator
while LMUSE is turning the production string into music".
Special thanks to:
D J Delorie for DJGPP v2
DJGPP web site
Shawn Hargreaves for Allegro v3.0
shawn@talula.demon.co.uk
http://www.talula.demon.co.uk/
Laurens Lapre for LParser.
ljlapre@xs4all.nl
http://www.xs4all.nl/~ljlapre/
LMUSe parsing routines are designed to be compatible with LJ Lapre's
LParser and the mutation routines adapted from LParser's source code.