Skip to content

Instantly share code, notes, and snippets.

@rareyman
Last active January 2, 2024 17:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rareyman/663a55e277e47724d926535092b46e37 to your computer and use it in GitHub Desktop.
Save rareyman/663a55e277e47724d926535092b46e37 to your computer and use it in GitHub Desktop.
Running this script will start a tempo and broadcast it to the Arturia KeyStep 37, then wait for you to enter "s" to stop the clock. Example: `python midi-tempo-broadcast.py 125` to set BPM to 125.
# MIDI Tempo Broadcast
# Ross A. Reyman • soundcloud.com/r89music
# This script broadcasts a MIDI clock signal to hardware devices.
#
# prerequisites:
# - Python 3+
# - install Mido `pip install mido`
#
# Run this script with:
# `python midi-tempo-broadcast.py` OR
# `python midi-tempo-broadcast.py <bpm>`
# where <bpm> is the tempo in beats per minute.
#
# The default tempo is 125 BPM.
import mido
import time
import threading
import argparse
import sys
# Define constants: the name of the MIDI device and the initial tempo
DEVICE_NAME = 'Arturia KeyStep 37'
INITIAL_TEMPO = 125
# Check if the device is available
if DEVICE_NAME not in mido.get_output_names():
print(f"\033[91mThe device '{DEVICE_NAME}' is not available. Check your MIDI connections.\033[0m")
sys.exit(1)
def start_midi_clock(bpm, stop_event, current_bpm):
# Calculate the delay between clock messages for the given tempo
delay = 60.0 / bpm / 24
# Open the output port
with mido.open_output(DEVICE_NAME) as outport:
# Send MIDI clock messages continually
while not stop_event.is_set():
start_time = time.perf_counter()
# Check if the tempo has changed
if bpm != current_bpm[0]:
bpm = current_bpm[0]
delay = 60.0 / bpm / 24
# Send a MIDI clock message
msg = mido.Message('clock')
outport.send(msg)
# Calculate the actual delay taking into account the time spent sending the message
actual_delay = delay - (time.perf_counter() - start_time)
actual_delay = max(0, actual_delay) # Ensure the delay is not negative
# Sleep for the calculated delay using time.perf_counter
while time.perf_counter() - start_time < actual_delay:
pass
def wait_for_stop_command(stop_event, current_bpm):
# Wait for user input in a loop
while True:
command = input("\033[2mEnter 's' to stop the MIDI clock or a number to change the tempo: ")
if command.lower() == 's':
stop_event.set()
break
elif command.isdigit():
current_bpm[0] = int(command)
print(f"\033[2mThe current tempo is now: \033[92m{current_bpm[0]} BPM.\033[0m")
# Create a stop event
stop_event = threading.Event()
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Start a MIDI clock with a specified tempo.')
parser.add_argument('bpm', type=int, nargs='?', default=INITIAL_TEMPO, help='The tempo in beats per minute.')
args = parser.parse_args()
# Print the initial tempo
print(f"\033####################################################")
print(f"\033[2mThe initial tempo is: \033[92m{args.bpm} BPM.\033[0m")
# Create a shared variable for the current BPM
current_bpm = [args.bpm] # Default BPM
# Start the MIDI clock at the specified BPM in a new thread
clock_thread = threading.Thread(target=start_midi_clock, args=(args.bpm, stop_event, current_bpm))
clock_thread.start()
# Start the command listener in a new thread
command_thread = threading.Thread(target=wait_for_stop_command, args=(stop_event, current_bpm))
command_thread.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment