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 =)
hola, tengo una duda, a ver si me puedes alludar.
ResponderEliminarestoy tratando de distorcionar una onda, que simplemente es cortarla despues de que pasa un rango, el concepto lo entiendo. lo que no entiendo es como puedo manipular la onda si esta representada como float en un rango de 1.0 y - 1.0 y no como un entero
No estoy seguro de entender el problema, pero creo que puedes cortar la onda en la función "setVolChan" después de "n = int(i * vol)" con algo como:
ResponderEliminarif n > loquesea:
n=loquesea
Saludos
hola, gracias por responder, si al final el problema lo resolvi así
ResponderEliminarif(in[i] > (max / 100) * por)
in[i] = (max / 100) * por;
else if(in[i] < (min / 100) * por)
in[i] = (min / 100) * por;
out[i] = in[i];
lo que no comprendia era como usar ese rango 1.0 y -1.0 ja ja estaba acostumbrado a que era un array de ints, no de floats