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

L-Systems (general)
Context Sensitivity and Stochastic Rules
Symbol Alphabet
Commands specific to music
Direction commands
Movement / Play commands
State stack commands
Increment and Decrement commands
Colors and intrument timbre commands
Rule File Format
Process Overview
Map
Menus
Production menu
Map
Edit Menu
Mutate menu
Play Screen
Scales
Rules Files Compatibility
Troubles
Credits


L-Systems
L-systems were invented by Aristid Lindenmayer in 1968 to model the development of living organisms. L- systems provide a concise grammar for generating fractals like von Koch snowflakes, dragon curves and space filling curves, as well as for modeling organic growth.

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'.


The LMUSe Symbol Alphabet

Most of this list was 'cut and pasted' from Laurens Lapre's lparser.txt

Commands specific to music

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.

Direction commands

+ turn left around up vector
+(x) turn x degrees left around up vector
- turn right around up vector
-(x) turn x degrees right around up vector
& pitch down around left vector
&(x) pitch x degrees down around left vector
^ pitch up around left vector
^(x) pitch x degrees up around left vector
< roll counter clockwise around forward vector
<(x) roll x degrees left around forward vector
> roll clockwise around forward vector
>(x) roll x degrees clockwise around forward vector
| turn 180 degrees around up vector
% roll 180 degrees around forward vector
$ roll until horizontal
~ turn/pitch/roll in a random direction
~(x) turn/pitch/roll in random direction to a maximum of x degrees

Movement / Play 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

Stack commands

[ 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/Decrement commands

" 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)


Rule File Format

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


Process Overview:


Menus
Production menu
(Also see Process Overview) Making the production string and interpreting can take a long time, depending on recursion level and how long the production string gets. You can cancel/abort a 'Make' or Interpret' with the ESC key or clicking the right mouse button . Depending on how far the interpretation gets, you may have enough to play.

Make and Interpret Production
Gets the name of the rule file to use, and makes the production string. Then it goes ahead and interprets the string and plays the result. Use this to load a file and make music with least fuss.
Make Production
Gets the rule file and makes the production string. (Doesn't interpret or play it). Pressing ESC or clicking the right mouse button interrupts and aborts making the production string.
Interpret Production
Interprets the production string. You are asked how you want the production string interpreted. That is, what parameter should be mapped to pitch, what maps to note durations and note volume. Pressing ESC or clicking the right mouse button aborts the interpretting.
Remake and Interpret
Makes and interprets the production string using the already loaded rules. Remake and Interpret also recalculates the necessary screen dimensions and state limits. Reinterpret(Remap) (see below) does not and so it is certainly safer to use Remake and Interpret, although it is more time consuming. Pressing ESC or clicking the right mouse button aborts/cancels the process.
Reinterpet (Remap)
Reinterpret the already made production string, going through the mapping process anew. Note that if the rule file contains stochastic rules, you will need to Remake the production string to get different results from rules's stochastic nature. Remap will just reinterpret the production. On the other hand, certain symbols, like '~', yield stochastic results during interpretation. Pressing ESC or clicking the right mouse button aborts the remapping.

Play
Go to the 'Play' screen to hear your interpreted production.

Save Rule File As
Saves current transformation rules, including the current recursion level and basic angle.

Get Production
Read a previously saved production string from disk. Since this production string is already 'made', the only Production Menu opotions that make sense to use on it are 'Interpret' or 'Reinterpret/Remap'. ('Make' will make a new production string from whatever rules are currently in use which may have nothing to do with the production string you have loaded from disk. In this case the production string is overwritten and will no longer be in memory). When a production string is saved, the map parameters and the rule file angle are not saved, so when you 'Get' a production, you will have to either remember these or supply new ones (or use the defaults)
View/Save Production
Look at the current production string. Option to save the production string (as *.out).

Save MIDI
Saves your music as a (SMF format 1, multitrack) MIDI file. MIDI files are widely supportedby sequencing programs, sound cards, and web browsers on nearly every type of computer. There are also utilities for converting MIDI files into CSound scores and other formats

Play a MIDI file
Load and play a previously created MIDI file.
Stop MIDI - Ctrl-s
Stops the playing of a MIDI file.

DOS shell
Shell to DOS command prompt.

Quit
Quits LMUSe

Map menu
Map
Opens the map dialog. connections dialogFor the most part this is not necessary since you are automatically presented with this map dialog on every Interpret. Click on the 'Pitch' box with the mouse and drag a line to the parameter you want to dictate the pitches to be played. Similarly for 'Durations' and 'Volume'. LMUSE keeps track of the x, y, and z coordinates of an interpreted production. At all times the interpretation has three directions: forward (where the line is headed), up, and left. These are unit vectors each with x, y, and z components. The difference between 'state length' and 'draw length' is that the 'state length' only changes with the length incrementing and decrementing commands (see Symbols under Help). The 'draw length' comes from the actual length being drawn (or not drawn) on the screen. The draw length is affected by the difference between F and F(5) for example, or the difference between F and Z. (see Symbols).

Use the three "spread" sliders to amplify the interpreted range of the parameters. For example, by making the "pitch spread" greater than 1.00, the mapped pitches will be spread out further from middle c. A pitch spread between 0 and 1.00 causes the mapped pitches to be squeezed closer to middle c. Basically, increasing the "spread" makes changes in pitch, duration, or volume more dramatic. A negative value for the spread `inverts' the map. For example, when the duration spread is positive and duration is mapped to `draw length', longer lines make for longer note durations, but if a negative value is chosen for the duration spread, longer lines will yield shorter notes.

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).

Edit Menu

Edit
Spawns an external editor that you have picked. If there is a currently loaded rule file, the name of the rule file is passed to the editor as a command line parameter. When you quit the editor, you should be returned to LMUSe and the rule file is reloaded from disk. If, while you are using the editor, you change files to edit one other than the one currently loaded into LMUSe and you want to run that other file, you will have to load the other file explicitly. LMUSe has no idea of what you are actually doing in the editor.
Edit Memory Rules
Writes the rules from memory to a temporary file, then spawns an external editor to edit the temporary file.
Pick editor
LMUSE does not contain an editor of its own. To use the Edit function, you must specify an external DOS editor, hopefully one that accepts filenames as a command line parameter.. (And don't specify a Windows editor, like 'Write' or 'WinWord', etc).

Mutate Menu

Mutate CTRL-m
Makes a random change/mutation to the currently loaded rules. These mutations include things like appending one replacement string to another, changing directions in a rule, etc. Hitting 'Mutate' once may have no effect on the production or it may have a devastating effect. It is, after all, random. It is common, after mutating to try and make a production string only to have LMUSE tell you it ran out of string space. Also, on occasion interpreting mutated rules leads to some arithmetic exception which I have not succeeded in tracking down. "Mutate" just mutates; it doesn't make a new production string. To go ahead and make a new production string and interpret it, use "Mutate, Remake/Interpret". There is only one set of rules in memory at any one time. To retrieve the original (before mutations) rules, use "Production/Make and Interpret" to reload the original rule file.
Mutate & Remake/Interpret
Does a mutation just like Mutate, but then makes a production string using the mutated rules, then interprets the new string. If you want to make, say, 5 mutations at once, hit "Mutate" 4 times (equivalently hit CTRL-m 4 times), then the last time use "Mutate & Remake/Interpret"
Save Mutated Rules
Saves the mutated version of the transformation rules as a rule file. The default extension for these 'mutated rule files' is '.lm' just to distinguish between the original rule file and the mutated version.


The Play Screenplay screen screendump

'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.


Using Musical Scales

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.


Rules Files Compatibility

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.


Trouble

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".


Credits

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.

End of File