Take-home assessment 7: musical improviser

Musical improvisation is the act of creating and performing a musical composition "on the spot." A hallmark of many modern musical genres such as rock, blues, and jazz, improvisation requires both technical mastery and creativity of the musician. In this assessment, you will build a simple improviser using randomness. We'll then observe that we this simple improviser doesn't sound so great!

Logistics and turn-in

You should write your program in a file called improv.scm and use the lab library to structure your program according to the parts outlined in this write-up:

;;; improv.scm
;;;   A musical improviser
;;;   CSC-151-XX 24fa.
;;;
;;; Author: Your Name Here
;;; Date submitted: YYYY-MM-DD
;;;
;;; Acknowledgements:
;;; 
;;; * ...
;;; * ...

(import lab)

(title "A musical improviser")

(part "Part 1: Infrastructure)

(part "Part 2: A basic improviser")

(part "Part 3: Licks")

(part "Part 4: Extensions")

Within each part you should use problem to separate the various problems found in each part. Your file should also include a header like the one given above.

Part 1: Scales

It is pretty easy to create a musical composition simply by throwing random notes together:

(import music)
(seq
  (note 45 sn) (note 30 sn)
  (note 42 sn) (note 64 sn)
  (note 46 sn) (note 74 sn)
  (note 58 sn) (note 59 sn)
  (note 72 sn) (note 32 sn)
  (note 50 sn) (note 55 sn)
  (note 46 sn) (note 58 sn)
  (note 38 sn) (note 73 sn))

However, it sounds more like chaos than music! How about this random set of notes?

(import music)
(seq
  (note 61 sn) (note 70 sn)
  (note 65 sn) (note 61 sn)
  (note 65 sn) (note 61 sn)
  (note 63 sn) (note 70 sn)
  (note 70 sn) (note 61 sn)
  (note 58 sn) (note 61 sn)
  (note 70 sn) (note 70 sn)
  (note 61 sn) (note 70 sn))

Hopefully, it sounds more pleasing! Why was this the case?

We drew the notes from a musical scale in the second case. A musical scale is a collection of notes separated by a fixed pattern of intervals starting at some root note. When improvising, a musician will choose notes from one or more scales to create a baseline for their composition.

Write a function (root->scale root degrees) that takes:

  • root, a number corresponding to a valid MIDI value, and
  • degrees, a list of scale degrees as strings.

And produces a list of MIDI note values of the provided scale starting at the given root.

Scale degrees are a common notation for specifying the notes of a scale relative to a root value. The degrees specify a note relative to the root in terms of semitones:

DegreeSemitones from Root
10
♭2/♯11
22
♭3/♯23
34
45
♭5/♯46
57
♭6/♯58
69
♭7/♯610
711
812

Adding a flat (♭) or a sharp (♯) subtracts or adds one semitone to the degree. For example, ♭5 (dually, ♯4) specifies a note 6 semitones from the root.

Because the flat and sharp symbols are not found on conventional keyboards, we'll use lowercase b and the hashtag symbol for flat and sharp, respectively. So the string "b5" represents the degree ♭5 and "#4" represents the degree ♯4.

Part 2: A basic improviser

With root->scale, we can now build a basic musical improviser! For the remainder of this assessment, we will write and evolve a function improvise that creates an improvised musical composition. Initially, this function will simply choose random notes from a scale. We will then iteratively add complexity to the function, some of which we'll specify, and some of which you'll design and play around with on your own!

Problem 2a: improvise-1: random notes

Write a function (improvise-1 root degrees num-measures) that takes a root MIDI note value and a list of degrees as strings as input and produces a musical composition that is a sequence of sixteenth notes (sn duration) whose note values are randomly drawn from the scale specified by root and degrees. Additionally, improvise-1 takes the number of measures num-measures of that the composition should last. improve-1 returns a composition that consists exclusively of sixteenth notes (duration sn) with note values drawn by the scale implemented by root and degrees. Note that a sixteenth note takes up a sixteenth of a measure, so 16 sixteenth notes is equivalent to one measure.

To set up the next parts of this assessment, we recommend implementing improvise-1 in a particular way. Write improvise-1 by writing a recursive helper function improvise-1/helper that takes an argument t that represents the amount of time that has elapsed so far in the composition. Dually, this argument could also represent the time remaining in the composition if this makes more sense to you. improvise-1/helper can take whatever additional parameters you need, but ultimately, the function returns the remainder of the composition left from time t to the end of the composition (dually, a composition of time t if we interpret t as time remaining). With this set up, each recursive call to improvise-1/helper will add one sixteenth note to the overall composition.

The time t should be represented as a dur value since we are adding notes to the composition on every recursive call. Because we are only adding sixteenth notes in this function, increasing (or decreasing) t amounts to adding (or subtracting) to t on every recursive call. Note that you can't simply use + on dur values because they are fractions, not numbers! You will need to use the numerator and/or denominator functions to manually perform the addition (or subtraction).

Problem 2b: improvise-2: random pulses

Next, let's write a function (improvise-2 root degrees num-measures) that acts like improvise-1 except that instead of sixteenth notes, the function randomly chooses between the durations with equal probability:

  • Sixteenth notes sn,
  • Eighth notes en,
  • Quarter notes qn, and
  • Half notes hn,

Additionally, improvise-2 also chooses between producing a random note from the scale and a rest (via rest) with equal probability.

Part 3: Licks

At this point, your improviser produces a random stream of notes from a scale with some variation in note duration and rests. While this is an "improvisation" in the strictest of senses, it certainly doesn't feel like music because it is too random!

To draw an analogy with a more common artistic medium, consider a writing some prose, whether it is an essay or story. When writing prose, we do not simply pick random words. Instead, we:

  • Employ patterns of writing, e.g., the five-paragraph essay, to provide structure.
  • Create a narrative arc that features tension and release to capture the reader's attention.
  • Utilize sayings, turns of phrases, and other literary tropes to pithily capture complicated ideas.

All of these techniques bring some amount of regularity and consistency to an otherwise creative, open-ended endeavor. Likewise, an improvising musician uses similar techniques to scaffold their composition. For example, musical progressions, i.e., patterns of chords, provide structure to an improvisation. With a progression in place, the choices of notes provide aural tension and release.

In this part of the assessment, we'll implement a way for our improviser to use the equivalent of "sayings and turns of phrase" in music: the lick. A lick is a short musical phrase that allows an improviser to build a musical vocabulary. Some licks are longer such as "The Lick" or "The Mario Kart Lick". We'll focus on smaller licks that fit in the rhythmic durations of our current improviser.

In this setup, we'll implement a lick library as an association list mapping one of our durations (currently sn, en, qn, and hn) to a list of licks. An individual lick is a list of pairs of scale degrees and durations with the property that the sum of the durations in a lick add up to the duration that it associated to in the library. In other words, every lick associated with a given duration fills up exactly the time indicated by the duration.

Write a function (improvise-3 root degrees num-measures licks) that behaves like improvise-2 except in addition to randomly choosing either a note or rest with equal probability, the function also chooses a random lick of the chosen duration with equal probability. Each lick in a given list is also chosen with equal probability.

Part 4: Extending the improviser

Licks are just one example of how we might extend our improviser to sound more human-like. For this final portion of the assignment, you should extend improvise-3 in no less than two significant ways to create a final improvising function, improvise. Examples of extensions you might consider include:

  • Adding additional licks to your library by experimenting with different combinations of notes and durations.
  • Experimenting with the probabilities involved with the various choices of notes, rests, and licks. For example, you may change your code so that it is more likely to select a note closer to the given degree rather than further away. Using interval notation, if you have just played a ♭2 note, then it could be more likely that you play a 3 note rather than a 7.
  • Adding accompaniment to the improvised solo, using your arpeggiator code from the previous assessment to generate chords. A simple progression you might add is a minor blues progression which, for B♭ consists of the chords:
    • B♭ minor (the "i")
    • E♭ minor (the "iv")
    • F minor (the "v")
    • B♭ minor (the "i") You might give each chord 1 or 2 measures each, for example.

Grading Rubric

Grading rubric

Redo or above

Submissions that lack any of these characteristics will get an N.

  • Displays a good faith attempt to complete every required part of the assignment.

Meets expectations or above

Submissions that lack any of these characteristics but have all the prior characteristics will get an R.

  • Includes the specified file, improv.scm.
  • Includes an appropriate header on all submitted files that includes the course, author, etc.
  • Correctness
    • Code runs without errors.
    • Core functions are present and correct (except for non-obvious corner cases, when present)
      • (root->scale root degrees)
      • (improvise-1 root degrees num-measures)
      • (improvise-2 root degrees num-measures)
      • (improvise-3 root degrees num-measures licks)
      • `(improvise ...)
    • root->scale has an appropriate test suite.
    • Each improvise function is called in code at least once with an appropriate description.
  • Design
    • Documents and names all core procedures correctly.
    • Code generally follows style guidelines.

Exemplary / Exceeds expectations

Submissions that lack any of these characteristics but have all the prior characteristics will get an M.

  • Correctness
    • Implementation of all core functions is completely correct, even in non-obvious corner cases when present.
    • improvise's additional functionality is clearly documented.
  • Design
    • Function documentation is complete and appropriately evocative of each function's behavior.
    • Code follows style guidelines completely, with at most three minor errors present.
    • Code is well-designed, avoiding repeated work through decomposition and appropriate language constructs.
    • improvise's additional functionality is sufficiently complex for the assignment.