gnosticdev commited on
Commit
142f56f
verified
1 Parent(s): 1e62b0b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +99 -85
app.py CHANGED
@@ -4,90 +4,104 @@ import librosa
4
  from scipy.signal import find_peaks
5
  from sklearn.cluster import KMeans
6
 
7
- def decodificar(audio):
8
-
9
- if audio is None:
10
- return "No audio"
11
-
12
- path = audio
13
-
14
- y, sr = librosa.load(path, sr=None)
15
-
16
- frame = int(sr * 0.04)
17
-
18
- stft = np.abs(
19
- librosa.stft(
20
- y,
21
- n_fft=frame*2,
22
- hop_length=frame
23
- )
24
- )
25
-
26
- freqs = librosa.fft_frequencies(sr=sr)
27
-
28
- tonos = []
29
-
30
- for f in stft.T:
31
-
32
- if np.max(f) == 0:
33
- continue
34
-
35
- f = f / np.max(f)
36
-
37
- peaks, _ = find_peaks(f, height=0.2)
38
-
39
- if len(peaks) == 0:
40
- continue
41
-
42
- peak_freqs = freqs[peaks]
43
-
44
- peak_freqs = peak_freqs[
45
- (peak_freqs > 300) &
46
- (peak_freqs < 4000)
47
- ]
48
-
49
- if len(peak_freqs):
50
- tonos.append(peak_freqs[0])
51
-
52
- if len(tonos) < 10:
53
- return "Sin se帽al tonal clara"
54
-
55
- tonos = np.array(tonos).reshape(-1,1)
56
-
57
- kmeans = KMeans(n_clusters=12, n_init=10)
58
- kmeans.fit(tonos)
59
-
60
- centros = sorted(kmeans.cluster_centers_.flatten())
61
-
62
- letras = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
63
-
64
- texto=""
65
-
66
- for f in tonos.flatten():
67
-
68
- cercano = min(centros, key=lambda x: abs(x-f))
69
- idx = centros.index(cercano)
70
-
71
- if idx < len(letras):
72
- texto += letras[idx]
73
-
74
- return texto
75
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  with gr.Blocks() as demo:
78
-
79
- gr.Markdown("# Decodificador de tonos estilo radio digital")
80
-
81
- audio = gr.Audio(type="filepath")
82
-
83
- boton = gr.Button("Decodificar")
84
-
85
- salida = gr.Textbox(lines=10)
86
-
87
- boton.click(
88
- decodificar,
89
- inputs=audio,
90
- outputs=salida
91
- )
92
-
93
- demo.launch()
 
4
  from scipy.signal import find_peaks
5
  from sklearn.cluster import KMeans
6
 
7
+ class DigitalToneDecoder:
8
+ def __init__(self, frame_ms=40, min_freq=300, max_freq=4000, peak_threshold=0.2, symbols=16):
9
+ self.frame_ms = frame_ms
10
+ self.min_freq = min_freq
11
+ self.max_freq = max_freq
12
+ self.peak_threshold = peak_threshold
13
+ self.symbols = symbols
14
+
15
+ def cargar_audio(self, path):
16
+ y, sr = librosa.load(path, sr=None, mono=True)
17
+ self.sr = sr
18
+ self.y = y
19
+ return y, sr
20
+
21
+ def calcular_stft(self):
22
+ frame = int(self.sr * self.frame_ms / 1000)
23
+ stft = np.abs(librosa.stft(self.y, n_fft=frame*2, hop_length=frame, window="hann"))
24
+ freqs = librosa.fft_frequencies(sr=self.sr)
25
+ return stft, freqs
26
+
27
+ def detectar_tonos(self, stft, freqs):
28
+ tonos = []
29
+ for frame in stft.T:
30
+ if np.max(frame) == 0:
31
+ continue
32
+ frame = frame / np.max(frame)
33
+ peaks, _ = find_peaks(frame, height=self.peak_threshold)
34
+ if len(peaks) == 0:
35
+ continue
36
+ peak_freqs = freqs[peaks]
37
+ peak_freqs = peak_freqs[(peak_freqs > self.min_freq) & (peak_freqs < self.max_freq)]
38
+ if len(peak_freqs):
39
+ tonos.append(peak_freqs[0])
40
+ return np.array(tonos)
41
+
42
+ def crear_simbolos(self, tonos):
43
+ if len(tonos) < self.symbols:
44
+ return sorted(tonos)
45
+ tonos = tonos.reshape(-1, 1)
46
+ kmeans = KMeans(n_clusters=self.symbols, n_init=10)
47
+ kmeans.fit(tonos)
48
+ return sorted(kmeans.cluster_centers_.flatten())
49
+
50
+ def decodificar(self, tonos, centros):
51
+ letras = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "
52
+ texto = ""
53
+ for f in tonos:
54
+ cercano = min(centros, key=lambda x: abs(x - f))
55
+ idx = centros.index(cercano)
56
+ texto += letras[idx % len(letras)]
57
+ return texto
58
+
59
+ def analizar(audio_path, progress=gr.Progress()):
60
+ if audio_path is None:
61
+ return "鈿狅笍 No hay audio"
62
+
63
+ progress(0.2, desc="Cargando audio...")
64
+ decoder = DigitalToneDecoder()
65
+ decoder.cargar_audio(audio_path)
66
+
67
+ progress(0.4, desc="Calculando STFT...")
68
+ stft, freqs = decoder.calcular_stft()
69
+
70
+ progress(0.6, desc="Detectando tonos...")
71
+ tonos = decoder.detectar_tonos(stft, freqs)
72
+
73
+ if len(tonos) == 0:
74
+ return "馃挙 No se detectaron tonos en el rango 煤til (300-4000Hz)"
75
+
76
+ progress(0.8, desc="Agrupando frecuencias...")
77
+ centros = decoder.crear_simbolos(tonos)
78
+
79
+ progress(1.0, desc="Decodificando...")
80
+ texto = decoder.decodificar(tonos, centros)
81
+
82
+ reporte = f"馃搳 **DATOS T脡CNICOS:**\n"
83
+ reporte += f"- Tonos detectados: {len(tonos)}\n"
84
+ reporte += f"- Clusters 煤nicos: {len(centros)}\n"
85
+ reporte += f"- Frecuencias base: {[f'{c:.1f}Hz' for c in centros]}\n\n"
86
+ reporte += f"馃敜 **SECUENCIA DECODIFICADA:**\n\n`{texto}`\n\n"
87
+ reporte += f"*Nota: Las letras se asignan por cluster de frecuencia, no por voz humana.*"
88
+
89
+ return reporte
90
 
91
  with gr.Blocks() as demo:
92
+ gr.Markdown("""
93
+ # 馃摗 Decodificador de Tonos Digitales (DSP Real)
94
+ ## STFT + Detecci贸n de Picos + KMeans Clustering
95
+
96
+ *Basado en modos digitales de radio (PSK31, RTTY).*
97
+ *Decodifica frecuencias dominantes a s铆mbolos. La interpretaci贸n es tuya.*
98
+ """)
99
+
100
+ audio = gr.Audio(label="Audio", type="filepath", sources=["upload", "microphone"])
101
+ btn = gr.Button("Decodificar", variant="primary")
102
+ output = gr.Textbox(label="Resultado", lines=12)
103
+
104
+ btn.click(analizar, inputs=audio, outputs=output)
105
+
106
+ if __name__ == "__main__":
107
+ demo.launch()