{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "2.2 Putting It All Together.ipynb",
"provenance": [],
"collapsed_sections": [],
"toc_visible": true,
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "qYCvJRm3yGca",
"colab_type": "text"
},
"source": [
"# Coding the Illiac: Generate and Test\n",
"How can we use random numbers, comparison operators, logical operators, the `if` statement, and the `while` loop to implement a \"generate and test\" composition program like *Illiac Suite*?\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f33_F4JWdrFR",
"colab_type": "text"
},
"source": [
"## Setup\n",
"Import the `random` module."
]
},
{
"cell_type": "code",
"metadata": {
"id": "9l4pLe7TWhNV",
"colab_type": "code",
"colab": {}
},
"source": [
"import random"
],
"execution_count": 0,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "dzuiKdWBV1xV",
"colab_type": "text"
},
"source": [
"## Generate\n",
"Let's start with generating random pitches. As in **Tutorial 1. Hello Python for Music**, let's assume the musical representation in which a melody is expressed as a list of numbers. "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "XB2PH3MJavEy",
"colab_type": "text"
},
"source": [
"First, let's write a `while` loop to generate a list of 12 random numbers. We'll need that `while` loop and random number generator from the previous **section 2.1**."
]
},
{
"cell_type": "code",
"metadata": {
"id": "vW1wPQteVj-M",
"colab_type": "code",
"outputId": "05183dbb-730c-422f-a54b-777310240cf9",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# start with an empty list\n",
"my_music = [60]\n",
"\n",
"# loop until we have 12 notes\n",
"while len(my_music) < 12:\n",
" \n",
" # generate a random note\n",
" new_note = random.randint(0, 127)\n",
"\n",
" # append it to the list\n",
" my_music += [new_note]\n",
" \n",
"# print the final list\n",
"print(my_music)"
],
"execution_count": 0,
"outputs": [
{
"output_type": "stream",
"text": [
"[60, 55, 127, 99, 44, 30, 103, 82, 21, 11, 105, 124]\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "LiUIDINQgQ5P",
"colab_type": "text"
},
"source": [
"**Caveat:** for now let's start with `my_music = [60]` rather than a completely empty list. You'll see why later..."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "vtw1QbBLW17Z",
"colab_type": "text"
},
"source": [
"## and Test\n",
"How would you express a musical rule such as \"no melodic line may span for an an octave\" or \"a melodic skip of a major or minor seventh is forbidden\" or \"no more than one successive repeat of a given note?\" The answer: *comparison* and *logical* operators."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mJR3tegltqf5",
"colab_type": "text"
},
"source": [
"## Expressing a musical rule\n",
"Let's use *comparison operators* to express the rule \"no melodic skip larger than a perfect fourth\"? It helps to break it down into smaller tasks:\n",
"\n",
"1. get the previous note\n",
"2. measure the interval between the new and previous notes\n",
"3. test if the interval is larger than a P4\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "-QT4PCbBuyl3",
"colab_type": "text"
},
"source": [
"First let's generate a new note."
]
},
{
"cell_type": "code",
"metadata": {
"id": "G2oTPSQFt02P",
"colab_type": "code",
"outputId": "3bd340b0-4e5f-4043-dcbd-664cb562c4d5",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# our list of notes thus far\n",
"my_music = [60, 62, 63]\n",
"\n",
"# choose a random note\n",
"new_note = random.randint(0, 127)\n",
"\n",
"# print the random note\n",
"print(new_note)"
],
"execution_count": 0,
"outputs": [
{
"output_type": "stream",
"text": [
"55\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "maQNmMeru0uo",
"colab_type": "text"
},
"source": [
"Now test it."
]
},
{
"cell_type": "code",
"metadata": {
"id": "tNEUGbH5uwqq",
"colab_type": "code",
"outputId": "d03f58b7-973b-4b68-a9e5-87b9805f2171",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# 1. get the previous note\n",
"prev_note = my_music[-1]\n",
"\n",
"# 2. measure the interval btwn new and prev note\n",
"interval = abs(new_note - prev_note)\n",
"\n",
"# 3. test if the interval is larger than a P4\n",
"print(interval <= 5)"
],
"execution_count": 0,
"outputs": [
{
"output_type": "stream",
"text": [
"False\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "yW4PFiFWwKgK",
"colab_type": "text"
},
"source": [
"**Important:** as we saw in class, we need the *absolute value* (`abs()`) of the interval because we don't care about the *sign*, just the *magnitude*. That is, we don't care whether or the intervals is *ascending* or *descending*, just the size of the jump between notes. Other rules however may care about the direction!"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7gLVCdZbuZC6",
"colab_type": "text"
},
"source": [
"Let's write the test all on one line."
]
},
{
"cell_type": "code",
"metadata": {
"id": "DS2EU-tmc5o8",
"colab_type": "code",
"outputId": "81850d2a-716c-4229-f30b-9f95200d2b12",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# is new_note larger than a perfect fourth?\n",
"abs(new_note - my_music[-1]) <= 5"
],
"execution_count": 0,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"False"
]
},
"metadata": {
"tags": []
},
"execution_count": 5
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "BB253XbPdIK4",
"colab_type": "text"
},
"source": [
"## to Add or not to Add\n",
"Those are the two major piece of the puzzle. We *know* whether or not the new note passes the rule, but how do we use that boolean value to modify the program behavior? "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "x4AMgaTfwANg",
"colab_type": "text"
},
"source": [
"An `if` statement is used when you want to perform different actions depending on whether or not a condition is `True` or `False`. Let's write an `if` statement to add or reject the new note depending on the rule."
]
},
{
"cell_type": "code",
"metadata": {
"id": "Ugg8USqNdJC3",
"colab_type": "code",
"outputId": "2be3270f-01a9-4831-d5c7-53faab460717",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# start with an empty list\n",
"my_music = [60]\n",
"\n",
"# loop until we have 12 notes\n",
"while len(my_music) < 12:\n",
" \n",
" # generate a random note\n",
" new_note = random.randint(0, 127)\n",
"\n",
" # is new_note larger than a perfect fourth?\n",
" if abs(new_note - my_music[-1]) <= 5:\n",
"\n",
" # if yes, append it to the list\n",
" my_music += [new_note]\n",
" \n",
"# print the final list\n",
"print(my_music)"
],
"execution_count": 0,
"outputs": [
{
"output_type": "stream",
"text": [
"[60, 65, 63, 67, 70, 70, 67, 65, 63, 59, 60, 62]\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mUxjL9olgxSx",
"colab_type": "text"
},
"source": [
"## Looking too far back\n",
"Things are never quite as simple as you'd like. Try initializing to an empty list with `my_music = []`. What's the problem?"
]
},
{
"cell_type": "code",
"metadata": {
"id": "8UP10hOLgyi5",
"colab_type": "code",
"outputId": "2ceea231-f89d-4d83-e602-9dee472a530d",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 236
}
},
"source": [
"# start with an empty list\n",
"my_music = []\n",
"\n",
"# loop until we have 12 notes\n",
"while len(my_music) < 12:\n",
" \n",
" # generate a random note\n",
" new_note = random.randint(0, 127)\n",
"\n",
" # is new_note larger than a perfect fourth?\n",
" if abs(new_note - my_music[-1]) <= 5:\n",
"\n",
" # append it to the list\n",
" my_music += [new_note]\n",
" \n",
"# print the final list\n",
"print(my_music)"
],
"execution_count": 0,
"outputs": [
{
"output_type": "error",
"ename": "IndexError",
"evalue": "ignored",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;31m# is new_note larger than a perfect fourth?\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 10\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mabs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnew_note\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mmy_music\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 11\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;31m# append it to the list\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mIndexError\u001b[0m: list index out of range"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "kk29sgm0hE4V",
"colab_type": "text"
},
"source": [
"We get an `IndexError: list index out of range`. What happened? We tried to access the previous note `my_music-[1]` before there was anything there! Ideally, we'd like to start from an empty list, so how can we fix this?"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Cgh6lXQPxg6e",
"colab_type": "text"
},
"source": [
"We want the rule to be `True` when either (1) the interval is not greater than a perfect fourth ***or*** (2) when `my_music` is an empty list (meaning we are choosing the first note). Can you think of a way to implement this? We'll use the logical operator *or*."
]
},
{
"cell_type": "code",
"metadata": {
"id": "IP-zEcT1g_fv",
"colab_type": "code",
"outputId": "55d0e51e-7319-45b0-b8e7-c273c755ea86",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# our list of notes thus far\n",
"my_music = []\n",
"\n",
"# choose a random note\n",
"new_note = random.randint(0, 127)\n",
"\n",
"# is new_note larger than a perfect fourth?\n",
"len(my_music) < 1 or abs(new_note - my_music[-1]) <= 5"
],
"execution_count": 0,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"True"
]
},
"metadata": {
"tags": []
},
"execution_count": 8
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "HPs2BhxzjJPJ",
"colab_type": "text"
},
"source": [
"We're actually doing something quite sophisticated here. Remember, if Python tries to access the list when it's empty, we'll get an error, so how does the `or` statement avioid that, aren't we still accessing the list? The `or` statement does a nifty trick called \"short circuiting.\" If the first condition is `True`, it doesn't bother to evaluate the second condition, because it knows the entire statement will be `True` regardless."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f9IHHQRL1TF3",
"colab_type": "text"
},
"source": [
"## Finally\n",
"Replace the test with your new line of code that is protected against errors and you are ready to make some music."
]
},
{
"cell_type": "code",
"metadata": {
"id": "zzjs96Vz1O4C",
"colab_type": "code",
"outputId": "d07c8b1e-0946-4e72-f3b3-624ada07f5ad",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 35
}
},
"source": [
"# start with an empty list\n",
"my_music = []\n",
"\n",
"# loop until we have 12 notes\n",
"while len(my_music) < 12:\n",
" \n",
" # generate a random note\n",
" new_note = random.randint(0, 127)\n",
"\n",
" # is new_note larger than a perfect fourth?\n",
" if len(my_music) < 1 or abs(new_note - my_music[-1]) <= 5:\n",
"\n",
" # append it to the list\n",
" my_music += [new_note]\n",
" \n",
"# print the final list\n",
"print(my_music)"
],
"execution_count": 0,
"outputs": [
{
"output_type": "stream",
"text": [
"[17, 15, 17, 14, 14, 14, 13, 15, 14, 15, 11, 13]\n"
],
"name": "stdout"
}
]
}
]
}