Cómo crear un generador de ondas sinusoidales que pueda hacer una transición suave entre frecuencias

27

Puedo escribir un generador de onda sinusoidal básica para audio, pero quiero que sea capaz de hacer una transición suave de una frecuencia a otra. Si dejo de generar una frecuencia e inmediatamente cambio a otra, habrá una discontinuidad en la señal y se escuchará un "clic".

Mi pregunta es, ¿cuál es un buen algoritmo para generar una onda que comienza en, digamos 250Hz, y luego pasa a 300Hz, sin introducir ningún clic. Si el algoritmo incluye un tiempo de deslizamiento / portamento opcional, mucho mejor.

Puedo pensar en algunos enfoques posibles, como el sobremuestreo seguido de un filtro de paso bajo, o tal vez usando una tabla de ondas, pero estoy seguro de que este es un problema lo suficientemente común como para que haya una forma estándar de abordarlo.

Mark Heath
fuente
2
¿Por qué no usaste la transición de frecuencia lineal durante el período de transición? Por ejemplo, necesita transitar desde la frecuencia f0 en el tiempo t0 a la frecuencia f1 en el tiempo t1, entonces ¿por qué no simplemente introducir una frecuencia de transición f (t) = f0 * (1-q) + f1 * q, donde q = (t -t0) / (t1-t0), luego produce una señal A (t) = sin (2 * Pi * f (t) * t)?
mbaitoff

Respuestas:

24

Un enfoque que he usado en el pasado es mantener un acumulador de fase que se usa como índice en una tabla de búsqueda de formas de onda. Se agrega un valor delta de fase al acumulador en cada intervalo de muestra:

phase_index += phase_delta

Para cambiar la frecuencia, cambie el delta de fase que se agrega al acumulador de fase en cada muestra, p. Ej.

phase_delta = N * f / Fs

dónde:

phase_delta is the number of LUT samples to increment
freq is the desired output frequency
Fs is the sample rate

Esto garantiza que la forma de onda de salida sea continua incluso si cambia phase_delta dinámicamente, por ejemplo, para cambios de frecuencia, FM, etc.

Para cambios más suaves en la frecuencia (portamento), puede aumentar el valor de phase_delta entre su valor antiguo y el nuevo valor en un número adecuado de intervalos de muestras en lugar de cambiarlo instantáneamente.

Tenga en cuenta que phase_indexy phase_deltaambos tienen un número entero y un componente fraccional, es decir, deben ser de coma flotante o punto fijo. La parte entera de phase_index (tamaño de tabla de módulo) se usa como índice en la forma de onda LUT, y la parte fraccional puede usarse opcionalmente para la interpolación entre valores LUT adyacentes para una salida de mayor calidad y / o un tamaño LUT más pequeño.

Paul R
fuente
gracias, esperaba que la respuesta pudiera involucrar LUT. Estaba pensando en usar una LUT que contiene una forma de onda a 1Hz (es decir, entradas Fs). ¿Existe una regla general que rija el tamaño óptimo de la LUT?
44
Depende de varios factores: qué SNR está buscando, ya sea una onda sinusoidal pura o una forma de onda más compleja, si planea interpolar entre entradas LUT adyacentes o simplemente truncar, etc. También depende de si solo va a tenga una sola tabla de cuadrante y maneje la aritmética de indexación y la inversión de signos usted mismo, o tenga una tabla completa de cuatro cuadrantes. Personalmente, comenzaría con una tabla de cuatro cuadrantes de 1024 puntos (NB: 2 ^ N es bueno para la indexación de módulos) sin interpolación, ya que esto es muy simple y debería dar buenos resultados, por ejemplo, para audio de "consumidor" de 16 bits.
Paul R
1
Buena respuesta, Paul. También hay una pregunta similar sobre el tema que se publicó hace un tiempo; Más información siempre ayuda.
Jason R
44
Otra forma de ver este enfoque es una emulación de un oscilador controlado por voltaje (VCO). La frecuencia de salida de un VCO depende del voltaje de entrada (generalmente una función lineal del voltaje de entrada) pero la señal de salida tiene una fase continua , incluso si el voltaje de entrada cambia instantáneamente. La salida es donde es una función continua del tiempo, mientras que la frecuencia de salida es la derivada de la fase, y es igual a donde es la frecuencia de reposo.
sin(ϕ(t))=sin(0tω0+kx(τ)dτ)
ϕ(t)
ω0+kx(t)
ω0
Dilip Sarwate
1
Tuve el mismo problema, gracias por la idea del acumulador (estaba usando el cálculo directo, que no funcionó debido a aproximaciones): jsfiddle.net/sebpiq/p3ND5/12
sebpiq
12

Una de las mejores formas de crear una onda sinusoidal es usar un fasor complejo con actualización recursiva. Es decir

z[n+1]=z[n]Ω

donde z [n] es el fasor, , siendo la frecuencia angular del oscilador en radianes el índice de muestra. Tanto la parte real como la imaginaria de son ondas sinusoidales, están desfasadas 90 grados. Muy conveniente si necesita tanto seno como coseno. Un solo cálculo de muestra solo requiere 4 múltiplos y 4 sumas y es MUCHO más barato que cualquier cosa que contenga sin () cos () o tablas de búsqueda. El problema potencial es que la amplitud puede variar con el tiempo debido a problemas de precisión numérica. Sin embargo, hay una manera bastante sencilla de reparar eso. Digamos que . Sabemos que debería tener una magnitud de unidad, es decir Ω=exp(jω)nωnz[n]z[n]=a+jbz[n]

aa+bb=1

Por lo tanto, podemos verificar de vez en cuando si ese es el caso y corregir en consecuencia. La corrección exacta sería

z[n]=z[n]aa+bb

Es un cálculo incómodo, pero dado que está muy cerca de la unidad, puede aproximar los términos con una expansión de Taylor alrededor de y obtenemosaa+bb1/xx=1

1x3x2

entonces la corrección se simplifica a

z[n]=z[n]3a2b22

La aplicación de esta simple corrección cada cientos de muestras mantendrá el oscilador estable para siempre.

Para variar la frecuencia continuamente, el multiplicador W debe actualizarse en consecuencia. Incluso un cambio no continuo en el multiplicador mantendrá una función continua del oscilador. Si se requiere una rampa de frecuencia, la actualización puede dividirse en unos pocos pasos o puede usar el mismo algoritmo de oscilador para actualizar el multiplicador (ya que también es un fasor complejo de ganancia unitaria).

Hilmar
fuente
gracias por esta respuesta, probablemente me tomará un poco de tiempo entender lo suficientemente bien como para convertirme en un código del mundo real, pero parece una alternativa interesante para probar.
Mark Heath el
2
Implementé esta solución en golang para referencia: github.com/rmichela/Acoustico/blob/…
Ryan Michela
Esta es una solución hermosa que, desafortunadamente, solo funciona bien si se usa una base de tiempo constante. Si no, necesita calcular un pecado y un cos para calcular la rotación compleja correcta.
Cameron Tacklind
2

De este sitio :

Para crear una transición suave de una frecuencia a otra o de una amplitud a otra, una onda sinusoidal incompleta debe modificarse con una sección adjunta para que la onda resultante después de cada iteración del bucle while finalice en el eje x.

Parece que debería funcionar.

(En realidad, si ambos están sincronizados en el eje x durante la transición, supongo que no es necesaria una transición gradual).


fuente
1
Esto dice, espere a que la sinusoide actual a la frecuencia complete un ciclo y llegue a y luego cambie a la otra sinusoide a la frecuencia . Esto mantiene efectivamente la continuidad de la fase y es probable que sea correcto para aplicaciones de audio en las que los pocos milisegundos o microsegundos de retraso entre el tiempo de conmutación deseado (ahora) y el tiempo de conmutación implementado (cuando mi sinusoide completa un ciclo) es intrascendente. Sin embargo, la diferencia podría generar problemas en otras aplicaciones. ¡Solo recuerda que una sinusoide es dos veces en un ciclo y asegúrate de elegir la correcta! 0 ω 1 0ω00ω10
Dilip Sarwate
2

Estoy de acuerdo con las sugerencias anteriores de usar un acumulador de fase. Esencialmente, la entrada de control es la cantidad de avance de fase por paso o por período de reloj (o por interrupción o lo que sea), de modo que cambiar ese valor cambia la frecuencia sin una discontinuidad en la fase. La amplitud de onda se determina a partir del valor de fase acumulado, ya sea a través de un LUT o simplemente el cálculo de sin (theta) o cos (theta).

Esto es esencialmente lo que comúnmente se conoce como un oscilador controlado numéricamente (NCO) o un sintetizador digital directo (DDS). Hacer una búsqueda web en esos términos probablemente arrojará más de lo que desea saber sobre la teoría y la práctica de hacer que funcionen bien.

Agregar un acumulador adicional puede permitir transiciones sin interrupciones entre frecuencias, como sugirió, si así lo desea, controlando la velocidad de cambio del valor de avance de fase. Esto a veces se denomina analizador diferencial digital o DDA.

Eric Jacobsen
fuente
Buena información adicional. Me alegro de verte por aquí, Eric; podríamos usar un Ministro de Algoritmos.
Jason R
1

1.er orden, debe ajustar la fase de inicio de la nueva sinusoide de frecuencia para que sea la misma que la fase de la sinusoide anterior en el primer punto de muestra de transición. Calcule la primera frecuencia y use su fase para la segunda frecuencia.

La segunda opción podría ser aumentar d_phase de una frecuencia a la siguiente en varias muestras. Esto limpiará la continuidad de la primera derivada y proporcionará un deslizamiento.

La tercera opción podría ser usar una ventana de suavizado, como un coseno elevado, en la tasa de aumento d_phase.

hotpaw2
fuente