Advertisement
Guest User

Untitled

a guest
Jun 17th, 2025
17
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.57 KB | None | 0 0
  1. import tkinter as tk
  2. from tkinter import ttk
  3. import numpy as np
  4. import aubio
  5. import pyaudio
  6. import threading
  7. import mido
  8. import time
  9. from collections import deque
  10.  
  11. class BPMDetector:
  12. def __init__(self, app):
  13. self.app = app
  14. self.running = False
  15. self.buffer_size = 1024
  16. self.sample_rate = 44100
  17. self.tempo_obj = aubio.tempo("default", 2048, 1024, self.sample_rate)
  18. self.p = pyaudio.PyAudio()
  19. self.device_indices = self._get_input_devices()
  20. self.bpm_history = deque(maxlen=5)
  21.  
  22. def _get_input_devices(self):
  23. devices = {}
  24. for i in range(self.p.get_device_count()):
  25. info = self.p.get_device_info_by_index(i)
  26. if info['maxInputChannels'] > 0:
  27. devices[info['name']] = i
  28. return devices
  29.  
  30. def get_device_names(self):
  31. return list(self.device_indices.keys())
  32.  
  33. def start(self):
  34. self.running = True
  35. threading.Thread(target=self._process_audio, daemon=True).start()
  36.  
  37. def stop(self):
  38. self.running = False
  39.  
  40. def _send_midi_clock_loop(self, bpm):
  41. if not self.app.send_midi.get():
  42. return
  43. midi_out = mido.open_output() # Default MIDI output
  44. interval = 60.0 / bpm / 24.0 # 24 MIDI clocks per quarter note
  45. while self.running and self.app.send_midi.get():
  46. midi_out.send(mido.Message('clock'))
  47. time.sleep(interval)
  48.  
  49. def _process_audio(self):
  50. device_name = self.app.selected_device.get()
  51. device_index = self.device_indices.get(device_name, None)
  52. if device_index is None:
  53. return
  54.  
  55. device_info = self.p.get_device_info_by_index(device_index)
  56. num_channels = int(device_info.get('maxInputChannels', 1))
  57. selected_channel = int(self.app.selected_channel.get()) - 1 # 0-based
  58.  
  59. stream = self.p.open(format=pyaudio.paFloat32,
  60. channels=num_channels,
  61. rate=self.sample_rate,
  62. input=True,
  63. input_device_index=device_index,
  64. frames_per_buffer=self.buffer_size)
  65.  
  66. last_beat_time = 0
  67. prev_interval = None
  68. self.bpm_history.clear()
  69.  
  70. while self.running:
  71. try:
  72. audio_data = stream.read(self.buffer_size, exception_on_overflow=False)
  73. samples = np.frombuffer(audio_data, dtype=aubio.float_type)
  74. if num_channels > 1:
  75. samples = samples[selected_channel::num_channels]
  76. is_beat = self.tempo_obj(samples)
  77.  
  78. if is_beat:
  79. current_time = time.time()
  80. interval = current_time - last_beat_time
  81.  
  82. plausible = True
  83. if prev_interval is not None:
  84. if abs(interval - 2 * prev_interval) < 0.15:
  85. interval = interval / 2
  86. elif interval > 1.8 * prev_interval:
  87. plausible = False
  88.  
  89. if plausible:
  90. last_beat_time = current_time
  91. prev_interval = interval
  92.  
  93. if 0.3 < interval < 2.0: # BPM between 30 and 200
  94. bpm = 60.0 / interval
  95. self.bpm_history.append(bpm)
  96.  
  97. if len(self.bpm_history) >= 2:
  98. avg_bpm = np.median(self.bpm_history)
  99. if avg_bpm < 80:
  100. double_bpm = avg_bpm * 2
  101. if 80 <= double_bpm <= 180:
  102. avg_bpm = double_bpm
  103. self.app.update_bpm_display(f"{avg_bpm:.0f}")
  104. elif len(self.bpm_history) < 2:
  105. self.app.update_bpm_display("---")
  106.  
  107. except Exception as e:
  108. self.app.update_bpm_display("---")
  109.  
  110. stream.stop_stream()
  111. stream.close()
  112.  
  113.  
  114. class BPMApp:
  115. def __init__(self, root):
  116. self.root = root
  117. self.root.title("BPM Detector")
  118. self.root.geometry("360x240")
  119. self.root.resizable(False, False)
  120.  
  121. self.selected_device = tk.StringVar()
  122. self.selected_channel = tk.StringVar(value="1")
  123. self.send_midi = tk.BooleanVar(value=True)
  124.  
  125. self.label_bpm = tk.Label(root, text="---", font=("Helvetica", 48))
  126. self.label_bpm.pack(pady=10)
  127.  
  128. frm = tk.Frame(root)
  129. frm.pack()
  130.  
  131. tk.Label(frm, text="Audio Device:").grid(row=0, column=0, sticky="e", padx=5, pady=5)
  132. self.device_menu = ttk.Combobox(frm, textvariable=self.selected_device, width=25, state="readonly")
  133. self.device_menu.grid(row=0, column=1, pady=5)
  134.  
  135. tk.Label(frm, text="Channel:").grid(row=1, column=0, sticky="e", padx=5, pady=5)
  136. self.channel_menu = ttk.Combobox(frm, textvariable=self.selected_channel, width=25, state="readonly",
  137. values=[str(i) for i in range(1, 17)])
  138. self.channel_menu.grid(row=1, column=1, pady=5)
  139.  
  140. self.checkbox = tk.Checkbutton(root, text="Send MIDI Clock", variable=self.send_midi)
  141. self.checkbox.pack(pady=10)
  142.  
  143. self.detector = BPMDetector(self)
  144. devices = self.detector.get_device_names()
  145. self.device_menu['values'] = devices
  146. if devices:
  147. self.device_menu.set(devices[0])
  148.  
  149. self.device_menu.bind("<<ComboboxSelected>>", self.update_channel_menu)
  150. self.update_channel_menu() # Initial call
  151.  
  152. self.detector.start()
  153. self.root.protocol("WM_DELETE_WINDOW", self.on_close)
  154.  
  155. def update_bpm_display(self, bpm):
  156. self.label_bpm.config(text=bpm)
  157.  
  158. def on_close(self):
  159. self.detector.stop()
  160. self.root.destroy()
  161.  
  162. def update_channel_menu(self, event=None):
  163. device_name = self.selected_device.get()
  164. if not device_name:
  165. return
  166. device_index = self.detector.device_indices.get(device_name, None)
  167. if device_index is None:
  168. return
  169. info = self.detector.p.get_device_info_by_index(device_index)
  170. channels = int(info.get('maxInputChannels', 1))
  171. self.channel_menu['values'] = [str(i) for i in range(1, channels + 1)]
  172. if int(self.selected_channel.get()) > channels:
  173. self.selected_channel.set("1")
  174.  
  175. root = tk.Tk()
  176. app = BPMApp(root)
  177. root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement