spobooks bbv9810.0001.001 in

    Chapter 7: Printing and Reading

    This chapter introduces you to writing output to your computer's monitor and reading data from the computer keyboard. Displaying information to your monitor is very helpful in locating problems in your programs.

    7.1 Print

    The Common LISP primitive PRINT causes its argument to be printed. The PRINT template is:
    (print whatever-needs-to-be-printed)

    PRINT will print a constant, symbol, string, or the value of a variable or expression.

    The examples in Section 7.1 and 7.2 use the Stella command interpreter. These examples could also be evaluated by the Common LISP interpreter.

    Example 7.1
    Stella [Top-Level]: (print 45)
    45
    45
    Stella [Top-Level]: (print 'a-symbol)
    Stella [Top-Level]:
    A-SYMBOL
    A-SYMBOL
    Stella [Top-Level]: (print "hello world!")
    "hello world!"
    "hello world!"
    Stella [Top-Level]: (print *standard-tempo*)
    60
    60
    Stella [Top-Level]: (print (+ 2 3 4))
    9
    9

    It seems as if PRINT prints everything twice. One line of output is caused because of PRINT. The second line of output is printed because LISP returns the last expression evaluated, which in this case, is the argument to PRINT.

    7.2 Format

    The Common LISP function FORMAT allows you greater control of the formatting of your output. The FORMAT template is:

    (FORMAT T FORMAT-CONTROL-STRING)

    FORMAT is followed by the symbol T to indicate that we want to print to the monitor. A string is used as the format-control-string.

    Stella [Top-Level]: (format t "I love Lisp!")
    I love Lisp!
    NIL

    Notice that FORMAT writes the string without the quotes and returns NIL. The format-control-string is always enclosed in double quotes. The format-control-string may include FORMAT directives- special characters in the format-control-string that cause output to appear in certain ways.FORMAT directives always begin with the tilde (~). Table 7.2.1 gives an overview of some very useful FORMAT directives.

    Table 7.2.1

    format Directive

    Result

    ~%

    Move to a new line

    ~&

    Move to a new line only if not already at the start of a new line

    ~A

    Print the value of a variable or expression. The value of the variable or expression is mapped to the format-control-string. The variable or expression is included in the FORMAT form immediately following the format-control-string.

    ~n,F

    Print the value of a variable or expression as a floating point value. The floating point value is mapped to the format-control-string. n is variable and specifies the number of positions that will be printed. n is optional.

    Example 7.2.1: The ~%FORMAT Directive
    Stella [Top-Level]: (format t "~%this is the first line ~%and this is the second")

    this is the first line

    and this is the second

    NIL

    Example 7.2.2: The ~&FORMAT Directive
    Stella [Top-Level]: (format t "~%this is the first line ~%~&and this is the second")
    this is the first line
    and this is the second
    NIL

    In Example 7.2.2, the second ~%FORMAT directive moves printing to a new line. The ~&FORMAT directive is ignored since printing is already at the start of a new line.

    Example 7.2.3: The ~A FORMAT Directive
    Stella [Top-Level]: (format t "~%this is the first line ~&and this is the second ~&and the value of *standard-tempo* ~A" *standard-tempo*)
    this is the first line
    and this is the second
    and the value of *standard-tempo* 60
    NIL

    Notice that in Example 7.2.3 the value of *standard-tempo* is mapped to the location of the ~A FORMAT directive in the format-control-string. Notice that the variable *standard-tempo* appears after the format-control-string.

    Example 7.2.4
    Stella [Top-Level]: (format t "~% this is a float ~3,F ~&and this is a float ~4,F" .02 6.98700)
    this is a float 0.02
    and this is a float 6.99
    NIL

    In Example 7.2.4, the FORMAT directive ~n,F is used to control formatting of floating point values.

    7.3 Using print and format in Common Music

    Sometimes, it may be useful to monitor how Common Music assigns slots by printing values to your computer monitor. An example of why you might want to do this is if you're just becoming familiar with some aspect of Common Music. For example, let's say you'd like to learn more about the Common Music function between. You look-up the documentation for between in the Common Music Dictionary and find:

    between lb ub &optional exclude state                [Function]

    Returns number n such that lb<=n<ub and n!=exclude. Use exclude to avoid direct repetition. state is the random state object to use in the calculation, and defaults to *cm-state*.

    At first glance, it may not be obvious what between does.between returns a random number between a lower bound (inclusive) and an upper bound (exclusive). If the upper bound or lower bound are floating point values, between returns a floating point value. The &optional indicates an optional argument in Common LISP. In this case, the optional arguments are for exclude and state.exclude allows you to prevent direct repetition of a randomly-selected value.state is the random state object used to calculate the random value and defaults to the Common Music global variable *cm-state*.

    We will use PRINT to gain first-hand experience with between. We use between to randomly specify an amplitude value in the range .5 (inclusive) to .9 (exclusive).

    Example 7.3.1: print.lisp
    (generator print midi-note ()
    (setf note (item (notes c5 d5 e5 in palindrome) :kill T))
    #|
    :kill is a keyword argument to the item function.
    A value of T means the item stream will terminate after it has completed its pattern.
    A value of NIL means the item stream should not terminate.
    |#
    (setf duration (item (items .2 .3 .4)))
    (setf rhythm (item (rhythms e e. q)))
    (setf channel 0)
    (setf amplitude (between .5 .9))
    (print amplitude))

    When we mix the generator, we see that the value of the amplitude slot is printed six times, once for each midi-note object that was created as specified by the note slot. Notice that the value of the amplitude slot is a random value between .5 and .9.

    Stella [Print]: mix
    Mix objects: (<cr>=Print)
    0.7422715057474961
    0.6896894376532909
    0.719959111921413
    0.7616195154592759
    0.5545485937121298
    0.7427924428932228

    Example 7.3.2 explores the optional argument exclude. In this case, we exclude the previous value of the amplitude slot to prevent direct repetition.

    Example 7.3.2: print-again.lisp
    (algorithm print-again midi-note (length 6)
    (setf note (item (notes c5 d5 e5 in palindrome)))
    (setf duration (item (items .2 .3 .4)))
    (setf rhythm (item (rhythms e e. q)))
    (setf channel 0)
    (setf amplitude (between .5 .9 amplitude))
    (print amplitude))
    Stella [Print-Again]: mix
    Mix objects: (<cr>=Print-Again)
    Start time offset:(<cr>=None)
    0.6944736566291632
    0.5009347789006956
    0.848018833152985
    0.5935138651980345
    0.5657223829361095
    0.7318362501111557

    You may think that it is strange that the generator print and the algorithm print-again contain one PRINT function that is evaluated six times. The reason for multiple evaluations of PRINT is that algorithms and generators have an implicit looping behavior. Algorithms and generators loop until they reach a specified end, length, or prescribed number of note events.

    It would be useful to exercise more control over the manner in which items are printed to your monitor. For example, you may wish to see the value of all of the slots on one line of output and format the floating point value of the amplitude slot so that there are only three positions to the right of the decimal point. You may do this using FORMAT.

    Example 7.3.3: format.lisp
    (generator format midi-note (length 6)
    (setf note (item (notes c5 d5 e5 in palindrome)))
    (setf duration (item (items .2 .3 .4)))
    (setf rhythm (item (rhythms e e. q tempo 120)))
    (setf channel 0)
    (setf amplitude (between .5 .9 amplitude))
    (format t "~&note = ~a duration = ~a rhythm = ~a channel = ~a amplitude = ~5,f" note duration rhythm channel amplitude))
    Stella [Format]: mix
    Mix objects: (<cr>=Format)
    Start time offset:(<cr>=None)
    note = C5 duration = 0.2 rhythm = 0.25 channel = 0 amplitude = 0.607
    note = D5 duration = 0.3 rhythm = 0.375 channel = 0 amplitude = 0.619
    note = E5 duration = 0.4 rhythm = 0.5 channel = 0 amplitude = 0.777
    note = E5 duration = 0.2 rhythm = 0.25 channel = 0 amplitude = 0.706
    note = D5 duration = 0.3 rhythm = 0.375 channel = 0 amplitude = 0.781
    note = C5 duration = 0.4 rhythm = 0.5 channel = 0 amplitude = 0.618
    Another Common Music function we can explore using FORMAT is interp. The Common Music Dictionary describes interp:

    interp x env &key :scale :offset :return-type [Function]

    Returns the interpolated y value of x in env with optional :scale and :offset values applied: f(x)*scale+offset. The type of the value returned normally depends on the type of the arguments specified to the function. Use :return-type to force the return value to be a specific type, either float, integer or ratio. float may also be specified as a list (float digits) in which case the floating point return value will be rounded to digitnumber of places.

    interp returns an interpolated value in a range as specified by env. Optionally, we can scale the value that it returned or add an offset to it. We may use the optional keyword return-type so that interp returns a floating point value, integer, or ratio.

    Example 7.3.4: format-again.lisp
    (algorithm format-again midi-note (length 10 channel 0)
    (setf note (interp (random 1.0) '(0 0 1 12) :offset 48 :return-type 'integer))
    #|
    interp selects a random number in the range 0 (inclusive) to 1 (exclusive). The random value is interpolated in the range using the x,y pairs 0,0 to 1, 12. An offset of 48 is added to the interpolated value now in the range of 1 to 12. Interp returns an integer.
    |#
    (setf rhythm (interp (random 1.0) '(0 0 1 2) :scale .75))
    #|
    interp selects a random value as it did for the note slot.
    The random value is interpolated into the range 1 to 2.
    The interpolated value is multiplied by .75.
    |#
    (setf duration rhythm)
    (setf amplitude (between .5 .9))
    (format t "~&note = ~a rhythm = ~5,F duration = ~5,F amplitude = ~5,F" note rhythm duration amplitude))
    Stella [Format-Again]: mix
    Mix objects: (<cr>=Format-Again)
    Start time offset:(<cr>=None)
    note = 57 rhythm = 1.053 duration = 1.053 amplitude = 0.589
    note = 58 rhythm = 1.442 duration = 1.442 amplitude = 0.691
    note = 49 rhythm = 1.208 duration = 1.208 amplitude = 0.552
    note = 49 rhythm = 0.116 duration = 0.116 amplitude = 0.767
    note = 56 rhythm = 1.442 duration = 1.442 amplitude = 0.884
    note = 58 rhythm = 0.355 duration = 0.355 amplitude = 0.718
    note = 49 rhythm = 0.804 duration = 0.804 amplitude = 0.661
    note = 52 rhythm = 0.679 duration = 0.679 amplitude = 0.785
    note = 57 rhythm = 0.381 duration = 0.381 amplitude = 0.582
    note = 51 rhythm = 0.446 duration = 0.446 amplitude = 0.787

    Your actual output may vary because of the RANDOM function.

    7.4 Reading Data from the Computer Keyboard

    The Common LISP function READ accepts input from the computer keyboard. Generally, READ is used to assign a variable or slot. The READ template is:

    (READ)

    We can allow the user to enter data from the computer keyboard during the evaluation of an algorithm or generator. Because of the looping behavior of algorithms and generators, Common Music evaluates the READ function as many times as the generator or algorithm loops. Consider the following example that assigns the note slot using READ.

    Example 7.4.1: read.lisp
    (generator read midi-note (length 5 channel 0 amplitude .5 duration .25 rhythm .5)
    (format t "~&Enter a note number: ")
    (setf note (read)))

    The generator read creates 5 midi-notes. The channel, amplitude, duration, and rhythm slots are assigned when the container is initialized. The body of the generator consists of the Common LISP FORMAT function that prints a user prompt to the computer monitor. The READ function accepts input from the keyboard and that input is assigned to the note slot. Mixing the generator yields the following:

    Example 7.4.2
    Stella [Read]: mix

    Mix objects: (<cr>=Read)

    Start time offset:(<cr>=None)

    Enter a note number: 60
    Enter a note number: 62
    Enter a note number: 64
    Enter a note number: 66
    Enter a note number: 68
    Stella [Read]: list
    Read:
    1. #<MIDI-NOTE | 60| 0.500| 0.250| 0.500| 0|>
    2. #<MIDI-NOTE | 62| 0.500| 0.250| 0.500| 0|>
    3. #<MIDI-NOTE | 64| 0.500| 0.250| 0.500| 0|>
    4. #<MIDI-NOTE | 66| 0.500| 0.250| 0.500| 0|>
    5. #<MIDI-NOTE | 68| 0.500| 0.250| 0.500| 0|>