85 lines
2.1 KiB
Python
85 lines
2.1 KiB
Python
#!/usr/bin/python3
|
|
|
|
###
|
|
#
|
|
# midi4beep.py
|
|
# Author: Adrien MALINGREY <adrien@malingrey.fr>
|
|
#
|
|
# Play MIDI files with PC speaker using the beep command.
|
|
#
|
|
# Usage: python3 midi4beep.py <midi file> [channel]
|
|
#
|
|
# Dependencies:
|
|
# - Python 3
|
|
# - mido library (https://mido.readthedocs.io/)
|
|
# - beep command
|
|
#
|
|
###
|
|
|
|
import sys
|
|
import subprocess
|
|
import math
|
|
import mido
|
|
|
|
|
|
class PCSpeaker(mido.ports.BaseOutput):
|
|
def __init__(self, channel=-1):
|
|
super().__init__(self)
|
|
self.channel = channel
|
|
self.note_on = 0
|
|
self.process = None
|
|
|
|
def _send(self, message):
|
|
if (message.type == "note_on" or message.type == "note_off") \
|
|
and (message.channel == self.channel or (self.channel == -1 and message.channel != 9)):
|
|
if message.type == "note_on" and message.velocity and message.note > self.note_on:
|
|
self.process = subprocess.Popen(
|
|
[
|
|
"beep",
|
|
"-f",
|
|
str(int(440 * math.pow(2, (message.note - 69) / 12))),
|
|
"-l100000",
|
|
]
|
|
)
|
|
self.note_on = message.note
|
|
|
|
elif self.process and (message.type == "note_off" or (message.type == "note_on" and message.note == self.note_on)):
|
|
self.process.terminate()
|
|
self.process = None
|
|
self.note_on = 0
|
|
|
|
def reset(self):
|
|
if self.process:
|
|
self.process.terminate()
|
|
self.process = None
|
|
self.note_on = 0
|
|
|
|
def panic(self):
|
|
if self.process:
|
|
self.process.kill()
|
|
self.process = None
|
|
self.note_on = 0
|
|
|
|
def _close(self):
|
|
self.reset()
|
|
|
|
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python3 midi4beep.py <midi file> [channel]")
|
|
sys.exit(1)
|
|
|
|
path = sys.argv[1]
|
|
try:
|
|
mid = mido.MidiFile(path)
|
|
except TypeError:
|
|
mido.read_syx_file(path)
|
|
|
|
if len(sys.argv) > 2:
|
|
channel = int(sys.argv[2])
|
|
else:
|
|
channel = -1
|
|
|
|
with PCSpeaker(channel) as speaker:
|
|
for msg in mid.play():
|
|
speaker.send(msg)
|