PAD synthesis random generator for Python

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

Writes a number (default is 10) of randomly generated PAD synth waveforms into the folder where it is executed. Format is 16bit unsigned int, Raw. Output length should be a power of two, as is demonstrated in the script. Requires the numpy module.

This copy paste maneuver might mess with the indentation so you might want to look it over after you paste it into the text editor.

Code: Select all

#!/usr/bin/python
# PAD Synthesis for python
# From a description by Paul Nasca
# By Rock Hardbuns, 2007
# Released to the Public Domain by the author.

import os, sys, math, struct
from numpy import *


# Length of the output sample
WaveNumSamples = int(math.pow(2, 15)) #useful range: 14 - 18


# PAD export config
SampleRate = 44100.0
F = 172.0
variations = 10    #the number of waves generated
outFilePrefix = "PadWave_"


# Set up export for unsigned short int
sampleFormat = 'H'
uIntMaxVal = 65535


### FUNCTIONS

#take an array of floats(0-1) and convert to unsigned ints
#and write to a raw binary file
def writeArrayRaw(N, outArray, filename, sampleFormat, uIntMaxVal):
	outFile = open(filename, 'wb')
	for i in range(N):
		binInt = struct.pack(sampleFormat, int( outArray[i] * uIntMaxVal ))
		outFile.write(binInt)

	outFile.close
	print 'Wrote sample ', filename
###	


# For the PAD algo. 
# Gives the shape of the harmonic
def profile(fi, bwi):
	x = float(fi / bwi)
	return (math.exp(-x*x) / bwi)
###


# Normalize
def normalize(Array):
	daMax = Array.max()
	if math.fabs(Array.min()) > daMax:
		daMax = math.fabs(Array.min())
	
	if daMax < 1e-5:
		daMax = 1e-5
	
	factor = (daMax * 1.4142)
	Array = Array / factor  
	Array = Array * 0.5 ##move to 0-1 range
	Array = Array + 0.5
	return Array
###


""" <-- Block Commented

# write testone ******
A  = 2.0 * math.sin(math.pi * 0.0019501133786848073) # 86Hz -> 512 sample length at 44100
s1 = 0.4
s2 = 0.0

index = 0
for index in range(WaveNumSamples):
	s1 = s1 - A * s2
	s2 = s2 + A * s1
	outArray[index] = s1 + 0.5

writeArrayRaw(WaveNumSamples, outArray, 'sin_u16.raw', sampleFormat, uIntMaxVal)

""" #end block comment



#write PAD synthesized samples ******
random.seed()

for iter in range(variations):
	print 'Started variation ', iter + 1, ' of ', variations

	#Bandwidth of first harmonic, random in the range 10-70
	BW = 10.0 + 60.0 * random.ranf()


	#Bandwidth scaling factor 
	#(lower than 1 means a "cleaner" tone, above more HF fuzz)
	BWScale = 1.0

	
	# Number of Harmonics
	# random selection between 8, 16, 32 or 64 harmonics, 
	# with 64 being only half as likely as the others
	X = int(random.ranf() * 3.5) + 3
	NumHarm = int(math.pow(2, X))



	# Harmonics Table generation
	# Low volume harmonic noise + some dominant harmonics
	HarmAmpTbl = random.rand(NumHarm) #fill with random floats
	HarmAmpTbl *= 0.2 		  #reduce amp
	
	for i in range( int(NumHarm * random.ranf()) ): #Emphasize a random number of harms
		HarmAmpTbl[int(NumHarm * random.ranf())] = 1.0 - (random.ranf() * random.ranf())



	#make work arrays
	freq_amp = zeros(WaveNumSamples / 2 - 1)
	freq_complex = zeros(WaveNumSamples / 2 - 1 , dtype=complex64 )
	
	#Paul Nascas PAD Algo
	for nh in range(1,NumHarm):
		bw_Hz=  ( pow( 2.0 , BW / 1200.0) - 1.0 ) * F * pow( nh, BWScale )
		bwi = float(bw_Hz / (2.0 * SampleRate))
		fi =  float(F * nh / SampleRate)
		
		for i in range(0, WaveNumSamples / 2 - 1 ):
			hprofile = profile((float(i) / float(WaveNumSamples))- fi, bwi)
			freq_amp[i] = freq_amp[i] + hprofile * HarmAmpTbl[nh]
	
	for i in range(0, WaveNumSamples / 2 - 1):
		phase = random.ranf() * 2.0 * math.pi
		freq_complex[i] =  complex(freq_amp[i] * math.cos(phase), freq_amp[i] * math.sin(phase))
	
	
	outArray = fft.irfft(freq_complex, WaveNumSamples)
	outArray = normalize(outArray)
	filename = outFilePrefix + str(iter + 1) + '.raw'
	writeArrayRaw(WaveNumSamples, outArray, filename, sampleFormat, uIntMaxVal)

