initial commit - will add more functionalities to this project
This commit is contained in:
@@ -0,0 +1,132 @@
|
|||||||
|
import librosa
|
||||||
|
import librosa.display
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from scipy.signal import butter, filtfilt
|
||||||
|
import sounddevice as sd
|
||||||
|
from tkinter import Tk
|
||||||
|
from tkinter.filedialog import askopenfilename
|
||||||
|
import threading
|
||||||
|
from matplotlib.widgets import Cursor
|
||||||
|
|
||||||
|
# Function to apply a band-pass filter
|
||||||
|
def bandpass_filter(data, lowcut, highcut, fs, order=5):
|
||||||
|
nyquist = 0.5 * fs
|
||||||
|
low = lowcut / nyquist
|
||||||
|
high = highcut / nyquist
|
||||||
|
b, a = butter(order, [low, high], btype='band')
|
||||||
|
y = filtfilt(b, a, data)
|
||||||
|
return y
|
||||||
|
|
||||||
|
# Function to detect snare hits
|
||||||
|
def detect_snare_hits(audio_file):
|
||||||
|
# Load the audio file
|
||||||
|
y, sr = librosa.load(audio_file, sr=None)
|
||||||
|
|
||||||
|
# Apply a band-pass filter to focus on snare frequencies
|
||||||
|
y_filtered = bandpass_filter(y, 1000, 5000, sr)
|
||||||
|
|
||||||
|
# Detect onsets (potential snare hits)
|
||||||
|
onset_frames = librosa.onset.onset_detect(y=y_filtered, sr=sr, backtrack=True)
|
||||||
|
onset_times = librosa.frames_to_time(onset_frames, sr=sr)
|
||||||
|
|
||||||
|
return y, sr, onset_times
|
||||||
|
|
||||||
|
# Function to visualize only snare hits with zoom functionality
|
||||||
|
def visualize_snare_hits(y, sr, onset_times, output_file=None):
|
||||||
|
fig, ax = plt.subplots(figsize=(14, 5))
|
||||||
|
|
||||||
|
# Plot snare hits only
|
||||||
|
ax.plot(onset_times, np.zeros_like(onset_times), 'go', label='Snare Hit')
|
||||||
|
|
||||||
|
# Add cursor for interactivity
|
||||||
|
cursor = Cursor(ax, useblit=True, color='red', linewidth=1)
|
||||||
|
|
||||||
|
def on_click(event):
|
||||||
|
if event.inaxes == ax:
|
||||||
|
time = event.xdata
|
||||||
|
closest_time = min(onset_times, key=lambda x: abs(x - time))
|
||||||
|
ax.annotate(f'Snare Hit\nTime: {closest_time:.2f}s', xy=(closest_time, 0),
|
||||||
|
xytext=(closest_time + 0.5, 0.1),
|
||||||
|
arrowprops=dict(facecolor='black', shrink=0.05))
|
||||||
|
plt.draw()
|
||||||
|
|
||||||
|
fig.canvas.mpl_connect('button_press_event', on_click)
|
||||||
|
|
||||||
|
ax.set_title('Detected Snare Hits')
|
||||||
|
ax.set_xlabel('Time (s)')
|
||||||
|
ax.set_ylabel('Amplitude')
|
||||||
|
ax.legend(loc="upper right")
|
||||||
|
|
||||||
|
# Save the plot if an output file is specified, otherwise show it
|
||||||
|
if output_file:
|
||||||
|
plt.savefig(output_file)
|
||||||
|
else:
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# Function to play/pause the audio
|
||||||
|
def play_audio(y, sr):
|
||||||
|
global audio_playing
|
||||||
|
play_pos = 0
|
||||||
|
|
||||||
|
def callback(outdata, frames, time, status):
|
||||||
|
nonlocal play_pos
|
||||||
|
if audio_playing:
|
||||||
|
if play_pos + frames > len(y):
|
||||||
|
frames = len(y) - play_pos
|
||||||
|
outdata[:frames] = y[play_pos:play_pos+frames].reshape(-1, 1)
|
||||||
|
outdata[frames:] = 0
|
||||||
|
else:
|
||||||
|
outdata[:] = y[play_pos:play_pos+frames].reshape(-1, 1)
|
||||||
|
play_pos += frames
|
||||||
|
else:
|
||||||
|
outdata[:] = np.zeros((frames, 1))
|
||||||
|
|
||||||
|
with sd.OutputStream(samplerate=sr, channels=1, callback=callback):
|
||||||
|
while audio_playing and play_pos < len(y):
|
||||||
|
sd.sleep(100)
|
||||||
|
|
||||||
|
# Function to toggle play/pause
|
||||||
|
def toggle_play_pause():
|
||||||
|
global audio_playing
|
||||||
|
audio_playing = not audio_playing
|
||||||
|
|
||||||
|
# Main function to run the app
|
||||||
|
def main():
|
||||||
|
global audio_playing
|
||||||
|
|
||||||
|
# Open a file dialog to select the audio file
|
||||||
|
Tk().withdraw() # Prevent the root window from appearing
|
||||||
|
audio_file = askopenfilename(filetypes=[("Mp3", "*.mp3"), ("All files", "*.*")])
|
||||||
|
|
||||||
|
if not audio_file:
|
||||||
|
print("No file selected.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Detect snare hits
|
||||||
|
y, sr, onset_times = detect_snare_hits(audio_file)
|
||||||
|
|
||||||
|
# Visualize the snare hits
|
||||||
|
visualize_snare_hits(y, sr, onset_times)
|
||||||
|
|
||||||
|
# Play/Pause functionality
|
||||||
|
audio_playing = False
|
||||||
|
play_thread = threading.Thread(target=play_audio, args=(y, sr))
|
||||||
|
play_thread.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
command = input("Enter 'play' to play, 'pause' to pause, or 'exit' to exit: ").strip().lower()
|
||||||
|
if command == 'play':
|
||||||
|
toggle_play_pause()
|
||||||
|
elif command == 'pause':
|
||||||
|
toggle_play_pause()
|
||||||
|
elif command == 'exit':
|
||||||
|
audio_playing = False
|
||||||
|
play_thread.join()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Invalid command. Please enter 'play', 'pause', or 'exit'.")
|
||||||
|
|
||||||
|
# Run the app
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user