Spaces:
Building
Building
MuGeminorum
commited on
Commit
•
0e22503
1
Parent(s):
0ce1c0b
sync ms
Browse files- app.py +13 -9
- convert.py +32 -1
- midi/DataTypeConverters.py +155 -0
- midi/EventDispatcher.py +289 -0
- midi/MidiFileParser.py +191 -0
- midi/MidiInFile.py +55 -0
- midi/MidiInStream.py +52 -0
- midi/MidiOutFile.py +448 -0
- midi/MidiOutStream.py +471 -0
- midi/MidiToText.py +149 -0
- midi/RawInstreamFile.py +118 -0
- midi/RawOutstreamFile.py +69 -0
- midi/__init__.py +6 -0
- midi/changes.txt +45 -0
- midi/constants.py +210 -0
- midi/example_mimimal_type0.py +29 -0
- midi/example_print_channel_0.py +22 -0
- midi/example_print_events.py +28 -0
- midi/example_print_file.py +19 -0
- midi/example_transpose_octave.py +40 -0
- midi/experimental/EventDispatcherBase.py +76 -0
- midi/experimental/MidiOutPassThrough.py +182 -0
- midi/experimental/MidiOutStreamBase.py +135 -0
- midi/experimental/readme.txt +1 -0
- midi/readme.txt +50 -0
- midi/version.txt +1 -0
- midi2abc.py +619 -0
- requirements.txt +1 -1
- simple_abc_parser.py +170 -0
app.py
CHANGED
@@ -4,6 +4,7 @@ import torch
|
|
4 |
import shutil
|
5 |
import gradio as gr
|
6 |
from piano_transcription_inference import PianoTranscription, sample_rate, load_audio
|
|
|
7 |
from convert import *
|
8 |
|
9 |
|
@@ -38,8 +39,8 @@ def audio2midi(audio_path: str):
|
|
38 |
|
39 |
def inference(audio_path: str):
|
40 |
midi_path = audio2midi(audio_path)
|
41 |
-
xml_path = midi2xml(midi_path)
|
42 |
-
abc =
|
43 |
mxl_path = xml2mxl(xml_path)
|
44 |
pdf_path, jpg_path = mxl2jpg(mxl_path)
|
45 |
return midi_path, pdf_path, xml_path, mxl_path, abc, jpg_path
|
@@ -47,14 +48,17 @@ def inference(audio_path: str):
|
|
47 |
|
48 |
iface = gr.Interface(
|
49 |
fn=inference,
|
50 |
-
inputs=gr.Audio(
|
|
|
|
|
|
|
51 |
outputs=[
|
52 |
-
gr.components.File(label="
|
53 |
-
gr.components.File(label="
|
54 |
-
gr.components.File(label="
|
55 |
-
gr.components.File(label="
|
56 |
-
gr.Textbox(label="abc
|
57 |
-
gr.Image(label="
|
58 |
],
|
59 |
)
|
60 |
|
|
|
4 |
import shutil
|
5 |
import gradio as gr
|
6 |
from piano_transcription_inference import PianoTranscription, sample_rate, load_audio
|
7 |
+
from midi2abc import midi2abc
|
8 |
from convert import *
|
9 |
|
10 |
|
|
|
39 |
|
40 |
def inference(audio_path: str):
|
41 |
midi_path = audio2midi(audio_path)
|
42 |
+
xml_path, title, artist = midi2xml(midi_path)
|
43 |
+
abc = midi2abc(midi_path, title, artist)
|
44 |
mxl_path = xml2mxl(xml_path)
|
45 |
pdf_path, jpg_path = mxl2jpg(mxl_path)
|
46 |
return midi_path, pdf_path, xml_path, mxl_path, abc, jpg_path
|
|
|
48 |
|
49 |
iface = gr.Interface(
|
50 |
fn=inference,
|
51 |
+
inputs=gr.Audio(
|
52 |
+
label="Please make sure that the audio has finished uploading before clicking submit.",
|
53 |
+
type="filepath",
|
54 |
+
),
|
55 |
outputs=[
|
56 |
+
gr.components.File(label="Download MIDI"),
|
57 |
+
gr.components.File(label="Download PDF score"),
|
58 |
+
gr.components.File(label="Download MusicXML"),
|
59 |
+
gr.components.File(label="Download MXL"),
|
60 |
+
gr.Textbox(label="abc notation", show_copy_button=True),
|
61 |
+
gr.Image(label="Staff", type="filepath"),
|
62 |
],
|
63 |
)
|
64 |
|
convert.py
CHANGED
@@ -4,6 +4,7 @@ import fitz
|
|
4 |
import requests
|
5 |
import subprocess
|
6 |
from PIL import Image
|
|
|
7 |
|
8 |
|
9 |
def download(url: str, directory: str, filename: str):
|
@@ -56,6 +57,27 @@ else:
|
|
56 |
mscore = "D:/Program Files/MuseScore 3/bin/MuseScore3.exe"
|
57 |
|
58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
def xml2abc(xml_path: str):
|
60 |
result = subprocess.run(
|
61 |
f"python xml2abc.py {xml_path}", stdout=subprocess.PIPE, text=True
|
@@ -78,8 +100,17 @@ def midi2xml(mid_file: str):
|
|
78 |
xml_file = mid_file.replace(".mid", ".musicxml")
|
79 |
command = [mscore, "-o", xml_file, mid_file]
|
80 |
result = subprocess.run(command)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
print(result)
|
82 |
-
return
|
83 |
|
84 |
|
85 |
def pdf2img(pdf_path: str):
|
|
|
4 |
import requests
|
5 |
import subprocess
|
6 |
from PIL import Image
|
7 |
+
from music21 import converter
|
8 |
|
9 |
|
10 |
def download(url: str, directory: str, filename: str):
|
|
|
57 |
mscore = "D:/Program Files/MuseScore 3/bin/MuseScore3.exe"
|
58 |
|
59 |
|
60 |
+
def add_title_to_xml(xml_path: str):
|
61 |
+
"""
|
62 |
+
给 MIDI 文件添加标题,标题为去后缀的文件名。
|
63 |
+
参数:
|
64 |
+
- midi_file_path: MIDI 文件的路径
|
65 |
+
"""
|
66 |
+
# 获取文件名(不带路径)
|
67 |
+
file_name = os.path.basename(xml_path)
|
68 |
+
title = file_name.split(".")[0]
|
69 |
+
artist = "Transcripted by AI"
|
70 |
+
# 读取 MIDI 文件
|
71 |
+
midi_data = converter.parse(xml_path)
|
72 |
+
# 将标题添加到 MIDI 文件中
|
73 |
+
midi_data.metadata.title = title
|
74 |
+
midi_data.metadata.movementName = title
|
75 |
+
midi_data.metadata.composer = artist
|
76 |
+
# 保存修改后的 MIDI 文件
|
77 |
+
midi_data.write("musicxml", fp=xml_path)
|
78 |
+
return title, artist
|
79 |
+
|
80 |
+
|
81 |
def xml2abc(xml_path: str):
|
82 |
result = subprocess.run(
|
83 |
f"python xml2abc.py {xml_path}", stdout=subprocess.PIPE, text=True
|
|
|
100 |
xml_file = mid_file.replace(".mid", ".musicxml")
|
101 |
command = [mscore, "-o", xml_file, mid_file]
|
102 |
result = subprocess.run(command)
|
103 |
+
title, artist = add_title_to_xml(xml_file)
|
104 |
+
print(result)
|
105 |
+
return xml_file, title, artist
|
106 |
+
|
107 |
+
|
108 |
+
def xml2midi(xml_file: str):
|
109 |
+
midi_file = xml_file.replace(".musicxml", ".mid")
|
110 |
+
command = [mscore, "-o", midi_file, xml_file]
|
111 |
+
result = subprocess.run(command)
|
112 |
print(result)
|
113 |
+
return midi_file
|
114 |
|
115 |
|
116 |
def pdf2img(pdf_path: str):
|
midi/DataTypeConverters.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
from struct import pack, unpack
|
4 |
+
|
5 |
+
"""
|
6 |
+
This module contains functions for reading and writing the special data types
|
7 |
+
that a midi file contains.
|
8 |
+
"""
|
9 |
+
|
10 |
+
"""
|
11 |
+
nibbles are four bits. A byte consists of two nibles.
|
12 |
+
hiBits==0xF0, loBits==0x0F Especially used for setting
|
13 |
+
channel and event in 1. byte of musical midi events
|
14 |
+
"""
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
def getNibbles(byte):
|
19 |
+
"""
|
20 |
+
Returns hi and lo bits in a byte as a tuple
|
21 |
+
>>> getNibbles(142)
|
22 |
+
(8, 14)
|
23 |
+
|
24 |
+
Asserts byte value in byte range
|
25 |
+
>>> getNibbles(256)
|
26 |
+
Traceback (most recent call last):
|
27 |
+
...
|
28 |
+
ValueError: Byte value out of range 0-255: 256
|
29 |
+
"""
|
30 |
+
if not 0 <= byte <= 255:
|
31 |
+
raise ValueError('Byte value out of range 0-255: %s' % byte)
|
32 |
+
return (byte >> 4 & 0xF, byte & 0xF)
|
33 |
+
|
34 |
+
|
35 |
+
def setNibbles(hiNibble, loNibble):
|
36 |
+
"""
|
37 |
+
Returns byte with value set according to hi and lo bits
|
38 |
+
Asserts hiNibble and loNibble in range(16)
|
39 |
+
>>> setNibbles(8, 14)
|
40 |
+
142
|
41 |
+
|
42 |
+
>>> setNibbles(8, 16)
|
43 |
+
Traceback (most recent call last):
|
44 |
+
...
|
45 |
+
ValueError: Nible value out of range 0-15: (8, 16)
|
46 |
+
"""
|
47 |
+
if not (0 <= hiNibble <= 15) or not (0 <= loNibble <= 15):
|
48 |
+
raise ValueError('Nible value out of range 0-15: (%s, %s)' % (hiNibble, loNibble))
|
49 |
+
return (hiNibble << 4) + loNibble
|
50 |
+
|
51 |
+
|
52 |
+
|
53 |
+
def readBew(value):
|
54 |
+
"""
|
55 |
+
Reads string as big endian word, (asserts len(value) in [1,2,4])
|
56 |
+
>>> readBew('a���')
|
57 |
+
1642193635L
|
58 |
+
>>> readBew('a�')
|
59 |
+
25057
|
60 |
+
"""
|
61 |
+
return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0]
|
62 |
+
|
63 |
+
|
64 |
+
def writeBew(value, length):
|
65 |
+
"""
|
66 |
+
Write int as big endian formatted string, (asserts length in [1,2,4])
|
67 |
+
Difficult to print the result in doctest, so I do a simple roundabout test.
|
68 |
+
>>> readBew(writeBew(25057, 2))
|
69 |
+
25057
|
70 |
+
>>> readBew(writeBew(1642193635L, 4))
|
71 |
+
1642193635L
|
72 |
+
"""
|
73 |
+
return pack('>%s' % {1:'B', 2:'H', 4:'L'}[length], value)
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
"""
|
78 |
+
Variable Length Data (varlen) is a data format sprayed liberally throughout
|
79 |
+
a midi file. It can be anywhere from 1 to 4 bytes long.
|
80 |
+
If the 8'th bit is set in a byte another byte follows. The value is stored
|
81 |
+
in the lowest 7 bits of each byte. So max value is 4x7 bits = 28 bits.
|
82 |
+
"""
|
83 |
+
|
84 |
+
|
85 |
+
def readVar(value):
|
86 |
+
"""
|
87 |
+
Converts varlength format to integer. Just pass it 0 or more chars that
|
88 |
+
might be a varlen and it will only use the relevant chars.
|
89 |
+
use varLen(readVar(value)) to see how many bytes the integer value takes.
|
90 |
+
asserts len(value) >= 0
|
91 |
+
>>> readVar('�@')
|
92 |
+
64
|
93 |
+
>>> readVar('���a')
|
94 |
+
205042145
|
95 |
+
"""
|
96 |
+
sum = 0
|
97 |
+
for byte in unpack('%sB' % len(value), value):
|
98 |
+
sum = (sum << 7) + (byte & 0x7F)
|
99 |
+
if not 0x80 & byte: break # stop after last byte
|
100 |
+
return sum
|
101 |
+
|
102 |
+
|
103 |
+
|
104 |
+
def varLen(value):
|
105 |
+
"""
|
106 |
+
Returns the the number of bytes an integer will be when
|
107 |
+
converted to varlength
|
108 |
+
"""
|
109 |
+
if value <= 127:
|
110 |
+
return 1
|
111 |
+
elif value <= 16383:
|
112 |
+
return 2
|
113 |
+
elif value <= 2097151:
|
114 |
+
return 3
|
115 |
+
else:
|
116 |
+
return 4
|
117 |
+
|
118 |
+
|
119 |
+
def writeVar(value):
|
120 |
+
"Converts an integer to varlength format"
|
121 |
+
sevens = to_n_bits(value, varLen(value))
|
122 |
+
for i in range(len(sevens)-1):
|
123 |
+
sevens[i] = sevens[i] | 0x80
|
124 |
+
return fromBytes(sevens)
|
125 |
+
|
126 |
+
|
127 |
+
def to_n_bits(value, length=1, nbits=7):
|
128 |
+
"returns the integer value as a sequence of nbits bytes"
|
129 |
+
bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)]
|
130 |
+
bytes.reverse()
|
131 |
+
return bytes
|
132 |
+
|
133 |
+
|
134 |
+
def toBytes(value):
|
135 |
+
"Turns a string into a list of byte values"
|
136 |
+
return unpack('%sB' % len(value), value)
|
137 |
+
|
138 |
+
|
139 |
+
def fromBytes(value):
|
140 |
+
"Turns a list of bytes into a string"
|
141 |
+
if not value:
|
142 |
+
return ''
|
143 |
+
return pack('%sB' % len(value), *value)
|
144 |
+
|
145 |
+
|
146 |
+
|
147 |
+
|
148 |
+
|
149 |
+
|
150 |
+
|
151 |
+
|
152 |
+
|
153 |
+
|
154 |
+
|
155 |
+
|
midi/EventDispatcher.py
ADDED
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
# std library
|
4 |
+
from struct import unpack
|
5 |
+
|
6 |
+
# custom
|
7 |
+
from .DataTypeConverters import readBew, readVar, varLen, toBytes
|
8 |
+
|
9 |
+
# uhh I don't really like this, but there are so many constants to
|
10 |
+
# import otherwise
|
11 |
+
from .constants import *
|
12 |
+
|
13 |
+
|
14 |
+
class EventDispatcher:
|
15 |
+
|
16 |
+
|
17 |
+
def __init__(self, outstream):
|
18 |
+
|
19 |
+
"""
|
20 |
+
|
21 |
+
The event dispatcher generates events on the outstream.
|
22 |
+
|
23 |
+
"""
|
24 |
+
|
25 |
+
# internal values, don't mess with 'em directly
|
26 |
+
self.outstream = outstream
|
27 |
+
|
28 |
+
# public flags
|
29 |
+
|
30 |
+
# A note_on with a velocity of 0x00 is actually the same as a
|
31 |
+
# note_off with a velocity of 0x40. When
|
32 |
+
# "convert_zero_velocity" is set, the zero velocity note_on's
|
33 |
+
# automatically gets converted into note_off's. This is a less
|
34 |
+
# suprising behaviour for those that are not into the intimate
|
35 |
+
# details of the midi spec.
|
36 |
+
self.convert_zero_velocity = 1
|
37 |
+
|
38 |
+
# If dispatch_continuos_controllers is true, continuos
|
39 |
+
# controllers gets dispatched to their defined handlers. Else
|
40 |
+
# they just trigger the "continuous_controller" event handler.
|
41 |
+
self.dispatch_continuos_controllers = 1 # NOT IMPLEMENTED YET
|
42 |
+
|
43 |
+
# If dispatch_meta_events is true, meta events get's dispatched
|
44 |
+
# to their defined events. Else they all they trigger the
|
45 |
+
# "meta_event" handler.
|
46 |
+
self.dispatch_meta_events = 1
|
47 |
+
|
48 |
+
|
49 |
+
|
50 |
+
def header(self, format, nTracks, division):
|
51 |
+
"Triggers the header event"
|
52 |
+
self.outstream.header(format, nTracks, division)
|
53 |
+
|
54 |
+
|
55 |
+
def start_of_track(self, current_track):
|
56 |
+
"Triggers the start of track event"
|
57 |
+
|
58 |
+
# I do this twice so that users can overwrite the
|
59 |
+
# start_of_track event handler without worrying whether the
|
60 |
+
# track number is updated correctly.
|
61 |
+
self.outstream.set_current_track(current_track)
|
62 |
+
self.outstream.start_of_track(current_track)
|
63 |
+
|
64 |
+
|
65 |
+
def sysex_event(self, data):
|
66 |
+
"Dispatcher for sysex events"
|
67 |
+
self.outstream.sysex_event(data)
|
68 |
+
|
69 |
+
|
70 |
+
def eof(self):
|
71 |
+
"End of file!"
|
72 |
+
self.outstream.eof()
|
73 |
+
|
74 |
+
|
75 |
+
def update_time(self, new_time=0, relative=1):
|
76 |
+
"Updates relative/absolute time."
|
77 |
+
self.outstream.update_time(new_time, relative)
|
78 |
+
|
79 |
+
|
80 |
+
def reset_time(self):
|
81 |
+
"Updates relative/absolute time."
|
82 |
+
self.outstream.reset_time()
|
83 |
+
|
84 |
+
|
85 |
+
# Event dispatchers for similar types of events
|
86 |
+
|
87 |
+
|
88 |
+
def channel_messages(self, hi_nible, channel, data):
|
89 |
+
|
90 |
+
"Dispatches channel messages"
|
91 |
+
|
92 |
+
stream = self.outstream
|
93 |
+
data = toBytes(data)
|
94 |
+
|
95 |
+
if (NOTE_ON & 0xF0) == hi_nible:
|
96 |
+
note, velocity = data
|
97 |
+
# note_on with velocity 0x00 are same as note
|
98 |
+
# off with velocity 0x40 according to spec!
|
99 |
+
if velocity==0 and self.convert_zero_velocity:
|
100 |
+
stream.note_off(channel, note, 0x40)
|
101 |
+
else:
|
102 |
+
stream.note_on(channel, note, velocity)
|
103 |
+
|
104 |
+
elif (NOTE_OFF & 0xF0) == hi_nible:
|
105 |
+
note, velocity = data
|
106 |
+
stream.note_off(channel, note, velocity)
|
107 |
+
|
108 |
+
elif (AFTERTOUCH & 0xF0) == hi_nible:
|
109 |
+
note, velocity = data
|
110 |
+
stream.aftertouch(channel, note, velocity)
|
111 |
+
|
112 |
+
elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible:
|
113 |
+
controller, value = data
|
114 |
+
# A lot of the cc's are defined, so we trigger those directly
|
115 |
+
if self.dispatch_continuos_controllers:
|
116 |
+
self.continuous_controllers(channel, controller, value)
|
117 |
+
else:
|
118 |
+
stream.continuous_controller(channel, controller, value)
|
119 |
+
|
120 |
+
elif (PATCH_CHANGE & 0xF0) == hi_nible:
|
121 |
+
program = data[0]
|
122 |
+
stream.patch_change(channel, program)
|
123 |
+
|
124 |
+
elif (CHANNEL_PRESSURE & 0xF0) == hi_nible:
|
125 |
+
pressure = data[0]
|
126 |
+
stream.channel_pressure(channel, pressure)
|
127 |
+
|
128 |
+
elif (PITCH_BEND & 0xF0) == hi_nible:
|
129 |
+
hibyte, lobyte = data
|
130 |
+
value = (hibyte<<7) + lobyte
|
131 |
+
stream.pitch_bend(channel, value)
|
132 |
+
|
133 |
+
else:
|
134 |
+
|
135 |
+
raise ValueError('Illegal channel message!')
|
136 |
+
|
137 |
+
|
138 |
+
|
139 |
+
def continuous_controllers(self, channel, controller, value):
|
140 |
+
|
141 |
+
"Dispatches channel messages"
|
142 |
+
|
143 |
+
stream = self.outstream
|
144 |
+
|
145 |
+
# I am not really shure if I ought to dispatch continuous controllers
|
146 |
+
# There's so many of them that it can clutter up the OutStream
|
147 |
+
# classes.
|
148 |
+
|
149 |
+
# So I just trigger the default event handler
|
150 |
+
stream.continuous_controller(channel, controller, value)
|
151 |
+
|
152 |
+
|
153 |
+
|
154 |
+
def system_commons(self, common_type, common_data):
|
155 |
+
|
156 |
+
"Dispatches system common messages"
|
157 |
+
|
158 |
+
stream = self.outstream
|
159 |
+
|
160 |
+
# MTC Midi time code Quarter value
|
161 |
+
if common_type == MTC:
|
162 |
+
data = readBew(common_data)
|
163 |
+
msg_type = (data & 0x07) >> 4
|
164 |
+
values = (data & 0x0F)
|
165 |
+
stream.midi_time_code(msg_type, values)
|
166 |
+
|
167 |
+
elif common_type == SONG_POSITION_POINTER:
|
168 |
+
hibyte, lobyte = toBytes(common_data)
|
169 |
+
value = (hibyte<<7) + lobyte
|
170 |
+
stream.song_position_pointer(value)
|
171 |
+
|
172 |
+
elif common_type == SONG_SELECT:
|
173 |
+
data = readBew(common_data)
|
174 |
+
stream.song_select(data)
|
175 |
+
|
176 |
+
elif common_type == TUNING_REQUEST:
|
177 |
+
# no data then
|
178 |
+
stream.tuning_request(time=None)
|
179 |
+
|
180 |
+
|
181 |
+
|
182 |
+
def meta_event(self, meta_type, data):
|
183 |
+
|
184 |
+
"Dispatches meta events"
|
185 |
+
|
186 |
+
stream = self.outstream
|
187 |
+
|
188 |
+
# SEQUENCE_NUMBER = 0x00 (00 02 ss ss (seq-number))
|
189 |
+
if meta_type == SEQUENCE_NUMBER:
|
190 |
+
number = readBew(data)
|
191 |
+
stream.sequence_number(number)
|
192 |
+
|
193 |
+
# TEXT = 0x01 (01 len text...)
|
194 |
+
elif meta_type == TEXT:
|
195 |
+
stream.text(data)
|
196 |
+
|
197 |
+
# COPYRIGHT = 0x02 (02 len text...)
|
198 |
+
elif meta_type == COPYRIGHT:
|
199 |
+
stream.copyright(data)
|
200 |
+
|
201 |
+
# SEQUENCE_NAME = 0x03 (03 len text...)
|
202 |
+
elif meta_type == SEQUENCE_NAME:
|
203 |
+
stream.sequence_name(data)
|
204 |
+
|
205 |
+
# INSTRUMENT_NAME = 0x04 (04 len text...)
|
206 |
+
elif meta_type == INSTRUMENT_NAME:
|
207 |
+
stream.instrument_name(data)
|
208 |
+
|
209 |
+
# LYRIC = 0x05 (05 len text...)
|
210 |
+
elif meta_type == LYRIC:
|
211 |
+
stream.lyric(data)
|
212 |
+
|
213 |
+
# MARKER = 0x06 (06 len text...)
|
214 |
+
elif meta_type == MARKER:
|
215 |
+
stream.marker(data)
|
216 |
+
|
217 |
+
# CUEPOINT = 0x07 (07 len text...)
|
218 |
+
elif meta_type == CUEPOINT:
|
219 |
+
stream.cuepoint(data)
|
220 |
+
|
221 |
+
# PROGRAM_NAME = 0x08 (05 len text...)
|
222 |
+
elif meta_type == PROGRAM_NAME:
|
223 |
+
stream.program_name(data)
|
224 |
+
|
225 |
+
# DEVICE_NAME = 0x09 (09 len text...)
|
226 |
+
elif meta_type == DEVICE_NAME:
|
227 |
+
try: #p08 patch
|
228 |
+
stream.device_name(data)
|
229 |
+
except:
|
230 |
+
pass
|
231 |
+
# MIDI_CH_PREFIX = 0x20 (20 01 channel)
|
232 |
+
elif meta_type == MIDI_CH_PREFIX:
|
233 |
+
channel = readBew(data)
|
234 |
+
stream.midi_ch_prefix(channel)
|
235 |
+
|
236 |
+
# MIDI_PORT = 0x21 (21 01 port (legacy stuff))
|
237 |
+
elif meta_type == MIDI_PORT:
|
238 |
+
port = readBew(data)
|
239 |
+
stream.midi_port(port)
|
240 |
+
|
241 |
+
# END_OFF_TRACK = 0x2F (2F 00)
|
242 |
+
elif meta_type == END_OF_TRACK:
|
243 |
+
stream.end_of_track()
|
244 |
+
|
245 |
+
# TEMPO = 0x51 (51 03 tt tt tt (tempo in us/quarternote))
|
246 |
+
elif meta_type == TEMPO:
|
247 |
+
b1, b2, b3 = toBytes(data)
|
248 |
+
# uses 3 bytes to represent time between quarter
|
249 |
+
# notes in microseconds
|
250 |
+
stream.tempo((b1<<16) + (b2<<8) + b3)
|
251 |
+
|
252 |
+
# SMTP_OFFSET = 0x54 (54 05 hh mm ss ff xx)
|
253 |
+
elif meta_type == SMTP_OFFSET:
|
254 |
+
hour, minute, second, frame, framePart = toBytes(data)
|
255 |
+
stream.smtp_offset(
|
256 |
+
hour, minute, second, frame, framePart)
|
257 |
+
|
258 |
+
# TIME_SIGNATURE = 0x58 (58 04 nn dd cc bb)
|
259 |
+
elif meta_type == TIME_SIGNATURE:
|
260 |
+
nn, dd, cc, bb = toBytes(data)
|
261 |
+
stream.time_signature(nn, dd, cc, bb)
|
262 |
+
|
263 |
+
# KEY_SIGNATURE = 0x59 (59 02 sf mi)
|
264 |
+
elif meta_type == KEY_SIGNATURE:
|
265 |
+
sf, mi = toBytes(data)
|
266 |
+
stream.key_signature(sf, mi)
|
267 |
+
|
268 |
+
# SPECIFIC = 0x7F (Sequencer specific event)
|
269 |
+
elif meta_type == SPECIFIC:
|
270 |
+
meta_data = toBytes(data)
|
271 |
+
stream.sequencer_specific(meta_data)
|
272 |
+
|
273 |
+
# Handles any undefined meta events
|
274 |
+
else: # undefined meta type
|
275 |
+
meta_data = toBytes(data)
|
276 |
+
stream.meta_event(meta_type, meta_data)
|
277 |
+
|
278 |
+
|
279 |
+
|
280 |
+
|
281 |
+
|
282 |
+
if __name__ == '__main__':
|
283 |
+
|
284 |
+
|
285 |
+
from MidiToText import MidiToText
|
286 |
+
|
287 |
+
outstream = MidiToText()
|
288 |
+
dispatcher = EventDispatcher(outstream)
|
289 |
+
dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')
|
midi/MidiFileParser.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
# std library
|
4 |
+
from struct import unpack
|
5 |
+
|
6 |
+
# uhh I don't really like this, but there are so many constants to
|
7 |
+
# import otherwise
|
8 |
+
from .constants import *
|
9 |
+
|
10 |
+
from .EventDispatcher import EventDispatcher
|
11 |
+
|
12 |
+
class MidiFileParser:
|
13 |
+
|
14 |
+
"""
|
15 |
+
|
16 |
+
The MidiFileParser is the lowest level parser that see the data as
|
17 |
+
midi data. It generates events that gets triggered on the outstream.
|
18 |
+
|
19 |
+
"""
|
20 |
+
|
21 |
+
def __init__(self, raw_in, outstream):
|
22 |
+
|
23 |
+
"""
|
24 |
+
raw_data is the raw content of a midi file as a string.
|
25 |
+
"""
|
26 |
+
|
27 |
+
# internal values, don't mess with 'em directly
|
28 |
+
self.raw_in = raw_in
|
29 |
+
self.dispatch = EventDispatcher(outstream)
|
30 |
+
|
31 |
+
# Used to keep track of stuff
|
32 |
+
self._running_status = None
|
33 |
+
|
34 |
+
|
35 |
+
|
36 |
+
|
37 |
+
def parseMThdChunk(self):
|
38 |
+
|
39 |
+
"Parses the header chunk"
|
40 |
+
|
41 |
+
raw_in = self.raw_in
|
42 |
+
|
43 |
+
header_chunk_type = raw_in.nextSlice(4)
|
44 |
+
header_chunk_zise = raw_in.readBew(4)
|
45 |
+
# check if it is a proper midi file
|
46 |
+
if header_chunk_type != b'MThd':
|
47 |
+
raise TypeError("It is not a valid midi file!")
|
48 |
+
|
49 |
+
# Header values are at fixed locations, so no reason to be clever
|
50 |
+
self.format = raw_in.readBew(2)
|
51 |
+
self.nTracks = raw_in.readBew(2)
|
52 |
+
self.division = raw_in.readBew(2)
|
53 |
+
|
54 |
+
# Theoretically a header larger than 6 bytes can exist
|
55 |
+
# but no one has seen one in the wild
|
56 |
+
# But correctly ignore unknown data if it is though
|
57 |
+
if header_chunk_zise > 6:
|
58 |
+
raw_in.moveCursor(header_chunk_zise-6)
|
59 |
+
|
60 |
+
# call the header event handler on the stream
|
61 |
+
self.dispatch.header(self.format, self.nTracks, self.division)
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
def parseMTrkChunk(self):
|
66 |
+
|
67 |
+
"Parses a track chunk. This is the most important part of the parser."
|
68 |
+
|
69 |
+
# set time to 0 at start of a track
|
70 |
+
self.dispatch.reset_time()
|
71 |
+
|
72 |
+
dispatch = self.dispatch
|
73 |
+
raw_in = self.raw_in
|
74 |
+
|
75 |
+
# Trigger event at the start of a track
|
76 |
+
dispatch.start_of_track(self._current_track)
|
77 |
+
# position cursor after track header
|
78 |
+
raw_in.moveCursor(4)
|
79 |
+
# unsigned long is 4 bytes
|
80 |
+
tracklength = raw_in.readBew(4)
|
81 |
+
track_endposition = raw_in.getCursor() + tracklength # absolute position!
|
82 |
+
|
83 |
+
while raw_in.getCursor() < track_endposition:
|
84 |
+
|
85 |
+
# find relative time of the event
|
86 |
+
time = raw_in.readVarLen()
|
87 |
+
dispatch.update_time(time)
|
88 |
+
|
89 |
+
# be aware of running status!!!!
|
90 |
+
peak_ahead = raw_in.readBew(move_cursor=0)
|
91 |
+
if (peak_ahead & 0x80):
|
92 |
+
# the status byte has the high bit set, so it
|
93 |
+
# was not running data but proper status byte
|
94 |
+
status = self._running_status = raw_in.readBew()
|
95 |
+
else:
|
96 |
+
# use that darn running status
|
97 |
+
status = self._running_status
|
98 |
+
# could it be illegal data ?? Do we need to test for that?
|
99 |
+
# I need more example midi files to be shure.
|
100 |
+
|
101 |
+
# Also, while I am almost certain that no realtime
|
102 |
+
# messages will pop up in a midi file, I might need to
|
103 |
+
# change my mind later.
|
104 |
+
|
105 |
+
# we need to look at nibbles here
|
106 |
+
hi_nible, lo_nible = status & 0xF0, status & 0x0F
|
107 |
+
|
108 |
+
# match up with events
|
109 |
+
|
110 |
+
# Is it a meta_event ??
|
111 |
+
# these only exists in midi files, not in transmitted midi data
|
112 |
+
# In transmitted data META_EVENT (0xFF) is a system reset
|
113 |
+
if status == META_EVENT:
|
114 |
+
meta_type = raw_in.readBew()
|
115 |
+
meta_length = raw_in.readVarLen()
|
116 |
+
meta_data = raw_in.nextSlice(meta_length)
|
117 |
+
dispatch.meta_event(meta_type, meta_data)
|
118 |
+
|
119 |
+
|
120 |
+
# Is it a sysex_event ??
|
121 |
+
elif status == SYSTEM_EXCLUSIVE:
|
122 |
+
# ignore sysex events
|
123 |
+
sysex_length = raw_in.readVarLen()
|
124 |
+
# don't read sysex terminator
|
125 |
+
sysex_data = raw_in.nextSlice(sysex_length-1)
|
126 |
+
# only read last data byte if it is a sysex terminator
|
127 |
+
# It should allways be there, but better safe than sorry
|
128 |
+
if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:
|
129 |
+
eo_sysex = raw_in.readBew()
|
130 |
+
dispatch.sysex_event(sysex_data)
|
131 |
+
# the sysex code has not been properly tested, and might be fishy!
|
132 |
+
|
133 |
+
|
134 |
+
# is it a system common event?
|
135 |
+
elif hi_nible == 0xF0: # Hi bits are set then
|
136 |
+
data_sizes = {
|
137 |
+
MTC:1,
|
138 |
+
SONG_POSITION_POINTER:2,
|
139 |
+
SONG_SELECT:1,
|
140 |
+
}
|
141 |
+
data_size = data_sizes.get(hi_nible, 0)
|
142 |
+
common_data = raw_in.nextSlice(data_size)
|
143 |
+
common_type = lo_nible
|
144 |
+
dispatch.system_common(common_type, common_data)
|
145 |
+
|
146 |
+
|
147 |
+
# Oh! Then it must be a midi event (channel voice message)
|
148 |
+
else:
|
149 |
+
data_sizes = {
|
150 |
+
PATCH_CHANGE:1,
|
151 |
+
CHANNEL_PRESSURE:1,
|
152 |
+
NOTE_OFF:2,
|
153 |
+
NOTE_ON:2,
|
154 |
+
AFTERTOUCH:2,
|
155 |
+
CONTINUOUS_CONTROLLER:2,
|
156 |
+
PITCH_BEND:2,
|
157 |
+
}
|
158 |
+
data_size = data_sizes.get(hi_nible, 0)
|
159 |
+
channel_data = raw_in.nextSlice(data_size)
|
160 |
+
event_type, channel = hi_nible, lo_nible
|
161 |
+
dispatch.channel_messages(event_type, channel, channel_data)
|
162 |
+
|
163 |
+
|
164 |
+
def parseMTrkChunks(self):
|
165 |
+
"Parses all track chunks."
|
166 |
+
for t in range(self.nTracks):
|
167 |
+
self._current_track = t
|
168 |
+
self.parseMTrkChunk() # this is where it's at!
|
169 |
+
self.dispatch.eof()
|
170 |
+
|
171 |
+
|
172 |
+
|
173 |
+
if __name__ == '__main__':
|
174 |
+
|
175 |
+
# get data
|
176 |
+
test_file = 'test/midifiles/minimal.mid'
|
177 |
+
test_file = 'test/midifiles/cubase-minimal.mid'
|
178 |
+
test_file = 'test/midifiles/Lola.mid'
|
179 |
+
# f = open(test_file, 'rb')
|
180 |
+
# raw_data = f.read()
|
181 |
+
# f.close()
|
182 |
+
#
|
183 |
+
#
|
184 |
+
# # do parsing
|
185 |
+
from MidiToText import MidiToText
|
186 |
+
from RawInstreamFile import RawInstreamFile
|
187 |
+
|
188 |
+
midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText())
|
189 |
+
midi_in.parseMThdChunk()
|
190 |
+
midi_in.parseMTrkChunks()
|
191 |
+
|
midi/MidiInFile.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
from .RawInstreamFile import RawInstreamFile
|
4 |
+
from .MidiFileParser import MidiFileParser
|
5 |
+
|
6 |
+
|
7 |
+
class MidiInFile:
|
8 |
+
|
9 |
+
"""
|
10 |
+
|
11 |
+
Parses a midi file, and triggers the midi events on the outStream
|
12 |
+
object.
|
13 |
+
|
14 |
+
Get example data from a minimal midi file, generated with cubase.
|
15 |
+
>>> test_file = 'C:/Documents and Settings/maxm/Desktop/temp/midi/src/midi/tests/midifiles/minimal-cubase-type0.mid'
|
16 |
+
|
17 |
+
Do parsing, and generate events with MidiToText,
|
18 |
+
so we can see what a minimal midi file contains
|
19 |
+
>>> from MidiToText import MidiToText
|
20 |
+
>>> midi_in = MidiInFile(MidiToText(), test_file)
|
21 |
+
>>> midi_in.read()
|
22 |
+
format: 0, nTracks: 1, division: 480
|
23 |
+
----------------------------------
|
24 |
+
<BLANKLINE>
|
25 |
+
Start - track #0
|
26 |
+
sequence_name: Type 0
|
27 |
+
tempo: 500000
|
28 |
+
time_signature: 4 2 24 8
|
29 |
+
note_on - ch:00, note:48, vel:64 time:0
|
30 |
+
note_off - ch:00, note:48, vel:40 time:480
|
31 |
+
End of track
|
32 |
+
<BLANKLINE>
|
33 |
+
End of file
|
34 |
+
|
35 |
+
|
36 |
+
"""
|
37 |
+
|
38 |
+
def __init__(self, outStream, infile):
|
39 |
+
# these could also have been mixins, would that be better? Nah!
|
40 |
+
self.raw_in = RawInstreamFile(infile)
|
41 |
+
self.parser = MidiFileParser(self.raw_in, outStream)
|
42 |
+
|
43 |
+
|
44 |
+
def read(self):
|
45 |
+
"Start parsing the file"
|
46 |
+
p = self.parser
|
47 |
+
p.parseMThdChunk()
|
48 |
+
p.parseMTrkChunks()
|
49 |
+
|
50 |
+
|
51 |
+
def setData(self, data=''):
|
52 |
+
"Sets the data from a plain string"
|
53 |
+
self.raw_in.setData(data)
|
54 |
+
|
55 |
+
|
midi/MidiInStream.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
from MidiOutStream import MidiOutStream
|
4 |
+
|
5 |
+
class MidiInStream:
|
6 |
+
|
7 |
+
"""
|
8 |
+
Takes midi events from the midi input and calls the apropriate
|
9 |
+
method in the eventhandler object
|
10 |
+
"""
|
11 |
+
|
12 |
+
def __init__(self, midiOutStream, device):
|
13 |
+
|
14 |
+
"""
|
15 |
+
|
16 |
+
Sets a default output stream, and sets the device from where
|
17 |
+
the input comes
|
18 |
+
|
19 |
+
"""
|
20 |
+
|
21 |
+
if midiOutStream is None:
|
22 |
+
self.midiOutStream = MidiOutStream()
|
23 |
+
else:
|
24 |
+
self.midiOutStream = midiOutStream
|
25 |
+
|
26 |
+
|
27 |
+
def close(self):
|
28 |
+
|
29 |
+
"""
|
30 |
+
Stop the MidiInstream
|
31 |
+
"""
|
32 |
+
|
33 |
+
|
34 |
+
def read(self, time=0):
|
35 |
+
|
36 |
+
"""
|
37 |
+
|
38 |
+
Start the MidiInstream.
|
39 |
+
|
40 |
+
"time" sets timer to specific start value.
|
41 |
+
|
42 |
+
"""
|
43 |
+
|
44 |
+
|
45 |
+
def resetTimer(self, time=0):
|
46 |
+
"""
|
47 |
+
|
48 |
+
Resets the timer, probably a good idea if there is some kind
|
49 |
+
of looping going on
|
50 |
+
|
51 |
+
"""
|
52 |
+
|
midi/MidiOutFile.py
ADDED
@@ -0,0 +1,448 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
from MidiOutStream import MidiOutStream
|
4 |
+
from RawOutstreamFile import RawOutstreamFile
|
5 |
+
|
6 |
+
from constants import *
|
7 |
+
from DataTypeConverters import fromBytes, writeVar
|
8 |
+
|
9 |
+
class MidiOutFile(MidiOutStream):
|
10 |
+
|
11 |
+
|
12 |
+
"""
|
13 |
+
MidiOutFile is an eventhandler that subclasses MidiOutStream.
|
14 |
+
"""
|
15 |
+
|
16 |
+
|
17 |
+
def __init__(self, raw_out=''):
|
18 |
+
|
19 |
+
self.raw_out = RawOutstreamFile(raw_out)
|
20 |
+
MidiOutStream.__init__(self)
|
21 |
+
|
22 |
+
|
23 |
+
def write(self):
|
24 |
+
self.raw_out.write()
|
25 |
+
|
26 |
+
|
27 |
+
def event_slice(self, slc):
|
28 |
+
"""
|
29 |
+
Writes the slice of an event to the current track. Correctly
|
30 |
+
inserting a varlen timestamp too.
|
31 |
+
"""
|
32 |
+
trk = self._current_track_buffer
|
33 |
+
trk.writeVarLen(self.rel_time())
|
34 |
+
trk.writeSlice(slc)
|
35 |
+
|
36 |
+
|
37 |
+
#####################
|
38 |
+
## Midi events
|
39 |
+
|
40 |
+
|
41 |
+
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
42 |
+
|
43 |
+
"""
|
44 |
+
channel: 0-15
|
45 |
+
note, velocity: 0-127
|
46 |
+
"""
|
47 |
+
slc = fromBytes([NOTE_ON + channel, note, velocity])
|
48 |
+
self.event_slice(slc)
|
49 |
+
|
50 |
+
|
51 |
+
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
52 |
+
|
53 |
+
"""
|
54 |
+
channel: 0-15
|
55 |
+
note, velocity: 0-127
|
56 |
+
"""
|
57 |
+
slc = fromBytes([NOTE_OFF + channel, note, velocity])
|
58 |
+
self.event_slice(slc)
|
59 |
+
|
60 |
+
|
61 |
+
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
|
62 |
+
|
63 |
+
"""
|
64 |
+
channel: 0-15
|
65 |
+
note, velocity: 0-127
|
66 |
+
"""
|
67 |
+
slc = fromBytes([AFTERTOUCH + channel, note, velocity])
|
68 |
+
self.event_slice(slc)
|
69 |
+
|
70 |
+
|
71 |
+
def continuous_controller(self, channel, controller, value):
|
72 |
+
|
73 |
+
"""
|
74 |
+
channel: 0-15
|
75 |
+
controller, value: 0-127
|
76 |
+
"""
|
77 |
+
slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])
|
78 |
+
self.event_slice(slc)
|
79 |
+
# These should probably be implemented
|
80 |
+
# http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums
|
81 |
+
|
82 |
+
|
83 |
+
def patch_change(self, channel, patch):
|
84 |
+
|
85 |
+
"""
|
86 |
+
channel: 0-15
|
87 |
+
patch: 0-127
|
88 |
+
"""
|
89 |
+
slc = fromBytes([PATCH_CHANGE + channel, patch])
|
90 |
+
self.event_slice(slc)
|
91 |
+
|
92 |
+
|
93 |
+
def channel_pressure(self, channel, pressure):
|
94 |
+
|
95 |
+
"""
|
96 |
+
channel: 0-15
|
97 |
+
pressure: 0-127
|
98 |
+
"""
|
99 |
+
slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])
|
100 |
+
self.event_slice(slc)
|
101 |
+
|
102 |
+
|
103 |
+
def pitch_bend(self, channel, value):
|
104 |
+
|
105 |
+
"""
|
106 |
+
channel: 0-15
|
107 |
+
value: 0-16383
|
108 |
+
"""
|
109 |
+
msb = (value>>7) & 0xFF
|
110 |
+
lsb = value & 0xFF
|
111 |
+
slc = fromBytes([PITCH_BEND + channel, msb, lsb])
|
112 |
+
self.event_slice(slc)
|
113 |
+
|
114 |
+
|
115 |
+
|
116 |
+
|
117 |
+
#####################
|
118 |
+
## System Exclusive
|
119 |
+
|
120 |
+
# def sysex_slice(sysex_type, data):
|
121 |
+
# ""
|
122 |
+
# sysex_len = writeVar(len(data)+1)
|
123 |
+
# self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE)
|
124 |
+
#
|
125 |
+
def system_exclusive(self, data):
|
126 |
+
|
127 |
+
"""
|
128 |
+
data: list of values in range(128)
|
129 |
+
"""
|
130 |
+
sysex_len = writeVar(len(data)+1)
|
131 |
+
self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))
|
132 |
+
|
133 |
+
|
134 |
+
#####################
|
135 |
+
## Common events
|
136 |
+
|
137 |
+
def midi_time_code(self, msg_type, values):
|
138 |
+
"""
|
139 |
+
msg_type: 0-7
|
140 |
+
values: 0-15
|
141 |
+
"""
|
142 |
+
value = (msg_type<<4) + values
|
143 |
+
self.event_slice(fromBytes([MIDI_TIME_CODE, value]))
|
144 |
+
|
145 |
+
|
146 |
+
def song_position_pointer(self, value):
|
147 |
+
|
148 |
+
"""
|
149 |
+
value: 0-16383
|
150 |
+
"""
|
151 |
+
lsb = (value & 0x7F)
|
152 |
+
msb = (value >> 7) & 0x7F
|
153 |
+
self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))
|
154 |
+
|
155 |
+
|
156 |
+
def song_select(self, songNumber):
|
157 |
+
|
158 |
+
"""
|
159 |
+
songNumber: 0-127
|
160 |
+
"""
|
161 |
+
self.event_slice(fromBytes([SONG_SELECT, songNumber]))
|
162 |
+
|
163 |
+
|
164 |
+
def tuning_request(self):
|
165 |
+
|
166 |
+
"""
|
167 |
+
No values passed
|
168 |
+
"""
|
169 |
+
self.event_slice(chr(TUNING_REQUEST))
|
170 |
+
|
171 |
+
|
172 |
+
#########################
|
173 |
+
# header does not really belong here. But anyhoo!!!
|
174 |
+
|
175 |
+
def header(self, format=0, nTracks=1, division=96):
|
176 |
+
|
177 |
+
"""
|
178 |
+
format: type of midi file in [0,1,2]
|
179 |
+
nTracks: number of tracks. 1 track for type 0 file
|
180 |
+
division: timing division ie. 96 ppq.
|
181 |
+
|
182 |
+
"""
|
183 |
+
raw = self.raw_out
|
184 |
+
raw.writeSlice('MThd')
|
185 |
+
bew = raw.writeBew
|
186 |
+
bew(6, 4) # header size
|
187 |
+
bew(format, 2)
|
188 |
+
bew(nTracks, 2)
|
189 |
+
bew(division, 2)
|
190 |
+
|
191 |
+
|
192 |
+
def eof(self):
|
193 |
+
|
194 |
+
"""
|
195 |
+
End of file. No more events to be processed.
|
196 |
+
"""
|
197 |
+
# just write the file then.
|
198 |
+
self.write()
|
199 |
+
|
200 |
+
|
201 |
+
#####################
|
202 |
+
## meta events
|
203 |
+
|
204 |
+
|
205 |
+
def meta_slice(self, meta_type, data_slice):
|
206 |
+
"Writes a meta event"
|
207 |
+
slc = fromBytes([META_EVENT, meta_type]) + \
|
208 |
+
writeVar(len(data_slice)) + data_slice
|
209 |
+
self.event_slice(slc)
|
210 |
+
|
211 |
+
|
212 |
+
def meta_event(self, meta_type, data):
|
213 |
+
"""
|
214 |
+
Handles any undefined meta events
|
215 |
+
"""
|
216 |
+
self.meta_slice(meta_type, fromBytes(data))
|
217 |
+
|
218 |
+
|
219 |
+
def start_of_track(self, n_track=0):
|
220 |
+
"""
|
221 |
+
n_track: number of track
|
222 |
+
"""
|
223 |
+
self._current_track_buffer = RawOutstreamFile()
|
224 |
+
self.reset_time()
|
225 |
+
self._current_track += 1
|
226 |
+
|
227 |
+
|
228 |
+
def end_of_track(self):
|
229 |
+
"""
|
230 |
+
Writes the track to the buffer.
|
231 |
+
"""
|
232 |
+
raw = self.raw_out
|
233 |
+
raw.writeSlice(TRACK_HEADER)
|
234 |
+
track_data = self._current_track_buffer.getvalue()
|
235 |
+
# wee need to know size of track data.
|
236 |
+
eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])
|
237 |
+
raw.writeBew(len(track_data)+len(eot_slice), 4)
|
238 |
+
# then write
|
239 |
+
raw.writeSlice(track_data)
|
240 |
+
raw.writeSlice(eot_slice)
|
241 |
+
|
242 |
+
|
243 |
+
|
244 |
+
def sequence_number(self, value):
|
245 |
+
|
246 |
+
"""
|
247 |
+
value: 0-65535
|
248 |
+
"""
|
249 |
+
self.meta_slice(meta_type, writeBew(value, 2))
|
250 |
+
|
251 |
+
|
252 |
+
def text(self, text):
|
253 |
+
"""
|
254 |
+
Text event
|
255 |
+
text: string
|
256 |
+
"""
|
257 |
+
self.meta_slice(TEXT, text)
|
258 |
+
|
259 |
+
|
260 |
+
def copyright(self, text):
|
261 |
+
|
262 |
+
"""
|
263 |
+
Copyright notice
|
264 |
+
text: string
|
265 |
+
"""
|
266 |
+
self.meta_slice(COPYRIGHT, text)
|
267 |
+
|
268 |
+
|
269 |
+
def sequence_name(self, text):
|
270 |
+
"""
|
271 |
+
Sequence/track name
|
272 |
+
text: string
|
273 |
+
"""
|
274 |
+
self.meta_slice(SEQUENCE_NAME, text)
|
275 |
+
|
276 |
+
|
277 |
+
def instrument_name(self, text):
|
278 |
+
|
279 |
+
"""
|
280 |
+
text: string
|
281 |
+
"""
|
282 |
+
self.meta_slice(INSTRUMENT_NAME, text)
|
283 |
+
|
284 |
+
|
285 |
+
def lyric(self, text):
|
286 |
+
|
287 |
+
"""
|
288 |
+
text: string
|
289 |
+
"""
|
290 |
+
self.meta_slice(LYRIC, text)
|
291 |
+
|
292 |
+
|
293 |
+
def marker(self, text):
|
294 |
+
|
295 |
+
"""
|
296 |
+
text: string
|
297 |
+
"""
|
298 |
+
self.meta_slice(MARKER, text)
|
299 |
+
|
300 |
+
|
301 |
+
def cuepoint(self, text):
|
302 |
+
|
303 |
+
"""
|
304 |
+
text: string
|
305 |
+
"""
|
306 |
+
self.meta_slice(CUEPOINT, text)
|
307 |
+
|
308 |
+
|
309 |
+
def midi_ch_prefix(self, channel):
|
310 |
+
|
311 |
+
"""
|
312 |
+
channel: midi channel for subsequent data
|
313 |
+
(deprecated in the spec)
|
314 |
+
"""
|
315 |
+
self.meta_slice(MIDI_CH_PREFIX, chr(channel))
|
316 |
+
|
317 |
+
|
318 |
+
def midi_port(self, value):
|
319 |
+
|
320 |
+
"""
|
321 |
+
value: Midi port (deprecated in the spec)
|
322 |
+
"""
|
323 |
+
self.meta_slice(MIDI_CH_PREFIX, chr(value))
|
324 |
+
|
325 |
+
|
326 |
+
def tempo(self, value):
|
327 |
+
|
328 |
+
"""
|
329 |
+
value: 0-2097151
|
330 |
+
tempo in us/quarternote
|
331 |
+
(to calculate value from bpm: int(60,000,000.00 / BPM))
|
332 |
+
"""
|
333 |
+
hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)
|
334 |
+
self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))
|
335 |
+
|
336 |
+
|
337 |
+
def smtp_offset(self, hour, minute, second, frame, framePart):
|
338 |
+
|
339 |
+
"""
|
340 |
+
hour,
|
341 |
+
minute,
|
342 |
+
second: 3 bytes specifying the hour (0-23), minutes (0-59) and
|
343 |
+
seconds (0-59), respectively. The hour should be
|
344 |
+
encoded with the SMPTE format, just as it is in MIDI
|
345 |
+
Time Code.
|
346 |
+
frame: A byte specifying the number of frames per second (one
|
347 |
+
of : 24, 25, 29, 30).
|
348 |
+
framePart: A byte specifying the number of fractional frames,
|
349 |
+
in 100ths of a frame (even in SMPTE-based tracks
|
350 |
+
using a different frame subdivision, defined in the
|
351 |
+
MThd chunk).
|
352 |
+
"""
|
353 |
+
self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))
|
354 |
+
|
355 |
+
|
356 |
+
|
357 |
+
def time_signature(self, nn, dd, cc, bb):
|
358 |
+
|
359 |
+
"""
|
360 |
+
nn: Numerator of the signature as notated on sheet music
|
361 |
+
dd: Denominator of the signature as notated on sheet music
|
362 |
+
The denominator is a negative power of 2: 2 = quarter
|
363 |
+
note, 3 = eighth, etc.
|
364 |
+
cc: The number of MIDI clocks in a metronome click
|
365 |
+
bb: The number of notated 32nd notes in a MIDI quarter note
|
366 |
+
(24 MIDI clocks)
|
367 |
+
"""
|
368 |
+
self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))
|
369 |
+
|
370 |
+
|
371 |
+
|
372 |
+
|
373 |
+
def key_signature(self, sf, mi):
|
374 |
+
|
375 |
+
"""
|
376 |
+
sf: is a byte specifying the number of flats (-ve) or sharps
|
377 |
+
(+ve) that identifies the key signature (-7 = 7 flats, -1
|
378 |
+
= 1 flat, 0 = key of C, 1 = 1 sharp, etc).
|
379 |
+
mi: is a byte specifying a major (0) or minor (1) key.
|
380 |
+
"""
|
381 |
+
self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))
|
382 |
+
|
383 |
+
|
384 |
+
|
385 |
+
def sequencer_specific(self, data):
|
386 |
+
|
387 |
+
"""
|
388 |
+
data: The data as byte values
|
389 |
+
"""
|
390 |
+
self.meta_slice(SEQUENCER_SPECIFIC, data)
|
391 |
+
|
392 |
+
|
393 |
+
|
394 |
+
|
395 |
+
|
396 |
+
# #####################
|
397 |
+
# ## realtime events
|
398 |
+
|
399 |
+
# These are of no use in a midi file, so they are ignored!!!
|
400 |
+
|
401 |
+
# def timing_clock(self):
|
402 |
+
# def song_start(self):
|
403 |
+
# def song_stop(self):
|
404 |
+
# def song_continue(self):
|
405 |
+
# def active_sensing(self):
|
406 |
+
# def system_reset(self):
|
407 |
+
|
408 |
+
|
409 |
+
|
410 |
+
if __name__ == '__main__':
|
411 |
+
|
412 |
+
out_file = 'test/midifiles/midiout.mid'
|
413 |
+
midi = MidiOutFile(out_file)
|
414 |
+
|
415 |
+
#format: 0, nTracks: 1, division: 480
|
416 |
+
#----------------------------------
|
417 |
+
#
|
418 |
+
#Start - track #0
|
419 |
+
#sequence_name: Type 0
|
420 |
+
#tempo: 500000
|
421 |
+
#time_signature: 4 2 24 8
|
422 |
+
#note_on - ch:00, note:48, vel:64 time:0
|
423 |
+
#note_off - ch:00, note:48, vel:40 time:480
|
424 |
+
#End of track
|
425 |
+
#
|
426 |
+
#End of file
|
427 |
+
|
428 |
+
|
429 |
+
midi.header(0, 1, 480)
|
430 |
+
|
431 |
+
midi.start_of_track()
|
432 |
+
midi.sequence_name('Type 0')
|
433 |
+
midi.tempo(750000)
|
434 |
+
midi.time_signature(4, 2, 24, 8)
|
435 |
+
ch = 0
|
436 |
+
for i in range(127):
|
437 |
+
midi.note_on(ch, i, 0x64)
|
438 |
+
midi.update_time(96)
|
439 |
+
midi.note_off(ch, i, 0x40)
|
440 |
+
midi.update_time(0)
|
441 |
+
|
442 |
+
midi.update_time(0)
|
443 |
+
midi.end_of_track()
|
444 |
+
|
445 |
+
midi.eof() # currently optional, should it do the write instead of write??
|
446 |
+
|
447 |
+
|
448 |
+
midi.write()
|
midi/MidiOutStream.py
ADDED
@@ -0,0 +1,471 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
class MidiOutStream:
|
4 |
+
|
5 |
+
|
6 |
+
"""
|
7 |
+
|
8 |
+
MidiOutstream is Basically an eventhandler. It is the most central
|
9 |
+
class in the Midi library. You use it both for writing events to
|
10 |
+
an output stream, and as an event handler for an input stream.
|
11 |
+
|
12 |
+
This makes it extremely easy to take input from one stream and
|
13 |
+
send it to another. Ie. if you want to read a Midi file, do some
|
14 |
+
processing, and send it to a midiport.
|
15 |
+
|
16 |
+
All time values are in absolute values from the opening of a
|
17 |
+
stream. To calculate time values, please use the MidiTime and
|
18 |
+
MidiDeltaTime classes.
|
19 |
+
|
20 |
+
"""
|
21 |
+
|
22 |
+
def __init__(self):
|
23 |
+
|
24 |
+
# the time is rather global, so it needs to be stored
|
25 |
+
# here. Otherwise there would be no really simple way to
|
26 |
+
# calculate it. The alternative would be to have each event
|
27 |
+
# handler do it. That sucks even worse!
|
28 |
+
self._absolute_time = 0
|
29 |
+
self._relative_time = 0
|
30 |
+
self._current_track = 0
|
31 |
+
self._running_status = None
|
32 |
+
|
33 |
+
# time handling event handlers. They should be overwritten with care
|
34 |
+
|
35 |
+
def update_time(self, new_time=0, relative=1):
|
36 |
+
"""
|
37 |
+
Updates the time, if relative is true, new_time is relative,
|
38 |
+
else it's absolute.
|
39 |
+
"""
|
40 |
+
if relative:
|
41 |
+
self._relative_time = new_time
|
42 |
+
self._absolute_time += new_time
|
43 |
+
else:
|
44 |
+
self._relative_time = new_time - self._absolute_time
|
45 |
+
self._absolute_time = new_time
|
46 |
+
|
47 |
+
def reset_time(self):
|
48 |
+
"""
|
49 |
+
reset time to 0
|
50 |
+
"""
|
51 |
+
self._relative_time = 0
|
52 |
+
self._absolute_time = 0
|
53 |
+
|
54 |
+
def rel_time(self):
|
55 |
+
"Returns the relative time"
|
56 |
+
return self._relative_time
|
57 |
+
|
58 |
+
def abs_time(self):
|
59 |
+
"Returns the absolute time"
|
60 |
+
return self._absolute_time
|
61 |
+
|
62 |
+
# running status methods
|
63 |
+
|
64 |
+
def reset_run_stat(self):
|
65 |
+
"Invalidates the running status"
|
66 |
+
self._running_status = None
|
67 |
+
|
68 |
+
def set_run_stat(self, new_status):
|
69 |
+
"Set the new running status"
|
70 |
+
self._running_status = new_status
|
71 |
+
|
72 |
+
def get_run_stat(self):
|
73 |
+
"Set the new running status"
|
74 |
+
return self._running_status
|
75 |
+
|
76 |
+
# track handling event handlers
|
77 |
+
|
78 |
+
def set_current_track(self, new_track):
|
79 |
+
"Sets the current track number"
|
80 |
+
self._current_track = new_track
|
81 |
+
|
82 |
+
def get_current_track(self):
|
83 |
+
"Returns the current track number"
|
84 |
+
return self._current_track
|
85 |
+
|
86 |
+
|
87 |
+
#####################
|
88 |
+
## Midi events
|
89 |
+
|
90 |
+
|
91 |
+
def channel_message(self, message_type, channel, data):
|
92 |
+
"""The default event handler for channel messages"""
|
93 |
+
pass
|
94 |
+
|
95 |
+
|
96 |
+
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
97 |
+
|
98 |
+
"""
|
99 |
+
channel: 0-15
|
100 |
+
note, velocity: 0-127
|
101 |
+
"""
|
102 |
+
pass
|
103 |
+
|
104 |
+
|
105 |
+
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
106 |
+
|
107 |
+
"""
|
108 |
+
channel: 0-15
|
109 |
+
note, velocity: 0-127
|
110 |
+
"""
|
111 |
+
pass
|
112 |
+
|
113 |
+
|
114 |
+
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
|
115 |
+
|
116 |
+
"""
|
117 |
+
channel: 0-15
|
118 |
+
note, velocity: 0-127
|
119 |
+
"""
|
120 |
+
pass
|
121 |
+
|
122 |
+
|
123 |
+
def continuous_controller(self, channel, controller, value):
|
124 |
+
|
125 |
+
"""
|
126 |
+
channel: 0-15
|
127 |
+
controller, value: 0-127
|
128 |
+
"""
|
129 |
+
pass
|
130 |
+
|
131 |
+
|
132 |
+
def patch_change(self, channel, patch):
|
133 |
+
|
134 |
+
"""
|
135 |
+
channel: 0-15
|
136 |
+
patch: 0-127
|
137 |
+
"""
|
138 |
+
pass
|
139 |
+
|
140 |
+
|
141 |
+
def channel_pressure(self, channel, pressure):
|
142 |
+
|
143 |
+
"""
|
144 |
+
channel: 0-15
|
145 |
+
pressure: 0-127
|
146 |
+
"""
|
147 |
+
pass
|
148 |
+
|
149 |
+
|
150 |
+
def pitch_bend(self, channel, value):
|
151 |
+
|
152 |
+
"""
|
153 |
+
channel: 0-15
|
154 |
+
value: 0-16383
|
155 |
+
|
156 |
+
"""
|
157 |
+
pass
|
158 |
+
|
159 |
+
|
160 |
+
|
161 |
+
|
162 |
+
#####################
|
163 |
+
## System Exclusive
|
164 |
+
|
165 |
+
def system_exclusive(self, data):
|
166 |
+
|
167 |
+
"""
|
168 |
+
data: list of values in range(128)
|
169 |
+
"""
|
170 |
+
pass
|
171 |
+
|
172 |
+
|
173 |
+
#####################
|
174 |
+
## Common events
|
175 |
+
|
176 |
+
def song_position_pointer(self, value):
|
177 |
+
|
178 |
+
"""
|
179 |
+
value: 0-16383
|
180 |
+
"""
|
181 |
+
pass
|
182 |
+
|
183 |
+
|
184 |
+
def song_select(self, songNumber):
|
185 |
+
|
186 |
+
"""
|
187 |
+
songNumber: 0-127
|
188 |
+
"""
|
189 |
+
pass
|
190 |
+
|
191 |
+
|
192 |
+
def tuning_request(self):
|
193 |
+
|
194 |
+
"""
|
195 |
+
No values passed
|
196 |
+
"""
|
197 |
+
pass
|
198 |
+
|
199 |
+
|
200 |
+
def midi_time_code(self, msg_type, values):
|
201 |
+
"""
|
202 |
+
msg_type: 0-7
|
203 |
+
values: 0-15
|
204 |
+
"""
|
205 |
+
pass
|
206 |
+
|
207 |
+
|
208 |
+
#########################
|
209 |
+
# header does not really belong here. But anyhoo!!!
|
210 |
+
|
211 |
+
def header(self, format=0, nTracks=1, division=96):
|
212 |
+
|
213 |
+
"""
|
214 |
+
format: type of midi file in [1,2]
|
215 |
+
nTracks: number of tracks
|
216 |
+
division: timing division
|
217 |
+
"""
|
218 |
+
pass
|
219 |
+
|
220 |
+
|
221 |
+
def eof(self):
|
222 |
+
|
223 |
+
"""
|
224 |
+
End of file. No more events to be processed.
|
225 |
+
"""
|
226 |
+
pass
|
227 |
+
|
228 |
+
|
229 |
+
#####################
|
230 |
+
## meta events
|
231 |
+
|
232 |
+
|
233 |
+
def meta_event(self, meta_type, data):
|
234 |
+
|
235 |
+
"""
|
236 |
+
Handles any undefined meta events
|
237 |
+
"""
|
238 |
+
pass
|
239 |
+
|
240 |
+
|
241 |
+
def start_of_track(self, n_track=0):
|
242 |
+
|
243 |
+
"""
|
244 |
+
n_track: number of track
|
245 |
+
"""
|
246 |
+
pass
|
247 |
+
|
248 |
+
|
249 |
+
def end_of_track(self):
|
250 |
+
|
251 |
+
"""
|
252 |
+
n_track: number of track
|
253 |
+
"""
|
254 |
+
pass
|
255 |
+
|
256 |
+
|
257 |
+
def sequence_number(self, value):
|
258 |
+
|
259 |
+
"""
|
260 |
+
value: 0-16383
|
261 |
+
"""
|
262 |
+
pass
|
263 |
+
|
264 |
+
|
265 |
+
def text(self, text):
|
266 |
+
|
267 |
+
"""
|
268 |
+
Text event
|
269 |
+
text: string
|
270 |
+
"""
|
271 |
+
pass
|
272 |
+
|
273 |
+
|
274 |
+
def copyright(self, text):
|
275 |
+
|
276 |
+
"""
|
277 |
+
Copyright notice
|
278 |
+
text: string
|
279 |
+
"""
|
280 |
+
pass
|
281 |
+
|
282 |
+
|
283 |
+
def sequence_name(self, text):
|
284 |
+
|
285 |
+
"""
|
286 |
+
Sequence/track name
|
287 |
+
text: string
|
288 |
+
"""
|
289 |
+
pass
|
290 |
+
|
291 |
+
|
292 |
+
def instrument_name(self, text):
|
293 |
+
|
294 |
+
"""
|
295 |
+
text: string
|
296 |
+
"""
|
297 |
+
pass
|
298 |
+
|
299 |
+
|
300 |
+
def lyric(self, text):
|
301 |
+
|
302 |
+
"""
|
303 |
+
text: string
|
304 |
+
"""
|
305 |
+
pass
|
306 |
+
|
307 |
+
|
308 |
+
def marker(self, text):
|
309 |
+
|
310 |
+
"""
|
311 |
+
text: string
|
312 |
+
"""
|
313 |
+
pass
|
314 |
+
|
315 |
+
|
316 |
+
def cuepoint(self, text):
|
317 |
+
|
318 |
+
"""
|
319 |
+
text: string
|
320 |
+
"""
|
321 |
+
pass
|
322 |
+
|
323 |
+
|
324 |
+
def midi_ch_prefix(self, channel):
|
325 |
+
|
326 |
+
"""
|
327 |
+
channel: midi channel for subsequent data (deprecated in the spec)
|
328 |
+
"""
|
329 |
+
pass
|
330 |
+
|
331 |
+
|
332 |
+
def midi_port(self, value):
|
333 |
+
|
334 |
+
"""
|
335 |
+
value: Midi port (deprecated in the spec)
|
336 |
+
"""
|
337 |
+
pass
|
338 |
+
|
339 |
+
|
340 |
+
def tempo(self, value):
|
341 |
+
|
342 |
+
"""
|
343 |
+
value: 0-2097151
|
344 |
+
tempo in us/quarternote
|
345 |
+
(to calculate value from bpm: int(60,000,000.00 / BPM))
|
346 |
+
"""
|
347 |
+
pass
|
348 |
+
|
349 |
+
|
350 |
+
def smtp_offset(self, hour, minute, second, frame, framePart):
|
351 |
+
|
352 |
+
"""
|
353 |
+
hour,
|
354 |
+
minute,
|
355 |
+
second: 3 bytes specifying the hour (0-23), minutes (0-59) and
|
356 |
+
seconds (0-59), respectively. The hour should be
|
357 |
+
encoded with the SMPTE format, just as it is in MIDI
|
358 |
+
Time Code.
|
359 |
+
frame: A byte specifying the number of frames per second (one
|
360 |
+
of : 24, 25, 29, 30).
|
361 |
+
framePart: A byte specifying the number of fractional frames,
|
362 |
+
in 100ths of a frame (even in SMPTE-based tracks
|
363 |
+
using a different frame subdivision, defined in the
|
364 |
+
MThd chunk).
|
365 |
+
"""
|
366 |
+
pass
|
367 |
+
|
368 |
+
|
369 |
+
|
370 |
+
def time_signature(self, nn, dd, cc, bb):
|
371 |
+
|
372 |
+
"""
|
373 |
+
nn: Numerator of the signature as notated on sheet music
|
374 |
+
dd: Denominator of the signature as notated on sheet music
|
375 |
+
The denominator is a negative power of 2: 2 = quarter
|
376 |
+
note, 3 = eighth, etc.
|
377 |
+
cc: The number of MIDI clocks in a metronome click
|
378 |
+
bb: The number of notated 32nd notes in a MIDI quarter note
|
379 |
+
(24 MIDI clocks)
|
380 |
+
"""
|
381 |
+
pass
|
382 |
+
|
383 |
+
|
384 |
+
|
385 |
+
def key_signature(self, sf, mi):
|
386 |
+
|
387 |
+
"""
|
388 |
+
sf: is a byte specifying the number of flats (-ve) or sharps
|
389 |
+
(+ve) that identifies the key signature (-7 = 7 flats, -1
|
390 |
+
= 1 flat, 0 = key of C, 1 = 1 sharp, etc).
|
391 |
+
mi: is a byte specifying a major (0) or minor (1) key.
|
392 |
+
"""
|
393 |
+
pass
|
394 |
+
|
395 |
+
|
396 |
+
|
397 |
+
def sequencer_specific(self, data):
|
398 |
+
|
399 |
+
"""
|
400 |
+
data: The data as byte values
|
401 |
+
"""
|
402 |
+
pass
|
403 |
+
|
404 |
+
|
405 |
+
|
406 |
+
|
407 |
+
#####################
|
408 |
+
## realtime events
|
409 |
+
|
410 |
+
def timing_clock(self):
|
411 |
+
|
412 |
+
"""
|
413 |
+
No values passed
|
414 |
+
"""
|
415 |
+
pass
|
416 |
+
|
417 |
+
|
418 |
+
|
419 |
+
def song_start(self):
|
420 |
+
|
421 |
+
"""
|
422 |
+
No values passed
|
423 |
+
"""
|
424 |
+
pass
|
425 |
+
|
426 |
+
|
427 |
+
|
428 |
+
def song_stop(self):
|
429 |
+
|
430 |
+
"""
|
431 |
+
No values passed
|
432 |
+
"""
|
433 |
+
pass
|
434 |
+
|
435 |
+
|
436 |
+
|
437 |
+
def song_continue(self):
|
438 |
+
|
439 |
+
"""
|
440 |
+
No values passed
|
441 |
+
"""
|
442 |
+
pass
|
443 |
+
|
444 |
+
|
445 |
+
|
446 |
+
def active_sensing(self):
|
447 |
+
|
448 |
+
"""
|
449 |
+
No values passed
|
450 |
+
"""
|
451 |
+
pass
|
452 |
+
|
453 |
+
|
454 |
+
|
455 |
+
def system_reset(self):
|
456 |
+
|
457 |
+
"""
|
458 |
+
No values passed
|
459 |
+
"""
|
460 |
+
pass
|
461 |
+
|
462 |
+
|
463 |
+
|
464 |
+
if __name__ == '__main__':
|
465 |
+
|
466 |
+
midiOut = MidiOutStream()
|
467 |
+
midiOut.update_time(0,0)
|
468 |
+
midiOut.note_on(0, 63, 127)
|
469 |
+
midiOut.note_off(0, 63, 127)
|
470 |
+
|
471 |
+
|
midi/MidiToText.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
from MidiOutStream import MidiOutStream
|
4 |
+
|
5 |
+
|
6 |
+
class MidiToText(MidiOutStream):
|
7 |
+
|
8 |
+
"""
|
9 |
+
This class renders a midi file as text. It is mostly used for debugging
|
10 |
+
"""
|
11 |
+
|
12 |
+
#############################
|
13 |
+
# channel events
|
14 |
+
|
15 |
+
def channel_message(self, message_type, channel, data):
|
16 |
+
"""The default event handler for channel messages"""
|
17 |
+
print('message_type:%X, channel:%X, data size:%X' %
|
18 |
+
(message_type, channel, len(data)))
|
19 |
+
|
20 |
+
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
21 |
+
print('note_on - ch:%02X, note:%02X, vel:%02X time:%s' %
|
22 |
+
(channel, note, velocity, self.rel_time()))
|
23 |
+
|
24 |
+
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
25 |
+
print('note_off - ch:%02X, note:%02X, vel:%02X time:%s' %
|
26 |
+
(channel, note, velocity, self.rel_time()))
|
27 |
+
|
28 |
+
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
|
29 |
+
print('aftertouch', channel, note, velocity)
|
30 |
+
|
31 |
+
def continuous_controller(self, channel, controller, value):
|
32 |
+
print('controller - ch: %02X, cont: #%02X, value: %02X' %
|
33 |
+
(channel, controller, value))
|
34 |
+
|
35 |
+
def patch_change(self, channel, patch):
|
36 |
+
print('patch_change - ch:%02X, patch:%02X' % (channel, patch))
|
37 |
+
|
38 |
+
def channel_pressure(self, channel, pressure):
|
39 |
+
print('channel_pressure', channel, pressure)
|
40 |
+
|
41 |
+
def pitch_bend(self, channel, value):
|
42 |
+
print('pitch_bend ch:%s, value:%s' % (channel, value))
|
43 |
+
|
44 |
+
#####################
|
45 |
+
# Common events
|
46 |
+
|
47 |
+
def system_exclusive(self, data):
|
48 |
+
print('system_exclusive - data size: %s' % len(data))
|
49 |
+
|
50 |
+
def song_position_pointer(self, value):
|
51 |
+
print('song_position_pointer: %s' % value)
|
52 |
+
|
53 |
+
def song_select(self, songNumber):
|
54 |
+
print('song_select: %s' % songNumber)
|
55 |
+
|
56 |
+
def tuning_request(self):
|
57 |
+
print('tuning_request')
|
58 |
+
|
59 |
+
def midi_time_code(self, msg_type, values):
|
60 |
+
print('midi_time_code - msg_type: %s, values: %s' % (msg_type, values))
|
61 |
+
|
62 |
+
#########################
|
63 |
+
# header does not really belong here. But anyhoo!!!
|
64 |
+
|
65 |
+
def header(self, format=0, nTracks=1, division=96):
|
66 |
+
print('format: %s, nTracks: %s, division: %s' %
|
67 |
+
(format, nTracks, division))
|
68 |
+
print('----------------------------------')
|
69 |
+
print('')
|
70 |
+
|
71 |
+
def eof(self):
|
72 |
+
print('End of file')
|
73 |
+
|
74 |
+
def start_of_track(self, n_track=0):
|
75 |
+
print('Start - track #%s' % n_track)
|
76 |
+
|
77 |
+
def end_of_track(self):
|
78 |
+
print('End of track')
|
79 |
+
print('')
|
80 |
+
|
81 |
+
###############
|
82 |
+
# sysex event
|
83 |
+
|
84 |
+
def sysex_event(self, data):
|
85 |
+
print('sysex_event - datasize: %X' % len(data))
|
86 |
+
|
87 |
+
#####################
|
88 |
+
# meta events
|
89 |
+
|
90 |
+
def meta_event(self, meta_type, data):
|
91 |
+
print('undefined_meta_event:', meta_type, len(data))
|
92 |
+
|
93 |
+
def sequence_number(self, value):
|
94 |
+
print('sequence_number', value)
|
95 |
+
|
96 |
+
def text(self, text):
|
97 |
+
print('text', text)
|
98 |
+
|
99 |
+
def copyright(self, text):
|
100 |
+
print('copyright', text)
|
101 |
+
|
102 |
+
def sequence_name(self, text):
|
103 |
+
print('sequence_name:', text)
|
104 |
+
|
105 |
+
def instrument_name(self, text):
|
106 |
+
print('instrument_name:', text)
|
107 |
+
|
108 |
+
def lyric(self, text):
|
109 |
+
print('lyric', text)
|
110 |
+
|
111 |
+
def marker(self, text):
|
112 |
+
print('marker', text)
|
113 |
+
|
114 |
+
def cuepoint(self, text):
|
115 |
+
print('cuepoint', text)
|
116 |
+
|
117 |
+
def midi_ch_prefix(self, channel):
|
118 |
+
print('midi_ch_prefix', channel)
|
119 |
+
|
120 |
+
def midi_port(self, value):
|
121 |
+
print('midi_port:', value)
|
122 |
+
|
123 |
+
def tempo(self, value):
|
124 |
+
print('tempo:', value)
|
125 |
+
|
126 |
+
def smtp_offset(self, hour, minute, second, frame, framePart):
|
127 |
+
print('smtp_offset', hour, minute, second, frame, framePart)
|
128 |
+
|
129 |
+
def time_signature(self, nn, dd, cc, bb):
|
130 |
+
print('time_signature:', nn, dd, cc, bb)
|
131 |
+
|
132 |
+
def key_signature(self, sf, mi):
|
133 |
+
print('key_signature', sf, mi)
|
134 |
+
|
135 |
+
def sequencer_specific(self, data):
|
136 |
+
print('sequencer_specific', len(data))
|
137 |
+
|
138 |
+
|
139 |
+
if __name__ == '__main__':
|
140 |
+
|
141 |
+
# get data
|
142 |
+
test_file = 'test/midifiles/minimal.mid'
|
143 |
+
f = open(test_file, 'rb')
|
144 |
+
|
145 |
+
# do parsing
|
146 |
+
from MidiInFile import MidiInFile
|
147 |
+
midiIn = MidiInFile(MidiToText(), f)
|
148 |
+
midiIn.read()
|
149 |
+
f.close()
|
midi/RawInstreamFile.py
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
import sys
|
4 |
+
PY3 = sys.version_info.major > 2
|
5 |
+
|
6 |
+
if PY3:
|
7 |
+
unichr = chr
|
8 |
+
xrange = range
|
9 |
+
def unicode(s):
|
10 |
+
return s
|
11 |
+
max_int = sys.maxsize
|
12 |
+
basestring = str
|
13 |
+
|
14 |
+
# standard library imports
|
15 |
+
from struct import unpack
|
16 |
+
|
17 |
+
# custom import
|
18 |
+
from .DataTypeConverters import readBew, readVar, varLen
|
19 |
+
|
20 |
+
|
21 |
+
class RawInstreamFile:
|
22 |
+
|
23 |
+
"""
|
24 |
+
|
25 |
+
It parses and reads data from an input file. It takes care of big
|
26 |
+
endianess, and keeps track of the cursor position. The midi parser
|
27 |
+
only reads from this object. Never directly from the file.
|
28 |
+
|
29 |
+
"""
|
30 |
+
|
31 |
+
def __init__(self, infile=''):
|
32 |
+
"""
|
33 |
+
If 'file' is a string we assume it is a path and read from
|
34 |
+
that file.
|
35 |
+
If it is a file descriptor we read from the file, but we don't
|
36 |
+
close it.
|
37 |
+
Midi files are usually pretty small, so it should be safe to
|
38 |
+
copy them into memory.
|
39 |
+
"""
|
40 |
+
if infile:
|
41 |
+
if isinstance(infile, basestring):
|
42 |
+
infile = open(infile, 'rb')
|
43 |
+
self.data = infile.read()
|
44 |
+
infile.close()
|
45 |
+
else:
|
46 |
+
# don't close the f
|
47 |
+
self.data = infile.read()
|
48 |
+
else:
|
49 |
+
self.data = ''
|
50 |
+
# start at beginning ;-)
|
51 |
+
self.cursor = 0
|
52 |
+
|
53 |
+
|
54 |
+
# setting up data manually
|
55 |
+
|
56 |
+
def setData(self, data=''):
|
57 |
+
"Sets the data from a string."
|
58 |
+
self.data = data
|
59 |
+
|
60 |
+
# cursor operations
|
61 |
+
|
62 |
+
def setCursor(self, position=0):
|
63 |
+
"Sets the absolute position if the cursor"
|
64 |
+
self.cursor = position
|
65 |
+
|
66 |
+
|
67 |
+
def getCursor(self):
|
68 |
+
"Returns the value of the cursor"
|
69 |
+
return self.cursor
|
70 |
+
|
71 |
+
|
72 |
+
def moveCursor(self, relative_position=0):
|
73 |
+
"Moves the cursor to a new relative position"
|
74 |
+
self.cursor += relative_position
|
75 |
+
|
76 |
+
# native data reading functions
|
77 |
+
|
78 |
+
def nextSlice(self, length, move_cursor=1):
|
79 |
+
"Reads the next text slice from the raw data, with length"
|
80 |
+
c = self.cursor
|
81 |
+
slc = self.data[c:c+length]
|
82 |
+
if move_cursor:
|
83 |
+
self.moveCursor(length)
|
84 |
+
return slc
|
85 |
+
|
86 |
+
|
87 |
+
def readBew(self, n_bytes=1, move_cursor=1):
|
88 |
+
"""
|
89 |
+
Reads n bytes of date from the current cursor position.
|
90 |
+
Moves cursor if move_cursor is true
|
91 |
+
"""
|
92 |
+
return readBew(self.nextSlice(n_bytes, move_cursor))
|
93 |
+
|
94 |
+
|
95 |
+
def readVarLen(self):
|
96 |
+
"""
|
97 |
+
Reads a variable length value from the current cursor position.
|
98 |
+
Moves cursor if move_cursor is true
|
99 |
+
"""
|
100 |
+
MAX_VARLEN = 4 # Max value varlen can be
|
101 |
+
var = readVar(self.nextSlice(MAX_VARLEN, 0))
|
102 |
+
# only move cursor the actual bytes in varlen
|
103 |
+
self.moveCursor(varLen(var))
|
104 |
+
return var
|
105 |
+
|
106 |
+
|
107 |
+
|
108 |
+
if __name__ == '__main__':
|
109 |
+
|
110 |
+
test_file = 'test/midifiles/minimal.mid'
|
111 |
+
fis = RawInstreamFile(test_file)
|
112 |
+
print(fis.nextSlice(len(fis.data)))
|
113 |
+
|
114 |
+
test_file = 'test/midifiles/cubase-minimal.mid'
|
115 |
+
cubase_minimal = open(test_file, 'rb')
|
116 |
+
fis2 = RawInstreamFile(cubase_minimal)
|
117 |
+
print(fis2.nextSlice(len(fis2.data)))
|
118 |
+
cubase_minimal.close()
|
midi/RawOutstreamFile.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
# standard library imports
|
4 |
+
import sys
|
5 |
+
from types import StringType
|
6 |
+
from struct import unpack
|
7 |
+
from cStringIO import StringIO
|
8 |
+
|
9 |
+
# custom import
|
10 |
+
from DataTypeConverters import writeBew, writeVar, fromBytes
|
11 |
+
|
12 |
+
class RawOutstreamFile:
|
13 |
+
|
14 |
+
"""
|
15 |
+
|
16 |
+
Writes a midi file to disk.
|
17 |
+
|
18 |
+
"""
|
19 |
+
|
20 |
+
def __init__(self, outfile=''):
|
21 |
+
self.buffer = StringIO()
|
22 |
+
self.outfile = outfile
|
23 |
+
|
24 |
+
|
25 |
+
# native data reading functions
|
26 |
+
|
27 |
+
|
28 |
+
def writeSlice(self, str_slice):
|
29 |
+
"Writes the next text slice to the raw data"
|
30 |
+
self.buffer.write(str_slice)
|
31 |
+
|
32 |
+
|
33 |
+
def writeBew(self, value, length=1):
|
34 |
+
"Writes a value to the file as big endian word"
|
35 |
+
self.writeSlice(writeBew(value, length))
|
36 |
+
|
37 |
+
|
38 |
+
def writeVarLen(self, value):
|
39 |
+
"Writes a variable length word to the file"
|
40 |
+
var = self.writeSlice(writeVar(value))
|
41 |
+
|
42 |
+
|
43 |
+
def write(self):
|
44 |
+
"Writes to disc"
|
45 |
+
if self.outfile:
|
46 |
+
if isinstance(self.outfile, StringType):
|
47 |
+
outfile = open(self.outfile, 'wb')
|
48 |
+
outfile.write(self.getvalue())
|
49 |
+
outfile.close()
|
50 |
+
else:
|
51 |
+
self.outfile.write(self.getvalue())
|
52 |
+
else:
|
53 |
+
sys.stdout.write(self.getvalue())
|
54 |
+
|
55 |
+
def getvalue(self):
|
56 |
+
return self.buffer.getvalue()
|
57 |
+
|
58 |
+
|
59 |
+
if __name__ == '__main__':
|
60 |
+
|
61 |
+
out_file = 'test/midifiles/midiout.mid'
|
62 |
+
out_file = ''
|
63 |
+
rawOut = RawOutstreamFile(out_file)
|
64 |
+
rawOut.writeSlice('MThd')
|
65 |
+
rawOut.writeBew(6, 4)
|
66 |
+
rawOut.writeBew(1, 2)
|
67 |
+
rawOut.writeBew(2, 2)
|
68 |
+
rawOut.writeBew(15360, 2)
|
69 |
+
rawOut.write()
|
midi/__init__.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
#import MidiOutStream
|
4 |
+
#import MidiInStream
|
5 |
+
#import MidiInFile
|
6 |
+
#import MidiToText
|
midi/changes.txt
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
------------------------------------------------------------------------
|
2 |
+
r409 | maxm | 2006-01-05 16:37:29 +0100 (to, 05 jan 2006) | 1 line
|
3 |
+
|
4 |
+
Made RawOutstreamFile.py write to std out if no outfile is given.
|
5 |
+
------------------------------------------------------------------------
|
6 |
+
r403 | maxm | 2006-01-05 13:34:11 +0100 (to, 05 jan 2006) | 1 line
|
7 |
+
|
8 |
+
|
9 |
+
------------------------------------------------------------------------
|
10 |
+
r402 | maxm | 2006-01-05 13:33:56 +0100 (to, 05 jan 2006) | 1 line
|
11 |
+
|
12 |
+
- Fixed minor bugs, added coding headers
|
13 |
+
------------------------------------------------------------------------
|
14 |
+
r401 | maxm | 2006-01-01 23:09:17 +0100 (s_, 01 jan 2006) | 1 line
|
15 |
+
|
16 |
+
Fixed sysex dispathcer bug.
|
17 |
+
------------------------------------------------------------------------
|
18 |
+
r268 | maxm | 2005-02-04 12:26:59 +0100 (fr, 04 feb 2005) | 1 line
|
19 |
+
|
20 |
+
|
21 |
+
------------------------------------------------------------------------
|
22 |
+
r128 | maxm | 2004-12-18 14:05:27 +0100 (l_, 18 dec 2004) | 1 line
|
23 |
+
|
24 |
+
Fixed bug when using relative time
|
25 |
+
------------------------------------------------------------------------
|
26 |
+
r15 | maxm | 2004-03-09 15:01:41 +0100 (ti, 09 mar 2004) | 1 line
|
27 |
+
|
28 |
+
made a copy to meta folder
|
29 |
+
------------------------------------------------------------------------
|
30 |
+
r13 | maxm | 2004-03-09 09:17:23 +0100 (ti, 09 mar 2004) | 1 line
|
31 |
+
|
32 |
+
Deleted .pyc files
|
33 |
+
------------------------------------------------------------------------
|
34 |
+
r12 | maxm | 2004-03-09 09:15:54 +0100 (ti, 09 mar 2004) | 1 line
|
35 |
+
|
36 |
+
Removed file/folder
|
37 |
+
------------------------------------------------------------------------
|
38 |
+
r3 | maxm | 2004-03-08 23:16:25 +0100 (ma, 08 mar 2004) | 1 line
|
39 |
+
|
40 |
+
Adde midi
|
41 |
+
------------------------------------------------------------------------
|
42 |
+
r1 | maxm | 2004-03-08 22:49:23 +0100 (ma, 08 mar 2004) | 1 line
|
43 |
+
|
44 |
+
Initial Import
|
45 |
+
------------------------------------------------------------------------
|
midi/constants.py
ADDED
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: ISO-8859-1 -*-
|
2 |
+
|
3 |
+
###################################################
|
4 |
+
## Definitions of the different midi events
|
5 |
+
|
6 |
+
|
7 |
+
|
8 |
+
###################################################
|
9 |
+
## Midi channel events (The most usual events)
|
10 |
+
## also called "Channel Voice Messages"
|
11 |
+
|
12 |
+
NOTE_OFF = 0x80
|
13 |
+
# 1000cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
|
14 |
+
|
15 |
+
NOTE_ON = 0x90
|
16 |
+
# 1001cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
|
17 |
+
|
18 |
+
AFTERTOUCH = 0xA0
|
19 |
+
# 1010cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
|
20 |
+
|
21 |
+
CONTINUOUS_CONTROLLER = 0xB0 # see Channel Mode Messages!!!
|
22 |
+
# 1011cccc 0ccccccc 0vvvvvvv (channel, controller, value)
|
23 |
+
|
24 |
+
PATCH_CHANGE = 0xC0
|
25 |
+
# 1100cccc 0ppppppp (channel, program)
|
26 |
+
|
27 |
+
CHANNEL_PRESSURE = 0xD0
|
28 |
+
# 1101cccc 0ppppppp (channel, pressure)
|
29 |
+
|
30 |
+
PITCH_BEND = 0xE0
|
31 |
+
# 1110cccc 0vvvvvvv 0wwwwwww (channel, value-lo, value-hi)
|
32 |
+
|
33 |
+
|
34 |
+
###################################################
|
35 |
+
## Channel Mode Messages (Continuous Controller)
|
36 |
+
## They share a status byte.
|
37 |
+
## The controller makes the difference here
|
38 |
+
|
39 |
+
# High resolution continuous controllers (MSB)
|
40 |
+
|
41 |
+
BANK_SELECT = 0x00
|
42 |
+
MODULATION_WHEEL = 0x01
|
43 |
+
BREATH_CONTROLLER = 0x02
|
44 |
+
FOOT_CONTROLLER = 0x04
|
45 |
+
PORTAMENTO_TIME = 0x05
|
46 |
+
DATA_ENTRY = 0x06
|
47 |
+
CHANNEL_VOLUME = 0x07
|
48 |
+
BALANCE = 0x08
|
49 |
+
PAN = 0x0A
|
50 |
+
EXPRESSION_CONTROLLER = 0x0B
|
51 |
+
EFFECT_CONTROL_1 = 0x0C
|
52 |
+
EFFECT_CONTROL_2 = 0x0D
|
53 |
+
GEN_PURPOSE_CONTROLLER_1 = 0x10
|
54 |
+
GEN_PURPOSE_CONTROLLER_2 = 0x11
|
55 |
+
GEN_PURPOSE_CONTROLLER_3 = 0x12
|
56 |
+
GEN_PURPOSE_CONTROLLER_4 = 0x13
|
57 |
+
|
58 |
+
# High resolution continuous controllers (LSB)
|
59 |
+
|
60 |
+
BANK_SELECT = 0x20
|
61 |
+
MODULATION_WHEEL = 0x21
|
62 |
+
BREATH_CONTROLLER = 0x22
|
63 |
+
FOOT_CONTROLLER = 0x24
|
64 |
+
PORTAMENTO_TIME = 0x25
|
65 |
+
DATA_ENTRY = 0x26
|
66 |
+
CHANNEL_VOLUME = 0x27
|
67 |
+
BALANCE = 0x28
|
68 |
+
PAN = 0x2A
|
69 |
+
EXPRESSION_CONTROLLER = 0x2B
|
70 |
+
EFFECT_CONTROL_1 = 0x2C
|
71 |
+
EFFECT_CONTROL_2 = 0x2D
|
72 |
+
GENERAL_PURPOSE_CONTROLLER_1 = 0x30
|
73 |
+
GENERAL_PURPOSE_CONTROLLER_2 = 0x31
|
74 |
+
GENERAL_PURPOSE_CONTROLLER_3 = 0x32
|
75 |
+
GENERAL_PURPOSE_CONTROLLER_4 = 0x33
|
76 |
+
|
77 |
+
# Switches
|
78 |
+
|
79 |
+
SUSTAIN_ONOFF = 0x40
|
80 |
+
PORTAMENTO_ONOFF = 0x41
|
81 |
+
SOSTENUTO_ONOFF = 0x42
|
82 |
+
SOFT_PEDAL_ONOFF = 0x43
|
83 |
+
LEGATO_ONOFF = 0x44
|
84 |
+
HOLD_2_ONOFF = 0x45
|
85 |
+
|
86 |
+
# Low resolution continuous controllers
|
87 |
+
|
88 |
+
SOUND_CONTROLLER_1 = 0x46 # (TG: Sound Variation; FX: Exciter On/Off)
|
89 |
+
SOUND_CONTROLLER_2 = 0x47 # (TG: Harmonic Content; FX: Compressor On/Off)
|
90 |
+
SOUND_CONTROLLER_3 = 0x48 # (TG: Release Time; FX: Distortion On/Off)
|
91 |
+
SOUND_CONTROLLER_4 = 0x49 # (TG: Attack Time; FX: EQ On/Off)
|
92 |
+
SOUND_CONTROLLER_5 = 0x4A # (TG: Brightness; FX: Expander On/Off)75 SOUND_CONTROLLER_6 (TG: Undefined; FX: Reverb OnOff)
|
93 |
+
SOUND_CONTROLLER_7 = 0x4C # (TG: Undefined; FX: Delay OnOff)
|
94 |
+
SOUND_CONTROLLER_8 = 0x4D # (TG: Undefined; FX: Pitch Transpose OnOff)
|
95 |
+
SOUND_CONTROLLER_9 = 0x4E # (TG: Undefined; FX: Flange/Chorus OnOff)
|
96 |
+
SOUND_CONTROLLER_10 = 0x4F # (TG: Undefined; FX: Special Effects OnOff)
|
97 |
+
GENERAL_PURPOSE_CONTROLLER_5 = 0x50
|
98 |
+
GENERAL_PURPOSE_CONTROLLER_6 = 0x51
|
99 |
+
GENERAL_PURPOSE_CONTROLLER_7 = 0x52
|
100 |
+
GENERAL_PURPOSE_CONTROLLER_8 = 0x53
|
101 |
+
PORTAMENTO_CONTROL = 0x54 # (PTC) (0vvvvvvv is the source Note number) (Detail)
|
102 |
+
EFFECTS_1 = 0x5B # (Ext. Effects Depth)
|
103 |
+
EFFECTS_2 = 0x5C # (Tremelo Depth)
|
104 |
+
EFFECTS_3 = 0x5D # (Chorus Depth)
|
105 |
+
EFFECTS_4 = 0x5E # (Celeste Depth)
|
106 |
+
EFFECTS_5 = 0x5F # (Phaser Depth)
|
107 |
+
DATA_INCREMENT = 0x60 # (0vvvvvvv is n/a; use 0)
|
108 |
+
DATA_DECREMENT = 0x61 # (0vvvvvvv is n/a; use 0)
|
109 |
+
NON_REGISTERED_PARAMETER_NUMBER = 0x62 # (LSB)
|
110 |
+
NON_REGISTERED_PARAMETER_NUMBER = 0x63 # (MSB)
|
111 |
+
REGISTERED_PARAMETER_NUMBER = 0x64 # (LSB)
|
112 |
+
REGISTERED_PARAMETER_NUMBER = 0x65 # (MSB)
|
113 |
+
|
114 |
+
# Channel Mode messages - (Detail)
|
115 |
+
|
116 |
+
ALL_SOUND_OFF = 0x78
|
117 |
+
RESET_ALL_CONTROLLERS = 0x79
|
118 |
+
LOCAL_CONTROL_ONOFF = 0x7A
|
119 |
+
ALL_NOTES_OFF = 0x7B
|
120 |
+
OMNI_MODE_OFF = 0x7C # (also causes ANO)
|
121 |
+
OMNI_MODE_ON = 0x7D # (also causes ANO)
|
122 |
+
MONO_MODE_ON = 0x7E # (Poly Off; also causes ANO)
|
123 |
+
POLY_MODE_ON = 0x7F # (Mono Off; also causes ANO)
|
124 |
+
|
125 |
+
|
126 |
+
|
127 |
+
###################################################
|
128 |
+
## System Common Messages, for all channels
|
129 |
+
|
130 |
+
SYSTEM_EXCLUSIVE = 0xF0
|
131 |
+
# 11110000 0iiiiiii 0ddddddd ... 11110111
|
132 |
+
|
133 |
+
MTC = 0xF1 # MIDI Time Code Quarter Frame
|
134 |
+
# 11110001
|
135 |
+
|
136 |
+
SONG_POSITION_POINTER = 0xF2
|
137 |
+
# 11110010 0vvvvvvv 0wwwwwww (lo-position, hi-position)
|
138 |
+
|
139 |
+
SONG_SELECT = 0xF3
|
140 |
+
# 11110011 0sssssss (songnumber)
|
141 |
+
|
142 |
+
#UNDEFINED = 0xF4
|
143 |
+
## 11110100
|
144 |
+
|
145 |
+
#UNDEFINED = 0xF5
|
146 |
+
## 11110101
|
147 |
+
|
148 |
+
TUNING_REQUEST = 0xF6
|
149 |
+
# 11110110
|
150 |
+
|
151 |
+
END_OFF_EXCLUSIVE = 0xF7 # terminator
|
152 |
+
# 11110111 # End of system exclusive
|
153 |
+
|
154 |
+
|
155 |
+
###################################################
|
156 |
+
## Midifile meta-events
|
157 |
+
|
158 |
+
SEQUENCE_NUMBER = 0x00 # 00 02 ss ss (seq-number)
|
159 |
+
TEXT = 0x01 # 01 len text...
|
160 |
+
COPYRIGHT = 0x02 # 02 len text...
|
161 |
+
SEQUENCE_NAME = 0x03 # 03 len text...
|
162 |
+
INSTRUMENT_NAME = 0x04 # 04 len text...
|
163 |
+
LYRIC = 0x05 # 05 len text...
|
164 |
+
MARKER = 0x06 # 06 len text...
|
165 |
+
CUEPOINT = 0x07 # 07 len text...
|
166 |
+
PROGRAM_NAME = 0x08 # 08 len text...
|
167 |
+
DEVICE_NAME = 0x09 # 09 len text...
|
168 |
+
|
169 |
+
MIDI_CH_PREFIX = 0x20 # MIDI channel prefix assignment (unofficial)
|
170 |
+
|
171 |
+
MIDI_PORT = 0x21 # 21 01 port, legacy stuff but still used
|
172 |
+
END_OF_TRACK = 0x2F # 2f 00
|
173 |
+
TEMPO = 0x51 # 51 03 tt tt tt (tempo in us/quarternote)
|
174 |
+
SMTP_OFFSET = 0x54 # 54 05 hh mm ss ff xx
|
175 |
+
TIME_SIGNATURE = 0x58 # 58 04 nn dd cc bb
|
176 |
+
KEY_SIGNATURE = 0x59 # ??? len text...
|
177 |
+
SPECIFIC = 0x7F # Sequencer specific event
|
178 |
+
|
179 |
+
FILE_HEADER = 'MThd'
|
180 |
+
TRACK_HEADER = 'MTrk'
|
181 |
+
|
182 |
+
###################################################
|
183 |
+
## System Realtime messages
|
184 |
+
## I don't supose these are to be found in midi files?!
|
185 |
+
|
186 |
+
TIMING_CLOCK = 0xF8
|
187 |
+
# undefined = 0xF9
|
188 |
+
SONG_START = 0xFA
|
189 |
+
SONG_CONTINUE = 0xFB
|
190 |
+
SONG_STOP = 0xFC
|
191 |
+
# undefined = 0xFD
|
192 |
+
ACTIVE_SENSING = 0xFE
|
193 |
+
SYSTEM_RESET = 0xFF
|
194 |
+
|
195 |
+
|
196 |
+
###################################################
|
197 |
+
## META EVENT, it is used only in midi files.
|
198 |
+
## In transmitted data it means system reset!!!
|
199 |
+
|
200 |
+
META_EVENT = 0xFF
|
201 |
+
# 11111111
|
202 |
+
|
203 |
+
|
204 |
+
###################################################
|
205 |
+
## Helper functions
|
206 |
+
|
207 |
+
def is_status(byte):
|
208 |
+
return (byte & 0x80) == 0x80 # 1000 0000
|
209 |
+
|
210 |
+
|
midi/example_mimimal_type0.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from MidiOutFile import MidiOutFile
|
2 |
+
|
3 |
+
"""
|
4 |
+
This is an example of the smallest possible type 0 midi file, where
|
5 |
+
all the midi events are in the same track.
|
6 |
+
"""
|
7 |
+
|
8 |
+
out_file = 'midiout/minimal_type0.mid'
|
9 |
+
midi = MidiOutFile(out_file)
|
10 |
+
|
11 |
+
# non optional midi framework
|
12 |
+
midi.header()
|
13 |
+
midi.start_of_track()
|
14 |
+
|
15 |
+
|
16 |
+
# musical events
|
17 |
+
|
18 |
+
midi.update_time(0)
|
19 |
+
midi.note_on(channel=0, note=0x40)
|
20 |
+
|
21 |
+
midi.update_time(192)
|
22 |
+
midi.note_off(channel=0, note=0x40)
|
23 |
+
|
24 |
+
|
25 |
+
# non optional midi framework
|
26 |
+
midi.update_time(0)
|
27 |
+
midi.end_of_track()
|
28 |
+
|
29 |
+
midi.eof()
|
midi/example_print_channel_0.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from MidiOutStream import MidiOutStream
|
2 |
+
from MidiInFile import MidiInFile
|
3 |
+
|
4 |
+
"""
|
5 |
+
This prints all note on events on midi channel 0
|
6 |
+
"""
|
7 |
+
|
8 |
+
|
9 |
+
class Transposer(MidiOutStream):
|
10 |
+
|
11 |
+
"Transposes all notes by 1 octave"
|
12 |
+
|
13 |
+
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
14 |
+
if channel == 0:
|
15 |
+
print(channel, note, velocity, self.rel_time())
|
16 |
+
|
17 |
+
|
18 |
+
event_handler = Transposer()
|
19 |
+
|
20 |
+
in_file = 'midiout/minimal_type0.mid'
|
21 |
+
midi_in = MidiInFile(event_handler, in_file)
|
22 |
+
midi_in.read()
|
midi/example_print_events.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from MidiToText import MidiToText
|
2 |
+
|
3 |
+
"""
|
4 |
+
This is an example that uses the MidiToText eventhandler. When an
|
5 |
+
event is triggered on it, it prints the event to the console.
|
6 |
+
"""
|
7 |
+
|
8 |
+
midi = MidiToText()
|
9 |
+
|
10 |
+
# non optional midi framework
|
11 |
+
midi.header()
|
12 |
+
midi.start_of_track()
|
13 |
+
|
14 |
+
|
15 |
+
# musical events
|
16 |
+
|
17 |
+
midi.update_time(0)
|
18 |
+
midi.note_on(channel=0, note=0x40)
|
19 |
+
|
20 |
+
midi.update_time(192)
|
21 |
+
midi.note_off(channel=0, note=0x40)
|
22 |
+
|
23 |
+
|
24 |
+
# non optional midi framework
|
25 |
+
midi.update_time(0)
|
26 |
+
midi.end_of_track() # not optional!
|
27 |
+
|
28 |
+
midi.eof()
|
midi/example_print_file.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
This is an example that uses the MidiToText eventhandler. When an
|
3 |
+
event is triggered on it, it prints the event to the console.
|
4 |
+
|
5 |
+
It gets the events from the MidiInFile.
|
6 |
+
|
7 |
+
So it prints all the events from the infile to the console. great for
|
8 |
+
debugging :-s
|
9 |
+
"""
|
10 |
+
|
11 |
+
|
12 |
+
# get data
|
13 |
+
test_file = 'test/midifiles/minimal-cubase-type0.mid'
|
14 |
+
|
15 |
+
# do parsing
|
16 |
+
from MidiInFile import MidiInFile
|
17 |
+
from MidiToText import MidiToText # the event handler
|
18 |
+
midiIn = MidiInFile(MidiToText(), test_file)
|
19 |
+
midiIn.read()
|
midi/example_transpose_octave.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from MidiOutFile import MidiOutFile
|
2 |
+
from MidiInFile import MidiInFile
|
3 |
+
|
4 |
+
"""
|
5 |
+
This is an example of the smallest possible type 0 midi file, where
|
6 |
+
all the midi events are in the same track.
|
7 |
+
"""
|
8 |
+
|
9 |
+
|
10 |
+
class Transposer(MidiOutFile):
|
11 |
+
|
12 |
+
"Transposes all notes by 1 octave"
|
13 |
+
|
14 |
+
def _transp(self, ch, note):
|
15 |
+
if ch != 9: # not the drums!
|
16 |
+
note += 12
|
17 |
+
if note > 127:
|
18 |
+
note = 127
|
19 |
+
return note
|
20 |
+
|
21 |
+
|
22 |
+
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
23 |
+
note = self._transp(channel, note)
|
24 |
+
MidiOutFile.note_on(self, channel, note, velocity)
|
25 |
+
|
26 |
+
|
27 |
+
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
28 |
+
note = self._transp(channel, note)
|
29 |
+
MidiOutFile.note_off(self, channel, note, velocity)
|
30 |
+
|
31 |
+
|
32 |
+
out_file = 'midiout/transposed.mid'
|
33 |
+
midi_out = Transposer(out_file)
|
34 |
+
|
35 |
+
#in_file = 'midiout/minimal_type0.mid'
|
36 |
+
#in_file = 'test/midifiles/Lola.mid'
|
37 |
+
in_file = 'test/midifiles/tennessee_waltz.mid'
|
38 |
+
midi_in = MidiInFile(midi_out, in_file)
|
39 |
+
midi_in.read()
|
40 |
+
|
midi/experimental/EventDispatcherBase.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class EventDispatcherBase:
|
2 |
+
|
3 |
+
|
4 |
+
def __init__(self, outstream):
|
5 |
+
"""
|
6 |
+
The event dispatcher generates events on the outstream. This
|
7 |
+
is the base implementation. It is more like an interface for
|
8 |
+
how the EventDispatcher. It has the methods that are used by
|
9 |
+
the Midi Parser.
|
10 |
+
"""
|
11 |
+
# internal values, don't mess with 'em directly
|
12 |
+
self.outstream = outstream
|
13 |
+
|
14 |
+
|
15 |
+
def eof(self):
|
16 |
+
"End of file!"
|
17 |
+
self.outstream.eof()
|
18 |
+
|
19 |
+
|
20 |
+
def update_time(self, new_time=0, relative=1):
|
21 |
+
"Updates relative/absolute time."
|
22 |
+
self.outstream.update_time(new_time, relative)
|
23 |
+
|
24 |
+
# 'official' midi events
|
25 |
+
|
26 |
+
def header(self, format, nTracks, division):
|
27 |
+
"Triggers the header event"
|
28 |
+
self.outstream.header(format, nTracks, division)
|
29 |
+
|
30 |
+
|
31 |
+
def start_of_track(self, current_track):
|
32 |
+
"Triggers the start of track event"
|
33 |
+
|
34 |
+
# I do this twice so that users can overwrite the
|
35 |
+
# start_of_track event handler without worrying whether the
|
36 |
+
# track number is updated correctly.
|
37 |
+
self.outstream.set_current_track(current_track)
|
38 |
+
self.outstream.start_of_track(current_track)
|
39 |
+
|
40 |
+
# Event dispatchers for midi events
|
41 |
+
|
42 |
+
def channel_messages(self, hi_nible, channel, data):
|
43 |
+
"Dispatches channel messages"
|
44 |
+
self.outstream.channel_message(hi_nible, channel, data)
|
45 |
+
|
46 |
+
|
47 |
+
def continuous_controllers(self, channel, controller, value):
|
48 |
+
"Dispatches channel messages"
|
49 |
+
self.outstream.continuous_controller(channel, controller, value)
|
50 |
+
|
51 |
+
|
52 |
+
def system_commons(self, common_type, common_data):
|
53 |
+
"Dispatches system common messages"
|
54 |
+
self.outstream.system_common(common_type, common_data)
|
55 |
+
|
56 |
+
|
57 |
+
def meta_event(self, meta_type, data):
|
58 |
+
"Dispatches meta events"
|
59 |
+
self.outstream.meta_event(meta_type, data)
|
60 |
+
|
61 |
+
|
62 |
+
def sysex_events(self, data):
|
63 |
+
"Dispatcher for sysex events"
|
64 |
+
self.outstream.sysex_event(data)
|
65 |
+
|
66 |
+
|
67 |
+
|
68 |
+
if __name__ == '__main__':
|
69 |
+
|
70 |
+
|
71 |
+
from MidiToText import MidiToText
|
72 |
+
from constants import NOTE_ON
|
73 |
+
|
74 |
+
outstream = MidiToText()
|
75 |
+
dispatcher = EventDispatcherBase(outstream)
|
76 |
+
dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')
|
midi/experimental/MidiOutPassThrough.py
ADDED
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from MidiOutStream import MidiOutStream
|
2 |
+
|
3 |
+
class MidiOutPassThrough(MidiOutStream):
|
4 |
+
|
5 |
+
|
6 |
+
"""
|
7 |
+
|
8 |
+
This class i mainly used for testing the event dispatcher. The
|
9 |
+
methods just returns the passed parameters as a tupple.
|
10 |
+
|
11 |
+
"""
|
12 |
+
|
13 |
+
|
14 |
+
#####################
|
15 |
+
## Midi channel events
|
16 |
+
|
17 |
+
|
18 |
+
def note_on(self, channel, note, velocity, time=None):
|
19 |
+
return channel, note, velocity, time
|
20 |
+
|
21 |
+
|
22 |
+
def note_off(self, channel, note, velocity, time=None):
|
23 |
+
return channel, note, velocity, time
|
24 |
+
|
25 |
+
|
26 |
+
def aftertouch(self, channel, note, velocity, time=None):
|
27 |
+
return channel, note, velocity, time
|
28 |
+
|
29 |
+
|
30 |
+
def continuous_controller(self, channel, controller, value, time=None):
|
31 |
+
return channel, controller, value, time
|
32 |
+
|
33 |
+
|
34 |
+
def patch_change(self, channel, patch, time=None):
|
35 |
+
return channel, patch, time
|
36 |
+
|
37 |
+
|
38 |
+
def channel_pressure(self, channel, pressure, time=None):
|
39 |
+
return channel, pressure, time
|
40 |
+
|
41 |
+
|
42 |
+
#####################
|
43 |
+
## defined continuous controller events
|
44 |
+
|
45 |
+
# def cc_
|
46 |
+
|
47 |
+
#####################
|
48 |
+
## Common events
|
49 |
+
|
50 |
+
def system_exclusive(self, data, time=None):
|
51 |
+
return data, time
|
52 |
+
|
53 |
+
|
54 |
+
def song_position_pointer(self, hiPos, loPos, time=None):
|
55 |
+
return hiPos, loPos, time
|
56 |
+
|
57 |
+
|
58 |
+
def song_select(self, songNumber, time=None):
|
59 |
+
return songNumber, time
|
60 |
+
|
61 |
+
|
62 |
+
def tuning_request(self, time=None):
|
63 |
+
return time
|
64 |
+
|
65 |
+
|
66 |
+
|
67 |
+
#########################
|
68 |
+
# header does not really belong here. But anyhoo!!!
|
69 |
+
|
70 |
+
def header(self, format, nTracks, division):
|
71 |
+
return format, nTracks, division
|
72 |
+
|
73 |
+
|
74 |
+
def eof(self):
|
75 |
+
return 'eof'
|
76 |
+
|
77 |
+
|
78 |
+
#####################
|
79 |
+
## meta events
|
80 |
+
|
81 |
+
def start_of_track(self, n_track=0):
|
82 |
+
return n_track
|
83 |
+
|
84 |
+
|
85 |
+
def end_of_track(self, n_track=0, time=None):
|
86 |
+
return n_track, time
|
87 |
+
|
88 |
+
|
89 |
+
def sequence_number(self, hiVal, loVal, time=None):
|
90 |
+
return hiVal, loVal, time
|
91 |
+
|
92 |
+
|
93 |
+
def text(self, text, time=None):
|
94 |
+
return text, time
|
95 |
+
|
96 |
+
|
97 |
+
def copyright(self, text, time=None):
|
98 |
+
return text, time
|
99 |
+
|
100 |
+
|
101 |
+
def sequence_name(self, text, time=None):
|
102 |
+
return text, time
|
103 |
+
|
104 |
+
|
105 |
+
def instrument_name(self, text, time=None):
|
106 |
+
return text, time
|
107 |
+
|
108 |
+
|
109 |
+
def lyric(self, text, time=None):
|
110 |
+
return text, time
|
111 |
+
|
112 |
+
|
113 |
+
def marker(self, text, time=None):
|
114 |
+
return text, time
|
115 |
+
|
116 |
+
|
117 |
+
def cuepoint(self, text, time=None):
|
118 |
+
return text, time
|
119 |
+
|
120 |
+
|
121 |
+
def midi_port(self, value, time=None):
|
122 |
+
return value, time
|
123 |
+
|
124 |
+
|
125 |
+
def tempo(self, value, time=None):
|
126 |
+
return value, time
|
127 |
+
|
128 |
+
def smtp_offset(self, hour, minute, second, frame, framePart, time=None):
|
129 |
+
return hour, minute, second, frame, framePart, time
|
130 |
+
|
131 |
+
|
132 |
+
def time_signature(self, nn, dd, cc, bb, time=None):
|
133 |
+
return nn, dd, cc, bb, time
|
134 |
+
|
135 |
+
|
136 |
+
def key_signature(self, sf, mi, time=None):
|
137 |
+
return sf, mi, time
|
138 |
+
|
139 |
+
|
140 |
+
def sequencer_specific(self, data, time=None):
|
141 |
+
return data, time
|
142 |
+
|
143 |
+
|
144 |
+
|
145 |
+
|
146 |
+
#####################
|
147 |
+
## realtime events
|
148 |
+
|
149 |
+
def timing_clock(self, time=None):
|
150 |
+
return time
|
151 |
+
|
152 |
+
|
153 |
+
def song_start(self, time=None):
|
154 |
+
return time
|
155 |
+
|
156 |
+
|
157 |
+
def song_stop(self, time=None):
|
158 |
+
return time
|
159 |
+
|
160 |
+
|
161 |
+
def song_continue(self, time=None):
|
162 |
+
return time
|
163 |
+
|
164 |
+
|
165 |
+
def active_sensing(self, time=None):
|
166 |
+
return time
|
167 |
+
|
168 |
+
|
169 |
+
def system_reset(self, time=None):
|
170 |
+
return time
|
171 |
+
|
172 |
+
|
173 |
+
|
174 |
+
|
175 |
+
|
176 |
+
if __name__ == '__main__':
|
177 |
+
|
178 |
+
midiOut = MidiOutStream()
|
179 |
+
midiOut.note_on(0, 63, 127, 0)
|
180 |
+
midiOut.note_off(0, 63, 127, 384)
|
181 |
+
|
182 |
+
|
midi/experimental/MidiOutStreamBase.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class MidiOutStreamBase:
|
2 |
+
|
3 |
+
|
4 |
+
"""
|
5 |
+
|
6 |
+
MidiOutStreamBase is Basically an eventhandler. It is the most central
|
7 |
+
class in the Midi library. You use it both for writing events to
|
8 |
+
an output stream, and as an event handler for an input stream.
|
9 |
+
|
10 |
+
This makes it extremely easy to take input from one stream and
|
11 |
+
send it to another. Ie. if you want to read a Midi file, do some
|
12 |
+
processing, and send it to a midiport.
|
13 |
+
|
14 |
+
All time values are in absolute values from the opening of a
|
15 |
+
stream. To calculate time values, please use the MidiTime and
|
16 |
+
MidiDeltaTime classes.
|
17 |
+
|
18 |
+
"""
|
19 |
+
|
20 |
+
def __init__(self):
|
21 |
+
|
22 |
+
# the time is rather global, so it needs to be stored
|
23 |
+
# here. Otherwise there would be no really simple way to
|
24 |
+
# calculate it. The alternative would be to have each event
|
25 |
+
# handler do it. That sucks even worse!
|
26 |
+
self._absolute_time = 0
|
27 |
+
self._relative_time = 0
|
28 |
+
self._current_track = 0
|
29 |
+
|
30 |
+
# time handling event handlers. They should overwritten with care
|
31 |
+
|
32 |
+
def update_time(self, new_time=0, relative=1):
|
33 |
+
"""
|
34 |
+
Updates the time, if relative is true, new_time is relative,
|
35 |
+
else it's absolute.
|
36 |
+
"""
|
37 |
+
if relative:
|
38 |
+
self._relative_time = new_time
|
39 |
+
self._absolute_time += new_time
|
40 |
+
else:
|
41 |
+
self._absolute_time = new_time
|
42 |
+
self._relative_time = new_time - self._absolute_time
|
43 |
+
|
44 |
+
def rel_time(self):
|
45 |
+
"Returns the relative time"
|
46 |
+
return self._relative_time
|
47 |
+
|
48 |
+
def abs_time(self):
|
49 |
+
"Returns the absolute time"
|
50 |
+
return self._absolute_time
|
51 |
+
|
52 |
+
# track handling event handlers
|
53 |
+
|
54 |
+
def set_current_track(self, new_track):
|
55 |
+
"Sets the current track number"
|
56 |
+
self._current_track = new_track
|
57 |
+
|
58 |
+
def get_current_track(self):
|
59 |
+
"Returns the current track number"
|
60 |
+
return self._current_track
|
61 |
+
|
62 |
+
|
63 |
+
#####################
|
64 |
+
## Midi events
|
65 |
+
|
66 |
+
|
67 |
+
def channel_message(self, message_type, channel, data):
|
68 |
+
"""The default event handler for channel messages"""
|
69 |
+
pass
|
70 |
+
|
71 |
+
|
72 |
+
#####################
|
73 |
+
## Common events
|
74 |
+
|
75 |
+
def system_exclusive(self, data):
|
76 |
+
|
77 |
+
"""The default event handler for system_exclusive messages"""
|
78 |
+
pass
|
79 |
+
|
80 |
+
|
81 |
+
def system_common(self, common_type, common_data):
|
82 |
+
|
83 |
+
"""The default event handler for system common messages"""
|
84 |
+
pass
|
85 |
+
|
86 |
+
|
87 |
+
#########################
|
88 |
+
# header does not really belong here. But anyhoo!!!
|
89 |
+
|
90 |
+
def header(self, format, nTracks, division):
|
91 |
+
|
92 |
+
"""
|
93 |
+
format: type of midi file in [1,2]
|
94 |
+
nTracks: number of tracks
|
95 |
+
division: timing division
|
96 |
+
"""
|
97 |
+
pass
|
98 |
+
|
99 |
+
|
100 |
+
def start_of_track(self, n_track=0):
|
101 |
+
|
102 |
+
"""
|
103 |
+
n_track: number of track
|
104 |
+
"""
|
105 |
+
pass
|
106 |
+
|
107 |
+
|
108 |
+
def eof(self):
|
109 |
+
|
110 |
+
"""
|
111 |
+
End of file. No more events to be processed.
|
112 |
+
"""
|
113 |
+
pass
|
114 |
+
|
115 |
+
|
116 |
+
#####################
|
117 |
+
## meta events
|
118 |
+
|
119 |
+
|
120 |
+
def meta_event(self, meta_type, data, time):
|
121 |
+
|
122 |
+
"""The default event handler for meta_events"""
|
123 |
+
pass
|
124 |
+
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
if __name__ == '__main__':
|
129 |
+
|
130 |
+
midiOut = MidiOutStreamBase()
|
131 |
+
midiOut.update_time(0,0)
|
132 |
+
midiOut.note_on(0, 63, 127)
|
133 |
+
midiOut.note_off(0, 63, 127)
|
134 |
+
|
135 |
+
|
midi/experimental/readme.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Stuff that I am just playing around with
|
midi/readme.txt
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
This is the documentation for the midi package
|
2 |
+
==============================================
|
3 |
+
|
4 |
+
|
5 |
+
The modules follows the following naming convention:
|
6 |
+
|
7 |
+
|
8 |
+
MidiIn<StreamType>.py
|
9 |
+
---------------------
|
10 |
+
|
11 |
+
The MidiIn modules reads midi content for a specific type of stream. Ie. a file or a midi port. It then generates events and triggers them on a MidiOutStream.
|
12 |
+
|
13 |
+
|
14 |
+
MidiOut<StreamType>.py
|
15 |
+
----------------------
|
16 |
+
|
17 |
+
The MidiOut modules are event handlers, that reacts to events generated by a a Midi in module.
|
18 |
+
|
19 |
+
|
20 |
+
MidiInBase.py
|
21 |
+
---------------
|
22 |
+
|
23 |
+
The base class for input streams.
|
24 |
+
|
25 |
+
|
26 |
+
MidiOutBase.py
|
27 |
+
----------------
|
28 |
+
|
29 |
+
The base class for the output streams.
|
30 |
+
|
31 |
+
|
32 |
+
|
33 |
+
|
34 |
+
|
35 |
+
|
36 |
+
Internal modules
|
37 |
+
================
|
38 |
+
|
39 |
+
|
40 |
+
DataTypeConverters.py
|
41 |
+
---------------------
|
42 |
+
|
43 |
+
A collection of functions that converts the special data types used in midi files to and from strings.
|
44 |
+
|
45 |
+
|
46 |
+
constants.py
|
47 |
+
------------
|
48 |
+
|
49 |
+
A collection of constants from the midi spec.
|
50 |
+
|
midi/version.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
0.1.4
|
midi2abc.py
ADDED
@@ -0,0 +1,619 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/python
|
2 |
+
#
|
3 |
+
# Copyright (C) 2011 Nils Liberg (mail: kotorinl at yahoo.co.uk)
|
4 |
+
#
|
5 |
+
# This program is free software: you can redistribute it and/or modify
|
6 |
+
# it under the terms of the GNU Lesser General Public License as published by
|
7 |
+
# the Free Software Foundation, either version 3 of the License, or
|
8 |
+
# (at your option) any later version.
|
9 |
+
#
|
10 |
+
# This program is distributed in the hope that it will be useful,
|
11 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
# GNU Lesser General Public License for more details.
|
14 |
+
#
|
15 |
+
# You should have received a copy of the GNU Lesser General Public License
|
16 |
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
#
|
18 |
+
#
|
19 |
+
# New in version 0.2:
|
20 |
+
# better handling of accidentals in keys with many flats and sharps
|
21 |
+
|
22 |
+
from midi.MidiOutStream import MidiOutStream
|
23 |
+
from midi.MidiInFile import MidiInFile
|
24 |
+
from fractions import Fraction
|
25 |
+
import os
|
26 |
+
import os.path
|
27 |
+
import sys
|
28 |
+
import getopt
|
29 |
+
import math
|
30 |
+
from simple_abc_parser import get_best_key_for_midi_notes, get_accidentals_for_key
|
31 |
+
from io import StringIO
|
32 |
+
|
33 |
+
num_quarter_notes_per_bar = 3
|
34 |
+
bars_per_line = 4
|
35 |
+
|
36 |
+
usage = """PyMidi2Abc version 0.1 May 30 2008, usage:
|
37 |
+
|
38 |
+
python pymidi2abc.py <options>
|
39 |
+
[-f] <input file>
|
40 |
+
-o <output file>
|
41 |
+
-k <key signature> key name, or -6 to 6 sharps
|
42 |
+
-l <default note length (L: field)>
|
43 |
+
-m <time signature>
|
44 |
+
--nbb no beam breaks
|
45 |
+
--aux=<denominator of L: unit length>
|
46 |
+
--nt Do not look for triplets or broken rhythm
|
47 |
+
--s8 insert slurs on groups of 8th notes
|
48 |
+
--s16 insert slurs on groups of 16th notes
|
49 |
+
--bpl=<number> of bars printed per line
|
50 |
+
--title=<string> Adds T: field containing string
|
51 |
+
--origin=<string> Adds O: field containing string
|
52 |
+
"""
|
53 |
+
|
54 |
+
|
55 |
+
def time_to_note_length(time):
|
56 |
+
# corresponds to the length of a 16th note (one forth of a quarter which corresponds to 1.0)
|
57 |
+
unit_time = 0.25
|
58 |
+
num = int(round(time / unit_time))
|
59 |
+
length = Fraction(1, 16) * num
|
60 |
+
return length
|
61 |
+
|
62 |
+
|
63 |
+
class Note:
|
64 |
+
def __init__(self, start, end, note):
|
65 |
+
self.start = start
|
66 |
+
self.end = end
|
67 |
+
self.note = note
|
68 |
+
self.length = time_to_note_length(self.end - self.start)
|
69 |
+
|
70 |
+
|
71 |
+
class MidiHandler(MidiOutStream):
|
72 |
+
def __init__(self, first_channel, last_channel):
|
73 |
+
MidiOutStream.__init__(self)
|
74 |
+
self.division = None
|
75 |
+
self.first_channel = first_channel
|
76 |
+
self.last_channel = last_channel
|
77 |
+
self.noteon_time = {}
|
78 |
+
self.notes = []
|
79 |
+
|
80 |
+
def sysex_event(self, data):
|
81 |
+
pass
|
82 |
+
|
83 |
+
def time_signature(self, nn, dd, cc, bb):
|
84 |
+
sig = Fraction(nn, 2**dd)
|
85 |
+
|
86 |
+
def header(self, format=0, nTracks=1, division=96):
|
87 |
+
self.division = division
|
88 |
+
|
89 |
+
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
90 |
+
if self.first_channel <= channel <= self.last_channel:
|
91 |
+
self.noteon_time[note] = float(self.abs_time()) / self.division
|
92 |
+
|
93 |
+
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
94 |
+
if self.first_channel <= channel <= self.last_channel:
|
95 |
+
if note in self.noteon_time:
|
96 |
+
end_time = float(self.abs_time()) / self.division
|
97 |
+
self.notes.append(Note(self.noteon_time[note], end_time, note))
|
98 |
+
|
99 |
+
|
100 |
+
def is_triplet(notes):
|
101 |
+
if len(notes) < 3:
|
102 |
+
return False
|
103 |
+
tolerance = 0.025
|
104 |
+
for total_len in [1.0, 0.5]:
|
105 |
+
if (
|
106 |
+
abs(notes[0].end - notes[1].start) < tolerance
|
107 |
+
and abs(notes[1].end - notes[2].start) < tolerance
|
108 |
+
and abs(notes[2].end - notes[0].start - total_len) < tolerance
|
109 |
+
and abs(notes[1].start - notes[0].start - total_len / 3) < tolerance
|
110 |
+
):
|
111 |
+
return True
|
112 |
+
return False
|
113 |
+
|
114 |
+
|
115 |
+
def bar(time):
|
116 |
+
global num_quarter_notes_per_bar
|
117 |
+
return int(time / num_quarter_notes_per_bar + 0.003)
|
118 |
+
|
119 |
+
|
120 |
+
def bar_residue(time):
|
121 |
+
global num_quarter_notes_per_bar
|
122 |
+
return time - bar(time) * float(num_quarter_notes_per_bar)
|
123 |
+
|
124 |
+
|
125 |
+
def duration2abc(f):
|
126 |
+
if f == Fraction(1, 1):
|
127 |
+
return ""
|
128 |
+
elif f == Fraction(1, 2):
|
129 |
+
return "/"
|
130 |
+
elif f == Fraction(1, 4):
|
131 |
+
return "//"
|
132 |
+
elif f.numerator == "1":
|
133 |
+
return "/%d" % f.denumerator
|
134 |
+
else:
|
135 |
+
return str(f)
|
136 |
+
|
137 |
+
|
138 |
+
def note_to_string(note, duration, default_len, key_accidentals, cur_accidentals):
|
139 |
+
n_accidentals = sum(key_accidentals)
|
140 |
+
accidentals_to_scale = {
|
141 |
+
7: "^B ^C ^^C ^D ^^D ^E ^F ^^F ^G ^^G ^A =B",
|
142 |
+
6: "^B ^C ^^C ^D =E ^E ^F ^^F ^G ^^G ^A B",
|
143 |
+
5: "^B ^C ^^C ^D E ^E ^F ^^F ^G =A ^A B",
|
144 |
+
4: "^B ^C =D ^D E ^E ^F ^^F ^G A ^A B",
|
145 |
+
3: "^B ^C D ^D E ^E ^F =G ^G A ^A B",
|
146 |
+
2: "=C ^C D ^D E ^E ^F G ^G A ^A B",
|
147 |
+
1: "C ^C D ^D E =F ^F G ^G A ^A B",
|
148 |
+
0: "C ^C D ^D E F ^F G ^G A _B =B",
|
149 |
+
-1: "C ^C D _E =E F ^F G ^G A _B =B",
|
150 |
+
-2: "C ^C D _E =E F ^F G _A =A _B =B",
|
151 |
+
-3: "C _D =D _E =E F ^F G _A =A _B =B",
|
152 |
+
-4: "C _D =D _E =E F _G =G _A =A _B =B",
|
153 |
+
-5: "=C _D =D _E =E F _G =G _A =A _B _C",
|
154 |
+
-6: "=C _D =D _E _F =F _G =G _A =A _B _C",
|
155 |
+
-7: "=C _D =D _E _F =F _G =G _A __B _B _C",
|
156 |
+
}
|
157 |
+
|
158 |
+
scale = accidentals_to_scale[n_accidentals].split()
|
159 |
+
notes = [
|
160 |
+
n.lower().translate(str.maketrans("", "", "_=^")) for n in scale
|
161 |
+
] # for semitone: the name of the note
|
162 |
+
# for semitone: 0 if white key, -1 or -2 if flat, 1 or 2 if sharp
|
163 |
+
accidentals = [n.count("^") - n.count("_") for n in scale]
|
164 |
+
|
165 |
+
note_scale = "CDEFGAB"
|
166 |
+
middle_C = 60
|
167 |
+
octave_note = (note - middle_C) % 12
|
168 |
+
|
169 |
+
n = notes[octave_note].upper()
|
170 |
+
accidental_num = accidentals[octave_note]
|
171 |
+
accidental_string = ""
|
172 |
+
scale_number = note_scale.index(n)
|
173 |
+
if cur_accidentals[scale_number] != accidental_num:
|
174 |
+
cur_accidentals[scale_number] = accidental_num
|
175 |
+
accidental_string = {-1: "_", -2: "__", 0: "=", 1: "^", 2: "^^"}[accidental_num]
|
176 |
+
|
177 |
+
octave = (note - middle_C) // 12
|
178 |
+
|
179 |
+
# handle the border cases of Cb and B# to make sure that we use the right octave
|
180 |
+
if octave_note == 11 and accidental_num == -1:
|
181 |
+
octave += 1
|
182 |
+
elif octave_note == 0 and accidental_num == 1:
|
183 |
+
octave -= 1
|
184 |
+
|
185 |
+
if octave > 0:
|
186 |
+
if octave >= 1:
|
187 |
+
n = n.lower()
|
188 |
+
if octave > 1:
|
189 |
+
n = n + "'" * int(octave - 1)
|
190 |
+
elif octave < 0:
|
191 |
+
if abs(octave) >= 1:
|
192 |
+
n = n + "," * abs(octave)
|
193 |
+
result = accidental_string + n
|
194 |
+
shown_duration = duration / default_len
|
195 |
+
if shown_duration != Fraction(1, 1):
|
196 |
+
result = result + duration2abc(shown_duration)
|
197 |
+
return result
|
198 |
+
|
199 |
+
|
200 |
+
def is_at_even(time, note_value):
|
201 |
+
offset_in_16ths = time_to_note_length(bar_residue(time)) / note_value
|
202 |
+
return offset_in_16ths.denominator == 1
|
203 |
+
|
204 |
+
|
205 |
+
def fix_lengths(notes):
|
206 |
+
for i1 in range(0, len(notes)):
|
207 |
+
for i2 in range(i1 + 1, len(notes)):
|
208 |
+
# if the i2 note starts at a later time and there is a sufficiently large difference between either start times or end times
|
209 |
+
if (
|
210 |
+
notes[i2].start > notes[i1].start
|
211 |
+
and abs(notes[i2].start - notes[i1].start)
|
212 |
+
+ abs(notes[i2].end - notes[i1].end)
|
213 |
+
> 0.25
|
214 |
+
):
|
215 |
+
notes[i1].end = notes[i2].start
|
216 |
+
notes[i1].length = time_to_note_length(
|
217 |
+
notes[i1].end - notes[i1].start
|
218 |
+
) # update length
|
219 |
+
break
|
220 |
+
|
221 |
+
|
222 |
+
def midi_to_abc(
|
223 |
+
filename=None,
|
224 |
+
notes=None,
|
225 |
+
key=None,
|
226 |
+
metre=Fraction(3, 4),
|
227 |
+
default_len=Fraction(1, 16),
|
228 |
+
bars_per_line=4,
|
229 |
+
title="",
|
230 |
+
artist="",
|
231 |
+
source="",
|
232 |
+
no_triplets=False,
|
233 |
+
no_broken_rythms=False,
|
234 |
+
slur_8th_pairs=False,
|
235 |
+
slur_16th_pairs=False,
|
236 |
+
slur_triplets=True,
|
237 |
+
no_beam_breaks=False,
|
238 |
+
index="1",
|
239 |
+
anacrusis_notes=0,
|
240 |
+
):
|
241 |
+
global num_quarter_notes_per_bar
|
242 |
+
num_quarter_notes_per_bar = metre * 4 # int(metre * 4)
|
243 |
+
|
244 |
+
if filename and not notes:
|
245 |
+
# read midi notes
|
246 |
+
handler1 = MidiHandler(0, 15) # channels 0-15
|
247 |
+
# handler1 = MidiHandler(0, 0) # channels 0-15
|
248 |
+
MidiInFile(handler1, filename).read()
|
249 |
+
notes = handler1.notes
|
250 |
+
elif not filename and not notes:
|
251 |
+
raise Exception(
|
252 |
+
"midi_to_abc needs to be passed either a filename or a notes argument"
|
253 |
+
)
|
254 |
+
|
255 |
+
# sequence of Note(start, end, note)
|
256 |
+
notes = sorted(notes, key=lambda n: n.start)
|
257 |
+
fix_lengths(notes)
|
258 |
+
|
259 |
+
# determine key and accidentals
|
260 |
+
if not key:
|
261 |
+
key = get_best_key_for_midi_notes([note.note for note in notes])
|
262 |
+
key_accidentals = get_accidentals_for_key(key)
|
263 |
+
cur_accidentals = key_accidentals[:]
|
264 |
+
|
265 |
+
output = StringIO()
|
266 |
+
output.write("X:%s\n" % index)
|
267 |
+
if source:
|
268 |
+
output.write("S:%s\n" % source)
|
269 |
+
if title:
|
270 |
+
output.write("T:%s\n" % title)
|
271 |
+
if artist:
|
272 |
+
output.write("A:%s\n" % artist)
|
273 |
+
output.write("M:%s\n" % metre)
|
274 |
+
output.write("L:%s\n" % default_len)
|
275 |
+
output.write("K:%s\n" % key.capitalize())
|
276 |
+
|
277 |
+
# initialize variables used in loop
|
278 |
+
last_note_start = -1.0
|
279 |
+
bow_started = False
|
280 |
+
broken_rythm_factor = Fraction(1, 1)
|
281 |
+
num_notes = len(notes)
|
282 |
+
bar_num = -1
|
283 |
+
|
284 |
+
if anacrusis_notes:
|
285 |
+
time_shift = 4 * float(metre) - notes[anacrusis_notes].start
|
286 |
+
# print notes[0].start, notes[1].start, notes[2].start
|
287 |
+
# print 'shift', time_shift
|
288 |
+
for n in notes:
|
289 |
+
n.start += time_shift
|
290 |
+
n.end += time_shift
|
291 |
+
|
292 |
+
# don't count the first bar if it's an upbeat (to get equal number of bars on each line)
|
293 |
+
inside_upbeat = (notes and bar_residue(notes[0].start) > 1.0) or anacrusis_notes
|
294 |
+
|
295 |
+
while notes:
|
296 |
+
# if current note is in a different bar than the last one, emit '|'
|
297 |
+
if bar(notes[0].start) > bar(last_note_start):
|
298 |
+
if len(notes) != num_notes:
|
299 |
+
output.write(" |")
|
300 |
+
cur_accidentals = key_accidentals[:]
|
301 |
+
if not inside_upbeat:
|
302 |
+
bar_num += 1
|
303 |
+
inside_upbeat = False
|
304 |
+
if bar_num % bars_per_line == 0 and bar_num > 0:
|
305 |
+
output.write("\n")
|
306 |
+
|
307 |
+
# if we have advanced the length of a quarter note, emit space (for note grouping)
|
308 |
+
br = bar_residue(notes[0].start)
|
309 |
+
if is_at_even(notes[0].start, Fraction(1, 4)) and not no_beam_breaks:
|
310 |
+
output.write(" ")
|
311 |
+
|
312 |
+
# check if next three notes can be interpreted as a triplet
|
313 |
+
if is_triplet(notes) and not no_triplets:
|
314 |
+
length = time_to_note_length(notes[2].end - notes[0].start)
|
315 |
+
_notes = [n.note for n in notes[:3]]
|
316 |
+
|
317 |
+
# convert notes to string representation
|
318 |
+
s = "(3" + "".join(
|
319 |
+
[
|
320 |
+
note_to_string(
|
321 |
+
n.note,
|
322 |
+
n.length * 2,
|
323 |
+
default_len,
|
324 |
+
key_accidentals,
|
325 |
+
cur_accidentals,
|
326 |
+
)
|
327 |
+
for n in notes[:3]
|
328 |
+
]
|
329 |
+
)
|
330 |
+
if slur_triplets:
|
331 |
+
s = "(" + s + ")"
|
332 |
+
output.write(s)
|
333 |
+
|
334 |
+
last_note_start = notes[0].start
|
335 |
+
notes = notes[3:]
|
336 |
+
|
337 |
+
# else handle notes one by one or as a chord
|
338 |
+
else:
|
339 |
+
is_four_16th_notes = (
|
340 |
+
len([n for n in notes[0:4] if n.length == Fraction(1, 16)]) == 4
|
341 |
+
)
|
342 |
+
# either two eights or two eights with broken rythm
|
343 |
+
is_two_8th_notes = (
|
344 |
+
len([n for n in notes[0:2] if n.length == Fraction(1, 8)]) == 2
|
345 |
+
or len(notes) >= 2
|
346 |
+
and (notes[0].length, notes[1].length)
|
347 |
+
in [
|
348 |
+
(Fraction(3, 16), Fraction(1, 16)),
|
349 |
+
(Fraction(1, 16), Fraction(3, 16)),
|
350 |
+
]
|
351 |
+
)
|
352 |
+
|
353 |
+
note = notes.pop(0)
|
354 |
+
last_note_start = note.start
|
355 |
+
|
356 |
+
# build a chord from notes near each other in time (will result in just one element in the non-chord case)
|
357 |
+
chord_notes = [note.note]
|
358 |
+
while notes and abs(notes[0].start - note.start) < 1.0 / 50:
|
359 |
+
chord_notes.append(notes.pop(0).note)
|
360 |
+
|
361 |
+
# let the duration of the first note determine the chord's duration (for simplicity)
|
362 |
+
length = note.length
|
363 |
+
|
364 |
+
# if the current note is the first of four 16th notes then add a bow on the two first notes
|
365 |
+
bow_started_here = False
|
366 |
+
if (
|
367 |
+
notes
|
368 |
+
and abs(br - int(br)) < 1.0 / 20
|
369 |
+
and not bow_started
|
370 |
+
and is_four_16th_notes
|
371 |
+
and slur_16th_pairs
|
372 |
+
):
|
373 |
+
bow_started_here = True
|
374 |
+
bow_started = True
|
375 |
+
output.write("(")
|
376 |
+
elif (
|
377 |
+
notes
|
378 |
+
and abs(br - int(br)) < 1.0 / 20
|
379 |
+
and not bow_started
|
380 |
+
and is_two_8th_notes
|
381 |
+
and slur_8th_pairs
|
382 |
+
and br < 2.0
|
383 |
+
):
|
384 |
+
bow_started_here = True
|
385 |
+
bow_started = True
|
386 |
+
output.write("(")
|
387 |
+
|
388 |
+
# check if it's possible to use a broken rytm (< or >) between the current and next note/chord
|
389 |
+
broken_rythm_symbol = ""
|
390 |
+
if not no_broken_rythms:
|
391 |
+
# a broken rythm was activated at the previous note
|
392 |
+
if broken_rythm_factor != Fraction(1, 1):
|
393 |
+
length = length * broken_rythm_factor
|
394 |
+
broken_rythm_factor = Fraction(1, 1)
|
395 |
+
elif (
|
396 |
+
notes
|
397 |
+
and is_at_even(last_note_start, Fraction(1, 8))
|
398 |
+
and bar(last_note_start) == bar(notes[0].start)
|
399 |
+
):
|
400 |
+
# use > between this and next note
|
401 |
+
if notes[0].length == length / 3:
|
402 |
+
broken_rythm_symbol = ">"
|
403 |
+
length = length * Fraction(2, 3)
|
404 |
+
broken_rythm_factor = Fraction(2, 1)
|
405 |
+
# use < between this and next note
|
406 |
+
if notes[0].length == length * 3:
|
407 |
+
broken_rythm_symbol = "<"
|
408 |
+
length = length * Fraction(2, 1)
|
409 |
+
broken_rythm_factor = Fraction(2, 3)
|
410 |
+
|
411 |
+
# convert notes to string representation and output
|
412 |
+
s = "".join(
|
413 |
+
[
|
414 |
+
note_to_string(
|
415 |
+
n, length, default_len, key_accidentals, cur_accidentals
|
416 |
+
)
|
417 |
+
for n in chord_notes
|
418 |
+
]
|
419 |
+
)
|
420 |
+
if len(chord_notes) > 1:
|
421 |
+
s = "[" + s + "]" # wrap chord
|
422 |
+
output.write(s)
|
423 |
+
|
424 |
+
# output broken rythm symbol if set
|
425 |
+
if broken_rythm_symbol:
|
426 |
+
output.write(str(broken_rythm_symbol))
|
427 |
+
|
428 |
+
# if a bow was previously started end it here
|
429 |
+
if bow_started and not bow_started_here:
|
430 |
+
output.write(")")
|
431 |
+
bow_started = False
|
432 |
+
|
433 |
+
# print 'note', note.start, length, chord_notes, '%.2f' % last_note_start, bar(last_note_start), '%.2f' % bar_residue(last_note_start)#, note.note
|
434 |
+
|
435 |
+
output.write(" |")
|
436 |
+
output.write("\n")
|
437 |
+
|
438 |
+
# left strip lines
|
439 |
+
lines = output.getvalue().split("\n")
|
440 |
+
lines = [l.lstrip() for l in lines]
|
441 |
+
return "\n".join(lines)
|
442 |
+
|
443 |
+
|
444 |
+
def main(argv):
|
445 |
+
# setup default values
|
446 |
+
filename = ""
|
447 |
+
output_filename = ""
|
448 |
+
key = None
|
449 |
+
default_len = Fraction(1, 16)
|
450 |
+
metre = Fraction(3, 4)
|
451 |
+
bars_per_line = 4
|
452 |
+
title = ""
|
453 |
+
source = ""
|
454 |
+
no_triplets = False
|
455 |
+
no_broken_rythms = False
|
456 |
+
slur_8th_pairs = False
|
457 |
+
slur_16th_pairs = False
|
458 |
+
no_beam_breaks = False
|
459 |
+
|
460 |
+
# if a single parameter was given and it's a path to an existing file use it as filename
|
461 |
+
if len(argv) == 1 and os.path.exists(argv[0]):
|
462 |
+
filename = argv[0]
|
463 |
+
|
464 |
+
# otherwise interpret options
|
465 |
+
else:
|
466 |
+
try:
|
467 |
+
opts, args = getopt.getopt(
|
468 |
+
argv,
|
469 |
+
"hm:k:f:o:l:",
|
470 |
+
[
|
471 |
+
"help",
|
472 |
+
"metre=",
|
473 |
+
"key=",
|
474 |
+
"bpl=",
|
475 |
+
"file=",
|
476 |
+
"outfile=",
|
477 |
+
"length=",
|
478 |
+
"bpl=",
|
479 |
+
"title=",
|
480 |
+
"source=",
|
481 |
+
"nbb",
|
482 |
+
"nt",
|
483 |
+
"s8",
|
484 |
+
"s16",
|
485 |
+
"aux=",
|
486 |
+
],
|
487 |
+
)
|
488 |
+
except getopt.GetoptError:
|
489 |
+
print(usage)
|
490 |
+
sys.exit(2)
|
491 |
+
|
492 |
+
for opt, arg in opts:
|
493 |
+
try:
|
494 |
+
if opt in ("-h", "--help"):
|
495 |
+
print(usage)
|
496 |
+
sys.exit()
|
497 |
+
elif opt in ("-m", "--metre"):
|
498 |
+
x, y = map(int, arg.split("/"))
|
499 |
+
metre = Fraction(x, y)
|
500 |
+
elif opt in ("-k", "--key"):
|
501 |
+
try:
|
502 |
+
k = int(arg)
|
503 |
+
key = {
|
504 |
+
-7: "cb",
|
505 |
+
-6: "gb",
|
506 |
+
-5: "db",
|
507 |
+
-4: "ab",
|
508 |
+
-3: "eb",
|
509 |
+
-2: "bb",
|
510 |
+
-1: "f",
|
511 |
+
0: "c",
|
512 |
+
1: "g",
|
513 |
+
2: "d",
|
514 |
+
3: "a",
|
515 |
+
4: "e",
|
516 |
+
5: "b",
|
517 |
+
6: "f#",
|
518 |
+
7: "c#",
|
519 |
+
}[k]
|
520 |
+
except ValueError:
|
521 |
+
key = arg.lower()
|
522 |
+
elif opt in ("--bpl",):
|
523 |
+
bars_per_line = int(arg)
|
524 |
+
elif opt in ("--title",):
|
525 |
+
title = arg
|
526 |
+
elif opt in ("--source",):
|
527 |
+
source = arg
|
528 |
+
elif opt in ("--aux",):
|
529 |
+
default_len = Fraction(1, int(arg))
|
530 |
+
elif opt in (
|
531 |
+
"-l",
|
532 |
+
"--length",
|
533 |
+
):
|
534 |
+
x, y = map(int, arg.split("/"))
|
535 |
+
default_len = Fraction(x, y)
|
536 |
+
elif opt in ("--nt"):
|
537 |
+
no_triplets = True
|
538 |
+
no_broken_rythms = True
|
539 |
+
elif opt in ("--nbb"):
|
540 |
+
no_beam_breaks = True
|
541 |
+
elif opt in ("--s8"):
|
542 |
+
slur_8th_pairs = True
|
543 |
+
elif opt in ("--s16"):
|
544 |
+
slur_16th_pairs = True
|
545 |
+
elif opt in ("-f",):
|
546 |
+
filename = arg
|
547 |
+
elif opt in ("-o",):
|
548 |
+
output_filename = arg
|
549 |
+
except Exception:
|
550 |
+
print("Error parsing argument: %s" % opt)
|
551 |
+
print(usage)
|
552 |
+
sys.exit(2)
|
553 |
+
|
554 |
+
if not filename:
|
555 |
+
print("Error: filename (-f) required!")
|
556 |
+
print(usage)
|
557 |
+
sys.exit(2)
|
558 |
+
|
559 |
+
# do midi to abc conversion
|
560 |
+
result = midi_to_abc(
|
561 |
+
filename,
|
562 |
+
None,
|
563 |
+
key,
|
564 |
+
metre,
|
565 |
+
default_len,
|
566 |
+
bars_per_line,
|
567 |
+
title,
|
568 |
+
source,
|
569 |
+
no_triplets,
|
570 |
+
no_broken_rythms,
|
571 |
+
slur_8th_pairs,
|
572 |
+
slur_16th_pairs,
|
573 |
+
no_beam_breaks=no_beam_breaks,
|
574 |
+
)
|
575 |
+
|
576 |
+
# output to stdout or file
|
577 |
+
if output_filename:
|
578 |
+
open(output_filename, "w").write(result)
|
579 |
+
else:
|
580 |
+
print(result)
|
581 |
+
|
582 |
+
|
583 |
+
def midi2abc(filename, title, artist):
|
584 |
+
key = None
|
585 |
+
default_len = Fraction(1, 16)
|
586 |
+
metre = Fraction(3, 4)
|
587 |
+
bars_per_line = 4
|
588 |
+
no_triplets = False
|
589 |
+
no_broken_rythms = False
|
590 |
+
slur_8th_pairs = False
|
591 |
+
slur_16th_pairs = False
|
592 |
+
no_beam_breaks = False
|
593 |
+
|
594 |
+
if not filename:
|
595 |
+
print("Error: filename (-f) required!")
|
596 |
+
print(usage)
|
597 |
+
sys.exit(2)
|
598 |
+
|
599 |
+
# do midi to abc conversion
|
600 |
+
return midi_to_abc(
|
601 |
+
filename,
|
602 |
+
None,
|
603 |
+
key,
|
604 |
+
metre,
|
605 |
+
default_len,
|
606 |
+
bars_per_line,
|
607 |
+
title,
|
608 |
+
artist,
|
609 |
+
"",
|
610 |
+
no_triplets,
|
611 |
+
no_broken_rythms,
|
612 |
+
slur_8th_pairs,
|
613 |
+
slur_16th_pairs,
|
614 |
+
no_beam_breaks=no_beam_breaks,
|
615 |
+
)
|
616 |
+
|
617 |
+
|
618 |
+
if __name__ == "__main__":
|
619 |
+
main(sys.argv[1:])
|
requirements.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
librosa==0.9.2
|
2 |
piano_transcription_inference
|
3 |
pymupdf
|
4 |
-
|
|
|
1 |
librosa==0.9.2
|
2 |
piano_transcription_inference
|
3 |
pymupdf
|
4 |
+
music21
|
simple_abc_parser.py
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (C) 2011 Nils Liberg (mail: kotorinl at yahoo.co.uk)
|
2 |
+
#
|
3 |
+
# This program is free software: you can redistribute it and/or modify
|
4 |
+
# it under the terms of the GNU Lesser General Public License as published by
|
5 |
+
# the Free Software Foundation, either version 3 of the License, or
|
6 |
+
# (at your option) any later version.
|
7 |
+
#
|
8 |
+
# This program is distributed in the hope that it will be useful,
|
9 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11 |
+
# GNU Lesser General Public License for more details.
|
12 |
+
#
|
13 |
+
# You should have received a copy of the GNU Lesser General Public License
|
14 |
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15 |
+
|
16 |
+
from fractions import Fraction
|
17 |
+
|
18 |
+
key_data = \
|
19 |
+
{'c': ('#', 0), 'am': ('#', 0), 'ddor': ('#', 0),
|
20 |
+
'f': ('b', 1), 'dm': ('b', 1), 'gdor': ('#', 1),
|
21 |
+
'bb': ('b', 2), 'gm': ('b', 2), 'cdor': ('#', 2),
|
22 |
+
'eb': ('b', 3), 'cm': ('b', 3), 'bbdor': ('#', 3),
|
23 |
+
'ab': ('b', 4), 'fm': ('b', 4), 'ebdor': ('#', 4),
|
24 |
+
'db': ('b', 5), 'bbm': ('b', 5), 'abdor': ('#', 5),
|
25 |
+
'gb': ('b', 6), 'ebm': ('b', 6), 'abdor': ('#', 6),
|
26 |
+
'cb': ('b', 7), 'abm': ('b', 7), 'dbdor': ('#', 7),
|
27 |
+
'g': ('#', 1), 'em': ('#', 1), 'ador': ('#', 0),
|
28 |
+
'd': ('#', 2), 'bm': ('#', 2), 'edor': ('#', 0),
|
29 |
+
'a': ('#', 3), 'f#m': ('#', 3), 'bdor': ('#', 0),
|
30 |
+
'e': ('#', 4), 'c#m': ('#', 4), 'f#dor': ('#', 0),
|
31 |
+
'b': ('#', 5), 'g#m': ('#', 5), 'c#dor': ('#', 0),
|
32 |
+
'f#': ('#', 6), 'd#m': ('#', 6), 'g#dor': ('#', 0),
|
33 |
+
'c#': ('#', 7), 'a#m': ('#', 7), 'c#dor': ('#', 0), }
|
34 |
+
|
35 |
+
|
36 |
+
def get_base_note_for_key(key):
|
37 |
+
key = key[:2]
|
38 |
+
# print key, notes_sharp, notes_flat
|
39 |
+
if key in notes_sharp:
|
40 |
+
return notes_sharp.index(key)
|
41 |
+
if key in notes_flat:
|
42 |
+
return notes_flat.index(key)
|
43 |
+
key = key[:1]
|
44 |
+
if key in notes_sharp:
|
45 |
+
return notes_sharp.index(key)
|
46 |
+
if key in notes_flat:
|
47 |
+
return notes_flat.index(key)
|
48 |
+
raise Exception("error, incorrect key:%s" % key)
|
49 |
+
|
50 |
+
|
51 |
+
def get_best_key_for_midi_notes(notes):
|
52 |
+
notes = [n % 12 for n in notes]
|
53 |
+
penalty_key_tuples = []
|
54 |
+
for key in key_data:
|
55 |
+
accidentals = get_accidentals_for_key(key)
|
56 |
+
basic_notes_in_key = [x+y for x,
|
57 |
+
y in zip(doremi2chromatic, accidentals)]
|
58 |
+
penalty = len(
|
59 |
+
[note for note in notes if not note in basic_notes_in_key])
|
60 |
+
if notes[-1] != get_base_note_for_key(key):
|
61 |
+
penalty += 2
|
62 |
+
if key.endswith('dor'):
|
63 |
+
penalty += 1
|
64 |
+
penalty_key_tuples.append((penalty, key))
|
65 |
+
penalty_key_tuples.sort()
|
66 |
+
# print '\n'.join((str(x) for x in penalty_key_tuples))
|
67 |
+
# print 'notes[-1]=', notes[-1]
|
68 |
+
# print 'penalty', penalty_key_tuples[0][0]
|
69 |
+
return penalty_key_tuples[0][1] # key of the first (and best) item
|
70 |
+
|
71 |
+
|
72 |
+
def update_extra_accidentals_for_note(basic_accidentals, extra_accidentals, note):
|
73 |
+
# merge the basic and extra accidentals and let the later have precedence over the former
|
74 |
+
accidentals = [b if b is not None else a for a,
|
75 |
+
b in zip(basic_accidentals, extra_accidentals)]
|
76 |
+
|
77 |
+
basic_notes_in_key = [x+y for x,
|
78 |
+
y in zip(doremi2chromatic, basic_accidentals)]
|
79 |
+
current_notes_in_key = [x+y for x, y in zip(doremi2chromatic, accidentals)]
|
80 |
+
|
81 |
+
change = None
|
82 |
+
|
83 |
+
# if not note in current_notes_in_key:
|
84 |
+
# if
|
85 |
+
|
86 |
+
return {None: '',
|
87 |
+
0: '=',
|
88 |
+
-1: '_',
|
89 |
+
1: '^'}[change]
|
90 |
+
|
91 |
+
|
92 |
+
# map from do-re-mi scale index to chromatic interval
|
93 |
+
doremi2chromatic = [0, 2, 4, 5, 7, 9, 11, 13]
|
94 |
+
|
95 |
+
# map from note name to note number in a C scale
|
96 |
+
note2number = 'cdefgab'
|
97 |
+
|
98 |
+
notes_sharp = 'c c# d d# e f f# g g# a a# b'.split()
|
99 |
+
notes_flat = 'c db d eb e f gb g ab a bb b'.split()
|
100 |
+
|
101 |
+
|
102 |
+
def get_accidentals(fifths):
|
103 |
+
# determine the accidentals for one octave
|
104 |
+
steps = 'C D E F G A B'.split()
|
105 |
+
one_octave = [0] * 7
|
106 |
+
if fifths > 0:
|
107 |
+
for i in range(fifths):
|
108 |
+
one_octave[(3 + i*4) % 7] = 1
|
109 |
+
else:
|
110 |
+
for i in range(fifths+1, 1):
|
111 |
+
one_octave[(6 + i*4) % 7] = -1
|
112 |
+
scale = []
|
113 |
+
for i in range(7):
|
114 |
+
scale.append(steps[i] + {-1: 'b', 0: '', 1: '#'}[one_octave[i]])
|
115 |
+
print(' '.join(scale))
|
116 |
+
|
117 |
+
# use the same for all octaves
|
118 |
+
accidentals = {}
|
119 |
+
for octave in range(1, 7):
|
120 |
+
for step in range(7):
|
121 |
+
note = '%s%s' % (steps[step], octave) # eg. G4
|
122 |
+
accidentals[note] = one_octave[step]
|
123 |
+
return accidentals
|
124 |
+
|
125 |
+
|
126 |
+
def get_accidentals_for_key(key):
|
127 |
+
''' returns a list of seven values corresponding to the notes in the C scale. 1 means #, -1 means b, and 0 means no change. '''
|
128 |
+
key = key.lower().strip().strip()
|
129 |
+
sharp_or_flat, number = key_data[key]
|
130 |
+
# initially no of the seven notes are flat or sharp (0=neutral)
|
131 |
+
accidentals = [0] * 7
|
132 |
+
# add accidentals
|
133 |
+
for i in range(number):
|
134 |
+
if sharp_or_flat == '#':
|
135 |
+
accidentals[(3 - 3*i) % 7] = 1
|
136 |
+
else:
|
137 |
+
accidentals[(6 + 3*i) % 7] = -1
|
138 |
+
return accidentals
|
139 |
+
|
140 |
+
|
141 |
+
class Note:
|
142 |
+
def __init__(self, note, duration, ties_to_next=False, broken_rythm=''):
|
143 |
+
self.note = note # number of semitones from centre note
|
144 |
+
self.duration = duration # duration expressed as a Fraction object
|
145 |
+
# whether the abc representation of this note ended with '-' representing a tie to the next note
|
146 |
+
self.ties_to_next = ties_to_next
|
147 |
+
self.broken_rythm = broken_rythm # the ABC broken rythm symbol or an empty string
|
148 |
+
# (used when the duration of the next note is established)
|
149 |
+
|
150 |
+
def __repr__(self):
|
151 |
+
''' returns a string representation of this Note object '''
|
152 |
+
notes = ('c', 'c#', 'd', 'd#', 'e', 'f',
|
153 |
+
'f#', 'g', 'g#', 'a', 'a#', 'b')
|
154 |
+
n = notes[self.note % 12].upper()
|
155 |
+
octave = self.note / 12
|
156 |
+
if octave > 0:
|
157 |
+
if octave >= 1:
|
158 |
+
n = n.lower()
|
159 |
+
if octave > 1:
|
160 |
+
n = n + "'" * (octave - 1)
|
161 |
+
elif octave < 0:
|
162 |
+
if abs(octave) > 1:
|
163 |
+
n = n + "," * abs(octave)
|
164 |
+
return n # + str(self.duration)
|
165 |
+
|
166 |
+
def __str__(self):
|
167 |
+
return repr(self)
|
168 |
+
|
169 |
+
# print get_best_key_for_midi_notes([0, 2, 4, 6, 6, 6, 7, 0])
|
170 |
+
# print get_base_note_for_key('c')
|