The AlgoRythm Opera Challenge

Download exercises zip

Browse files online

finalsol

Some people say music is not important, pupils should do math instead. Let’s show them music is math.

A musical sheet is divided vertically in voices. Each voice has a pentagram divided in measures (or bars, or battute in italian), each having a number of beats indicated by its time signature, like \(\large \substack{3\\4}\)

Simple time signatures consist of two numerals, one stacked above the other:

  • The upper numeral time_sig_num (3) indicates how many such beats constitute a bar

  • The lower numeral time_sig_denom (4) indicates the note value that represents one beat (the beat unit)

  • NOTE: the lower numeral is not important for our purposes, you will just print it on the pentagrams

For the purposes of this exercise, we assume each measure contains exactly time_sig_num notes.

On the chart, note the x axis measures start at 1 and each measure has chart length 1. Notice the pentagrams start a bit before the 1 position because is some info like the time signature and the clef.

  • NOTE: horizontal tick 0 is not shown

Vertically, each voice pentagram begins at an integer position, starting from 1. Each line of the pentagram occupies a vertical space we can imagine subdivided in divs=7 divisions, in the chart you see 5 divisions for the pentagram lines and 2 invisible ones to separate from voice above.

  • NOTE: vertical tick 0 is not shown

The variables

DO NOT put unnecessary constants in your code !

For example, instead of writing 5 you should use the variable measures defined down here

[1]:
# this is *not* a python command, it is a Jupyter-specific magic command,
# to tell jupyter we want the graphs displayed in the cell outputs
%matplotlib inline

# imports matplotlib
import matplotlib.pyplot as plt
from pprint import pprint
import numpy as np

# USE THESE VARIABLES !!
measures       = 5   # also called bars
voices         = 3   # number of pentagrams
time_sig_num   = 3
time_sig_denom = 4
divs = 7  # number of vertical divisions for each voice  (5 lines for each pentagram + 2 imaginary lines)

1. plot_sheet

Implement plot_sheet, which draws sheet info like title, axes, xticks, yticks …

  • DO NOT draw the pentagrams

  • NOTE: tick 0 is not shown

sheet

WARNING 1: you need only this ONE call to plt.figure

WARNING 2: beware of plt.show()

If you execute this outside of Jupyter, you will need to call plt.show() ONLY ONCE, at the very end of all plotting stuff (outside the functions!)

[2]:

def plot_sheet():
    fig = plt.figure(figsize=(10,7))  # 10 inches large by 7 high
    raise Exception("TODO IMPLEMENT ME!")


plot_sheet()
#plt.savefig('sheet-sol.png')
../_images/visualization_algorythm-opera-chal_8_0.png

2. plot_pentagram

Given a voice integer from 1 (NOT ZERO!!!) to voices included, draws its pentagram.

  • DO NOT draw the notes

pentagram-sol

Try to draw stuff in this sequence:

1 - draw horizontal lines, starting from measure 1. Leave some space before 1 to put later the clef. To obtain the y coordinates, use np.linspace

  • REMEMBER to check you have a measure drawn after the 5 tick !

  • HINT: Since you will have to plot several detached lines, for each you will need to do a separate call to plt.plot.

2 - draw vertical bars between measures - don’t put a bar at beginning of first measure. To obtain the x coordinates, use np.linspace

3 - draw time signature text. To draw a string s at position x, y, you need to call this:

plt.text(x, y, s, fontsize=19,  fontweight='bold')

4 - draw clef: put provided image clef.png. To draw the image, you need to call some code like this, by using the appropriate numerical coordinates in place of xleft, xright, ybottom, ytop which delimit the place where the image is put.

clef = plt.imread('clef.png')
plt.imshow(clef, extent=[xleft, xright, ybottom, ytop])
  • NOTE 1: If you see nothing, it maybe be you are drawing outside of the area or the given frame is too small.

  • NOTE 2: xright and ytop are absolute coordinates, not width and height!

