1
$\begingroup$

For one of my music projects, I'm playing back periodic audio signals (by looping single periods). Unfortunately, one of my waveforms sounds too quiet (even at maximum volume).

I'm trying to use FFT to obtain harmonic strengths, then phase-shift each harmonic to minimize the peak-to-peak amplitude, and then amplify the signal.

Is there any algorithm to find the optimal phase shifts, or should I use scipy.optimize.basinhopping (or generating thousands of random phases) for an approximate result?

$\endgroup$
  • $\begingroup$ Is there a particular reason you would like the amplitude to be set as a function of phase instead of normalising the output waveform to a new level? $\endgroup$ – A_A Jan 26 '18 at 6:39
  • $\begingroup$ Edited my post. I'd like to create a waveform with the same harmonic content (aside from phase) but louder (when normalized to a fixed peak-peak amplitude). $\endgroup$ – jimbo1qaz Jan 26 '18 at 7:43
1
$\begingroup$

There are algorithms for determining low-peak-factor signals, for example:

"Synthesis of low-peak-factor signals and binary sequences with low autocorrelation (Corresp.)", M. Schroeder, 1970

I have used this for flat-spectra signals to minimise peak to peak amplitude.

However I think this may be an XY problem. Do you have a limit on how much distortion you can introduce to the signals? Have you tried using a dynamic range compression algorithm? There are limiting algorithms which can increase the RMS amplitude significantly without introducing significant audio artifacts.

$\endgroup$
1
$\begingroup$

I don't think the FFT is the proper tool for this. If you know the amplitudes of the harmonics in your waveform, I think the best approach is to do a brute force coarse search for the combination with the smallest maximum. Once that is found you can either do a gradient search for the optimum, or probably easier, do another brute force fine search in the neighborhood of the best coarse search results. When you find the combination with the lowest maximum (don't forget to include troughs either) you can multiply all the amplitudes to bring the peak to your clipping limit.

Hope this helps,

Ced

===============================================

Followup:

I was curious enough to code it in Python. Here you go:


from array  import array
import numpy as np
import matplotlib.pyplot as plt

#================================================
def Main():

#---- Set up

        N = 200

        RPS = 2 * np.pi / N # Radians Per Sample 

        P = array( 'd', [0,0,0,0,0] ) # Phases
        A = array( 'd', [0,1,1,1,1] ) # Amplitudes

#---- Coarse Search

        Step = 2 * np.pi / 20

        MP = FindSmallestPeakPhases( RPS, N, A, P, Step )

#---- Fine Search

        Step /= 20

        MP = FindSmallestPeakPhases( RPS, N, A, MP, Step )

#---- Finer Search

        Step /= 20

        MP = FindSmallestPeakPhases( RPS, N, A, MP, Step )

#---- Print Results

        print "1:", MP[1]        
        print "2:", MP[2]        
        print "3:", MP[3]        
        print "4:", MP[4]        

#---- Build Wave from Results

        x = np.arange( N )

        Tone1 = A[1] * np.sin(   x*RPS + MP[1] )
        Tone2 = A[2] * np.sin( 2*x*RPS + MP[2] )
        Tone3 = A[3] * np.sin( 3*x*RPS + MP[3] )
        Tone4 = A[4] * np.sin( 4*x*RPS + MP[4] )

        Wave = Tone1 + Tone2 + Tone3 + Tone4

#---- Show Wave from Results

        plt.plot(x, Wave, c='g')
        plt.show()

#---- Exit

        print "Done"
        return

#================================================
def FindSmallestPeakPhases( RPS, N, A, P, Step ):

        MinPhases = array( 'd', [0,0,0,0,0] )

        x = np.arange( N )

        Peak = 1000

        Wave1 = A[1] * np.sin( x*RPS + P[1] )
        Phase1 = P[1]
        MinPhases[1] = Phase1

        for p2 in range( 1, 22 ):
         Phase2 = P[2] + ( p2 - 11 ) * Step
         Tone2 = A[2] * np.sin( 2*x*RPS + Phase2 )
         Wave2 = Wave1 + Tone2

         for p3 in range( 1, 22 ):
          Phase3 = P[3] + ( p3 - 11 ) * Step
          Tone3 = A[3] * np.sin( 3*x*RPS + Phase3 )
          Wave3 = Wave2 + Tone3

          for p4 in range( 1, 22 ):
           Phase4 = P[4] + ( p4 - 11 ) * Step
           Tone4 = A[4] * np.sin( 4*x*RPS + Phase4 )
           Wave4 = Wave3 + Tone4

           Min = min( Wave4 )
           Max = max( Wave4 )

           if Max > -Min:
              AbsMax = Max
           else:
              AbsMax = -Min

           if AbsMax < Peak:
              Peak = AbsMax
              MinPhases[2] = Phase2
              MinPhases[3] = Phase3
              MinPhases[4] = Phase4

        print "Peak = ", Peak

        return MinPhases

#================================================
Main()
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.