简体   繁体   中英

Detect last zero-crossing

I'm generating an exponential sweep with the following function:

@jit(nopython=True)
def generate_exponential_sweep(time_in_seconds, sr):
    time_in_samples = time_in_seconds * sr
    exponential_sweep = np.zeros(time_in_samples, dtype=np.double)
    for n in range(time_in_samples):
        t = n / sr
        exponential_sweep[n] = np.sin(
            (2.0 * np.pi * starting_frequency * sweep_duration)
            / np.log(ending_frequency / starting_frequency)
            * (np.exp((t / sweep_duration) * np.log(ending_frequency / starting_frequency)) - 1.0))
    
    number_of_samples = 50
    exponential_sweep[-number_of_samples:] = fade(exponential_sweep[-number_of_samples:], 1, 0)

    return exponential_sweep

Right now the sine wave does not finish at a zero-crossing, so for avoiding the problem I managed to make a fade function that simply fades the volume to zero:

@jit(nopython=True)
def fade(data, gain_start,
         gain_end):
    gain = gain_start
    delta = (gain_end - gain_start) / (len(data) - 1)
    for i in range(len(data)):
        data[i] = data[i] * gain
        gain = gain + delta

    return data

The question is:

  1. Would it be better/faster to detect the last zero-crossing in the array and make the sine wave finish there?
  2. If better, how can it be done?

Since time_in_seconds , sr , starting_frequency and ending_frequency are all unknown, we can't guarantee that it will hit any zeroes or even cross it, without any giving them any constraints. The only way to properly do this is to use a window (or fade in/out), with a known frequency behaviour.

This rules out 1. We can continue with 2.


I would suggest a tapered cosine window for this task - scipy.signal.windows.tukey - which offers the fade-in/-out from 0 to 1 and vv, and is a very common choice for audio tasks.

An example of this can be implemented as -

import numpy as np
import scipy

def fade(data: np.ndarray, fade_time: float, sr: float) -> np.ndarray:
  alpha = sr * 2 * fade_time / len(data)
  window = scipy.signal.windows.tukey(len(data), alpha)

  return data * window

The resulting window - with a fade time of 0.1 s - would look like this

To add this to your already existing code and simplying it -

import numpy as np

def generate_exponential_sweep(
    time_in_seconds: float, sr: float, starting_frequency: float, 
    ending_frequency: float, fade_time: float) -> np.ndarray:
  t = np.arange(0, time_in_seconds, 1/sr)
  
  exponential_sweep = np.sin(2 * np.pi * (
      starting_frequency * time_in_seconds * (
          (ending_frequency / starting_frequency) ** (t / time_in_seconds) - 1
          ) / np.log(starting_frequency / ending_frequency)
      )
  )

  exponential_sweep = fade(exponential_sweep, fade_time, sr)

  return exponential_sweep

We can replace that whole block creating the sweep by scipy.signal.chirp which does exactly the same -

import numpy as np
import scipy

def generate_exponential_sweep(
    time_in_seconds: float, sr: float, starting_frequency: float, 
    ending_frequency: float, fade_time: float) -> np.ndarray:
  t = np.arange(0, time_in_seconds, 1/sr)
  
  exponential_sweep = scipy.signal.chirp(
      t, f0=starting_frequency, f1=ending_frequency, 
      t1=time_in_seconds, method='logarithmic')

  exponential_sweep = fade(exponential_sweep, fade_time, sr)

  return exponential_sweep

And just a general comment - don't mix putting variables as arguments and not. Please include all in

def generate_exponential_sweep(time_in_seconds, sr, starting_frequency, ending_frequency):
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM