With respect to designing algorithms, there is no conceptual difference between real-time and run-time modes. However, implementation details are likely to vary between modes due to two concerns of real-time composition: speed and memory usage. As an introduction to these topics, start out by opening MIDI with the scheduler set to real-time mode:
Stella [Top-Level]: open midi scheduling real-time Warning: The current clock mode :SECONDS will result in floating point calculations that may cause GC to affect real time processing. Stream: #<Port: Midi to MODEM> Stella [Top-Level]:The scheduling option enables any stream (not just the MIDI port) to process musical events in either run-time or real-time mode. A warning occurred because -- although the scheduling mode was set to real-time -- the system clock mode is currently seconds. This means that if event processing occurs, time will be calculated using floating point numbers. Floating point calculations involve temporary memory allocation; this in turn will probably cause Lisp to reclaim memory, or "garbage collect" (also called GC) during the real-time performance. Garbage collection will be audible as a dropout in the sound processing.
So the most important rule for real-time composition in Lisp is: avoid allocating temporary memory, or "ephemeral consing" as it is sometimes referred to. Since floating point numbers involve ephemeral consing, avoid using them in your algorithm code and don't force the system to use them either. The latter can be achieved by setting either the global system clock mode or a particular stream's clock mode to milliseconds. This next example fixes the warning by setting the clock mode of the MIDI port to milliseconds:
Stella [Top-Level]: open midi clock milliseconds Stream: #<Port: Midi to MODEM> Stella [Top-Level]:Although neither example demonstrates it, the scheduling and clock options can appear together in any order within a single open statement.
The global clock mode can be set using the (clock-mode) function and the clock mode of an individual stream can set using the clock option, as seen in the last example. For more information on clock modes, see About Time and clock-mode.
? (time (loop repeat 1000 do (random 1.0))) (LOOP REPEAT 1000 DO (RANDOM 1.0)) took 1,990 milliseconds (1.990 seconds) to run. Of that, 1,648 milliseconds (1.648 seconds) was spent in GC. 10,960 bytes of memory allocated.
The example consists of two algorithms, named writer and reader. Writer plays random notes at random times for 20 seconds. The amplitude of each note depends on the value of the global velocity variable:
(defparameter velocity 64) ;;; Writer sends random notes at random times for 20 seconds. ;;; The amplitude of each note is taken from Velocity. (algorithm writer midi-note (end 20000 duration 2000) (setf rhythm (between 50 500)) (setf note (between 40 90)) (setf amplitude velocity))First listen to writer in run-time mode to hear what it does. Note that since the clock mode is milliseconds it is necessary to specify the start time offset to mix in the same format:
Stella [Top-Level]: open midi scheduling run-time clock milliseconds Stream: #<Port: Midi to MODEM> Stella [Top-Level]: mix writer 1000 Stella [Top-Level]:
The second algorithm is called reader. Reader's rhythm is set to .01 seconds; each time reader executes it reads all pending MIDI messages from the MIDI port and passes them to a function called message-handler:
;;; Reader reads all pending midi messages every 10 milliseconds. (mute reader (rhythm 10 end 20000) (midi-read-messages #'message-handler)) ;;; Send message out and update Velocity if a noteOn velocity is > 0. (defun message-handler (msg now) (midi-write-message msg now) (message-case msg (KEY-DOWN (setf velocity (note-on-velocity msg)))))Whenever reader calls midi-read-messages the pending MIDI input messages and their times are passed to message-handler in the order they are received. The first thing message-handler does is send whatever it receives out the MIDI port to the synthesizer, otherwise the input signal would not be heard. After sending the message, a message-case statement "filters out" all MIDI messages except a certain type of message called a "key-down". If a key-down occurs (a key-down is a MIDI note on message with a velocity greater than 0) then the global velocity variable is set to the velocity of that particular note on. This means that writer adjusts its amplitudes to the volume that the performer is currently playing at. For more information on reading messages see message-case and midi-read-messages.
Finally, reset the scheduling mode to real-time and mix reader and writer together. Offset the mix by 1 second so you have time to reach your MIDI keyboard.
Stella [Top-Level]: open midi scheduling real-time Stream: #<Port: Midi to MODEM> Stella [Top-Level]: mix reader,writer 1000 Stella [Top-Level]:
Next Chapter
Previous Chapter
Table of Contents