Although the code works (not the extract below) but I don't realy like it. Then, reading the docs again I noticed concepts. Would that be a better choise than the current type?
In the complete code the pattern is added to a tile that sets the duration of the pattern and the start time on a time-line. Each tile is rendere seperatly and the result is added in the right position to an outbut buffer.
My main problem is the composition of the patterns. My way around the requirement of the funk*: proc(x, y: float): float. My line of thought, make a pattern a concept that requires an x, y: float. The make the SndTile accept anything pattern regardles of the rest in it beyond the x,y. Kind off? Havn't figured it all out.
Some code that clarifies what I do, I hope:
type
Pattern* = object
funk*: proc(x, y: float): float
greymap: seq[tuple[gi:float, gt:float]]
transf: M3x3
invTransf: M3x3
SndTile* = object
baseFreq: float #defaults at #E0 = 20.6 Hz at init
octaveBands: seq[int] #at init @[5, 10, 20, 30, 60, 60, 120, 120, 60, 30]
frequency: seq[float] #frequencies to sample at
freqIndex: seq[float] # index of freqencies in a [0,1] range
amp*: float
startsAt: int #filled when added to time line
duration: float # 1 s, at init
ticks: int #duration/samplerate
osc: OscBank
pattern*: Pattern
proc eval(p:var Pattern, x, y: float): float =
var val: float
let point = p.invTransf * v2(x, y)
val = p.funk(point.x, point.y)
if p.greymap != @[]:
val = val.mapgrey(p.greymap)
val
func initPattern(funk: proc(x, y: float): float): Pattern =
Pattern(
funk: funk,
transf: default(M3x3),
invTransf: default(M3x3),
)
# pattern composition.
func gradientSquare(): Pattern =
func gradientSquare(x, y: float): float =
1.0 - min(1, max(abs(x), abs(y)))
initPattern(gradientSquare)
func smoothstepX*(minedge, maxedge: float): Pattern =
func smoothstepX(x, y: float): float =
result = clipzo((x - minedge) / (maxedge - minedge))
result = result * result * (3.0 - 2.0 * result)
initPattern(smoothstepX)
func adsr(a, d, sx, sy, r: float): Pattern =
func adsr(x, y: float): float =
if 0.0 < x and x <= a:
return pow(x, 1/3)
elif a < x and x <= (a + d):
return ((sy - 1) / d) * (x - a) + 1
elif (a + d) < x and x <= a + d + sx :
return sy
elif (a + d + sx) < x and x <= (a + d + sx + r) :
return (-sy / r) * (x - (a + d + sx)) + sy
initPattern(adsr)
func intersect*(patt0, patt1: var Pattern): Pattern =
var patt0 = patt0 #?
var patt1 = patt1 #?
proc intersect(x, y: float): float =
let
v0 = eval(patt0, x, y)
v1 = eval(patt1, x, y)
result = (v0 + v1) / 2.0
if v0 ~= 0.0 or v1 ~= 0.0:
result = 0.0
initPattern(intersect)
var c = gradientSquare()
echo c.eval(0.5, 0.5)
You might want to have a look at various ways to implement reverse-mode autodifferentiation:
1. Via callbacks, which is what you use:
2. Via Wengert's List / Wengert's Tape and computational graphs https://openreview.net/pdf?id=BJJsrmfCZ
3. Via compiler:
For one-off, it's OK, the only think ugly is declaring closures in Nim which is way more verbose than Go's or Rust's.
Some std/sugar can help as well.
reverse-mode autodifferentiation
Thank you. That's quite a bit to read up upon and digest.