-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmidi_player.py
109 lines (85 loc) · 3.6 KB
/
midi_player.py
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
"""
Plugin for realtime_annotate.py, for automatically starting and
stopping MIDI devices.
(c) 2015 by Eric O. LEBIGOT (EOL)
"""
__version__ = "1.0"
__author__ = "Eric O. LEBIGOT (EOL) <[email protected]>"
# Reference for the MIDI Machine Control sequences:
# http://en.wikipedia.org/wiki/MIDI_Machine_Control.
import sys
import math
try:
# simplecoremidi 0.3 almost works (it is OS X only, though): it
# needs an undocumented initial SysEx which is ignored, which is
# not clean.
#
# More generally, any module that can send MIDI messages would
# work (rtmidi-python,...).
import rtmidi # This is from the python-rtmidi package
except ImportError:
sys.exit("MIDI support not available."
" It can be enabled with the python-rtmidi module.")
print("""\
*******************************************************************************
MIDI synthetizer support enabled: make sure that your synthetizer listens
to MMC messages (in Logic Pro: menu File > Project Settings > Synchronization >
MIDI > Listen to MMC Input).
*******************************************************************************\
""")
# Play head setting is based on the following frame rate. It should
# ideally match the frame rate of the MIDI controllers. Otherwise, the
# play head will be slightly incorrect (by some sub-second value,
# typically 0.1 s), and the MIDI player might be confused by a frame
# number which is incompatible with its setting (because the frame
# number exceeds the player's maximum frame rate). The user can change
# the FRAME_RATE value.
FRAME_RATE = 25 # frames/s
def out_port():
"""
Return a MIDI port for output.
"""
midi_out = rtmidi.MidiOut()
# This initialization code is from the documentation (it is required):
if midi_out.get_ports(): # If there are available ports...
midi_out.open_port(0)
else:
midi_out.open_virtual_port("realtime_annotate.py")
return midi_out
midi_out = out_port()
def send_MMC_command(command):
"""
Send a MIDI Machine Control command to all MIDI devices.
command -- value of the command (e.g. play = 2, stop = 1, etc.),
or bytes with the command.
"""
if isinstance(command, int):
command = bytes([command])
midi_out.send_message(
bytes([0xF0, 0x7F, 0x7F, 0x06])+command+bytes([0xF7]))
start = lambda: send_MMC_command(2)
stop = lambda: send_MMC_command(1)
def set_time(hours, minutes, seconds):
"""
Set the MIDI players to the given time (note that by default Logic
Pro X starts a MIDI recording at 1:00:00 [1 hour]).
hours and minutes are integers. seconds is a float.
minutes and seconds are in [0; 60).
hours must be in [0; 255].
Sub-second time setting is approximate, and is handled by setting
a frame number based on FRAME_RATE frames/seconds. The player's
frame rate should be set to a value close to this.
"""
assert 0 <= hours <= 255, (
"This number of hours cannot be handled: {}.".format(hours))
# The seconds must be split into integer seconds and fractional seconds:
fractional_seconds, seconds = math.modf(seconds)
seconds = int(seconds)
send_MMC_command(bytes([
0x44, 0x06, 0x01,
# The global rounding could be a tad more precise. Doing
# round() here instead of (int) would not be correct, though,
# because this could yield a frame number which is too high
# (equal to FRAME_RATE instead of the maximum FRAME_RATE-1),
# so a different calculation method should be used.
hours, minutes, seconds, int(FRAME_RATE*fractional_seconds), 0]))