Deep dive into the ThreeTap delay, part 2

Let's quickly take a look at this section of the spcbSliderLabel.xtend file.

The possible values for the slider scaling "option" include:

  • LENGTHTOTIME - scale the entered value to a delay buffer length

  • FILTTOTIME - I think this is supposed to map the slider value to the rise time of a single pole low pass filter, as we saw in the envelope detector code. At this point I am not certain. I don't think it's used anywhere.

  • LOGFREQ2 - maps the slider to the coefficient to give the desired corner frequency for a typical FV-1 2-pole state variable filter structure.

  • LOGFREQ- maps the slider to the coefficient to give the desired corner frequency for a typical FV-1 1-pole filter structure.

  • DBLEVEL - maps dB shown on the slider to a numeric value (e.g. for gain)

  • LINEAR - 1:1 mapping

  • SINLFOFREQ - maps the frequency as shown on the slider to the proper coefficient for an FV-1 Sin/Cos LFO.

  • RAMPLFOFREQ- maps the frequency as shown on the slider to the proper coefficient for an FV-1 Ramp LFO.

  • BOOSTCUT - maps the shelf boost/cut in dB shown on the slider to the proper coefficient to use with WRLX/WRHX shelving 1-pole filter instructions.

  • (no option) - probably the same as linear?

I took a more thorough look at this code. Its primary reason for existence is to have a unified way to convert between things shown on the screen (such as a slider showing the corner frequency of a filter, or the length of a delay buffer in milliseconds) and the numbers that actually go into the Spin ASM code. So, you'll find references to functions which go both ways, e.g.

  • filtToTime()

  • timeToFilt()

The "precision" value is used in the function genLabelUpdater().

As it turns out, "multiplier" is not used for the DBVALUE option, but is available for other options. However, to keep the syntax consistent, you have to use it anyway.

Back to the ThreeTap code, (skipping comments):

equ fbkGain 0.5

@sliderLabel fbkGain 'Feedback Gain: ' -24 0 -6 1.0 1 DBLEVEL

As with the input gain, this is declaring a slider to work with the feedback gain. That represents the maximum gain through the feedback input.

equ delayLength 32767

@sliderLabel delayLength 'Delay Time (ms): ' 0 32767 16384 1 0 LENGTHTOTIME

We are setting a variable to represent the maximum delay time. The slider shows the desired range of the parameter while the LENGTHTOTIME option selects the proper conversion in the code generator.

equ tap1Ratio 0.85

@sliderLabel tap1Ratio 'Tap 1 Time (%): ' 0.0 1.0 0.85 1000.0 2

This creates the slider and variable for the tap 1 time % selection. Tap2 and tap3 are quite similar! Note there is no explicit "option" called out. The multiplier here is 1000.

equ tap2Ratio 0.60

@sliderLabel tap2Ratio 'Tap 2 Time (%): ' 0.0 1.0 0.60 1000.0 2

equ tap3Ratio 0.45

@sliderLabel tap3Ratio 'Tap 3 Time (%): ' 0.0 1.0 0.45 1000.0 2

Next we have to deal with SpinCAD's delay RAM relocation feature. This is fairly complicated so I'm not going to discuss it at the moment other than to say "you have to do this" if you want to use Delay RAM in your patch.

equ delayOffset -1 // establish the base address for this module

@getBaseAddress // then allocate the buffer

mem threeTap delayLength

Note that the "delayLength" which actually allocates the RAM for this block is the scaled value from the slider.

Next up we encounter the very important @IsPinConnected macro. When the model of the algorithm is calculated, each pin gets flagged as to whether it is connected or not. This state can subsequently control code generation. In this case, this conditional block wraps ALL of the functional (instruction generating) code. That means that if the pin named "Input" is not connected, we won't generate any code at all!

@isPinConnected Input

Note that a "real" (analog) mixer would still work even if all of the inputs were not plugged in. You can try to implement complex this-or-that logic so that it would accept either of the inputs (Main or Feedback) as being acceptable for causing the block to generate code. Since I have yet to encounter the need to NOT use the main input when I use this block, I have not done that.

Next up, we see it again!

@isPinConnected Feedback

rdax feedback,fbkGain

@isPinConnected 'Feedback Gain'

mulx fbk

@endif

@endif

This block is only generated if we have connected something to the Feedback pin. We read the register named "feedback" (which is allocated in the block feeding this input), scaled by fbkGain, the value from the slider.

What's more, the multiplication by the feedback control input is only carried out if that pin "Feedback Gain" is connected.

Next up, we mix in the Main Input scaled by the "inputGain" from the control panel slider. That value gets written to the head of the delay.

rdax adcl, inputGain

wra threeTap, 0.0

This next bit of code handles tap1 processing. Note that we also have an @IsPinConnected macro for the individual tap outputs. This means that, if we don't connect a tap, no code is generated for that tap! So this block is efficient to use for 1, 2, or 3-tap purposes.

@isPinConnected 'Tap 1 Out'

equ output1 reg0

clr

or $7FFF00

@isPinConnected 'Delay Time 1'

mulx cIn1

@endif

@getDelayScaleControl tap1Ratio delayLength delayOffset

wrax ADDR_PTR, 0

rmpa 1.0

wrax output1, 0.0

@setOutputPin 'Tap 1 Out' output1

@endif

We declare a register "output1" as reg0. Note that register declarations in SpinCAD Builder are just placeholders. As you might imagine, the first blocks in the model get the low valued registers and the ones further down the chain get the higher numbered registers as they are allocated from bottom to top. You should just use reg0 on up as you would if this block were standalone code.

We then set up some things to get the delay time of tap1 as a scaled RAM address into the ADDR_PTR so we can do a RMPA (read from delay RAM) to read the delay at that location. This is where the @getDelayScaleControl macro comes in.

This macro is at the heart of the common comment "SpinCAD generated Spin ASM looks weird". All RAM access instructions are handled by calculating a value for the ADDR_PTR rather than abstractions such as "buffer^" (read the end of the allocated RAM area called "buffer"). While it makes sense, it is difficult to hand edit code which uses this macro.

That code can be found in this Xtend file. Note that there is an automatic scaling of the delay time from 5% to 100% of the maximum value of the slider. I do not let the delay time as set from the slider go all the way to zero as it was never what I wanted. Nevertheless, this was a design decision that I made that other people might want to remove or adjust somehow. However, if you connect a pot or control signal you can still go to zero.

The @setOutputPin macro is also needed here, although at the moment I can't remember exactly why, because earlier we also identified this pin. I'm sure I'll stumble across the reason if I dig into that area of code again.

Taps 2 and 3 use the exact same approach as tap 1.

So, here's the final block diagram of the delay part of this block. As you can see, there are @IsPinConnected macros inside of other @IsPinConnected macros which control code generation depending on whether or not pins are connected.

Note that I did not show the @IsPinConnected for the Main Input pin that wraps the entire block.

Last updated