Morse - Decodificador de un mensaje Morse desde el sonido en archivo.wav
Para analizar el sonido de un mensaje Morse, se procede de forma semejante al decodificador de un tono morse, revisando la duración de cada símbolo: punto o espacio tienen duración de una marca, la raya tiene una duración de 3 marcas, los simbolos se separan por una pausa con un intervalo de una marca.

Se divide el sonido del archivo.wav en partes o ventanas, usando la función separaventanas().

Cada ventana se analiza con la transformada de Fourier para encontrar la frecuencia del sonido mas fuerte o marcas de puntos '.', '-', ó ' '. El resultado de análisis de las ventanas es un vector con la marca de frecuencia más fuerte para cada una de las ventanas. (función marcasdeventanas())

La frecuencia o 'tono frecuente' se usa como una referencia para diferenciar si existe una señal para un punto o raya Morse. El tono frecuente se obtiene mediante la "moda" en el vector marcas usando scipy.stats.itemfreq() y usando la fila donde donde está la mayor cuenta usando np.argmax().
Para facilitar el análisis de un tono en una marca, se realiza una conversión a una secuencia binaria, usando '1' ó ,'0' si se encuentra el 'tono frecuente' en cada ventana.
Para el ejemplo, las ventanas se convierten al vector:
[1 1 1 1 1 1 0 0]
Al contar la duracion de cada intevalo, se determina si el sonido corresponde a un punto, una raya o una espacio:
Ejemplo:
. ... .--. --- .-..
Algoritmo en python
El sonido se obtiene de un archivo.wav del generador de tonos para un punto '.' o raya '-', del cual se leen los datos de sonido, y frecuencia de muestreo.
Para el ejemplo, el sonido del mensaje morse: morsetonoESPOL.wav
Las funciones para cada uno de los procesos descritos se encuentran a continuación
# Código Morse - Determina un mensaje desde sonido.wav
# propuesta: edelros@espol.edu.ec
import numpy as np
import matplotlib.pyplot as plt
import scipy.io.wavfile as waves
import scipy.fftpack as fourier
import scipy.stats as stats
def separaventanas(sonido, muestreo, duracion = 0.04, partes = 8 ):
# Divide el sonido en partes o ventanas para estudio
# duracion = 0.04 # segundos de un punto
# partes = 8 # divisiones de un punto
# Extraer ventanas para espectro por partes
tventana = duracion/partes
mventana = int(muestreo * tventana) # muestras de una ventana
# Ajuste de muestras de ventanas para matriz
anchosonido = int(len(sonido)/mventana)*mventana
sonidoajuste = np.resize(sonido,anchosonido)
ventanas = np.reshape(sonidoajuste,(-1,mventana))
# -1 indica que calcule las filas
return(ventanas)
def marcasdeventanas(ventanas, muestreo):
# Analiza con FFT todas las ventanas del sonido
filas, columnas = np.shape(ventanas)
marcas = np.zeros(filas,dtype=int)
# Espectro de Fourier de cada ventana
# frecuencias para eje frq = fourier.fftfreq(mventana, 1/muestreo)
frq = fourier.fftfreq(columnas, 1/muestreo)
for f in range(0,filas,1):
xf = fourier.fft(ventanas[f])
xf = np.abs(xf) # magnitud de xf
tono = np.argmax(xf) # tono, frecuencia mayor
marcas[f] = frq[tono]
return(marcas)
def secuenciabinaria(marcas):
# Busca el tono frecuente mayor que cero
tonosventana = stats.itemfreq(marcas)
donde = np.argmax(tonosventana[1:,1])
tonopunto = tonosventana[donde+1,0]
# Convierte los tonos a secuencia binaria
secuencia = ''
for valor in marcas:
if (valor==tonopunto):
secuencia = secuencia + '1'
else:
secuencia = secuencia + '0'
return(secuencia)
def duracionmarcas(secuencia):
# Duración de cada marca
conteo = []
caracter = '1'
if (len(secuencia)>0):
caracter = secuencia[0]
i = 0
k = 0
while (i<len(secuencia)):
if (secuencia[i]==caracter):
k = k + 1
else:
conteo.append([int(caracter),k])
if (caracter=='1'):
caracter = '0'
else:
caracter = '1'
k = 1
i = i+1
conteo = np.array(conteo)
return(conteo)
def marcasmorse(conteo):
# Determina la base de un tono
veces = stats.itemfreq(conteo[:,1])
donde = np.argmax(veces[:,1])
base = veces[donde,0]
# Genera el código Morse
tolera = 0.2 # Tolerancia en relacion
bajo = 1-tolera
alto = 1+tolera
morse = ''
for j in range(0,len(conteo)):
relacion = conteo[j,1]/base
simbolo = conteo[j,0]
if (simbolo==1):
if (relacion>(1*bajo) and relacion<(1*alto)):
morse = morse + '.'
if (relacion>(3*bajo) and relacion<(3*alto)):
morse = morse + '-'
if simbolo==0 :
if (relacion>(3*bajo) and relacion<(3*alto)):
morse = morse + ' '
if (relacion>(7*bajo) and relacion<(7*alto)):
morse=morse+' '
return(morse)
# INGRESO
# archivo = input('nombre del archivo: ')
archivo = 'morsetonoESPOL.wav'
muestreo, sonido = waves.read(archivo)
# PROCEDIMIENTO
ventanas = separaventanas(sonido, muestreo)
marcas = marcasdeventanas(ventanas, muestreo)
secuencia = secuenciabinaria(marcas)
conteo = duracionmarcas(secuencia)
morse = marcasmorse(conteo)
# SALIDA
print('codigo en morse: ')
print(morse)
codigo en morse:
. ... .--. --- .-.. .. -- .--. ..- .-.. ... .- -. -.. --- .-.. .- ... --- -.-. .. . -.. .- -.. -.. . .-.. -.-. --- -. --- -.-. .. -- .. . -. - ---
Revisión de valores en el procedimiento
Se presentan algunos de los valores intermedios para observar el proceso de detección de símbolos Morse.
# Salida /Observación intermedia
print('ventanas analizadas: ',len(marcas))
print('marcas: ', marcas)
ventanas analizadas: 3287
marcas: [400 400 400 ..., 0 0 0]
Para facilitar el proceso de detección morse se convierten las marcas a una secuencia binaria, usando como referencia la frecuencia del tono de un punto determinada en la sección anterior.
# Salida /Observación intermedia
if len(secuencia)<100:
print(secuencia)
else:
print(secuencia[0:200] + ' ... ')
11111111000000000000000000000000111111110000000011111111000000001111111110000000000000000000000011111111000000001111111111111111111111111000000011111111111111111111111110000000111111111000000000000000 ...
Cambiar la secuencia a simbolos morse, consiste en determinar la relación entre las veces que aparecen los '1's y '0's, el resultado se puede guardar en un arreglo.
La base de cuántas marcas corresponden a un punto de estima como la "moda" de las veces en que se repite un uno o un cero.
print('simbolo, cuenta:')
print(conteo)
simbolo, cuenta:
[[ 1 8]
[ 0 24]
[ 1 8]
[ 0 8]
[ 1 8]
[ 0 8]
[ 1 9]
[ 0 23]
[ 1 8]
[ 0 8]
[ 1 25]
[ 0 7]
[ 1 25]
...
# Salida
print('codigo en morse: ')
print(morse)
codigo en morse:
. ... .--. --- .-.. .. -- .--. ..- .-.. ... .- -. -.. --- .-.. .- ... --- -.-. .. . -.. .- -.. -.. . .-.. -.-. --- -. --- -.-. .. -- .. . -. - ---
Con el resultado puede usar el decodificador morse de los temas anteriores.
Referencia: Código Morse Wikipedia, Recommendation ITU-R M.1677-1 (10/2009) International Morse code, Leon-Couch, 5–9 Señalización Pasabanda Modulada Binaria (OOK)