commit 2cdb0235c548791266ae03e20273e4f844c95ad3 Author: CroneKorkN Date: Fri May 30 19:00:15 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..363b17d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.png +*.flac +*.wav diff --git a/create_chunks.py b/create_chunks.py new file mode 100644 index 0000000..b22792a --- /dev/null +++ b/create_chunks.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +import os +import subprocess +import datetime +import pytz + +OUTDIR = "chunks_unprocessed" +DURATION = 10800 # 3 Stunden +SOURCE = "pulse" # funktioniert mit PulseAudio + +os.makedirs(OUTDIR, exist_ok=True) + +def get_next_section_start(now): + hour = now.hour + next_boundary = (hour // 3 + 1) * 3 + if next_boundary >= 24: + next_time = now.replace(day=now.day, hour=0, minute=0, second=0, microsecond=0) + datetime.timedelta(days=1) + else: + next_time = now.replace(hour=next_boundary, minute=0, second=0, microsecond=0) + return next_time + +def record_section(): + tz = pytz.timezone("Europe/Berlin") + now = datetime.datetime.now(tz) + timestamp = now.strftime("%Y%m%d-%H%M") + filename = os.path.join(OUTDIR, f"{timestamp}.flac") + print(f"🎙️ Starte Aufnahme: {filename}") + + next_start = get_next_section_start(now) + duration_seconds = int((next_start - now).total_seconds()) + + cmd = [ + "ffmpeg", + "-f", "pulse", + "-i", "alsa_input.usb-HANMUS_USB_AUDIO_24BIT_2I2O_1612310-00.analog-stereo", + "-ac", "1", + "-ar", "96000", + "-sample_fmt", "s32", + "-t", str(duration_seconds), + "-c:a", "flac", + "-compression_level", "12", + filename + ] + subprocess.run(cmd) + print(f"✅ Aufnahme abgeschlossen: {filename}") + +def main(): + while True: + record_section() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/process_chunks.py b/process_chunks.py new file mode 100755 index 0000000..b187c9e --- /dev/null +++ b/process_chunks.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Erkennt 211 Hz + 422 Hz (Oberton) in WAV-Dateien. +Speichert WAV + PNG nur bei Erkennung. +Blockiert Folgetreffer für definierte Zeit (SKIP_SECONDS). +""" + +import numpy as np +import soundfile as sf +from scipy.fft import fft, fftfreq +import matplotlib.pyplot as plt +import os + +# === Konfiguration === +FILENAME = "1b.flac" +TARGET_FREQ = 211 +OCTAVE_FREQ = TARGET_FREQ * 2 +TOLERANCE = 1 +THRESHOLD_BASE = 0.3 +THRESHOLD_OCT = THRESHOLD_BASE / 10 +CHUNK_SECONDS = 2 +CLIP_PADDING_BEFORE = 2 +CLIP_PADDING_AFTER = 8 +SKIP_SECONDS = 10 +OUTDIR = "events" + +os.makedirs(OUTDIR, exist_ok=True) + +# === WAV/Audio-Datei laden === +data, rate = sf.read(FILENAME, dtype='float32') +if data.ndim > 1: + data = data.mean(axis=1) + +samples_per_chunk = int(rate * CHUNK_SECONDS) +total_chunks = len(data) // samples_per_chunk + +detections = [] +next_allowed_time = 0 # für Skip-Logik + +# === Analyse-Loop === +for i in range(total_chunks): + timestamp = i * CHUNK_SECONDS + if timestamp < next_allowed_time: + continue + + segment = data[i * samples_per_chunk : (i + 1) * samples_per_chunk] + if len(segment) == 0: + continue + + freqs = fftfreq(len(segment), d=1/rate) + fft_vals = np.abs(fft(segment)) + + pos_mask = freqs > 0 + freqs = freqs[pos_mask] + fft_vals = fft_vals[pos_mask] + + peak_freq = freqs[np.argmax(fft_vals)] + peak_mag = np.max(fft_vals) + + # Energien normiert + mask_base = (freqs >= TARGET_FREQ - TOLERANCE) & (freqs <= TARGET_FREQ + TOLERANCE) + energy_base = np.mean(fft_vals[mask_base]) / peak_mag + + mask_oct = (freqs >= OCTAVE_FREQ - TOLERANCE) & (freqs <= OCTAVE_FREQ + TOLERANCE) + energy_oct = np.mean(fft_vals[mask_oct]) / peak_mag + + is_peak_near_target = TARGET_FREQ - TOLERANCE <= peak_freq <= TARGET_FREQ + TOLERANCE + detected = is_peak_near_target and energy_base > THRESHOLD_BASE and energy_oct > THRESHOLD_OCT + + if detected: + detections.append((timestamp, round(energy_base, 4), round(energy_oct, 4), round(peak_freq, 2))) + next_allowed_time = timestamp + SKIP_SECONDS + + # Ausschnitt extrahieren + start = max(0, int((timestamp - CLIP_PADDING_BEFORE) * rate)) + end = min(len(data), int((timestamp + CLIP_PADDING_AFTER) * rate)) + clip = (data[start:end] * 32767).astype(np.int16) + + base_filename = os.path.join(OUTDIR, f"event_{int(timestamp):04}s") + wav_name = f"{base_filename}.wav" + png_name = f"{base_filename}.png" + + # WAV speichern + sf.write(wav_name, clip, rate, subtype="PCM_24") + print(f"🟢 WAV gespeichert: {wav_name} (211Hz: {energy_base:.4f}, 422Hz: {energy_oct:.4f}, Peak: {peak_freq:.1f} Hz)") + + # PNG Spektrogramm + plt.figure(figsize=(10, 4)) + # Verstärke das Signal künstlich, um schwache Ereignisse im dB-Spektrum sichtbarer zu machen + plt.specgram((clip / 32767.0), NFFT=32768, Fs=rate, noverlap=512, cmap="plasma", vmin=-80, vmax=-35) + plt.title(f"Ereignis @ {timestamp:.2f}s") + plt.xlabel("Zeit (s)") + plt.ylabel("Frequenz (Hz)") + plt.ylim(0, 1000) + plt.colorbar(label="Intensität (dB)") + plt.tight_layout() + plt.savefig(png_name) + plt.close() + print(f"📷 PNG gespeichert: {png_name}") + +# === Zusammenfassung === +print("\n🎯 Erkennungen:") +for ts, eb, eo, pf in detections: + print(f"- {ts:.2f}s | 211Hz: {eb} | 422Hz: {eo} | Peak: {pf:.1f} Hz") + +if not detections: + print("→ Keine gültigen Ereignisse erkannt.") \ No newline at end of file