mratsim / weave

A state-of-the-art multithreading runtime: message-passing based, fast, scalable, ultra-low overhead
Other
532 stars 22 forks source link

randomly "off values" #194

Closed ingoogni closed 8 months ago

ingoogni commented 9 months ago

When running the code below with one of the two weave procs there are every now and then little spikes on the resulting wave form. These are single samples where the value is off. The non weave trace function does not have this problem. In the sound these can be heard as "crackle".

The image shows spectrograms of the resulting signals, top left is clean and without weave, top right is with weave and shows spikes in the spectrogram. The two lower images show detail on sample level.

import std/[math, sequtils]
import easywave #https://github.com/johnnovak/easywave
import weave 

proc initWriteWave(filename: string, bitdepth:int, sampleRate:int, channels: int): RiffWriter =
  let wf = WaveFormat(
    sampleFormat:  sfPCM,
    bitsPerSample: bitdepth,
    sampleRate: samplerate.int,
    numChannels: channels
  )
  var rw = createRiffFile(filename, FourCC_WAVE, littleEndian)
  rw.writeFormatChunk(wf)
  rw.beginChunk(FourCC_WAVE_data)
  return rw

proc lerp*(t, minin, maxin, minout, maxout: float):float {.inline.}=
  ((t - minin) / (maxin - minin)) * (maxout - minout) + minout

func normalize(sampleseq:var seq[float], bitdepth: int, headroom: float = 1.0) =
  var 
    (minval, maxval) = minmax(sampleseq)
  let halfbitdepth = (pow(2.0, bitdepth.float) / 2.0) - 1.0
  if minval.abs > maxval.abs :
    maxval = abs minval
  else:
    minval = - maxval
  for i in 0..<sampleseq.len:
    sampleseq[i] = lerp(sampleseq[i], minval, maxval, -halfbitdepth, halfbitdepth) * pow(10.0, -headroom/20)

proc traceWeave(freqbands:seq[float], duration: float, samplerate:int):seq[float] {.inline.}=
  let numframes = (duration * samplerate.float).int
  var sampleseq = newSeq[float](numframes)
  let buf = cast[ptr UncheckedArray[float]](sampleseq[0].addr)
  init(Weave)
  parallelFor tick in 0..<numframes:
    captures: {buf, freqbands, samplerate}
    parallelFor i in 0..<freqbands.len:
      captures: {tick, buf, freqbands, samplerate}
      buf[tick] = buf[tick] + (sin(tick.float * TAU.float * (freqbands[i] / samplerate.float))).float
  exit(Weave)
  return sampleseq 

proc traceWeaveStaged(freqbands:seq[float], duration: float, samplerate:int):seq[float] {.inline.}=
  let numframes = (duration * samplerate.float).int
  var sampleseq = newSeq[float](numframes)
  let buf = cast[ptr UncheckedArray[float]](sampleseq[0].addr)
  init(Weave)
  parallelFor tick in 0..<numframes:
    captures: {buf, freqbands, samplerate}
    parallelForStaged i in 0..<freqbands.len:
      captures: {tick, buf, freqbands, samplerate}
      awaitable: iLoop
      prologue:
        var sigSum:float = 0.0
      loop:
        sigSum += (sin(tick.float * TAU.float * (freqbands[i] / samplerate.float))).float
      epilogue:
        buf[tick] = sigsum
  exit(Weave)
  return sampleseq 

proc trace(freqbands:seq[float], duration: float, samplerate:int):seq[float] {.inline.}=
  let numframes = (duration * samplerate.float).int
  var sampleseq = newSeq[float](numframes)
  for tick in 0..<numframes:
    for i in 0..<freqbands.len:
      sampleseq[tick] = sampleseq[tick] + (sin(tick.float * TAU.float * (freqbands[i] / samplerate.float))).float
  return sampleseq 

when isMainModule:
  var
    outbuf: seq[uint16]
  let
    path = "audiosample.wav"
    bitdepth = 16
    samplerate = 44100
    duration = 2.0
    channels = 1 
    rw = initWriteWave(path, bitdepth, samplerate, channels)
    freqbands = @[200.0,300.0,500.0]

  var samples = traceWeave(freqbands, duration, samplerate)
  #var samples = traceWeaveStaged(freqbands, duration, samplerate)
  #var samples = trace(freqbands, duration, samplerate)

  samples.normalize(bitdepth, 3.0)

  for i16 in 0..<samples.len: 
    outbuf.add(toBiggestInt(samples[i16]).uint16)  
  rw.write(outbuf, 0, outbuf.len)
  rw.close()  

spectrogrammes

mratsim commented 8 months ago

Your code is not threadsafe.

Assume:

Now you have 2 threads that load buf[tick] and write to it, if the start is 0 and both increment, the result between 0, 1 or 2 is random.

ingoogni commented 8 months ago

Thanks