Karplus fuerte generación de tiras -- python campo con numpy campo con audio camp codereview Relacionados El problema

Karplus Strong pluck generation


7
vote

problema

Español

Quiero usar una implementación simple del algoritmo Karplus y fuerte para generar sonidos y acordes de aranceles.

Mi código existente parece correcto, pero es lento cuando se genera más de unos pocos arcos. ¿Hay alguna manera de mejorar el rendimiento de esto? En particular, existe una mejor manera de usar adormecido aquí, o sería algo así como una colección. Ya sea más rápido.

  char* str0  

Esto produce un archivo de sonido en char* str1 con una forma de onda como esta:

parcela de una forma de onda de suspensión

Original en ingles

I want to use a simple implementation of the Karplus-Strong algorithm to generate pluck sounds and chords.

My existing code seems correct but is slow when more than a few plucks are being generated. Is there a way to improve the performance of this. In particular is there a better way to use numpy here, or would something like a collections.deque be faster.

import numpy as np  sample_rate = 44100 damping = 0.999  def generate(f, vol, nsamples):     """Generate a Karplus-Strong pluck.      Arguments:     f -- the frequency, as an integer     vol -- volume, a float in [0.0, 1.0]     nsamples -- the number of samples to generate. To generate a pluck t     seconds long, one needs t * sample_rate samples. nsamples is an int.      Return value:     A numpy array of floats with length nsamples.     """      N = sample_rate // f     buf = np.random.rand(N) * 2 - 1     samples = np.empty(nsamples, dtype=float)      for i in range(nsamples):         samples[i] = buf[i % N]         avg = damping * 0.5 * (buf[i % N] + buf[(1 + i) % N])         buf[i % N] = avg      return samples * vol   def generate_chord(f, nsamples):     """Generate a chord of several plucks      Arguments:     f -- a (usually short) list of integer frequencies     nsamples -- the length of the chord, a chord of length t seconds needs     t * sample_rate, an integer.      Return value:     A numpy array     """     samples = np.zeros(nsamples, dtype=float)     for note in f:         samples += generate(note, 0.5, nsamples)     return samples   if __name__ == "__main__":     import matplotlib.pyplot as plt     from scipy.io.wavfile import write      strum = generate_chord(  # A Major         [220, 220 * 5 // 4, 220 * 6 // 4, 220 * 2], sample_rate * 5)     plt.plot(strum)     plt.show()      write("pluck.wav", sample_rate, strum) 

This produces a sound file in pluck.wav with a waveform like this:

Plot of a pluck waveform

        
 
 

Lista de respuestas

5
 
vote
vote
La mejor respuesta
 

Estaba tratando de mejorar su función push4 y tropezó con un error potencial. Actualiza continuamente el búfer agregando dos valores siguientes. Esto debería ser vectorial fácilmente actuando en todo el búfer al mismo tiempo, haciendo algo como esto:

  push5  

Esto difiere de su algoritmo para cada paso de tiempo (es decir, una pasada en el push6 ) solo en el último elemento. Esto se debe a que este código actúa en toda la matriz en $ t_ {i} $ para generar el búfer en $ t_ {i + 1} $, mientras que su código utiliza el valor de push7 ya de $ t_ {i + 1} $.

Si puede vivir con esta diferencia (no puedo verificar si suena diferente, ahora mismo), el tiempo de ejecución del código baja de aproximadamente 0,71 segundos a 0.20 segundos.

 

I was trying to improve your generate function and stumbled on a potential bug. You continuously update the buffer by adding two following values. This should be vectorizable easily by acting on the whole buffer at the same time, by doing something like this:

for i in range(0, nsamples - N, N):     samples[i: i + N] = buf[:]     buf = damping * 0.5 * (buf + (np.roll(buf, -1))) # fractional buffer i += N k = nsamples - i if k:     samples[i:] = buf[:k] 

This differs from your algorithm for every time step (i.e. one pass on the buf) only in the last element. This is because this code acts on the whole array at \$t_{i}\$ to generate the buffer at \$t_{i+1}\$, whereas your code uses the value of buf[0] already from \$t_{i+1}\$.

If you can live with this difference (I can not check if it sounds any different, right now), the code execution time goes down from about 0.71 seconds to 0.20 seconds.

 
 
5
 
vote

Felicitaciones

Usted sigue principalmente los estándares que son buenos. También tienes buena documentación que es genial. En general, el código lee bien y es fácilmente comprensible.

Estilo-sabio, solo tengo algunos nitpicks:

  • de PEP8 , debe nombrar sus constantes utilizando todas las tapas (< Código> SAMPLE_RATE , DAMPING );
  • No debe abreviar ese número de su variable frequency volume3 > f y vol ;
  • Es posible que desee nombrar cosas que para evitar comentarios: A_major = [220, 220 * 5 // 4, 220 * 6 // 4, 220 * 2] .

SumPlation

en Pure Python World el siguiente código:

  value = 0 for element in array:     value += compute(element) return value   

es preferido escrito como:

  return sum(compute(element) for element in array)   

Como es más limpio y más rápido. En World Numpy, es casi lo mismo. Puedes escribir:

  def generate_chord(frequencies, samples):     plucks = (generate(note, 0.5, samples) for note in frequencies)     return np.sum(plucks, axis=0)   

y obtén el mismo resultado.

Interfaz

Al leer sus doctrings, me pregunté por qué le pediría a sus usuarios (o usted mismo) pasar en una serie de muestras si lo que es más natural para ellos es usar una duración en segundos. No exponga los detalles de la implementación, como la tasa de muestra a los usuarios y que utilicen lo que es más natural para ellos.

También me pregunto por qué codifica el volumen en DAMPING0 y deje que sea variable en DAMPING1 . Si el objetivo es no tener que elegirlo para cada prueba, aún puede usar un parámetro con el valor predeterminado en lugar de codificarlo. Hará las cosas más obvias.

Por último, realmente no entiendo la necesidad de frecuencias integrales, ya que los cálculos se pueden realizar tan bien con los valores de puntos flotantes. Simplemente asegúrese de truncar el cálculo de DAMPING2 .

Yo usaría el siguiente código como base para mejoras:

  DAMPING3  

Más allá de las frecuencias

Ahora, no soy músico. E incluso si fuera, tendría que buscar las frecuencias de la nota requerida, ya que no creo que su usuario promedio los conozca por corazón. Sería mejor proporcionar constantes nombrados que recuerden que para nosotros.

leyendo en https://en.wikipedia.org/wiki/musical_note uno puede fácilmente Definir algo como:

  DAMPING4  

Pero esto no tiene en cuenta cada octava. Vamos a arreglar eso y simplificar la lectura con una DAMPING5 :

  DAMPING6  

Ahora puedo definir fácilmente un mayor usando:

  DAMPING7  

Mejoras propuestas

  DAMPING8  
 

Congratulations

You mostly follows standards which is good. You also have good documentation which is great. All in all the code reads well and is easily understandable.

Style-wise, I just have some nitpicks:

  • from PEP8, you should name your constants using all caps (SAMPLE_RATE, DAMPING);
  • you should not abreviate that much your variable names frequency and volume are to be prefered to f and vol;
  • you may want to name things to avoid comments: A_major = [220, 220 * 5 // 4, 220 * 6 // 4, 220 * 2].

Summation

In pure Python world the following code:

value = 0 for element in array:     value += compute(element) return value 

is prefered written as:

return sum(compute(element) for element in array) 

As it is both cleaner and faster. In numpy world, it is pretty much the same. You can write:

def generate_chord(frequencies, samples):     plucks = (generate(note, 0.5, samples) for note in frequencies)     return np.sum(plucks, axis=0) 

and get the same result.

Interface

When reading your docstrings, I wondered why you would ask your users (or yourself) to pass in a number of samples if what is more natural to them is to use a duration in seconds. Do not expose implementation details such as your sample rate to the users and let them use what is more natural to them.

I also wonder why you hardcode the volume in generate_chord and let it variable in generate. If the aim is to not have to choose it for each test, you can still use a parameter with default value instead of hardcoding it. It will make things more obvious.

Lastly, I don't really understand the need for integral frequencies as the computations can be performed just as well with floating point values. Just make sure to truncate the computation of N.

I would use the following code as a base for improvements:

import numpy as np   SAMPLE_RATE = 44100 DAMPING = 0.999   def generate(frequency, volume, duration):     """Generate a Karplus-Strong pluck.      Arguments:     frequency -- the frequency, as a float     volume -- volume, a float in [0.0, 1.0]     duration -- the length of the generated pluck, in seconds.      Return value:     A numpy array of floats with length nsamples.     """      samples_count = duration * SAMPLE_RATE     N = int(SAMPLE_RATE / frequency)     buf = np.random.rand(N) * 2 - 1     samples = np.empty(samples_count, dtype=float)      for i in range(samples_count):         samples[i] = buf[i % N]         avg = DAMPING * 0.5 * (buf[i % N] + buf[(1 + i) % N])         buf[i % N] = avg      return samples * volume   def generate_chord(frequencies, duration, volume=0.5):     """Generate a chord of several plucks      Arguments:     frequencies -- a (usually short) list of frequencies     describing a chord.     duration -- the length of the generated pluck, in seconds.      Return value:     A numpy array     """      plucks = (generate(note, volume, duration) for note in frequencies)     return np.sum(plucks, axis=0)   if __name__ == "__main__":     import scipy.io.wavfile     import matplotlib.pyplot as plt      A_major = [220, 220 * 5 // 4, 220 * 6 // 4, 220 * 2]     strum = generate_chord(A_major, duration=5)      plt.plot(strum)     plt.show()      scipy.io.wavfile.write(filename, SAMPLE_RATE, strum) 

Beyond frequencies

Now, Ixe2x80x99m not musician. And even if I were, I would need to look up frequencies of required note as I don't think your average user would know them by heart. It would be best to provide named constants to remember that for us.

Reading at https://en.wikipedia.org/wiki/Musical_note one can easily define something like:

C = 2 ** (-9/12) * 440 C_sharp = 2 ** (-8/12) * 440 D_flat = 2 ** (-8/12) * 440 D = 2 ** (-7/12) * 440 D_sharp = 2 ** (-6/12) * 440 E_flat = 2 ** (-6/12) * 440 E = 2 ** (-5/12) * 440 F = 2 ** (-4/12) * 440 F_sharp = 2 ** (-3/12) * 440 G_flat = 2 ** (-3/12) * 440 G = 2 ** (-2/12) * 440 G_sharp = 2 ** (-1/12) * 440 A_flat = 2 ** (-1/12) * 440 A = 2 ** (0/12) * 440 A_sharp = 2 ** (1/12) * 440 B_flat = 2 ** (1/12) * 440 B = 2 ** (2/12) * 440 

But this does not account for every octave. Let's fix that and simplify the reading using an enum:

import enum   def octave(order):     frequency_of_A = 440 * 2 ** (order - 4)      class Octave(enum.Enum):         C = -9         C_sharp = -8         D_flat = -8         D = -7         D_sharp = -6         E_flat = -6         E = -5         F = -4         F_sharp = -3         G_flat = -3         G = -2         G_sharp = -1         A_flat = -1         A = 0         A_sharp = 1         B_flat = 1         B = 2          def __float__(self):             return 2 ** (self.value/12) * frequency_of_A      return Octave   SubSubContra = octave(-1) SubContra = octave(0) Contra = octave(1) Great = octave(2) Small = octave(3) OneLined = octave(4) TwoLined = octave(5) ThreeLined = octave(6) FourLined = octave(7) FiveLined = octave(8) SixLined = octave(9) 

Now I can easily define A Major using:

A_major = [Small.A, OneLined.C_sharp, OneLined.E, OneLined.A] 

Proposed improvements

import enum import numpy as np   SAMPLE_RATE = 44100 DAMPING = 0.999   def octave(order):     """Create an enum of all the notes in the octave     of the given order.     """     frequency_of_A = 440 * 2 ** (order - 4)      class Octave(enum.Enum):         C = -9         C_sharp = -8         D_flat = -8         D = -7         D_sharp = -6         E_flat = -6         E = -5         F = -4         F_sharp = -3         G_flat = -3         G = -2         G_sharp = -1         A_flat = -1         A = 0         A_sharp = 1         B_flat = 1         B = 2          def __float__(self):             return 2 ** (self.value/12) * frequency_of_A      return Octave   SubSubContra = octave(-1) SubContra = octave(0) Contra = octave(1) Great = octave(2) Small = octave(3) OneLined = octave(4) TwoLined = octave(5) ThreeLined = octave(6) FourLined = octave(7) FiveLined = octave(8) SixLined = octave(9)   def generate(frequency, volume, duration):     """Generate a Karplus-Strong pluck.      Arguments:     frequency -- the frequency, as a float     volume -- volume, a float in [0.0, 1.0]     duration -- the length of the generated pluck, in seconds.      Return value:     A numpy array of floats with length nsamples.     """      samples_count = duration * SAMPLE_RATE     N = int(SAMPLE_RATE / frequency)     buf = np.random.rand(N) * 2 - 1     samples = np.empty(samples_count, dtype=float)      for i in range(samples_count):         samples[i] = buf[i % N]         avg = DAMPING * 0.5 * (buf[i % N] + buf[(1 + i) % N])         buf[i % N] = avg      return samples * volume   def generate_chord(frequencies, duration, volume=0.5):     """Generate a chord of several plucks      Arguments:     frequencies -- a (usually short) list of frequencies     describing a chord.     duration -- the length of the generated pluck, in seconds.      Return value:     A numpy array     """      notes = map(float, frequencies)     plucks = (generate(note, volume, duration) for note in notes)     return np.sum(plucks, axis=0)   if __name__ == "__main__":     import scipy.io.wavfile     import matplotlib.pyplot as plt      A_major = [Small.A, OneLined.C_sharp, OneLined.E, OneLined.A]     strum = generate_chord(A_major, duration=5)      plt.plot(strum)     plt.show()      scipy.io.wavfile.write(filename, SAMPLE_RATE, strum) 
 
 
 
 
4
 
vote
  DAMPING9  

Hay alguna posibilidad definitiva de optimización al extraer valores repetidos.

  frequency0  

El siguiente nivel de optimización sería observar que cada elemento de frequency1 se copia en frequency2 y luego modificado. Así que si hay una manera rápida (asumo que hay, pero no sé numerable) de agregar una copia de frequency3 a frequency4 , podría reescribirlo como algo A lo largo de las líneas de

  frequency5  

El paso después de eso sería buscar una implementación vectorial del bucle de promedio ...

 
for i in range(nsamples):     samples[i] = buf[i % N]     avg = damping * 0.5 * (buf[i % N] + buf[(1 + i) % N])     buf[i % N] = avg 

There's some definite possibility of optimisation by extracting repeated values.

j, nextj = 0, 1 dampAvg = damping * 0.5 for i in range(nsamples):     sample = buf[j]     samples[i] = sample     buf[j] = dampAvg * (sample + buf[nextj])     j = nextj     nextj = nextj + 1     if nextj == N:         nextj = 0 

The next level of optimisation would be to observe that each element of buf is copied to samples and then modified. So if there's a fast way (I assume there is, but I don't know numpy) of appending a copy of buf to samples, you could rewrite as something along the lines of

samples = [] off = 0 while off < nsamples:     if nsamples - off <= N:         copy(buf, 0, samples, off, nsamples - off)         break     copy(buf, 0, samples, off, N)     off += N      for i in range(nsamples - 1):         buf[i] = dampAvg * (buf[i] + buf[i + 1])     buf[nsamples - 1] = dampAvg * (buf[nsamples - 1] + buf[0]) 

The step after that would be to look for a vectorised implementation of the averaging loop...

 
 

Relacionados problema

6  Reproductor de audio en Angular 2  ( Audio player in angular 2 ) 
Acabo de construir un reproductor de audio en Angular 2 usando un componente de reproductor y un servicio de jugador. Todo está funcionando bien, solo siento ...

7  Implementación de algoritmo de detección de vencimiento  ( Beat detection algorithm implementation ) 
¿Cuál es la calidad del código que he escrito? ¿Es fácil leer o es un pedazo de código de baja calidad? Además, ¿hay alguna forma en que pueda mejorar el algo...

5  Selector y reproductor de MP3 aleatorio  ( Random mp3 selector and player ) 
Entonces, soy un glotón para el castigo, o no tengo vida, pero yo escribió un script para responder una pregunta sobre Preguntar en preguntar Ubuntu como sol...

5  HTML y JQUERY AUDIO PLAYER  ( Html and jquery audio player ) 
Tuve una tarea para crear un reproductor de audio simple. Hasta ahora logré hacer funcionalidades y todo lo que se necesita. Otra parte de la misma tarea era ...

1  Un javafx para generar sonido de pitidos en Windows a través de JNI  ( A javafx for generating beeping sound on windows via jni ) 
Este es mi programa de práctica: un cuadro de diálogo simple que usa el cual un usuario puede especificar una frecuencia de una señal de audio sinusal y repro...

1  Pasando objetos atómicamente a través de hilos sin cerraduras o carreras de datos para la sincronización de audio  ( Passing objects atomically across threads without locks or data races for audio ) 
Estoy aprendiendo sobre una de las partes más difíciles del desarrollo de audio: la sincronización entre el hilo de audio y el hilo GUI. Por la discusión aquí...

2  Commandline StreamFormatter  ( Commandline streamformattester ) 
Uso del libro de Chris Adamson Aprendizaje de audio Core Me estoy familiarizando con el audio básico para Mac / iOS. Los ejemplos de código son 100% objetiv...

3  Clase de Audiocontroller para administrar audio en Juegos de Unity  ( Audiocontroller class for managing audio in unity games ) 
Estoy buscando una crítica de este sistema de gestión de audio que escribí para los juegos de unidad. Referencias a un archivo JSON para activar los clips por...

4  Optimización de SSE para remuestreo de audio [cerrado]  ( Sse optimisation for audio resampling ) 
cerrado. Esta pregunta es off-topic . Actualmente no está aceptando respuestas. ¿Quieres ...

1  Verificación de forma de JS  ( Js form verification ) 
Tengo tres checboxes en mi forma. Tengo que comprobar cuál se comprueba y comienza la acción. ¿Qué piensas? ¿Es esta la forma más fácil de hacerlo? casilla...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos