diff --git a/sequencer.py b/sequencer.py index 6d7ad83..f09e3ec 100644 --- a/sequencer.py +++ b/sequencer.py @@ -46,7 +46,7 @@ def chooseDevice(devices, searchString): def testIO(): - #synthOutPort = chooseDevice(mido.get_output_names(), 'LMMS') + # synthOutPort = chooseDevice(mido.get_output_names(), 'LMMS') synthOutPort = chooseDevice(mido.get_output_names(), 'OP-1') keyboardInPort = chooseDevice(mido.get_input_names(), 'CH345') @@ -62,7 +62,9 @@ def testIO(): def simpleSequencer(): - #synthOutPort = chooseDevice(mido.get_output_names(), 'LMMS') + debugTiming = False + + # synthOutPort = chooseDevice(mido.get_output_names(), 'LMMS') synthOutPort = chooseDevice(mido.get_output_names(), 'OP-1') keyboardInPort = chooseDevice(mido.get_input_names(), 'CH345') @@ -74,11 +76,21 @@ def simpleSequencer(): frameRate = (60 / 240) / 4 # Prevent the program from locking up if the frame rate gets too bad maximumCatchupTime = 0.25 + timeRoomForError = 0.0001 - sequence = [] + + # Start with a click + sequence = [(mido.Message( + 'note_on', note=60, velocity=64, time=0.0), 0.0), (mido.Message( + 'note_off', note=60, velocity=127, time=0.1), 0.1)] sequenceLastStartTime = 0.0 - sequenceTimeLength = 1.0 + sequenceTimeLength = 3.0 sequenceLastNotePlayedTime = 0.0 + sequenceFirstStartTime = 0.0 + sequenceDriftStartTime = 0.0 + sequenceNumTimesPlayed = 0 + sequenceNumTimesMeasureDrift = 4 + lastTime = time.time() timeAccumulated = 0.0 try: @@ -91,10 +103,12 @@ def simpleSequencer(): lastTime = currentTime timeAccumulated += frameDelta - print(frameDelta) + if debugTiming: + print(frameDelta) while timeAccumulated >= frameRate: - print('Updated') + if debugTiming: + print('Updated') # Poll input message = keyboardIn.poll() @@ -105,31 +119,55 @@ def simpleSequencer(): message = keyboardIn.poll() # Play sequencer notes if it's time - # TODO: sort notes by time + # TODO: sort notes by time, out messages work strangely for note in sequence: # TODO: this comparison should have a margin of error equal # to the frame rate if note[1] <= sequenceTime and note[1] >= sequenceLastNotePlayedTime: synthOut.send(note[0]) - sequenceLastNotePlayedTime = max(sequenceLastNotePlayedTime, note[1]) + sequenceLastNotePlayedTime = max( + sequenceLastNotePlayedTime, note[1]) + + if not sequenceFirstStartTime: + sequenceFirstStartTime = currentTime # Restart sequence if necessary if sequenceTime >= sequenceTimeLength: + # TODO: Minimize drift over time + if sequenceNumTimesPlayed % sequenceNumTimesMeasureDrift == 0: + print('Sequence played ' + str(sequenceNumTimesPlayed) + + ' times; drifted ' + + str((currentTime - sequenceFirstStartTime) + - (sequenceTimeLength * sequenceNumTimesPlayed)) + ', drifted ' + + str((currentTime - sequenceDriftStartTime) + - (sequenceTimeLength * sequenceNumTimesMeasureDrift)) + + ' this ' + str(sequenceNumTimesMeasureDrift) + ' drift frame') + sequenceDriftStartTime = currentTime + sequenceLastStartTime = currentTime sequenceLastNotePlayedTime = 0.0 + sequenceNumTimesPlayed += 1 timeAccumulated -= frameRate timeAccumulated = max(0.0, timeAccumulated) sleepTime = frameRate - timeAccumulated - timeRoomForError if sleepTime > 0: - print('Sleep ' + str(sleepTime)) + if debugTiming: + print('Sleep ' + str(sleepTime)) time.sleep(sleepTime) finally: print('Resetting synth due to exception') + synthOut.reset() - #synthOut.panic() + """ Sometimes notes hang because a note_off has not been sent. To (abruptly) stop all sounding + notes, you can call: + outport.panic() + This will not reset controllers. Unlike reset(), the notes will not be turned off + gracefully, but will stop immediately with no regard to decay time. + http://mido.readthedocs.io/en/latest/ports.html?highlight=reset """ + # synthOut.panic() if __name__ == '__main__': # testOutput()