You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
7.1 KiB
195 lines
7.1 KiB
import mido
|
|
import time
|
|
import curses
|
|
|
|
"""
|
|
mido.get_input_names()
|
|
mido.get_output_names()
|
|
"""
|
|
|
|
|
|
def testOutput():
|
|
outputs = mido.get_output_names()
|
|
lmmsOutPort = None
|
|
for output in outputs:
|
|
if 'LMMS'.lower() in output.lower():
|
|
lmmsOutPort = output
|
|
|
|
if not lmmsOutPort:
|
|
return
|
|
|
|
with mido.open_output(lmmsOutPort) as lmmsOut:
|
|
for i in range(1, 10):
|
|
song = [100, 80, 40, 80, 100]
|
|
for note in song:
|
|
testNote = mido.Message(
|
|
'note_on', note=note, velocity=127, time=0.1)
|
|
lmmsOut.send(testNote)
|
|
testNote = mido.Message(
|
|
'note_on', note=20, velocity=127, time=0.1)
|
|
lmmsOut.send(testNote)
|
|
time.sleep(0.1)
|
|
testNote = mido.Message('note_off', note=note,
|
|
velocity=127, time=0.2)
|
|
lmmsOut.send(testNote)
|
|
testNote = mido.Message(
|
|
'note_off', note=20, velocity=127, time=0.2)
|
|
lmmsOut.send(testNote)
|
|
time.sleep(0.1)
|
|
|
|
|
|
def chooseDevice(devices, searchString):
|
|
matchingDevice = None
|
|
for device in devices:
|
|
if searchString.lower() in device.lower():
|
|
matchingDevice = device
|
|
return matchingDevice
|
|
|
|
|
|
def testIO():
|
|
# synthOutPort = chooseDevice(mido.get_output_names(), 'LMMS')
|
|
synthOutPort = chooseDevice(mido.get_output_names(), 'OP-1')
|
|
keyboardInPort = chooseDevice(mido.get_input_names(), 'CH345')
|
|
|
|
if not synthOutPort or not keyboardInPort:
|
|
return
|
|
|
|
with mido.open_output(synthOutPort) as synthOut:
|
|
with mido.open_input(keyboardInPort) as keyboardIn:
|
|
while True:
|
|
message = keyboardIn.receive()
|
|
print(message)
|
|
synthOut.send(message)
|
|
|
|
|
|
def simpleSequencer():
|
|
debugTiming = False
|
|
|
|
# synthOutPort = chooseDevice(mido.get_output_names(), 'LMMS')
|
|
synthOutPort = chooseDevice(mido.get_output_names(), 'OP-1')
|
|
keyboardInPort = chooseDevice(mido.get_input_names(), 'CH345')
|
|
|
|
if not synthOutPort or not keyboardInPort:
|
|
return
|
|
|
|
with mido.open_output(synthOutPort) as synthOut, mido.open_input(keyboardInPort) as keyboardIn:
|
|
# 16th notes at 240 bpm should be fine
|
|
frameRate = (60 / 240) / 4
|
|
# Prevent the program from locking up if the frame rate gets too bad
|
|
maximumCatchupTime = 0.25
|
|
|
|
timeRoomForError = 0.0001
|
|
|
|
# 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 = 3.0
|
|
sequenceLastNotePlayedTime = 0.0
|
|
sequenceFirstStartTime = 0.0
|
|
sequenceDriftStartTime = 0.0
|
|
sequenceNumTimesPlayed = 0
|
|
sequenceNumTimesMeasureDrift = 4
|
|
|
|
lastTime = time.time()
|
|
timeAccumulated = 0.0
|
|
try:
|
|
while True:
|
|
currentTime = time.time()
|
|
sequenceTime = currentTime - sequenceLastStartTime
|
|
frameDelta = currentTime - lastTime
|
|
if frameDelta > maximumCatchupTime:
|
|
frameDelta = maximumCatchupTime
|
|
lastTime = currentTime
|
|
timeAccumulated += frameDelta
|
|
|
|
if debugTiming:
|
|
print(frameDelta)
|
|
|
|
while timeAccumulated >= frameRate:
|
|
if debugTiming:
|
|
print('Updated')
|
|
|
|
# Poll input
|
|
message = keyboardIn.poll()
|
|
while message:
|
|
print(message)
|
|
sequence.append((message, sequenceTime))
|
|
synthOut.send(message)
|
|
message = keyboardIn.poll()
|
|
|
|
# Play sequencer notes if it's 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])
|
|
|
|
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:
|
|
if debugTiming:
|
|
print('Sleep ' + str(sleepTime))
|
|
time.sleep(sleepTime)
|
|
|
|
finally:
|
|
print('Resetting synth due to exception')
|
|
|
|
synthOut.reset()
|
|
""" 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()
|
|
|
|
# Note that key repeats mean that key holding is fucking weird
|
|
def testKeyInput(stdscr):
|
|
# Make getch() nonblocking
|
|
stdscr.nodelay(1)
|
|
while True:
|
|
inputChar = stdscr.getch()
|
|
if inputChar == ord('f'):
|
|
stdscr.addstr("This is a test")
|
|
elif inputChar == ord('q'):
|
|
break
|
|
|
|
time.sleep(0.05)
|
|
|
|
def main():
|
|
# testOutput()
|
|
# testIO()
|
|
# Put in curses.wrapper so that the curses shit is cleaned up on close/exception
|
|
curses.wrapper(testKeyInput)
|
|
|
|
#simpleSequencer()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|