This is one of the 100 recipes of the IPython Cookbook, the definitive guide to high-performance scientific computing and data science in Python.

# 3.5. Using interactive widgets: a piano in the notebook¶

You need to download the Piano dataset on the book's website. (http://ipython-books.github.io)

This dataset contains synthetized piano notes obtained on archive.org (CC0 1.0 Universal licence). (https://archive.org/details/SynthesizedPianoNotes)

1. Let's import a few modules.
In [ ]:
import numpy as np
import os
from IPython.display import Audio, display, clear_output
from IPython.html import widgets
from functools import partial

1. To create a piano, we will draw one button per note. The corresponding note plays when the user clicks on the button. This is implemented by displaying an <audio> element.
In [ ]:
dir = 'data/synth'

In [ ]:
# This is the list of notes.
notes = 'C,C#,D,D#,E,F,F#,G,G#,A,A#,B,C'.split(',')

In [ ]:
def play(note, octave=0):
"""This function displays an HTML Audio element
that plays automatically when it appears."""
f = os.path.join(dir,
"piano_{i}.mp3".format(i=note+12*octave))
clear_output()
display(Audio(filename=f, autoplay=True))

1. We are going to place all buttons within a container widget. In IPython 2.0+, widgets can be organized hierarchically. One common use case is to organize several widgets in a given layout. Here, piano will contain 12 buttons for the 12 notes.
In [ ]:
piano = widgets.ContainerWidget()

1. We create our first widget: a slider control that specifies the octave (0 or 1 here).
In [ ]:
octave_slider = widgets.IntSliderWidget()
octave_slider.max = 1
octave_slider

1. Now, we create the buttons. There are several steps. First, we instantiate a ButtonWidget object. Then, we specify a callback function that plays the corresponding note (given by an index) at a given octave (given by the current value of the octave slider). Finally, we set the CSS of each button, notably the white or black color.
In [ ]:
buttons = []
for i, note in enumerate(notes):
button = widgets.ButtonWidget(description=note)

def on_button_clicked(i, _):
play(i+1, octave_slider.value)

button.on_click(partial(on_button_clicked, i))

button.set_css({'width': '30px',
'height': '60px',
'color': ('black',
'white')['#' in note],
'background': ('white', 'black')['#' in note],
'border': '1px solid black',
'float': 'left'})

buttons.append(button)

1. Finally, we arrange all widgets with the containers. The piano container contains the buttons, and the main container (container) contains the slider and the piano.
In [ ]:
piano.children = buttons

In [ ]:
container = widgets.ContainerWidget()
container.children = [octave_slider,
piano]


By default, widgets are organized vertically within a container. Here, the octave slider will be above the piano.

In [ ]:
display(container)
piano.remove_class('vbox')

Within the piano, we want all notes to be arranged horizontally. We do this by replacing the default vbox CSS class by the hbox class.