Compare commits
2 Commits
295a869839
...
8de4081a50
| Author | SHA1 | Date | |
|---|---|---|---|
| 8de4081a50 | |||
| eb84c93b7b |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
|
|
||||||
build/*
|
build/*
|
||||||
|
|
||||||
|
*/__pycache__/*
|
||||||
BIN
config/wavetables/saw.wt
Normal file
BIN
config/wavetables/saw.wt
Normal file
Binary file not shown.
BIN
config/wavetables/shark_fin.wt
Normal file
BIN
config/wavetables/shark_fin.wt
Normal file
Binary file not shown.
BIN
config/wavetables/sigmoid.wt
Normal file
BIN
config/wavetables/sigmoid.wt
Normal file
Binary file not shown.
BIN
config/wavetables/sine.wt
Normal file
BIN
config/wavetables/sine.wt
Normal file
Binary file not shown.
BIN
config/wavetables/square.wt
Normal file
BIN
config/wavetables/square.wt
Normal file
Binary file not shown.
BIN
config/wavetables/triangle.wt
Normal file
BIN
config/wavetables/triangle.wt
Normal file
Binary file not shown.
52
scripts/wavetables/example_wavetable.py
Normal file
52
scripts/wavetables/example_wavetable.py
Normal file
@@ -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()
|
||||||
67
scripts/wavetables/generate_wavetable.py
Normal file
67
scripts/wavetables/generate_wavetable.py
Normal file
@@ -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")
|
||||||
0
src/synth/Instruments/WavetableOscillator.cpp
Normal file
0
src/synth/Instruments/WavetableOscillator.cpp
Normal file
29
src/synth/Instruments/WavetableOscillator.hpp
Normal file
29
src/synth/Instruments/WavetableOscillator.hpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "synth/instruments/Instrument.hpp"
|
||||||
|
|
||||||
|
class PianoString : public Instrument {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
WavetableOscillator() = default;
|
||||||
|
WavetableOscillator(ConfigService* config, LoggerService* logger, float sampleRate);
|
||||||
|
~WavetableOscillator() = default;
|
||||||
|
|
||||||
|
void noteOn(float frequency, float velocity) override;
|
||||||
|
void noteOff() override;
|
||||||
|
|
||||||
|
bool isActive() override;
|
||||||
|
|
||||||
|
float process(bool& scopeTrigger) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool loadWavetable(std::string filename); // TODO: load from conf file instead
|
||||||
|
|
||||||
|
std::vector<float> wavetable;
|
||||||
|
|
||||||
|
};
|
||||||
0
src/synth/WavetableLoader.cpp
Normal file
0
src/synth/WavetableLoader.cpp
Normal file
4
src/synth/WavetableLoader.hpp
Normal file
4
src/synth/WavetableLoader.hpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
// placeholder to handle loading wavetables from files
|
||||||
|
// so that wavetables can be shared to each voice/instrument
|
||||||
|
// only need to need to load once for x number of voices
|
||||||
Reference in New Issue
Block a user