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 =)