Como generar una onda de Audio para archivos WAV (PCM)
Como en su momento no encontré nada sobre este tema, aquí va una pequeña explicación de como generar audio modulado en PCM, lo que usa por ejemplo, el formato WAV.
Partiremos de algunos conceptos básicos que supongo que conoceréis:
Sobre las ondas de sonido:
* Frecuencia de la onda: veces en un segundo que la onda se repite (o que
pasa por el valor intermedio).
* Amplitud de la onda: en este caso, la distancia entre el punto más alto
y el más bajo, determinará el volumen.
*
Sobre su representación en el archivo:
* Framerate: número de veces por segundo que se representa la onda (a más
framerate, más cercano es a la onda original y mejor calidad).
* Canales: número de salidas del audio (1: Mono, 2: Stereo).
*
* Ancho de canal: bits que se dedican a cada frame de cada canal (suele ser
de 16 bits).
*
Para la manipulación de los archivos WAV se puede usar la librería_estándar de_python_wave, haciendo que el manejo del archivo sea muy simple, por lo que me centraré en la generación de la onda, que es lo que no implementa.
Supongamos, por ejemplo, que queremos generar una onda de 300Hz que dure 1 segundo, lo primero es generar una onda completa que nos servirá de base:
- Si la frecuencia es cero, la onda es plana, así que se representaría como un solo valor, 0
Sino, tendremos para generar una onda completa, para ello se necesitan tantos frames como framerate/frecuencia, para que la onda esté completa, además, como la onda es sinusoidal, tomará valores de seno de entre 0 y 2 PI , así que por último hay que partir de la base b = (2 * pi) / frames y multiplicarlo por cada frame, el código de esta parte quedaría así:
import math
def getBase(freq, framerate):
# Si no es una onda plana
if (freq != 0):
base = []
# Se obtiene el numero de frames a generar
frames = int(framerate / abs(freq))
# Y la base de los valores que tomaran
p = (math.pi * 2) / frames
# Por ultimo se genera la onda sinusoidal entre seno de 0 y 2*Pi
for i in xrange(frames):
base.append(math.sin(i * p))
# Si es una onda plana
else:
base = [0]
return base
El siguiente paso es añadir a la onda base el volumen y los canales, para añadir el volumen simplemente hay que multiplicar el valor de un frame por el volumen, el valor del volumen (usando PCM con signo, como en WAV) puede variar entre 0 y 2**(ancho de banda - 1) - 1, para 8 bits sería entre 0 y 127, y para 16 bits, entre 0 y 32767. Por ultimo, solo hay que repetir el frame por cada canal (aunque si va a ser la misma onda no hay motivos para usar múltiples canales...)
Fija el volumen y los canales
def setVolChan(src, vol, n_can):
out = []
for i in src:
n = int(i * vol) # Se aplica el volumen
out.append([n] * n_can) # Y se replica para otros canales
return out
A continuación hay que extender la onda para todo el tiempo que cubra
Extiende la base a todo el tiempo
def mkFrames(base, t, framerate):
base_flen = len(base) / float(framerate) # Lo que dura la base
n = int(round(t / base_flen)) # Las veces que hay que repetirla
return base * n
Y solo queda convertir la onda a una cadena (es el formato que utiliza la librería wave), si los canales fueran de 8 bits se podría usar una funcion de array directamente:
cadena = array.array('b', original).tostring()
Sino, hay que hacerlo a mano
def arr2Stream(sample, width):
stream = ""
for i in sample: # Por cada frame
for k in i: # Y cada canal
for j in xrange(width):
stream += chr(k & 255 ) # Se extrae un byte
k >>= 8
return stream
Un código completo sería (los cambios se hacen en las variables al principio del codigo), entonces [audio_sample.py]:
!/usr/bin/env python
canales = 2 # Stereo
framerate = 11025
ancho = 16
volumen = 10000 # Para un ancho de canal de 16 bits
frecuencia = 300 # Hz
duracion = 2 # Segundos
archivo = "salida.wav"
def arr2Stream(sample, width):
stream = ""
for i in sample: # Por cada frame
for k in i: # Y cada canal
for j in xrange(width):
stream += chr(k & 255 ) # Se extrae un byte
k >>= 8
return stream
def getBase(freq, framerate):
# Si no es una onda plana
if (freq != 0):
base = []
# Se obtiene el numero de frames a generar
frames = int(framerate / abs(freq))
# Y la base de los valores que tomaran
p = (math.pi * 2) / frames
# Por ultimo se genera la onda sinusoidal entre seno de 0 y 2*Pi
for i in xrange(frames):
base.append(math.sin(i * p))
# Si es una onda plana
else:
base = [0]
return base
Fija el volumen y los canales
def setVolChan(src, vol, n_can):
out = []
for i in src:
n = int(i * vol) # Se aplica el volumen
out.append([n] * n_can) # Y se replica para otros canales
return out
Extiende la base a todo el tiempo
def mkFrames(base, t, framerate):
base_flen = len(base) / float(framerate) # Lo que dura la base
n = int(round(t / base_flen)) # Las veces que hay que repetirla
return base * n
def genFrames(freq, t, framerate, vol, chan, width):
s = getBase(freq, framerate)
s = setVolChan(s, vol, chan)
s = mkFrames( s, t, framerate)
return arr2Stream(s, width)
import math, wave
w = wave.open(archivo, "wb")
w.setnchannels(canales)
w.setsampwidth(ancho / 8)
w.setframerate(framerate)
w.writeframes(genFrames(frecuencia, duracion, framerate, volumen, canales, ancho / 8))
w.close()
Ve con root =)