print 'All Done'
sys.exit(0)

Post

AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.
Python 3 port (tested on 3.6). Also favors wav instead of raw.

Code: Select all (#)

#!/usr/bin/python
# PAD Synthesis for python
# From a description by Paul Nasca
# By Rock Hardbuns, 2007
# Released to the Public Domain by the author.

import struct, sys
from numpy import *
import wave

# Length of the output sample
WaveNumSamples = int(math.pow(2, 15))  # useful range: 14 - 18

# PAD export config
SampleRate = 44100
F = 172
variations = 10  # the number of waves generated
outFilePrefix = "PadWave_"


### FUNCTIONS

# take an array of floats(0-1) and convert to unsigned ints
# and write to a raw binary file
def write_array_wav(N, outArray, filename):
    outFile = wave.open(filename, 'wb')
    outFile.setparams((1, 2, SampleRate, None, 'NONE', 'noncompressed'))

    for i in range(N):
        outArray[i] = int(outArray[i] * 32767)
        data = struct.pack('<h', int(outArray[i]))
        outFile.writeframesraw(data)

    outFile.close()
    print('Wrote sample ', filename)


###


# For the PAD algo. 
# Gives the shape of the harmonic
def profile(fi, bwi):
    x = float(fi / bwi)
    return (math.exp(-x * x) / bwi)


###


# Normalize
def normalize(signal_array):
    daMax = signal_array.max()
    if math.fabs(signal_array.min()) > daMax:
        daMax = math.fabs(signal_array.min())

    if daMax < 1e-5:
        daMax = 1e-5

    factor = (daMax * 1.4142)
    signal_array = signal_array / factor
    return signal_array


###


# write PAD synthesized samples ******
random.seed()

for iter in range(variations):
    print('Started variation ', iter + 1, ' of ', variations)

    # Bandwidth of first harmonic, random in the range 10-70
    BW = 10.0 + 60.0 * random.ranf()

    # Bandwidth scaling factor
    # (lower than 1 means a "cleaner" tone, above more HF fuzz)
    BWScale = 1.0

    # Number of Harmonics
    # random selection between 8, 16, 32 or 64 harmonics,
    # with 64 being only half as likely as the others
    X = int(random.ranf() * 3.5) + 3
    NumHarm = int(math.pow(2, X))

    # Harmonics Table generation
    # Low volume harmonic noise + some dominant harmonics
    HarmAmpTbl = random.rand(NumHarm)  # fill with random floats
    HarmAmpTbl *= 0.2  # reduce amp

    for i in range(int(NumHarm * random.ranf())):  # Emphasize a random number of harms
        HarmAmpTbl[int(NumHarm * random.ranf())] = 1.0 - (random.ranf() * random.ranf())

    # make work arrays
    freq_amp = zeros(int(WaveNumSamples / 2 - 1))
    freq_complex = zeros(int(WaveNumSamples / 2 - 1), dtype=complex64)

    # Paul Nascas PAD Algo
    for nh in range(1, NumHarm):
        bw_Hz = (pow(2.0, BW / 1200.0) - 1.0) * F * pow(nh, BWScale)
        bwi = float(bw_Hz / (2.0 * SampleRate))
        fi = float(F * nh / SampleRate)

        for i in range(0, int(WaveNumSamples / 2 - 1)):
            hprofile = profile((float(i) / float(WaveNumSamples)) - fi, bwi)
            freq_amp[i] = freq_amp[i] + hprofile * HarmAmpTbl[nh]

    for i in range(0, int(WaveNumSamples / 2 - 1)):
        phase = random.ranf() * 2.0 * math.pi
        freq_complex[i] = complex(freq_amp[i] * math.cos(phase), freq_amp[i] * math.sin(phase))

    outArray = fft.irfft(freq_complex, WaveNumSamples)
    outArray = normalize(outArray)
    filename = outFilePrefix + str(iter + 1) + '.wav'
    write_array_wav(WaveNumSamples, outArray, filename)

print('All Done')
sys.exit(0)


Post

Hello,
Thanks for this implementation. I am trying to understand this algo. Are the generated wav files supposed to be playable?
Thanks

Post

odyssey wrote: Tue Aug 06, 2019 1:08 pm I am trying to understand this algo.
Then better read >this<
odyssey wrote: Tue Aug 06, 2019 1:08 pmAre the generated wav files supposed to be playable?
See for instance the PadSynth in Caustic3 for a real-world implementation that's easy to try out.
We are the KVR collective. Resistance is futile. You will be assimilated. Image
My MusicCalc is served over https!!

Post Reply

Return to “DSP and Plugin Development”