[Csnd] 'wrap around' looping with flooper2

Hello everybody!

I’m working on a little instrument and i have some problems with it.
More or less a question if this functioning with flooper2 or not:
i want to to have flexible control over the loopstart point and loopend point. flooper2 seems to be the right opcode for this.
i introduced the variable kLoopSize which multiplies the soundfile length with a factor between 0.1 and 1.
this is then added to the loopstart point to define the loopend point. to keep these numbers in range i working with the modulo operation.
so from time to time the loopstart point is higher then the loopend point. and it seems like flooper2 doesn’t like it like this. i call whit wrap around looping.
is flooper2 not capable of doing this? or is there a problem with my code?

Greetings,
Philipp

<CsoundSynthesizer>
<CsOptions>
-d -odac -W -3 -+rtmidi=coremidi -Ma
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 64
nchnls = 2
0dbfs = 1.0

gSFile = "" ; add to global section
giSndfl ftgen 0, 0, 0, 1, gSFile, 0, 0, 0

instr 1
  iFileLengthSec filelen gSFile
  ;; play a soundfile from function table with looping
  kPlaybackSpeed = 1
  kAmp = 0.9
  ;; loop position
  initc7 1,1,0
  kStart ctrl7 1,1,0,1
  kStart port kStart, 0.01
  printks2 "Loop-Start: f\\n&quot;, kStart &nbsp;&nbsp;kLoopStart = \(kStart\*iFileLengthSec\) iFileLengthSec
  
  initc7 1,2,1
  kLoopSize ctrl7 1,2,0.1,1
  kLoopSize = kLoopSize * iFileLengthSec
  printks2 "Loop-Size in Seconds: f\\n&quot;, kLoopSize &nbsp;&nbsp;kLoopSize port kLoopSize, 0\.01 &nbsp;&nbsp;kLoopEnd = kLoopStart\+kLoopSize iFileLengthSec
  printks2 "Loop-End: %f\n", kLoopEnd
; iFileLengthSmps = iFileLengthSec*sr

  kFadeTime = 0.025
  iSndflTable = giSndfl
  iNumChnls filenchnls gSFile
  if iNumChnls == 1 then
    aSndfl1 flooper2 kAmp, kPlaybackSpeed, kLoopStart, kLoopEnd, \
      kFadeTime, iSndflTable
    aSndfl2 = aSndfl1
  elseif iNumChnls == 2 then
    aSndfl1, aSndfl2 flooper2 kAmp, kPlaybackSpeed, kLoopStart, \
      kLoopEnd, kFadeTime, iSndflTable
  endif
  
  ;; output
  initc7 8,1,0
  kAmp ctrl7 8, 1, -90, 0
  printks2 "Amp: %d\n", kAmp
  kAmp port kAmp, 0.025
  aOut1 = aSndfl1 * ampdbfs(kAmp)
  aOut2 = aSndfl2 * ampdbfs(kAmp)
  outs aOut1, aOut2
endin
;-----------------------------------------------------------
</CsInstruments>
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer>
Csound mailing list
Csound@listserv.heanet.ie
https://listserv.heanet.ie/cgi-bin/wa?A0=CSOUND
Send bugs reports to
        Issues · csound/csound · GitHub
Discussions of bugs and features can be posted here

Hi Philipp!
Sep 14 2023, Philipp Neumann has written:
...

so from time to time the loopstart point is higher then the loopend point. and it seems like flooper2 doesn’t like it like this.

If you want looping like that, flooper2 might not be your opcode. AFAIK
it will loop from a start point early to an endpoint late. You can
use one of the table opcodes table, tablei, table3, \.\.\.. You would
have to manage your own crossfading then, but they can wrap around. If
you read a file with GEN01 you can get all the information you need.
With table opcodes, you best read one audio channel into one table. You
can then use ftsr find the samplerate and nsamp (the number of
samples/ table length) and then calculate the time of the audiofile. A
table can be read by a phasor, which uses a frequency. Well Frequency =
1/duration of file. Then you can add an offset to your phasor and scale
the phasor itself. The offset is your starting point, the scaling of the
phasor tells you how much of the table is read. If you have 1000 sample
in your table aPhasor * 1000 will read the whole table. That can be
scaled with a factor between .1 and 1. The offset loop start in
seconds must be multiplied by the tables samplerate.

This approach should work to loop one audio channel without crossfade.
If you want crossfades, you could loop through the table in parallel
with a slight offset. Take into acount the crossfade time.

There maybe better solutions. There are oscillators particularly geared
towards samle reading, allowing even stereo tables.

HTH.

Best wishes,

Jeanette

Thanks, Jeanette!

This one works perfectly fine now!

Now i’m asking how to implement a windowing function to eleminate clicks while looping.

Can someone help me a give me a hint?

<CsoundSynthesizer>
<CsOptions>
-d -odac -W -3 -+rtmidi=coremidi -Ma
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 64
nchnls = 2
0dbfs = 1.0

gSfile = "/Users/philippneumann/Library/Mobile Documents/com~apple~CloudDocs/Sound-Library/Elektromagnetisches/Elektromagnetisch_ElektroUsi_Stereo-01.wav" ; add to
;-----------------------------------------------------------
#include "/Users/philippneumann/Documents/Csound/UDOs/UDOs-pn.csd"
;-----------------------------------------------------------
instr 1
  iSndflSec filelen gSfile
  iSndflSr filesr gSfile
  iSndflSamps = iSndflSec*iSndflSr

  initc7 1,1,0
  kLoopStart ctrl7 1,1,0,1
  kStart = (kLoopStart*iSndflSamps)
  initc7 1,2,1
  kLoopSize ctrl7 1,2,0.005 ,1
  kSize = kLoopSize*iSndflSamps
  
  ;; read a soundfile into a table and read it with a phasor
  ;; mono and stereo files

  kSpeed ctrl7 1,3, -2, 4

  iSndflNumChnls filenchnls gSfile
  if iSndflNumChnls == 1 goto monoFile
  goto stereoFile

  monoFile:
    iSndflTblLeft ftgen 0, 0, 0, 1, gSfile, 0, 0, 1
    iSndflTblRight = iSndflTblLeft
    aIndex phasor kSpeed/(kSize/ftsr(iSndflTblLeft))
    aIndex = aIndex*kSize
    aIndex = aIndex+kStart
    aSmpL table aIndex, iSndflTblLeft, 0,0,1
    aSmpR table aIndex, iSndflTblRight, 0,0,1
    goto output

  stereoFile:
    iSndflTblLeft ftgen 0, 0, 0, 1, gSfile, 0, 0, 1
    iSndflTblRight ftgen 0, 0, 0, 1, gSfile, 0, 0, 2
    aIndex = (kSize*aIndex)+kStart
    aIndex phasor kSpeed/(kSize/ftsr(iSndflTblLeft))
    aIndex = aIndex*kSize
    aIndex = aIndex+kStart
    aSmpL table aIndex, iSndflTblLeft, 0,0,1
    aSmpR table aIndex, iSndflTblLeft, 0,0,1

  output:
    aOutL = aSmpL
    aOutR = aSmpR
    outs aOutL, aOutR

endin
;-----------------------------------------------------------
</CsInstruments>
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer>

Hi Philipp,
a classic windowing function may not help. but perhaps construct a phasor of the same frequency, though just scheduled 0-1, i.e. the normal phasor output, and let it read a linear (GEN07) table, or something similar, that starts with a very short ramp up and ends with a very short ramp down. Not ideal, because when you change the frequency, i.e. the amount of the file you play, the fade time will change. I can't think of anything else though.

If you adjust all the scaling and offsetting of your current phasor in a separate statement, you can even use the same phasor. Otherwise, if you can think of ways to do the proper calculations, you might try loopseg? Or if you can manage to set off triggers at the right time, you could use triglinseg. If you use a syncphasor (with 0 input for its sync input), you can get one trigger when the phasor starts at 0.

HTH.

Best wishes,

Jeanette

I guess this is the way to do it and i hope i calculated the speed of the window phasor correctly. It sounds right to me. Maybe someone want’s to try it out?

Now i’m at the point where i want to avoid these clicking sounds when i change the reading offset or loopsize.
I think they occur because i’m changing the values during the playback. So maybe i will have to implement the right time for updating the values. E.g. when the phasor is at 0.
Or is there a other solution?

opcode sndfl_looper, aaa, Skkkii
  SFile, kSpeed, kLoopStart, kLoopSize, iStereoOffset, iWndwFt xin

  ;; read data from soundfile
  iSndflSec filelen SFile
  iSndflSr filesr SFile
  iSndflSamps = iSndflSec*iSndflSr
  
  ;; create the tables for the soundfile
  iSndflNumChnls filenchnls SFile
  if iSndflNumChnls == 1 then
  iSndflTbl1 ftgen 0,0,0,1,SFile,0,0,1
  iSndflTbl2 = iSndflTbl1
  elseif iSndflNumChnls == 2 then
  iSndflTbl1 ftgen 0,0,0,1,SFile,0,0,1
  iSndflTbl2 ftgen 0,0,0,1,SFile,0,0,2
  endif

  ;; parameter for the table reading
  kSpeed = kSpeed
  kStart = (kLoopStart*iSndflSamps)
  kSize = kLoopSize*iSndflSamps
  kPhasorSpeed = kSpeed/(kSize/iSndflSr)
  aIndex1 phasor kPhasorSpeed
  aIndex2 phasor kPhasorSpeed,iStereoOffset
  aSndfl1 table3 (aIndex1*kSize)+kStart,iSndflTbl1,0,0,1
  aSndfl2 table3 (aIndex2*kSize)+kStart,iSndflTbl2,0,0,1
  aWin1 table3 aIndex1,iWndwFt,1
  aWin2 table3 aIndex2,iWndwFt,1

  ;; output
  aSndfl1 *= aWin1
  aSndfl2 *= aWin2
  xout aIndex1,aSndfl1,aSndfl2
