-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmetronome.py
More file actions
144 lines (117 loc) · 5.01 KB
/
metronome.py
File metadata and controls
144 lines (117 loc) · 5.01 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
131
132
133
134
135
136
137
138
139
140
141
142
143
""" Metronome class used to connect serially to a microcontroller that acts as a metronome.
To connect serially, the data port must be specified. Two ports will be seen when connecting
the microcontroller which is the data port and the REPL port. Trial and error may have to
be used to see when one is which. For example, if my REPL port is COM6 and my data port is COM3,
I connect with COM3
set_bpm, play, and pause all send commands to the microcontroller. set_bpm sets the speed of
the metronome, play starts the metronome, and pause stops the metronome. set_bpm must be
done before sending play or pause. calculate_bpm is used to calculate the bpm from a song
and is made to be used for set_bpm.
"""
import sys
import serial
import time
from aubio import tempo, source
from scipy.io import wavfile
from numpy import mean, median, diff
class metronome:
# constructor
# port and filename are strings, bpm is a float
# filename and bpm are optional parameters
def __init__(self, port=None, filename=None, bpm=-1):
self.port = port # port to communicate on
self.serial_connection = serial.Serial(self.port, 115200) # serial connection to microcontroller
self.filename = filename # file to calculate bpm from
self.bpm = bpm # current bpm of metronome
# set the metronome speed
def set_bpm(self, bpm):
# bpm valid check
if bpm < 0 or type == str:
print(bpm)
print(type(bpm))
print('Invalid bpm')
return
# send the bpm to the metronome to play
bpm_str = "set {bpm_str:.3f}\r\n".format(bpm_str = bpm)
input = bytes(bpm_str, encoding='utf-8')
self.serial_connection.write(input)
# play the metronome
def play(self):
self.serial_connection.write(b'play\r\n')
# pause the metronome
def pause(self):
self.serial_connection.write(b'pause\r\n')
# unpause the metronome to be in time with the music
# bpm is the specified bpm of the song
# song_time_pos is the time that the song is at in ms
def unpause(self, bpm, song_time_pos):
# checks that bpm isnt a string and isnt negative
if bpm < 0 or type == str:
print(bpm)
print(type(bpm))
print('Invalid bpm')
return
# checks that the time in the song isnt a string and isnt negative
if song_time_pos < 0 or type == str:
print('Invalid time of song')
return
# calculates the ms per beat and the time to the next beat
mspb = 60.0 / bpm * 1000
time_to_beat = mspb - (song_time_pos % mspb)
# creates and sends the command for the metronome
input_str = "unpause {}\r\n".format(time_to_beat)
input = bytes(input_str, encoding='utf-8')
self.serial_connection.write(input)
# tells the metronome to output a strum
# strum_type is either "up" or "down" since those are the only strums we are checking for
def strum(self, strum_type):
# validity check
if strum_type != "up" and strum_type != "down":
print("Strum is not a string")
return
# sends the command
input_str = "strum " + strum_type + "\r\n"
input = bytes(input_str, encoding='utf-8')
self.serial_connection.write(input)
# set the serial connection to the microcontroller
def set_serial(self, port):
self.port = port
self.serial_connection = serial.Serial(port, 115200)
# calculate bpm from a .wav file
# filename is an opional parameter, it is only needed
# if a file was not given when using the constructor
def calculate_bpm(self, filename=None):
# set the file to the one in the contructor if given
if filename != None:
self.filename = filename
# check that the file has been input and is a .wav file
if self.filename == None:
print("No file specified")
return -1
win_s = 512 # fft size
hop_s = win_s // 2 # hop size
# open wavfile to get samplerate
samplerate, tempaudio = wavfile.read(self.filename)
# get the tempo
s = source(self.filename, samplerate, hop_s)
samplerate = s.samplerate
o = tempo("default", win_s, hop_s, samplerate)
# tempo detection delay, in samples
# default to 4 blocks delay to catch up with
delay = 4. * hop_s
# list of beats, in samples
beats = []
# total number of frames read
total_frames = 0
while True:
samples, read = s()
is_beat = o(samples)
if is_beat:
this_beat = o.get_last_s()
beats.append(this_beat)
total_frames += read
if read < hop_s: break
# calculate bpm
bpms = 60./ diff(beats)
bpm = median(bpms)
return bpm