Sections

2019-11-02

Goniometric Synthesizer

Instead of working with knobs or searching for a sample, you could just enter the formula, offset, and speed, and make music more mathematical way. It's possible and mathematically proven to make any periodic sound using Fourier series, which is essentially infinite stacking of sines and cosines. Both our ears and equipment are finite, so we don't even need to stack infinitely. On my way to code a natively microtonal DAW, I ought to start with smaller bits and pieces. Coding it all at once is impossible for me.

The following QBASIC program was used to generate the initial waveform for SuperTan samplepack. This pack mostly contains this waveform variously layered and detuned, and then exported using OpenMPT.

---------------------------------------------------------------------------------

PRINT "Gsys Goniometric Sampler vO.2 - May 2015"
INPUT "XScale:"; c
INPUT "Sampledepth:"; d
INPUT "Color"; e
INPUT "InvYScale"; f
SCREEN 12
OPEN "gosawave" FOR OUTPUT AS #1 LEN = 640

FOR b = 0 TO 639
    a = TAN(b / d)
    LINE (b * c, a * (-240 / f) + 240)-((b + 1) * c, TAN((b + 1) / d) * (-240 / f) + 240), e
    g = INT(a * 256 / f + 128)

    IF g > 255 THEN
        g = 255
    ELSEIF g < 0 THEN
        g = 0
    END IF

    PRINT #1, CHR$(g);
    OUT 544, g
NEXT b
SLEEP

---------------------------------------------------------------------------------

Another program was written 4 years later when I heard how prospective R is. I knew it resembled Python and C enough that for starters I could resurrect my knowledge from the freshman courses 2 years before, and then explore optimization and the more R-like ways. Notwithstanding the Python popularity has gone through the roof at the expense of R since then. Normies learn and forget quick, we Aspies don't do that. This gives me confidence that some 40 years later I'll do what I now revere as magic and probably get paid bitcoins (fiat currencies suck in the long term). The program outputs the waveform in a series of double precision little-endian numbers without any header. You then import it in Audacity, cut as needed, and export to a format your DAW understands. No sample pack from this yet, but I have a name already: Fat Authentic Goniometric Synths.

---------------------------------------------------------------------------------

library(parallel)

cl = makeCluster(detectCores())

fourier = function(x, N = 100000, P = 2*pi, a0 = 0){
  # an, bn - cosine and sine coeficients, set inside loop, dependent on iteration variable
  ret = a0/2
  n = 1 # iteration start
  while(n <= N){
    if(n %% 2 == 0) {
      n = n+1
      next
    }
    an = 1/(pi*n)
    bn = 4/(pi*n)
    ret = ret + (an*cos(2*pi*n*x/P) + bn*sin(2*pi*n*x/P))
    n = n + 1
  }

  # somehow actually slower
  #ret = sum(a0/2, unlist(lapply(1:N, function(n){
  #  an = (-1)^(n)/(pi*n)
  #  bn = 2*n*(-1)^(n+0)/(pi*n)
  #  ret = (an*cos(2*pi*n*x/P) + bn*sin(2*pi*n*x/P))
  #  return(ret)
  #})))

  # apparently ret can be NA even if it was assigned a value few lines above
  if(!is.na(ret) && (ret > 1000 || ret < -1000)) {
    return (0)
  }
  #print(ret)
  return(ret)

}

x = seq(0, 4*pi, by = pi/96)
#x = NULL
#i = 0
#while(i < 4*pi){
#  x = c(x, i)
#  i = i + pi/24
#}

# this is very noob tier
clamp = function(x, bottom, top){
  if (!is.na(x) && x > top){
    return(top)
  }
  if (!is.na(x) && x < bottom){
    return(bottom)
  }
  return(x)
}

beginning = Sys.time()
output = parallel::parLapply(cl, x, fourier)
theend = Sys.time()
stopCluster(cl)
# stops working due to trying to set y scale to have infinite range, how stupid
plot(x, output, "l", main = "Function", sub = "a wonderful one", xlab = "argument", ylab = "value", asp = 0.5, xlim = c(0, 13))
print(output)
ofile = file("fourier.raw", "wb")
i = 1
while(i <= length(output)){
  writeBin(output[[i]][1], ofile)
  i = i+1
}
close(ofile)
print(theend - beginning)


#plot(0,0)
#i = 1
#while(i <= length(x)){
#  points(x[i]/10, fourier(x[i])/10, col = "black") # doesn't appear to do anything
#  i = i + 1
#}

# determine where to put a dot for each pair of argument and output
#scalex = 1/pi
#scaley = 1/10
#centerx = cols%/%2
#centery = rows%/%2
#i = 1
#results = NULL
#while(i < length(x)){
#  results = c(results, fourier(x))
#  idx1 = centerx + scalex*i
#  idx2 = centery + scaley*results[i]
#  if(!is.na(idx1) && !(is.na(idx2)) && idx1 > 0 && idx1 <= rows && idx2 > 0 && idx2 <= cols){
#    fb[idx1, idx2] = "@"
#  }
#}

---------------------------------------------------------------------------------

Now I should seek and learn some good audio library to have some way to output the sounds I'm generating. Old UNIX style piping to /dev/dsp doesn't work on all systems. It would be a good idea to support multiple sound APIs because one does not simply know whether this dependency will be available.

1 comment:

  1. doporučuju prozkoumat Csound a jeho IDE Cabbage - https://cabbageaudio.com/docs/introduction/

    ReplyDelete

Barely anyone comments, so I don't moderate. Free advertising, I guess.