PAD synthesis random generator for Python

DSP, Plug-in and Host development discussion.
Rock Hardbuns
KVRAF
1718 posts since 3 Sep, 2003

Post Sun Sep 23, 2007 2:12 am

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)

nuchoon
KVRer
3 posts since 27 Mar, 2006

Re: PAD synthesis random generator for Python

Post Mon Jan 01, 2018 9:41 am

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)


Return to “DSP and Plug-in Development”