[3]:
def plot_pentagram(voice):
    """ Takes a voice from 1 to n and draws its pentagram. Try to draw stuff in this sequence:

        - horizontal lines, starting from measure 1. Leave some space before 1 to put later the clef
        - vertical bars - don't put a bar at beginning of first measure
        - time signature text
        - clef - put image clef.png
    """
    raise Exception("TODO IMPLEMENT ME!")

# NOTE: putting *all* commands in a cell
plot_sheet()
plot_pentagram(1)
#plt.savefig('pentagram-sol.png')
../_images/visualization_algorythm-opera-chal_15_0.png

3. plot_notes

Implement function plot_notes which takes a voice integer from 1 to n and a database of notes as a list of lists and draws the notes of that voice

  • notes are integers from min_note=0 to max_note=8

  • we assume we can only have notes that con be positioned inside the pentagram, so bottom note starts at E4 (middle height of bottom pentagram line) and highest note is F5 (middle height of top pentagram line).

  • to set dots size, use markersize=9 parameter

  • to set dots color, use color='black' parameter

[4]:

# these are just labels, but you don't need to put them anywhere
# http://newt.phys.unsw.edu.au/jw/notes.html
           #  0    1    2    3    4    5    6    7    8
notes_scale=['E4','F4','G4','A4','B4','C5','D5','E5','F5']
min_note = 0
max_note = len(notes_scale) - 1


# This is provided, DO NOT TOUCH IT!
def random_notes(voices, measures, time_sig_num, seed):
    """ Generates a random list of lists of notes. Generated notes depend on seed numerical value.
    """
    import random
    random.seed(seed)
    ret = []
    for i in range(voices):
        ret.append([random.randint(min_note,max_note) for i in range(measures*(time_sig_num))])
    return ret


# This is provided, DO NOT TOUCH IT!
def musical_scale(voices, measures, time_sig_num):
    """ Generates a scale of notes
    """
    ret = []
    for i in range(voices):
        j = min_note
        ret.append((list(range(max_note+1))*100)[:measures*time_sig_num])
    return ret


def plot_notes(voice, notes):
    raise Exception("TODO IMPLEMENT ME!")
[5]:
notes = musical_scale(voices, measures, time_sig_num)
#notes = random_notes(voices, measures, time_sig_num, 0)
from pprint import pprint
print('notes:')
pprint(notes)
plot_sheet()
plot_pentagram(1)
plot_notes(1, notes)
#plt.savefig('notes-sol.png')
notes:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5],
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5]]
../_images/visualization_algorythm-opera-chal_18_1.png

notessol

Final result 1

Putting all together, and using random notes, you should get this:

[6]:
measures       = 5   # also called bars
voices         = 3   # number of pentagrams
time_sig_num   = 3
time_sig_denom = 4
divs = 7

plot_sheet()
for i in range(1,voices+1):
    plot_pentagram(i)

#notes = musical_scale(voices, measures, time_sig_num)
notes = random_notes(voices, measures, time_sig_num, 0)

for i in range(1,voices+1):
    plot_notes(i, notes)

#plt.savefig('final-sol-1.png')
../_images/visualization_algorythm-opera-chal_21_0.png

finalsol

Final result 2

Quite probably you used too many constants in your code instead of using variables at the beginning of the notebook, so let’s see if it is general enough to work with sheets that have a different number of voices, measures, etc:

[7]:
# WARNING: CHANGED VARIABLES !!!!
measures       = 7
voices         = 4
time_sig_num   = 5
time_sig_denom = 9
divs = 7

plot_sheet()
for i in range(1,voices+1):
    plot_pentagram(i)

#notes = musical_scale(voices, measures, time_sig_num)
notes = random_notes(voices, measures, time_sig_num, 0)

for i in range(1,voices+1):
    plot_notes(i, notes)

#plt.savefig('final-sol-2.png')
../_images/visualization_algorythm-opera-chal_24_0.png

final-sol-2