Login / Register  0 items | $0.00 New @ KVR
Rock Hardbuns
KVRAF
 
1729 posts since 3 Sep, 2003

Postby Rock Hardbuns; Sun Sep 23, 2007 2:12 am PAD synthesis random generator for Python

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

Postby nuchoon; Mon Jan 01, 2018 9:41 am Re: PAD synthesis random generator for Python

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)


Moderator: Moderators (Main)

Return to DSP and Plug-in Development