def test_pieces_integration(piece, tempo, recording): """ Sample audio from recording and put into integration. :param piece: pieces object :param tempo: int beats per minute :param recording: str path to recording :return: """ model = Model(None, piece=piece, tempo=tempo) t = 0 q = np.load(recording)[:, :] states = [0] * model.score.N while t < len(q[0]): obs = q[:, t] current_state, prob = model.next_observation(obs) t += 1 if prob < 1.0e-110: model.alpha *= 1.0e100 states[current_state[0]] += 1 res = states[1:len(states) - 1] desired_note_length = (model.recording_speed * model.score.sub_beat.value) / tempo average_note_length = sum(res) / len(res) # Check no notes were skipped assert all(count > 0 for count in res) # Check that average note length was within acceptable range assert abs(average_note_length - desired_note_length) < LENGTH_THRESHOLD
class Follower: """ Class that wraps together all the components needed to perform score following. """ def __init__(self, with_headset: bool, piece: Pieces = None, bpm: int = 60, local_ip: str = None, port: int = None): self.with_headset = with_headset try: if self.with_headset: assert local_ip is not None and port is not None # Connect to Websocket Server self.headset_client = HeadsetClient(local_ip, port) self.output_q = queue.Queue() logging.info(f"Waiting for Song Selection...") song = MessageBuilder.parse_message( self.headset_client.receive()) while type(song) != Pieces: logging.info( "Invalid Song Choice, Waiting for Song Selection...") time.sleep(0.05) piece, bpm = MessageBuilder.parse_message( self.headset_client.receive()) logging.info(f"Song Selected: {song}, Tempo {bpm}") else: assert piece is not None and bpm is not None except AssertionError as e: logging.error("Invalid Parameters") raise Exception(e.args) except Exception as e: logging.error("An Error Occurred") raise Exception(e.args) self.audio_client = AudioClient() self.model = Model(self.audio_client, piece=piece, tempo=bpm) self.accompaniment = AccompanimentService(self.model.score) self.tempo = KalmanFilter(self.model.score.tempo) self.math_helper = MathHelper() self.prev_state = None self.prev_note_val = None self.duration = 1 def _reset_probabilities(self, prob): """ Resets probabilities in alpha table if they get too small to prevent underflow :param prob: Probability returned by integration :return: None """ if prob < 1.0e-110: self.model.alpha *= 1.0e100 def _play_accompaniment(self, current_state): """ Play accompaniment given current state :param current_state: state predicted by integration :return: None """ if self.prev_state is not None and 2 >= current_state[ 0] - self.prev_state >= 0: note_event = current_state[0] self.accompaniment.play_accompaniment(note_event) def _update_tempo(self, current_state): """ Calculate expected duration of played note and compare/adjust to expected tempo :return: None """ # calculate how many frames per beat were observed in the last note if current_state[0] > 1 and current_state[ 0] != self.model.score.N and self.duration > 0: prev_expected_duration = self.model.score.notes[ self.prev_note_val].duration if type( self.model.score.notes[self.prev_note_val - 1].duration ) is int else self.model.score.notes[self.prev_note_val - 1].duration observed_fpb = self.duration * (1 / prev_expected_duration ) # This might be one off. observed_tempo = self.audio_client.frames_per_min / observed_fpb print("Observed Tempo: ", observed_tempo) # perform kalman filter update. if abs(observed_tempo - self.model.score.tempo) < 20: self.tempo.next_measurement(observed_tempo) if abs(self.tempo.current_estimate - self.model.score.tempo ) > 5 and abs(self.tempo.current_estimate - self.model.score.tempo) < 60: self.model.score.tempo = self.tempo.current_estimate self.model.update_tempo() def _get_observation(self, i): """ Get observation from queue :param i: :return: """ if i == 0: return self.model.mu["2"] # Bullshit note to set alpha correctly. else: return self.audio_client.q.get() def _send_accompaniment_to_headset(self, current_state): message = MessageBuilder.build_accompaniment_message( self.model.score.parts[:, current_state[0]]) self.output_q.put(message) pass def follow(self): ts = time.time() print("Start time: ", ts) try: record_thread = RecordThread(self.audio_client) record_thread.start() if self.with_headset: headset_thread = HeadSetCommThread(self) headset_thread.start() logging.info("Waiting for Start Signal...") while MessageBuilder.parse_message( self.headset_client.receive()) != MessageType.Start: time.sleep(.05) logging.info("Start Signal Received.") i = 0 while True: # Get observation from audio client queue and perform forward algorithm step obs = self._get_observation(i) current_state, prob = self.model.next_observation(obs) print(current_state, prob, self.duration, self.tempo.current_estimate) i += 1 self._reset_probabilities(prob) if not self.with_headset: self._play_accompaniment(current_state) else: self._send_accompaniment_to_headset(current_state) # get true event of current state, i.e. the half note when sub-beat is eighth. played_note_val = self.model.score.get_true_note_event( current_state[0]) if self.prev_state is None: self.prev_state = current_state[0] self.prev_note_val = self.model.score.get_true_note_event( self.prev_state) continue else: self.prev_note_val = self.model.score.get_true_note_event( self.prev_state) # Have we moved onto the next note if played_note_val == self.prev_note_val: self.duration += 1 self.prev_state = current_state[0] else: self._update_tempo(current_state) self.duration = 0 self.prev_state = current_state[0] self.prev_note_val = played_note_val finally: print("Time Elapsed: ", time.time() - ts)