-
Notifications
You must be signed in to change notification settings - Fork 2
/
synthesizer.py
78 lines (63 loc) · 2.26 KB
/
synthesizer.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
#!pypy3
import math
import random
import numpy as np
import matplotlib.pyplot as plt
import sounddevice
import mido
MASTER_VOLUME = 0.01
HALF_LIFE = 0.3
INITIAL_DELAY = 0.2
overtones = {i: 1 / (i ** 1.5) for i in range(1, 8)}
class Audio:
def play(self, samplerate=8000):
n_delay_samples = int(INITIAL_DELAY * samplerate)
time_array = np.arange(0, self.length, 1 / samplerate)
pressure_array = np.zeros(n_delay_samples + len(time_array))
sounddevice.play(pressure_array, samplerate=samplerate)
for i, t in enumerate(time_array, start=n_delay_samples):
pressure_array[i] = self.get_pressure(t.item())
sounddevice.wait()
class Note(Audio):
def __init__(self, frequency, volume=1):
self.frequency = frequency
self.volume = volume
self.length = 1.5
def get_pressure(self, t):
if 0 <= t <= self.length:
result = 0
for overtone, overtone_volume in overtones.items():
result += overtone_volume * math.sin(
math.tau * t * self.frequency * overtone
)
volume = MASTER_VOLUME * self.volume * 2 ** (-t / HALF_LIFE)
return result * volume
else:
return 0
class Sequence(Audio):
def __init__(self, offsets_and_notes):
self.offsets_and_notes = tuple(offsets_and_notes)
self.length = max(offset + note.length for offset, note
in self.offsets_and_notes)
def get_pressure(self, t):
return sum(note.get_pressure(t - offset) for offset, note in
self.offsets_and_notes)
class MidiSequence(Sequence):
def __init__(self, path):
offsets_and_notes = []
current_time = 0
for message in mido.MidiFile(path):
current_time += message.time
if message.type != 'note_on':
continue
offsets_and_notes.append((
current_time,
Note(
440 * 2 ** ((message.note - 69) / 12),
message.velocity / 127
)
))
Sequence.__init__(self, offsets_and_notes)
if __name__ == '__main__':
midi_sequence = MidiSequence('FurElise.mid')
midi_sequence.play()