|
| 1 | +#./assets/audio/(radzlan - Miami Hotline Vol.3 (feat. Demonicity)) 673473_-Miami-Hotline--Vol3.mp3 |
| 2 | +import tkinter as tk |
| 3 | +from tkinter import filedialog |
1 | 4 | import pygame |
| 5 | +from pydub import AudioSegment |
2 | 6 | import numpy as np |
3 | | -import scipy.signal |
| 7 | +from scipy.signal import resample |
| 8 | +import threading |
| 9 | +import time |
4 | 10 |
|
5 | | -def resample_sound(sound, pitch_factor): |
6 | | - # Extract raw sound buffer |
7 | | - raw = pygame.sndarray.array(sound) |
8 | | - |
9 | | - # Get the current shape |
10 | | - num_samples = raw.shape[0] |
| 11 | +# Init Pygame |
| 12 | +sample_rate = 44100 |
| 13 | +pygame.mixer.init(frequency=sample_rate, size=-16, channels=1) |
11 | 14 |
|
12 | | - # Calculate new length |
13 | | - new_length = int(num_samples / pitch_factor) |
| 15 | +# Globals |
| 16 | +audio_data = None |
| 17 | +playing = False |
| 18 | +start_frame = 0 |
| 19 | +frame_count = int(sample_rate * 0.1) # smaller chunks for smoother updates |
| 20 | +semitone_ratio = 2 ** (1 / 12) |
14 | 21 |
|
15 | | - # Resample using scipy |
16 | | - resampled = scipy.signal.resample(raw, new_length) |
| 22 | +# Load audio |
| 23 | +def load_audio(): |
| 24 | + global audio_data, start_frame |
| 25 | + file_path = filedialog.askopenfilename(filetypes=[("WAV files", "*.wav")]) |
| 26 | + if not file_path: |
| 27 | + return |
| 28 | + audio = AudioSegment.from_file(file_path).set_channels(1).set_frame_rate(sample_rate) |
| 29 | + raw = np.array(audio.get_array_of_samples()).astype(np.float32) |
| 30 | + audio_data = raw |
| 31 | + start_frame = 0 |
| 32 | + status_label.config(text=f"Loaded: {file_path.split('/')[-1]}") |
| 33 | + start_playback() |
17 | 34 |
|
18 | | - # Convert to int16 for pygame compatibility |
19 | | - resampled = resampled.astype(np.int16) |
| 35 | +# Resample chunk with pitch shift |
| 36 | +def resample_audio(data, pitch_factor): |
| 37 | + new_len = int(len(data) / pitch_factor) |
| 38 | + return resample(data, new_len) |
20 | 39 |
|
21 | | - # Convert back to a Sound object |
22 | | - return pygame.sndarray.make_sound(resampled) |
| 40 | +# Start playback |
| 41 | +def start_playback(): |
| 42 | + global playing |
| 43 | + if audio_data is None: |
| 44 | + status_label.config(text="No audio loaded.") |
| 45 | + return |
23 | 46 |
|
24 | | -# --- Usage --- |
25 | | -pygame.init() |
26 | | -pygame.mixer.init(frequency=44100, size=-16, channels=2) |
| 47 | + if not playing: |
| 48 | + playing = True |
| 49 | + threading.Thread(target=play_loop, daemon=True).start() |
27 | 50 |
|
28 | | -# Load original sound |
29 | | -original = pygame.mixer.Sound("./assets/audio/(radzlan - Miami Hotline Vol.3 (feat. Demonicity)) 673473_-Miami-Hotline--Vol3.wav") |
| 51 | +# Stop playback |
| 52 | +def stop_audio(): |
| 53 | + global playing |
| 54 | + playing = False |
| 55 | + pygame.mixer.stop() |
30 | 56 |
|
31 | | -# Resample (e.g., 1.5x pitch and speed) |
32 | | -new_sound = resample_sound(original, pitch_factor=1.5) |
| 57 | +# Main loop |
| 58 | +def play_loop(): |
| 59 | + global start_frame |
| 60 | + while playing and start_frame < len(audio_data): |
| 61 | + semitone_step = semitone_slider.get() |
| 62 | + pitch_factor = 2 ** (semitone_step / 12) |
33 | 63 |
|
34 | | -# Play new sound |
35 | | -new_sound.play() |
| 64 | + chunk = audio_data[start_frame:start_frame + frame_count] |
| 65 | + if len(chunk) == 0: |
| 66 | + break |
36 | 67 |
|
37 | | -# Keep program running while sound plays |
38 | | -pygame.time.wait(int(new_sound.get_length() * 1000)) |
39 | | -pygame.quit() |
| 68 | + # Resample for pitch |
| 69 | + pitched = resample_audio(chunk, 1 / pitch_factor) |
| 70 | + pitched = np.clip(pitched, -32768, 32767).astype(np.int16) |
| 71 | + |
| 72 | + sound = pygame.mixer.Sound(buffer=pitched.tobytes()) |
| 73 | + sound.play() |
| 74 | + |
| 75 | + time.sleep(len(pitched) / sample_rate) |
| 76 | + start_frame += frame_count |
| 77 | + |
| 78 | + playing = False |
| 79 | + |
| 80 | +# GUI |
| 81 | +root = tk.Tk() |
| 82 | +root.title("Live Pitch Shift Player") |
| 83 | + |
| 84 | +load_btn = tk.Button(root, text="Load Audio", command=load_audio) |
| 85 | +load_btn.pack() |
| 86 | + |
| 87 | +stop_btn = tk.Button(root, text="Stop", command=stop_audio) |
| 88 | +stop_btn.pack() |
| 89 | + |
| 90 | +semitone_slider = tk.Scale(root, from_=-12, to=12, orient=tk.HORIZONTAL, label="Semitone Shift") |
| 91 | +semitone_slider.pack() |
| 92 | + |
| 93 | +status_label = tk.Label(root, text="No audio loaded.") |
| 94 | +status_label.pack() |
| 95 | + |
| 96 | +root.mainloop() |
0 commit comments