endop

Hi Philipp,
that's something I thought about yesterday night. If you use a syncphasor to go through your table, it will set off a sync impulse in one of its two outputs. You can use that to allow changes. You might have changes at any time to the variables you have and then at this sync point, copy them to something like kRealLoopStart and kRealLoopEnd.

Given that, you can now avoid the window function and use a trigger envelope like triglinseg. Because you can trigger one envelope immediately and you can delay the sync impulse with some delay opcode by the current loop length. Since you don't allow update at any time, you will know the loop length at beginning of the loop. Then you can decide on a crossfade time and maybe on the exact shape of your envelope. Tip: I like to adapt such envelope shapes by passing them through logcurve or expcurve.

Best wishes,

Jeanette

Thanks Jeanette again!

Now this works perfectly fine and i have a lot of fun!

Can someone tell me what this warning means?
i implemented a reinit inside the udo so i can change the phase offset of one of the phasors.
WARNING: init phase truncation

And feel free to use this little UDO:
opcode sndfl_looper, aa, Skkkki
  SFile, kSpeed, kLoopStart, kLoopSize, kStereoOffset, iWndwFt xin
  setksmps 1
  ;; read data from soundfil
  iSndflSec filelen SFile
  iSndflSr filesr SFile
  iSndflSamps = iSndflSec*iSndflSr
  
  ;; create the tables for the soundfile
  iSndflNumChnls filenchnls SFile
  if iSndflNumChnls == 1 then
  iSndflTbl1 ftgen 0,0,0,1,SFile,0,0,1
  iSndflTbl2 = iSndflTbl1
  elseif iSndflNumChnls == 2 then
  iSndflTbl1 ftgen 0,0,0,1,SFile,0,0,1
  iSndflTbl2 ftgen 0,0,0,1,SFile,0,0,2
  endif

  ;; parameter for the table reading
  kChange changed kStereoOffset
  if kChange == 1 then
    reinit UPDATE
  endif

  kSpeed = kSpeed
  kStart = (kLoopStart*iSndflSamps)
  kSize = kLoopSize*iSndflSamps
  kPhasorSpeed = kSpeed/(kSize/iSndflSr)
  aSyncIn init 0
  aSyncOut1 init 1
  aSyncOut2 init 1
  kPhasorSpeed1 = (k(aSyncOut1) == 1 ? kPhasorSpeed : kPhasorSpeed1)
  kPhasorSpeed2 = (k(aSyncOut2) == 1 ? kPhasorSpeed : kPhasorSpeed2)
  UPDATE:
  aIndex1,aSyncOut1 syncphasor kPhasorSpeed1,aSyncIn

  aIndex2,aSyncOut2 syncphasor kPhasorSpeed2,aSyncIn,i(kStereoOffset)
  kSize1 = (k(aSyncOut1) == 1 ? kSize : kSize1)
  kSize2 = (k(aSyncOut2) == 1 ? kSize : kSize2)
  kStart1 = (k(aSyncOut1) == 1 ? kStart : kStart1)
  kStart2 = (k(aSyncOut2) == 1 ? kStart : kStart2)
  aSndfl1 table3 (aIndex1*kSize1)+kStart1,iSndflTbl1,0,0,1
  aSndfl2 table3 (aIndex2*kSize2)+kStart2,iSndflTbl2,0,0,1
  aWin1 table3 aIndex1,iWndwFt,1
  aWin2 table3 aIndex2,iWndwFt,1

  ;; output
  aSndfl1 *= aWin1
  aSndfl2 *= aWin2
  xout aSndfl1,aSndfl2
endop

Hi Philipp,
I can't exactly say why this is happening, I never really worked with reinit. But couldn't you solve the problem in a different way?

aPhasor, aSyncOut syncphasor ...
if (k(aSyncOut) == 1) then ; set new values
   kLoopStart = kLoopStartLiveInput
   ...
endif

With setksmps 1 you can do that and thus use standard flow control with if, while, etc.

HTH.

Best wishes,

Jeanette