add midi2csv.py and csv2wav.py
This commit is contained in:
parent
f5515a4666
commit
4c6e790127
96
csv2wav.py
Executable file
96
csv2wav.py
Executable file
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import csv
|
||||
from math import pi, sin
|
||||
import sys
|
||||
import wave
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("input", help="input CSV (pitch, duration, pause)")
|
||||
parser.add_argument("-o", "--output", default="/dev/stdout", help="output file (wav)")
|
||||
parser.add_argument("--framerate", default=48000, type=int, help="framerate")
|
||||
parser.add_argument("--bpm", default=60, type=float, help="beats per minute")
|
||||
args = parser.parse_args()
|
||||
|
||||
# starting from C3 = 130.81 Hz
|
||||
octave = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"]
|
||||
frequencies = {
|
||||
f"{octave[i % 12]}{i // 12 + 3}": 2 ** (i / 12) * 130.81 for i in range(36)
|
||||
}
|
||||
# duration of 1/16th beat
|
||||
base_duration = 60 / args.bpm / 16
|
||||
|
||||
with wave.open(args.output, "wb") as wav:
|
||||
wav.setnchannels(1)
|
||||
wav.setsampwidth(1)
|
||||
wav.setframerate(args.framerate)
|
||||
with open(args.input, "r") as notes:
|
||||
for pitch, duration, pause in csv.reader(notes):
|
||||
if pitch == "":
|
||||
# skip comment
|
||||
continue
|
||||
try:
|
||||
frequency = frequencies[pitch]
|
||||
except KeyError:
|
||||
sys.exit("pitch goes from c3 to b5")
|
||||
duration = int(duration)
|
||||
pause = int(pause)
|
||||
if duration < 1 or pause < -1:
|
||||
sys.exit("duration/pause are integer multiples of 1/16th beat")
|
||||
attack_samples = int(0.1 * args.framerate)
|
||||
duration_samples = int(
|
||||
max(0, duration * base_duration - 0.2) * args.framerate
|
||||
)
|
||||
wav.writeframes(
|
||||
bytes(
|
||||
[
|
||||
int(
|
||||
127
|
||||
* i
|
||||
* sin(2 * pi * frequency * i / args.framerate)
|
||||
/ attack_samples
|
||||
) % 256
|
||||
for i in range(attack_samples)
|
||||
]
|
||||
)
|
||||
)
|
||||
wav.writeframes(
|
||||
bytes(
|
||||
[
|
||||
int(
|
||||
127
|
||||
* sin(
|
||||
2
|
||||
* pi
|
||||
* frequency
|
||||
* (i + attack_samples)
|
||||
/ args.framerate
|
||||
)
|
||||
) % 256
|
||||
for i in range(duration_samples)
|
||||
]
|
||||
)
|
||||
)
|
||||
wav.writeframes(
|
||||
bytes(
|
||||
[
|
||||
int(
|
||||
127
|
||||
* (attack_samples - i)
|
||||
* sin(
|
||||
2
|
||||
* pi
|
||||
* frequency
|
||||
* (i + attack_samples + duration_samples)
|
||||
/ args.framerate
|
||||
)
|
||||
/ attack_samples
|
||||
) % 256
|
||||
for i in range(attack_samples)
|
||||
]
|
||||
)
|
||||
)
|
||||
if pause != -1:
|
||||
wav.writeframes(
|
||||
bytes([0] * int(pause * base_duration * args.framerate))
|
||||
)
|
45
midi2csv.py
Executable file
45
midi2csv.py
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import csv
|
||||
import mido
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("input", help="input file (midi)")
|
||||
parser.add_argument(
|
||||
"-o", "--output", default="/dev/stdout", help="output CSV (pitch, duration, pause)"
|
||||
)
|
||||
parser.add_argument("-t", "--track", default=0, type=int, help="track number")
|
||||
parser.add_argument("-c", "--channel", default=0, type=int, help="channel number")
|
||||
args = parser.parse_args()
|
||||
|
||||
octave = ["c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b"]
|
||||
pitches = [f"{octave[i % 12]}{i // 12 + 3}" for i in range(36)]
|
||||
|
||||
midifile = mido.MidiFile(args.input)
|
||||
time = 0
|
||||
note = 0
|
||||
time_on = 0
|
||||
time_off = 0
|
||||
with open(args.output, "w") as notes:
|
||||
writer = csv.writer(notes)
|
||||
for event in midifile.tracks[args.track]:
|
||||
writer.writerow(["", "", event])
|
||||
# convert time to 1/16th beats
|
||||
time += event.time * 16 // midifile.ticks_per_beat
|
||||
if event.type == "note_on" and event.channel == args.channel:
|
||||
# if mulitple notes start at the same time, play the highest
|
||||
if event.velocity != 0 and (time_on != time or note < event.note):
|
||||
if time_off <= time_on:
|
||||
time_off = time
|
||||
if note != 0 and time_off - time_on > 0:
|
||||
writer.writerow(
|
||||
[pitches[note - 60], time_off - time_on, time - time_off]
|
||||
)
|
||||
note = event.note
|
||||
if note - 60 < 0 or note - 60 >= len(pitches):
|
||||
sys.exit("note out of range c3 to b5")
|
||||
time_on = time
|
||||
elif note == event.note:
|
||||
time_off = time
|
||||
writer.writerow([pitches[note - 60], time - time_on, 0])
|
Loading…
Reference in a new issue