# The seven modes of Tetris

In this post, our goal is to transpose Tetris to all seven modes of the major scale and play it with the help of the Game Boy sound that we have developed in a previous post.

For those not familiar with the major scale and its modes, I will not attempt an explication here. Please look it up on Wikipedia as it is a complicated notion.

# Transposing to a different mode¶

First, we're going to define the modes of the major scale we will use.

In [1]:
from collections import OrderedDict

modes = OrderedDict()
for mode_name, alterations in zip(['lydian', 'ionian', 'mixolydian', 'dorian', 'aeolian', 'phrygian', 'locrian'],
[[0, 0, 0, -1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, -1],
[0, 0, -1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, -1, 0],
[0, -1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, -1, 0, 0],
[0, 1, 1, 1, 1, 1, 1]]):
modes[mode_name] = alterations


Then, we define the melody (Tetris in our case) in the Nokia melody format.

In [2]:
tetris = "4e6,8b5,8c6,8d6,16e6,16d6,8c6,8b5,4a5,8a5,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,2a5,8p,4d6,8f6,4a6,8g6,8f6,4e6,8e6,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,4a5"


Now, we import the tools we will use.

In [3]:
from pylab import *
from scipy.signal import square


Let's process the melody step by step. Our test case is that we want to go from aeolian, in which the melody is written, to phrygian. This means that only one note changes: b becomes a#.

In [4]:
key = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
starting_mode = 'aeolian'
ending_mode = 'dorian'

In [5]:
mode_names = modes.keys()
start_index = mode_names.index(starting_mode)
end_index = mode_names.index(ending_mode)
if end_index < start_index:
end_index += 7

In [6]:
print start_index, end_index

4 10

In [7]:
transposition = array([0, 0, 0, 0, 0, 0, 0])
for i in range(start_index, end_index):
transposition += array(modes[mode_names[i % 7]])
print transposition

note_scale = ["a", "a#", "b", "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#"]
transposed_melody =  []
for note in tetris.split(','):
if note[-1] == 'p':
transposed_melody.append(note)
else:
for target_note in note_scale:
if note.find(target_note) != -1:
duration, octave = note.split(target_note)
break
transposed_melody.append(
duration + note_scale[(note_scale.index(target_note) + transposition[key.index(target_note)]) % 12] + octave)
",".join(transposed_melody)

[0 0 0 0 0 1 0]

Out[7]:
'4e6,8b5,8c6,8d6,16e6,16d6,8c6,8b5,4a5,8a5,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,2a5,8p,4d6,8f#6,4a6,8g6,8f#6,4e6,8e6,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,4a5'
In [8]:
def transpose(melody, key, starting_mode, ending_mode):
mode_names = modes.keys()
start_index = mode_names.index(starting_mode)
end_index = mode_names.index(ending_mode)
if end_index < start_index:
end_index += 7
transposition = array([0, 0, 0, 0, 0, 0, 0])
for i in range(start_index, end_index):
transposition += array(modes[mode_names[i % 7]])
#print transposition
note_scale = ["a", "a#", "b", "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#"]
transposed_melody =  []
for note in melody.split(','):
if note[-1] == 'p':
transposed_melody.append(note)
else:
for target_note in note_scale:
if note.find(target_note) != -1:
duration, octave = note.split(target_note)
break
transposed_melody.append(
duration + note_scale[(note_scale.index(target_note) + transposition[key.index(target_note)]) % 12] + octave)
return ",".join(transposed_melody)

In [9]:
tetris

Out[9]:
'4e6,8b5,8c6,8d6,16e6,16d6,8c6,8b5,4a5,8a5,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,2a5,8p,4d6,8f6,4a6,8g6,8f6,4e6,8e6,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,4a5'
In [10]:
transpose(tetris, ['a', 'b', 'c', 'd', 'e', 'f', 'g'], 'aeolian', 'dorian')

Out[10]:
'4e6,8b5,8c6,8d6,16e6,16d6,8c6,8b5,4a5,8a5,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,2a5,8p,4d6,8f#6,4a6,8g6,8f#6,4e6,8e6,8c6,4e6,8d6,8c6,4b5,8b5,8c6,4d6,4e6,4c6,4a5,4a5'

# Playing the transposed melody with a Game Boy sound¶

From our previous post, we know how to play this sort of ringtone with the code below:

In [11]:
import re
from IPython.display import Audio, display

In [12]:
def play_melody(melody, sample_freq=10.e3, bpm=50):
duration = re.compile("^[0-9]+")
pitch = re.compile("[\D]+[\d]*")
measure_duration = 4 * 60. / bpm #usually it's 4/4 measures
output = zeros((0,))
for note in melody.split(','):
# regexp matching
duration_match = duration.findall(note)
pitch_match = pitch.findall(note)

# duration
if len(duration_match) == 0:
t_max = 1/4.
else:
t_max = 1/float(duration_match[0])
if "." in pitch_match[0]:
t_max *= 1.5
pitch_match[0] = "".join(pitch_match[0].split("."))
t_max = t_max * measure_duration

# pitch
if pitch_match[0] == 'p':
freq = 0
else:
if pitch_match[0][-1] in ["4", "5", "6", "7"]: # octave is known
octave = ["4", "5", "6", "7"].index(pitch_match[0][-1]) + 4
height = pitch_match[0][:-1]
else: # octave is not known
octave = 5
height = pitch_match[0]
freq = 261.626 * 2 ** ((["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"].index(height) / 12. + octave - 4))

# generate sound
t = arange(0, t_max, 1/sample_freq)
wave = square(2 * pi * freq * t)

# append to output
output = hstack((output, wave))

display(Audio(output, rate=sample_freq))


Using this rudimentary appartus, we can now listen to what this sounds like:

In [13]:
from IPython.html.widgets import interact, fixed

In [14]:
def play_transposed_melody(mode):
transposed_melody = transpose(tetris, ['a', 'b', 'c', 'd', 'e', 'f', 'g'], 'aeolian', mode)
#print transposed_melody
play_melody(transposed_melody, bpm=130)

interact(play_transposed_melody,
mode=modes.keys())


Out[14]:

For rendering purposes, we're outputting the 7 modes below:

In [15]:
for mode in modes.keys():
print mode
play_transposed_melody(mode)

lydian

ionian