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 símbolos 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 símbolos 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)