Question using the cho function

Post Reply
HKWijhe
Posts: 16
Joined: Wed Nov 16, 2022 4:27 am

Question using the cho function

Post by HKWijhe »

In the default program 5 the chorus (delay) memory is reserved as follows:

.mem delay 1024

But in the cho instructions some tap values are used bigger or smaller than the memory allows.

; voice 1
chr lfo0|sin delay+1400

; voice 3
chr lfo0|sin|neg delay+16

How is this handled within the fxcore since the read address is outside the memory boundary.
What happens if the read address + lfo modulation falls outside the 0..1023 memory bytes?
Frank
Posts: 159
Joined: Sun May 03, 2015 2:43 pm

Re: Question using the cho function

Post by Frank »

Well, looks like I made an error in the program. But it still works because there is only 1 memory block defined (which is partly an answer to your question)...

FXCore will always read from whatever address you pass to CHR/is calculated by CHR using the LFO even if outside the defined .mem range, it will simply read outside the range.

In this case it works as the memory above the defined range is not being used by another algorithm so it is not being overwritten. Had there been a second .mem defined using a reverb like:

.mem delay 1024
.mem reverb 2100

We would have heard problems as the CHR would be trying to use a reverb delay line as part of the chorus and that would sound really bad.

So in conclusion, CHR will chorus on what ever memory area you tell it even if it runs outside the .mem defined range.
If routines work in isolation but not together check for overlapping memory usage.
The assembler does not catch overlapping usage as there are times it may be desired, for example a delay and a chorus could both use the same delay line if they both only read from it.
Up to the programmer to make sure they don't exceed the defined range (which I did here but it worked which is why I didn't notice it in testing)
If something like a chorus sounds good/bad/good you are probably just stepping past the end of the delay into another used area of memory and need to lower the depth or extend the length of the delay depending on desired effect and available memory.
HKWijhe
Posts: 16
Joined: Wed Nov 16, 2022 4:27 am

Re: Question using the cho function

Post by HKWijhe »

OK, thanks for your explanation, makes sense!
DisasterArea
Posts: 26
Joined: Sat Jul 25, 2020 7:07 pm

Re: Question using the cho function

Post by DisasterArea »

You also have to be aware that the depth of the CHR instruction matters here. If you declare a memory block of a certain size, you need to make sure that you don't go past either end of it.

If you do:

Code: Select all

.mem delay 1024
chr lfo0_sin delay+512
The program will start in the middle of the delay memory and will range as far as you allow it to, using the value that is in r15. You'll be hearing a delay of 512 samples, about 10ms at 48kHz, but that delay will vary depending on the value of the LFO.

CHR is a special instruction that uses two registers for its parameters, check the datasheet.

The built-in LFOs always range from -1.0 to +0.999999 or whatever, 0xFFFFFFF to 0x7FFFFFFF, so it's up to you the designer to make sure the range that is in r15 suits what you're trying to do. If you put too large a value in there and exceed the delay boundaries you'll either get no audio (if you go negative) or the address counter will roll over (if you go positive.) Both are bad. I ran into that same issue on a reverb program where I was doing some modulation on the delay taps, and because my depth value was too large I was going negative and the signal muted about once a second.

From example #5:

Code: Select all

cpy_cs  temp, pot1_smth           ; read in depth control pot
wrdld   acc32, 400
multrr  temp, acc32
cpy_cc  r15, acc32
WRDLD puts an immediate 16-bit value into a register's upper half, so we're telling the CHR instruction it's okay to go 400 samples into the memory. I'll have to check with Frank to verify but I think since the LFO is bipolar (goes positive and negative) that the result here is that we are going +200 when the LFO is at max and -200 when it's at minimum. In this case since we are starting at address 512 of the "delay" block, we're going between 312 and 712 of the delay memory. This will get you a delay time that ranges between 6.5ms and 14.8ms at 48kHz sampling, which will sound pretty nice, if a bit "flangery." Really short delay times, below 5ms, tend to sound much more metallic. Longer delay times, around 30 ms, will sound more like a slapback and can get pretty seasick at high modulation rates, you might not like that. Or you might, I dunno. Turn down pot1 for less depth, increase for more. Want to get more depth on the effect? Load a bigger starting value into r15 here. You could even have it scale along with the speed, if you wanted, let's look at how to do that:

Chorus typically sounds better if you keep the depth low at fast rate settings, and vice versa. So we might want to slightly decrease the depth as we increase the rate, while still leaving it adjustable.

We start with the base of example #5:

Code: Select all

.equ    fs          48000
.equ    flow        .2
.equ    fhigh       10
.equ    pi          3.14159
.equ    clow        (2^31 - 1) * (2*pi*flow)/fs
.equ    chigh       (2^31 - 1) * (2*pi*fhigh)/fs
.equ    cdiff       chigh - clow

.mem    delay       1024

.rn     temp        r0
.rn     voice1      r1
.rn	scale	r2	; this one is new
.rn	depth	r15	; and we're just naming this one

cpy_cs  temp, pot0_smth           ; read in frequency control pot
wrdld   acc32, cdiff.u            ; load difference between low and high frequency
ori     acc32, cdiff.l
multrr  temp, acc32               ; pot0 * cdiff
cpy_cc  temp, acc32
wrdld   acc32, clow.u             ; load low freq coeff
ori     acc32, clow.l
adds    acc32, temp               ; add low freq
cpy_sc  lfo0_f, acc32             ; write to lfo0 frequency control
That declares all the registers and also sets the speed of the LFO. The higher values of pot0_smth will result in a faster chorus

Now we'll use pot0 AND pot1 to set the depth of the chorus effect. We want to let the user set the depth, but then we'll take that result and decrease it slightly as the rate goes up.

Code: Select all

cpy_cs  temp, pot1_smth           ; read in depth control pot
wrdld   acc32, 400			; put max value in acc32
multrr  temp, acc32			; scale max value by depth pot
cpy_cc	depth, acc32		; store this for later

cpy_cs	acc32, pot0_smth	; get the speed pot again, ranges from 0 to 1
multrr	acc32, acc32		; multiply acc32 by itself, this imparts a "log" or "audio" type taper to the result
multri	acc32, 0.2		; now ranges from 0 to 0.2
subs		depth, acc32		; subtract result from depth value, with speed at minimum the depth is not changed, at max it ranges from 0 to 0.8
cpy_cc	depth, acc32
So we're just taking another reading of pot0 and scaling it down lower. I originally wrote some more complex stuff to scale and invert the results, but since FXCore has a subtract instruction we can just do that with the positive values.

The rest of the code is the same as example #5, I've only included one voice here for clarity.

Code: Select all

cpy_cs  temp, in0
wrdel   delay, temp

; voice 1
chr     lfo0|sin delay+512	; changed this - used to be +1400 but that is out of range of our delay block
cpy_cc  voice1, acc32

; get effects level pot and scale effect
cpy_cs  temp, pot2_smth
multrr  acc32, temp

; add in dry
cpy_cs  temp, in0
adds    acc32, temp

; write it
cpy_sc  out0, acc32
cpy_sc  out1, acc32

So there you go. You're totally correct about the example being wrong but it works because as Frank said there is nothing else in that area to mess up. Just be careful when you're using CHR that you don't go out of range on the other end, either!
Post Reply