forked from lontas/python-learning-experiments
-
Notifications
You must be signed in to change notification settings - Fork 0
/
notes.py
executable file
·192 lines (160 loc) · 7.69 KB
/
notes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""This is a class that built on to of the pygame Sound class to play specific
notes on the system soundcard.
Can easily be used to compose 8-bit music"""
# This code is based on code from https://gist.github.com/ohsqueezy/6540433
# Built in libs
from array import array
from fractions import gcd
from time import sleep
# External libs
# import ipdb
import pygame
# It's more clear if we prefix the mixer functions with mixer.
from pygame import mixer
from pygame.mixer import Sound
def lcm(num1, num2):
if num1 == 0 and num2 == 0:
return 0
return abs(num1 * num2) / gcd(num1, num2)
class Note(Sound):
'''This class contains note information and creates a pygame Sound object
with a specific note set '''
notes = {u'C0': 16.35, u'C♯0': 17.32, u'D0': 18.35, u'D♯0': 19.45,
u'E0': 20.60, u'F0': 21.83, u'F♯0': 23.12, u'G0': 24.50,
u'A♭0': 25.96, u'A0': 27.50, u'B♭0': 29.14, u'B0': 30.87,
u'C1': 32.70, u'C♯1': 34.65, u'D1': 36.71, u'D♯1': 38.89,
u'E1': 41.20, u'F1': 43.65, u'F♯1': 46.25, u'G1': 49.00,
u'A♭1': 51.91, u'A1': 55.00, u'B♭1': 58.27, u'B1': 61.74,
u'C2': 65.41, u'C♯2': 69.30, u'D2': 73.42, u'D♯2': 77.78,
u'E2': 82.41, u'F2': 87.31, u'F♯2': 92.50, u'G2': 98.00,
u'A♭2': 103.83, u'A2': 110.00, u'B♭2': 116.54, u'B2': 123.47,
u'C3': 130.81, u'C♯3': 138.59, u'D3': 146.83, u'D♯3': 155.56,
u'E3': 164.81, u'F3': 174.61, u'F♯3': 185.00, u'G3': 196.00,
u'A♭3': 207.65, u'A3': 220.00, u'B♭3': 233.08, u'B3': 246.94,
u'C4': 261.63, u'C♯4': 277.18, u'D4': 293.66, u'D♯4': 311.13,
u'E4': 329.63, u'F4': 349.23, u'F♯4': 369.99, u'G4': 392.00,
u'A♭4': 415.30, u'A4': 440.00, u'B♭4': 466.16, u'B4': 493.88,
u'C5': 523.25, u'C♯5': 554.37, u'D5': 587.33, u'D♯5': 622.25,
u'E5': 659.26, u'F5': 698.46, u'F♯5': 739.99, u'G5': 783.99,
u'A♭5': 830.61, u'A5': 880.00, u'B♭5': 932.33, u'B5': 987.77,
u'C6': 1046.50, u'C♯6': 1108.73, u'D6': 1174.66, u'D♯6': 1244.51,
u'E6': 1318.51, u'F6': 1396.91, u'F♯6': 1479.98, u'G6': 1567.98,
u'A♭6': 1661.22, u'A6': 1760.00, u'B♭6': 1864.66, u'B6': 1975.53,
u'C7': 2093.00, u'C♯7': 2217.46, u'D7': 2349.32, u'D♯7': 2489.02,
u'E7': 2637.02, u'F7': 2793.83, u'F♯7': 2959.96, u'G7': 3135.96,
u'A♭7': 3322.44, u'A7': 3520.00, u'B♭7': 3729.31, u'B7': 3951.07,
u'C8': 4186.01, u'C♯8': 4434.92, u'D8': 4698.64, u'D♯8': 4978.04,
u'E8': 5274.04, u'F8': 5587.66, u'F♯8': 5919.92, u'G8': 6271.92,
u'A♭8': 6644.88, u'A8': 7040.00, u'B♭8': 7458.62, u'B8': 7902.14,
u'C9': 8372.02, u'C♯9': 8869.84, u'D9': 9397.28, u'D♯9': 9956.08,
u'E9': 10548.08, u'F9': 11175.32, u'F♯9': 11839.84,
u'G9': 12543.84, u'A♭9': 13289.76, u'A9': 14080.00,
u'B♭9': 14917.24, u'B9': 15804.28,
u'C10': 16744.04, u'C♯10': 17739.68, u'D10': 18794.56,
u'D♯10': 19912.16, u'E10': 21096.16, u'F10': 22350.64,
u'F♯10': 23679.68, u'G10': 25087.68, u'A♭10': 26579.52,
u'A10': 28160.00, u'B♭10': 29834.48, u'B10': 31608.56,
u'C11': 33488.08}
# A4 - G5
chromatic = [u'A♭', u'A', u'B♭', u'B',
u'C', u'C♯', u'D', u'D♯', u'E', u'F', u'F♯', u'G']
def __init__(self, note=None, frequency=440, volume=.1):
# Notes must be in the format A[♯,♭,♮][octave number], if the octave is
# left out then it will be centered around C5.
# If note is a tuple then map the calls to parse_note, etc.
if note is not None:
if type(note) in [list, tuple]:
note = map(Note.parse_note, note)
frequency = [Note.notes[n] for n in note]
sound_buffer = Note.build_chord(frequency)
else:
note = Note.parse_note(note)
frequency = Note.notes[note]
sound_buffer = Note.build_samples(frequency)
self.note = note
self.frequency = frequency
Sound.__init__(self, buffer=sound_buffer)
self.set_volume(volume)
@staticmethod
# This doesn't depend on anything in the class or instance so make it
# a static method
def parse_note(note):
'''Format the note into a form we're sure we can use and return the
frequency of that note'''
if not note:
return 440
note = unicode(note).replace(u'b', u'♭').replace(u'#', u'♯')
bad = [u'G♯', u'A♯', u'C♭', u'B♯', u'D♭', u'E♭', u'F♭', u'E♯', u'G♭']
good = [u'A♭', u'B♭', u'B', u'C', u'C♯', u'D♯', u'E', u'F', u'F♯']
for from_note, to_note in zip(bad, good):
note = note.replace(from_note, to_note)
if note in Note.chromatic[:4]:
note += u'4'
if note in Note.chromatic[4:]:
note += u'5'
return note
@staticmethod
# This doesn't depend on anything in the class or instance so make it
# a static method
def build_samples(frequency):
'''Build an array of the wave for this frequency'''
# Hz is cycles per second
# period = 44100 Hz / 440 Hz = 100 samples per cycle
# Given samples and cycles per second get the period of each cycle
period = int(round(mixer.get_init()[0] / frequency))
# Make one full wave's period
samples = array("h", [0] * period)
# Fill with a square wave
amplitude = 2 ** (abs(mixer.get_init()[1]) - 1) - 1
for time in xrange(period):
if time < period / 2:
samples[time] = amplitude
else:
samples[time] = -amplitude
return samples
@staticmethod
def build_chord(frequencies):
assert type(frequencies) in [tuple, list], (
"build_chord must be a tuple or list")
assert len(frequencies) >= 2, (
"build_chord must have multiple notes")
samples = [Note.build_samples(f) for f in frequencies]
# The minimum length of the combined wave is the LCM of the length of
# all the waves
lengths = [len(sample) for sample in samples]
multiple = lcm(lengths[0], lengths[1])
for length in lengths[2:]:
multiple = lcm(multiple, length)
# Make all samples the minimum needed length
samples = [s * (multiple / len(s)) for s in samples]
sample_buffer = [0] * len(samples[0])
for i in range(len(samples[0])):
for j in range(len(samples)):
sample_buffer[i] += samples[j][i]
sample_buffer[i] = int(round(sample_buffer[i] / len(samples)))
# Add the wave LCMs (and normalize?)
return array("h", sample_buffer)
def play_tune(tune, time=0.5):
'''Make sure this class can play basic notes'''
# change this to an array of notes.
music = [Note(note) for note in tune]
for note in music:
note.play(-1, int(time * 1000)) # Loop until we hit our time
print note.note, note.frequency,
sleep(time)
note.stop()
def test():
'''Make sure this class can play basic notes'''
tune = ['A', 'D', 'D#', 'B', 'F#', ('G', 'C', 'E'), 'Ab',
('E', 'G', 'C'), 'F', 'Bb', ('C', 'E', 'G'), 'C#']
pygame.init()
play_tune(tune)
pygame.quit()
if __name__ == '__main__':
# Pre-set the sample freq per sec, size, number of channels,
# and buffer size
mixer.pre_init(44100, -16, 1, 2 ** 16)
pygame.init()
test()