-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmulti_synth.py
More file actions
executable file
·130 lines (106 loc) · 3.69 KB
/
Copy pathmulti_synth.py
File metadata and controls
executable file
·130 lines (106 loc) · 3.69 KB
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
#!/usr/bin/env python3
# Polyphonic Python MIDI synthesizer
import pyaudio
import mido
import struct, math, time
# sleep time in main loop
SLEEP = 0.01
# audio buffer size (determines latency)
# Increase this if there is crackling audio output.
BSIZE = 256
# sample rate
ARATE = 44100
# maximum polyphony
MAXPOLY = 8
# volume
VOLUME = 3000
# sustain notes?
SUSTAIN = False
################################################################################
# list of currently active notes
notes = []
# callback function for audio data
def callback(in_data, frame_count, time_info, status):
data = b""
for i in range(frame_count):
v = 0
for n in notes:
v += n[2] * (
math.sin(n[0])
+ 0.5 * n[5] * math.sin(2 * n[0])
+ 0.25 * n[5] * math.sin(4 * n[0])
)
n[0] += 2 * math.pi / ARATE * n[1]
n[2] *= n[3]
b = struct.pack("h", round(VOLUME * v))
data += b
return data, pyaudio.paContinue
# open mido and pyaudio inputs/outputs
inport = mido.open_input()
paud = pyaudio.PyAudio()
stream = paud.open(
format=paud.get_format_from_width(2),
channels=1,
rate=ARATE,
output=True,
frames_per_buffer=BSIZE,
stream_callback=callback,
)
# print("latency [s] = %.5f" % stream.get_output_latency())
while True:
for msg in inport.iter_pending():
# process new note
if msg.type == "note_on":
if msg.velocity == 0:
# turn note off (if velocity = 0)
if not SUSTAIN:
for n in notes:
if n[4] == msg.note:
n[3] = n[3] ** 6
else:
# get note frequency in Hz
freq = 440 * 2 ** ((msg.note - 69) / 12)
# get amplitude loss factor per sample
# (higher frequencies decay more quickly)
a_min, a_max, a_sel = math.log(21), math.log(108), math.log(msg.note)
lossfac = 50000 - 49000 * ((a_sel - a_min) / (a_max - a_min))
lossfac *= ARATE / 44100
amp_loss = 1 - 1 / lossfac
# get harmonic content factor (like PySynth A)
# - strong harmonics in lower octaves
# - no harmonics in higher octaves
lf_fac = (math.log(freq) - 3) / 4
if lf_fac > 1:
harm = 0
else:
harm = 2 * (1 - lf_fac)
# append new note to list of active notes
# note data:
# 0 * current oscillator phase
# 1 * frequency in Hz
# 2 * current amplitude
# 3 * amplitude loss factor
# 4 * MIDI key number
# 5 * harmonic content factor
notes.append([0, freq, 1, amp_loss, msg.note, harm])
# remove notes that have gone almost silent
newnotes = []
for n in notes:
if n[2] > 0.001:
newnotes.append(n)
notes = newnotes
# apply maximum polyphony cutoff with priority for latest notes
if len(notes) > MAXPOLY:
notes = notes[-MAXPOLY:]
# increase amplitude loss of note when note_off event happens
if msg.type == "note_off" and not SUSTAIN:
for n in notes:
if n[4] == msg.note:
n[3] = n[3] ** 6
try:
time.sleep(SLEEP)
except: # exception handler hides ugly backtrace when pressing Ctrl-C
break
stream.close()
paud.terminate()
inport.close()