# How to minimize peak-to-peak amplitude of periodic waveform through FFT phase shifts?

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?

• 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? – A_A Jan 26 '18 at 6:39
• 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). – nyanpasu64 Jan 26 '18 at 7:43

## 2 Answers

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.

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