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.