Beat calculator python CLI utility
Pubblicato: martedì 31 gennaio 2023
categoria: Utility.
tags: | markdown | python | utility | coding |
categoria: Utility.
tags: | markdown | python | utility | coding |
Questo articolo vuole essere un esperimento del funzionamento del syntax highlighting del codice scritto con markdown, che pelican dovrebbe gestire in modo più o meno automatico.
Su debian stable attuale (bullseye) c'è una versione di pelican un po' vecchiotta, la 4.0.1 ma seguendo le istruzioni sulla relativa documentazione online dovrei esserci riuscito.
Per testare la cosa metto il codice di uno scriptino python che ho creato allo scopo di ottenere, dato un file audio o un bpm (beat per minute, lo standard musicale della velocità o tempo della musica) ritorna le durate dei vari tipi di note in millisecondi, e la frequenza in Hertz, utile per esempio per mettere a tempo un effetto delay e cose simili.
Ecco lo script:
#!/usr/bin/env python
import wave
from string import capwords
from optparse import OptionParser
def ms_to_bpm(length, measures=1.0, beat=1.0):
"""Calculates beat per minute of a soundfile given the lenght in
milliseconds, the number of measures and the beat, where 4/4 is 1.
Returns bpm
"""
bpm = round(60000 / ((float(length / beat) / measures) / 4), 2)
return bpm
def wavduration(wavfile: str) -> int:
"""Returns the duration of a wavfile in milliseconds"""
myfile = wave.open(wavfile, "r")
frames = 1.0 * myfile.getnframes()
sr = myfile.getframerate()
time = 1.0 * (frames / sr)
return int(round(time * 1000))
def delay_times(bpm=120.0):
"""Returns delay times for the specified bpm, in milliseconds"""
result = []
durations = [
(1, "whole"),
((3.0 / 4), "dotted half"),
((1.0 / 2), "half"),
((3.0 / 8), "dotted quarter"),
((1.0 / 4), "quarter"),
((3.0 / 16), "dotted eight"),
(round(((1.0 / 2) / 3), 5), "quarter triplet"),
((1.0 / 8), "eight"),
((3.0 / 32), "dotted sixteenth"),
(round(((1.0 / 4) / 3), 5), "eight triplet"),
((1.0 / 16), "sixteenth"),
((3.0 / 64), "dotted thirty second"),
(round(((1.0 / 8) / 3), 5), "sixteenth triplet"),
((1.0 / 32), "thirty second"),
]
for duration, description in durations:
title = capwords(description)
delay = (duration * 4000) / (bpm / 60.0)
frequency = 1000 / delay
result.append(
{
"title": title,
"delay": delay,
"frequency": frequency,
}
)
return result
def delay_times_format(bpm=120.0):
result = delay_times(bpm)
print(f"\n {bpm} beats per minute (bpm):")
print()
print("Note Delay time LFO freq")
print(55 * "-")
for line in result:
title = line["title"].ljust(20, " ")
delay = round(line["delay"], 3)
frequency = round(line["frequency"], 2)
print(
title,
delay.__str__().rjust(8),
"ms ",
10 * " ",
frequency.__str__().rjust(5),
"Hz",
)
if __name__ == "__main__":
parser = OptionParser()
parser.add_option(
"-f",
"--file",
dest="filename",
default="none",
help="wave FILE to load",
metavar="FILE",
)
parser.add_option(
"-b",
"--bpm",
dest="bpm",
default="analize",
help="beats per minute",
)
parser.add_option(
"-B",
"--bars",
dest="bars",
help="number of bars in the wav file",
)
parser.add_option(
"-m",
"--meter",
dest="meter",
help="as in 3/4 or 12/8, default 4/4",
)
(options, args) = parser.parse_args()
if options.meter:
a = options.meter.split("/")
meter = float(a[0]) / float(a[1])
print("\nMeter is", options.meter)
else:
meter = 1
print("\nMeter is 4/4")
if options.bars:
bars = int(options.bars)
else:
bars = 1
wavfile = options.filename
if wavfile == "none":
print("No files to analize, defaulting to bpm provided, or 120")
if options.bpm == "analize":
bpm = 120
else:
bpm = float(options.bpm)
else:
if options.bpm != "analize":
bpm = float(options.bpm)
delay_times_format(bpm)
else:
bpm = 120
print(75 * "-", "\n")
wavdur = wavduration(wavfile)
print(wavfile, "is", wavdur, "milliseconds")
bpm = ms_to_bpm(wavdur, bars, meter)
# bpm = ms_to_bpm(3850,2,1)
print("Bpm of", wavfile, "is", bpm)
delay_times_format(bpm)