parent
1f1276382e
commit
99c46de743
|
|
@ -0,0 +1,208 @@
|
|||
### AudioExplorer ###
|
||||
# Rev. 1.0, 4.3.2023
|
||||
# Open, plot and play near any soundfile
|
||||
# Dynamically change fidelity and samplerate
|
||||
#
|
||||
# Dependencies:
|
||||
# matplotlib
|
||||
# pydub (requires ffmpeg)
|
||||
# numpy
|
||||
# tkinter
|
||||
#
|
||||
# Potential improvements:
|
||||
# Automatically mirror visual zoom (set with matplotlib toolbar) to start and end times
|
||||
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
|
||||
from matplotlib.backend_bases import key_press_handler
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
import numpy as np
|
||||
import threading
|
||||
import sys, getopt
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
from pydub import AudioSegment
|
||||
from pydub.playback import play
|
||||
from time import sleep
|
||||
|
||||
BITS_PER_BYTE = 8
|
||||
DEFAULT_STARTTIME = 0
|
||||
DEFAULT_ENDTIME = 12500
|
||||
DEFAULT_SAMPLERATE = 11025
|
||||
DEFAULT_FIDELITY = 8 # Bitvalue
|
||||
DEFAULT_AUDIOFILENAME = "Maien.wav"
|
||||
|
||||
def configurePlot(figure):
|
||||
# plt.style.use("seaborn-v0_8-whitegrid")
|
||||
leftAxes = figure.add_axes([0.15,0.07,0.8,0.4])
|
||||
leftAxes.set_xlabel("s")
|
||||
leftAxes.set_ylabel("Wert")
|
||||
rightAxes = figure.add_axes([0.15,0.55,0.8,0.4])
|
||||
rightAxes.set_xlabel("s")
|
||||
rightAxes.set_ylabel("Wert")
|
||||
|
||||
def plotSound(sound, figure):
|
||||
sec = sound.duration_seconds
|
||||
try:
|
||||
(left,right) = sound.split_to_mono()
|
||||
leftData = left.get_array_of_samples()
|
||||
rightData = right.get_array_of_samples()
|
||||
except ValueError:
|
||||
leftData = sound.get_array_of_samples()
|
||||
rightData = sound.get_array_of_samples()
|
||||
|
||||
|
||||
dataCount = len(leftData)
|
||||
|
||||
time = np.linspace(0,dataCount/sound.frame_rate,dataCount)
|
||||
(leftAxes,rightAxes) = figure.axes
|
||||
leftAxes.plot(time,leftData,alpha=0.8)
|
||||
rightAxes.plot(time,rightData,alpha=0.8)
|
||||
|
||||
def downsampleAndCompress(sound,targetSamplerate=44100,targetFidelity=16):
|
||||
# sample_width is in bytes as no-one considered an odd number of bits to be useful
|
||||
# well, we need it so targetFidelity is in bits. Expand sample_width to bits
|
||||
fidelityRatio = 2**(sound.sample_width*BITS_PER_BYTE-targetFidelity)
|
||||
|
||||
newSound = sound.set_frame_rate(targetSamplerate)
|
||||
|
||||
try:
|
||||
(newLeft,newRight) = newSound.split_to_mono()
|
||||
except ValueError:
|
||||
newLeft = newSound
|
||||
newRight = newSound
|
||||
|
||||
newLeftData = np.multiply(np.divide(newLeft.get_array_of_samples(),fidelityRatio).astype('h'),fidelityRatio).astype('h')
|
||||
newRightData = np.multiply(np.divide(newRight.get_array_of_samples(),fidelityRatio).astype('h'),fidelityRatio).astype('h')
|
||||
newLeft = AudioSegment(newLeftData.tobytes(),frame_rate=targetSamplerate,sample_width=sound.sample_width,channels=1)
|
||||
newRight = AudioSegment(newRightData.tobytes(),frame_rate=targetSamplerate,sample_width=sound.sample_width,channels=1)
|
||||
|
||||
newSound = AudioSegment.from_mono_audiosegments(newLeft,newRight)
|
||||
return newSound
|
||||
|
||||
def playSound(sound):
|
||||
playThread = threading.Thread(target=play,args=(sound,))
|
||||
playThread.start()
|
||||
|
||||
def fullUpdate(sound, samplerate, fidelity, startTime, endTime, figure):
|
||||
global newSound
|
||||
newSound = downsampleAndCompress(sound[startTime:endTime], samplerate, fidelity)
|
||||
|
||||
(leftAxes,rightAxes) = figure.axes
|
||||
leftAxes.clear()
|
||||
rightAxes.clear()
|
||||
|
||||
plotSound(sound[startTime:endTime],figure)
|
||||
plotSound(newSound,figure)
|
||||
|
||||
figure.canvas.flush_events()
|
||||
figure.canvas.draw()
|
||||
|
||||
return newSound
|
||||
|
||||
def on_key_press(event):
|
||||
print(f"key {event.key} pressed")
|
||||
|
||||
def updateSlider(event):
|
||||
sliderVariable = str(event.widget.cget("variable"))
|
||||
sliderValue = event.widget.getvar(sliderVariable)
|
||||
|
||||
def main(argv):
|
||||
|
||||
soundFileName = DEFAULT_AUDIOFILENAME
|
||||
|
||||
root = tk.Tk()
|
||||
root.wm_title("AudioExplorer")
|
||||
|
||||
# define tk-Variables to store the widget values
|
||||
startTime = tk.IntVar()
|
||||
startTime.set(DEFAULT_STARTTIME)
|
||||
endTime = tk.IntVar()
|
||||
endTime.set(DEFAULT_ENDTIME)
|
||||
targetFidelity = tk.IntVar()
|
||||
targetFidelity.set(DEFAULT_FIDELITY)
|
||||
targetSamplerate = tk.IntVar()
|
||||
targetSamplerate.set(DEFAULT_SAMPLERATE)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv,"hf:b:e:s:r:",["help","file=","begin=","end=","samplerate=","resolution="])
|
||||
except getopt.GetoptError:
|
||||
print("AudioExplorer.py -f <soundfile> [-b <begintime in ms>][-e <endtime in ms>] [-s <samplerate in Hz>] [-r <bits of resolution>]")
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print("AudioExplorer.py -f <soundfile> [-b <begintime in ms>][-e <endtime in ms>] [-s <samplerate in Hz>] [-r <bits of resolution>]")
|
||||
sys.exit()
|
||||
elif opt in ('-f', '--file'):
|
||||
soundFilePath = arg
|
||||
directory,soundFileName = os.path.split(soundFilePath)
|
||||
elif opt in ('-b', '--begin'):
|
||||
startTime.set(int(arg))
|
||||
elif opt in ('-e','--end'):
|
||||
endTime.set(int(arg))
|
||||
elif opt in ('-s','--samplerate'):
|
||||
targetSamplerate.set(int(arg))
|
||||
elif opt in ('-r','--resolution'):
|
||||
targetFidelity.set(int(arg))
|
||||
|
||||
|
||||
sound = AudioSegment.from_file(soundFileName)
|
||||
maxLength = len(sound)
|
||||
|
||||
startTime.set(0)
|
||||
endTime.set(maxLength)
|
||||
shortSound = sound[startTime.get():endTime.get()]
|
||||
|
||||
figure = Figure(figsize=(14,5), dpi=100)
|
||||
canvas = FigureCanvasTkAgg(figure, master=root)
|
||||
canvas.draw()
|
||||
canvas.get_tk_widget().grid(row=1)
|
||||
|
||||
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
|
||||
toolbar.grid(row=1)
|
||||
toolbar.update()
|
||||
canvas.get_tk_widget().grid(row=5)
|
||||
|
||||
canvas.mpl_connect("key_press_event", on_key_press)
|
||||
|
||||
configurePlot(figure)
|
||||
global newSound
|
||||
newSound = fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure)
|
||||
|
||||
#plotSound(sound[startTime.get():endTime.get()],figure)
|
||||
|
||||
frm = ttk.Frame(root, padding=10)
|
||||
frm.grid()
|
||||
tk.Label(frm, text="Filename: " + soundFileName).grid(row=2,column=0)
|
||||
|
||||
tk.Button(frm, text="Play", command=lambda: playSound(newSound)).grid(row=2,column=1)
|
||||
tk.Button(frm, text="Quit", command=root.destroy).grid(row=2,column=2)
|
||||
|
||||
tk.Label(frm, text="Fidelity [Bits]").grid(row=3,column=0)
|
||||
fidelitySlider = tk.Scale(frm, from_=1, to=16, tickinterval=1, variable=targetFidelity, length=400, orient=tk.HORIZONTAL)#, command=lambda x: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
fidelitySlider.grid(row=3,column=1)
|
||||
fidelitySlider.bind("<ButtonRelease-1>", lambda event: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
|
||||
tk.Label(frm, text="Samplerate [Hz]").grid(row=4,column=0)
|
||||
samplerateSlider = tk.Scale(frm, from_=100, to=44100, tickinterval=10000, variable=targetSamplerate, length=400, orient=tk.HORIZONTAL)#, command=lambda x: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
samplerateSlider.grid(row=4,column=1)
|
||||
samplerateSlider.bind("<ButtonRelease-1>", lambda event: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
|
||||
tk.Label(frm, text="Start [ms]").grid(row=5,column=0)
|
||||
startSlider = tk.Scale(frm, from_=0, to=maxLength-1, tickinterval=50000, variable=startTime, length=400, orient=tk.HORIZONTAL)#, command=lambda x: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
startSlider.grid(row=5,column=1)
|
||||
startSlider.bind("<ButtonRelease-1>", lambda event: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
|
||||
tk.Label(frm, text="End [ms]").grid(row=6,column=0)
|
||||
endSlider = tk.Scale(frm, from_=1, to=maxLength, tickinterval=50000, variable=endTime, length=400, orient=tk.HORIZONTAL)#, command=lambda x: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
endSlider.grid(row=6,column=1)
|
||||
endSlider.bind("<ButtonRelease-1>", lambda event: fullUpdate(shortSound,targetSamplerate.get(),targetFidelity.get(),startTime.get(), endTime.get(), figure))
|
||||
|
||||
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
Loading…
Reference in New Issue