Lab 10

In this lab, you must develop two functions (and their supporting functions, if necessary) to allow melody generation using a simple markov chain model.

FIRST: In order to work on this lab, you’re going to need to be running DrRacket version 5.3.1. You can download it from www.racket-lang.org.

Next: here’s the code we developed in class, minus a few key functions:

(require (planet clements/rsound:4:4))
(require (planet clements/rsound:4:4/reverb-typed))
 
; pick-trans : pick one of the transitions, as in class.
; ASSUMES THAT THE PROBABILITIES OF THE TRANSES ADD UP TO ONE
; number list-of-transes -> idx
; --> YOU MUST WRITE THIS FUNCTION
 
 
; random-states: generate a table of 4 random states
(define num-states 4)
; -> list-of-states
; --> YOU MUST WRITE THIS FUNCTION
 
 
; a pd is [make-pd midi-note-num beats]
(define-struct pd (pitch duration))
 
; a trans is [make-trans number number]
; representing a transition to state 'idx' with
; probability 'prob'
(define-struct trans (prob idx))
 
; a state is [make-state pd transes]
(define-struct state (pd transes))
 
; iter-state : produce a piece of music of 'n' beats
; (er, more or less)
; number index list-of-states -> list-of-pds
(define (iter-state dur state-idx states)
  (cond [(<= dur 0) empty]
        [else (local
                [(define the-state (list-ref states state-idx))
                 (define the-note (state-pd the-state))]
                (cons the-note (iter-state
                                (- dur (pd-duration the-note))
                                (pick-trans
                                 (random)
                                 (state-transes the-state))
                                states)))]))
 
; sample states, used in testing:
(define sample-states
  (list (make-state (make-pd 60 1) (list (make-trans 1 1)))
        (make-state (make-pd 62 1) (list (make-trans 1 2)))
        (make-state (make-pd 64 1) (list (make-trans 1 3)))
        (make-state (make-pd 65 1) (list (make-trans 1 0)))))
 
; these states have one random transition:
(define awesome-states
  (list (make-state (make-pd 60 1) (list (make-trans 1 1)))
        (make-state (make-pd 62 1) (list (make-trans 1 2)))
        (make-state (make-pd 64 1) (list (make-trans 1 3)))
        (make-state (make-pd 65 1) (list (make-trans 1/2 0)
                                         (make-trans 1/4 1)
                                         (make-trans 1/4 2)))))
 
; these states use a randomly genererated chain:
(define rand-states
  (random-states))
 
; a simple bass line:
(define bass-states
  (list (make-state (make-pd 48 2) (list (make-trans 1 1)))
        (make-state (make-pd 55 2) (list (make-trans 1 2)))
        (make-state (make-pd 53 2) (list (make-trans 1 3)))
        (make-state (make-pd 55 2) (list (make-trans 1 0)))))
 
; TEST CASES:
(check-expect (iter-state 0 0 sample-states) empty)
(check-expect (iter-state 1 0 sample-states)
              (list (make-pd 60 1)))
(check-expect (iter-state 2 0 sample-states)
              (list (make-pd 60 1)
                    (make-pd 62 1)))
(check-expect (iter-state 3 0 sample-states)
              (list (make-pd 60 1)
                    (make-pd 62 1)
                    (make-pd 64 1)))
 
; MUSICAL CONSTANTS
(define BPM 120)
(define beat-secs (/ 60 BPM))
(define beat-frames (round (* 44100 beat-secs)))
(define quarter-frames beat-frames)
 
 
; convert a pd into an rsound:
; pd -> rsound
(define (make-note pd)
  (cond [(number? (pd-pitch pd))
         (rs-overlay (synth-note "main"
                                 43
                                 (pd-pitch pd)
                                 (round
                                  (* quarter-frames
                                     (pd-duration pd))))
                     (synth-note "main"
                                 43
                                 (* 1.00025 (pd-pitch pd))
                                 (round
                                  (* quarter-frames
                                     (pd-duration pd)))))]
        [else
         (silence (round
                   (* quarter-frames
                      (pd-duration pd))))]))
 
 
 
; define the song as an rsound
(define song
  (rs-overlay
   (rs-scale
    0.4
    (rs-append*
     (map make-note (iter-state 16 0 awesome-states))))
   (rs-scale
    0.4
    (rs-append*
     (map make-note (iter-state 16 0 bass-states))))))
 
; a network that loops a sound:
(define (looper sound)
  (local [(define len (rs-frames sound))]
    (network ()
             [ctr ((loop-ctr len 1))]
             [out (rs-ith/left sound ctr)])))
 
; play it, with reverb please:
(signal-play
 (network ()
          [s ((looper song))]
          [r (reverb s)]))

Paste this code into a new buffer, and change the language level in this buffer to "Advanced".

Comment out the definition of rand-states, for now.

First, implement pick-trans, to allow the given code to run. Follow the design recipe, and make sure to write your test cases *first*.

Then, implement random-states. This function should generate a markov model with 4 states, where each state contains a pitch and duration chosen randomly from a list, and each state has a set of random transitions to other existing states. In order to make it easier to ensure that your transition probabilities add up to one, I suggest that you always generate a fixed number of transitions (say, 6) for each state, and that each one have the same probability (in this case, 1/6). Uncomment the definition of rand-states, and update the definition of "song" so that it generates a song using your randomly generated model.