{ "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": [ "\"Open" ] }, { "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" } ] } ] }