diff --git a/.gitignore b/.gitignore index a9bea9b..ddcf4f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -build/* \ No newline at end of file +build/* + +*/__pycache__/* \ No newline at end of file diff --git a/config/wavetables/saw.wt b/config/wavetables/saw.wt new file mode 100644 index 0000000..16eb6ce Binary files /dev/null and b/config/wavetables/saw.wt differ diff --git a/config/wavetables/shark_fin.wt b/config/wavetables/shark_fin.wt new file mode 100644 index 0000000..733864b Binary files /dev/null and b/config/wavetables/shark_fin.wt differ diff --git a/config/wavetables/sigmoid.wt b/config/wavetables/sigmoid.wt new file mode 100644 index 0000000..adf473d Binary files /dev/null and b/config/wavetables/sigmoid.wt differ diff --git a/config/wavetables/sine.wt b/config/wavetables/sine.wt new file mode 100644 index 0000000..c37a42b Binary files /dev/null and b/config/wavetables/sine.wt differ diff --git a/config/wavetables/square.wt b/config/wavetables/square.wt new file mode 100644 index 0000000..2563e5f Binary files /dev/null and b/config/wavetables/square.wt differ diff --git a/config/wavetables/triangle.wt b/config/wavetables/triangle.wt new file mode 100644 index 0000000..2920fa5 Binary files /dev/null and b/config/wavetables/triangle.wt differ diff --git a/scripts/wavetables/example_wavetable.py b/scripts/wavetables/example_wavetable.py new file mode 100644 index 0000000..066086c --- /dev/null +++ b/scripts/wavetables/example_wavetable.py @@ -0,0 +1,52 @@ + +import math +from generate_wavetable import generateWavetable + +WAVETABLE_FILE_NAME = "config/wavetables/shark_fin.wt" + +# process expects a waveform from x=[0, 2pi) centered around f(x)=0 +# normalization is handled by the wavetable generator + +def sine(phase): + return math.sin(phase) + +def square(phase): + sample = 1.0 + if(phase <= math.pi): + sample = -1 + return sample + +def saw(phase): + return (phase / math.pi) - 1.0 + +def triangle(phase): + sample = 0.0 + if(phase <= math.pi/2.0): + sample = phase * 2.0/math.pi + elif(phase <= 3.0*math.pi/2.0): + sample = phase * (-2.0/math.pi) + 2.0 + else: + sample = phase * 2.0/math.pi - 4.0 + return sample + +def sharkFin(phase): + k = 10.0 + sample = 0.0 + if(phase <= math.pi): + sample = 2.0 * (phase/math.pi) ** k - 1.0 + else: + sample = -2.0 * (phase/math.pi - 1) ** k + 1.0 + return sample + +def sigmoid(phase): + k = 4.0 + a = -k * (phase - math.pi) + sample = 2.0 / (1.0 + math.exp(a)) - 1.0 + return sample + +# pass in the name of your wavetable file and the process function +def main(): + generateWavetable(WAVETABLE_FILE_NAME, sharkFin) + +if __name__ == "__main__": + main() diff --git a/scripts/wavetables/generate_wavetable.py b/scripts/wavetables/generate_wavetable.py new file mode 100644 index 0000000..d918578 --- /dev/null +++ b/scripts/wavetables/generate_wavetable.py @@ -0,0 +1,67 @@ + +# this is a script for generating a wavetable file +# a wavetable file consists of a one-dimensional array of samples representing one period of a waveform +# metadata includes: +# - file version (for program compatibilty) +# - binary format (float, double, int32, etc.) +# - domain (eg phase is represented from [0, 2pi) ) +# - range (determined by datatype, e.g. float=[-1,1], int32=[-2^15, 2^15-1]) +# - waveform RMS (for loudness normalization) +# - sample count +# the synth program uses the filename, not any metadata + +# this script uses the input function to calculate samples +# if you want a custom wavetable, copt/edit/modify the example function (i used desmos for brainstorming) +# import this script and call generateWavetable(processFunction) to generate a custom wavetable + +from array import array +import math + +wavetableLength = 2048 + +def createFile(name): + filename = name + print("Creating file " + filename) + file = open(filename, "wb") + return file + +def writeMetadata(file): + print(">> im writing metadata") + +def writeData(file, processFunction): + print(">> im generating the wavetable") + + # init variables + data_list = [None] * wavetableLength + phaseInc = 2*math.pi / wavetableLength + x = 0 + accumulator = 0 + + # generate each discrete sample + for i in range(wavetableLength): + sample = processFunction(x) + accumulator += sample * sample + x += phaseInc + data_list[i] = sample + + # normalize by rms + rms = math.sqrt(accumulator/wavetableLength) + print(">> wavetable RMS: ", rms) + for i in range(wavetableLength): + data_list[i] /= rms + + # write to file + binary_data = array("f", data_list) + file.write(binary_data) + +def closeFile(file): + print(">> finishing up") + file.close() + +def generateWavetable(name, processFunction): + print("Hello main") + file = createFile(name) + writeMetadata(file) + writeData(file, processFunction) + closeFile(file) + print("Done")