Axo delay implementation


#1

Hi there, so for a project i'm doing for university, i am attempting to make a delay among other things (chorus & flanger, but they are derivatives on delay), and have found the way its done in factory objects is rather different to what i've been taught. To clear up any confusion, the object i am using is a combination of delay rite/read interp.
If we look at the local data it seems there are 3 delay related variables

I can see what these are defined as, but not really sure as to what each of them really do. In the processing code (s-rate) "LENGTH" isn't actually used at all, only LENGTHPOW * LENGTHMASK are used.

If we look at line 2 & 3 for example, this looks like the inlet actually being written to the buffer, but unsure as to what &LENGTHMASK is doing? and LENGTHPOW for that matter

And again with the processing code from read interp, I've been taught how to use buffers by having a read and a write pointer in a circular buffer that wraps around, but cant see anything to do with a read pointer, instead it looks like on line 7, tmp_di is a variable relating to the write point which acts as the read point, is this right ? but the code after that does get a bit lost on me, its so different to what i was taught! You're probably just thinking why not use what you where taught ? well i i would love to, but we where given a framework to work in written by our lecturer, and haven't had much success in porting it over (way too many error messages)

The main reason i am investigating all of this is because i am attempting to make a chorus from this as a building block, i have put the lfo inside the object and that all works, but i cant seem to figure out where to apply the lfo as there isnt an obvious read pointer!

Here is what i've done so far dead chorus.axo (4.1 KB) please let me know if you can help :slight_smile:


#2

I am pretty sure it refers to size and length of the delay line. You know, the attribute where you set the delay length in 1024, 2048, 4096, etc...

When I look at the picture I see it is not a factory delay line, cause the reference to the delay/write object has been removed. WHich one are you working on? The attr_ references for the array and writpos, which is usually located in the delay/write sdram object have been removed.

So if this is a combined object of the delay/write and the delay/read int, take a look in the local code page. There you should see this:

If not a combined object, then look inside the delay/write sdram.


#3

It looks like LEGTHPOW is the normal delay size, then what about the (1<<attr_size) for LENGTH and LENGTHMASK ?

I did write in the original post but there was a lot of writing so could have got a bit lost, i have combined the factory read/write interp into one object, what makes you think its not factory ? i have that same section you posted as the first picture in my initial post.

I can see what the names are, it just this has been done very differently to the only way in which i know, so i need to try and get my head around whats going on with each variable, as i understand the processes involved, i just can't pick that out from the code. Does that make sense ?


#4

Cause the factory objects are NOT combined. They are seperate, Anyway, i doesnt really matter :slight_smile:

I opened the code and looked. Look in "local code". There you see all three. If you want to find where the LENGTH is used, look in "init code". But in general ALL three refers to the delay size;


#5

Sorry should have been more clear then, it is a 'custom' object combined of the factory read/write :slight_smile:

My general question was about what these 3 are each actually doing, why have 3 variables for the length of the buffer ? for example whats the point of having LENGTH bitshifted left 1 position ?
but the second part was more trying to get an understanding of whats going on in the s-rate code for the delay/read interp and how those variables are being used, as ultimately i want to be modulating the read position with the LFO, as there isn't a specific function for the read pointer


#6

Sorry cant help you wit that. I am still learning things like that, so I am definately following your post :slight_smile:


#7

No worries! yes its quite hard just going from code without any comments or documentation, i think that is going to be one of the next big steps to get this product to the masses, documentation!


#8

mod note: ive renamed topic to a bit a bit clearer

I suspect its doing as you have been taught, just it using binary operations to keep it efficient..

LENGTHMASK is being used as a modulo operation to keep the array read within bounds.

LENGTHPOW
first instance to calculate the read position, (this is a division, taking into account this is in Q27 format, see user guide for more on this)
second instance, this is part of the interpolation, so how much of the delay time, is between samples.

sorry this is a bit 'overview' , the best idea is to work thru an example.
this integer maths is at first a bit confusing, again check the post in the user guide section.


#9

btw: if you are doing this for 'educational purposes' , I'm not sure id advise going down the whole Q27/integer maths route, its not really going to gain you much understanding in delay lines/fx (if that's your goal), and its hard when reading code, to try to learn multiple things at the same time.

so I think, I would be tempted to convert the audio/parameters into floats, and use a more understandable implementation... (as you are being taught), similarly your implementation doesn't need to use the optimisation tips used by the factory objects.

once you have your 'simple' implementation working, then you can take that knowledge and go back and revise to a more optimised form if you feel its necessary - but then you will be able to concentrate on that, rather than the delay implementation.

just my personal opinion , I'm sure you'll get there in the end either way.

EDIT, for float to int32 and back you need q27_to_float(), and float_to_q27(),
if you look at fx/rngs/reverb k-rate code you will see us doing this conversion.
(you don't need to look at the reverb code, just how its doing the conversion in Axoloti to float, which is probably how you've been taught delay lines)

I think that's probably an unrealistic expectation, I don't Axoloti factory objects are created to be an education in how to write dsp code/integer maths, rather to provide a visual patching environment so users do not have to know about dsp coding.
if you can learn coding techniques from them (as I have) I personally have seen this as a bonus.
(Johannes may have a different opinion, this is just my view)


#10

So first of all thanks @thetechnobear for another very helpful reply, much appreciated! also good choice in title update, mine was a bit vague.

I have taken your advice and a converting all data into floats for my working and then back again for the output which has helped me in other areas (distortion implementation).

I started trying to port over my old code into axo and i seem to have run into a bit of a halt. Basically, i set it all up as it worked with xcode, changing variable names and references of course so they match the axo framework, and when i run the object, the compiler takes a little longer than normal to run, then when it runs i got a couple of odd, squeaky noises, spits out a huuuge error in the console window and then crashes.
Before it crashed, i got a really quick look at the console and it said something about overflow of 2605 samples, which i'm assuming is something to do with how i'm creating and using my buffer, but i'm really not too sure as its not something i dealt with on my course.

So after axo had crashed i lost my object, so i re-wrote it out (only a few lines) and now when i go to compile the green LED flashes really fast, then it seems to reboot as it disconnects from my laptop, while both led's flash and im back to the start :confused: there is now no more error in console, only:
Starting patch...
patch start taking too long, disconnecting
Disconnect request
Done starting patch

Have uploaded the object in question. All i want to be doing here is taking the input and adding to buffer, then have a fixed delay time of 0.25s mixed with the original. All variable names should be obvious simple echo.axo (1.6 KB)


#11

ok, I don't have time to debug your code, so I can only point out some errors that 'stand out' to me.

the main issue, is you are trying to store the float signal into a 16 bit int, its should be a float (which is 32bit), make your array a float.

( are you not getting 'type conversion' warnings when you compile this?)

buffreadpos = buffwritepos - 12000; 
if(buffreadpos < 0) buffreadpos = 0;

this is wrong,
essentially what your trying to do is create a circular buffer...
if the writepos = 100, then readpos = 12000 -writepos,
i.e. 11500

note, you also need to be careful here, if your buffer size is less that the delay line size, you need to handle this too.
(your default is 5000, so cannot work for 12000 samples)

also your check will not work..
bufferreadpos will never be < 0 as you have declared it as a uint, i.e. an unsigned int...

this is probably what is crashing your patch, as on the first execution
bufreadpos = 0-12000, which will evaluation to UINT32_MAX-12000,
which is a very large number, well beyond your array boundary :wink:

finally, if you are using larger delay lines you will want to declare the array in sdram, as the default (sram) is quite small... look up one of the factory objects with sdram in title to see how to do this.

also id be tempted to add a 'wet/dry' param, so your out only has a part of you in signal.


#12

awesome, dont worry about that! that fact you can find time to help me at all i am gracious for :slight_smile: you must be a very busy man!

aha thought there might have been a few oversights in the porting of code, sorted, and no not that i saw!

buffreadpos = buffwritepos - 12000;
if(buffreadpos < 0) buffreadpos = 0;

This little bit is giving me quite a hard time to get my head around its wrong because this is the way we've been taught and is in my working code. I understand your example, but not sure as to what the work around would be.
To create the circular buffer, you wrap the write/read pointers so for the read, instead of the above code, would it be something like:

buffreadpos = buffwritepos - 12000;
if(buffreadpos <= 0) buffreadpos = buffreadpos + attr_size;

this in my head would fix it as it would just add the full buffer size with the offset from being negative ?

Have now declared the array into sdram, was getting the error: incompatible types in assignment of 'float*' to 'float* [17]' for this line...

array = &_array[parent->polyIndex][0];

but a pointer to the declaration in local data seemed to solve that.

Think there is something wrong with my floats though somewhere, these 2 lines of code which are the only ones accessing the array, are giving me the error "can't convert float to float in assignment", which has me headscratching as the only time im converting from/to a float is on an inlet variable first thing when audio comes in and last thing as audio is sent to the outlet.

array[writepos] = fIn;
fDelSig = array[readpos];

Does that error message imply that because the array is a float, the input will automatically get converted before it goes into the array ? that is my only thought (fIn and fDelSig are delcared as float type)


#13

sorry dont have time to read now...
the 'trick' with this, is probably to actually track readpos (not write) and use modulo

off the top of my head something like

r = (r + 1) % len;
w = (r + delay) % len

(could be wrong, as I've not really thought it thru :wink:)


#14

sorry dont have time to read now...
No worries! as i said earlier the fact you have time to lend a hand every now and then i am grateful for :slight_smile: you are a big help!

does your "% len" refer to the percentage of buffer used ?


#15

@Walla
http://www.cplusplus.com/forum/general/19502/


#16

thank you @Sputnki for the link, since technobear responded i have looked into this and can now see the big benefit of having a modulo (wondering why i didn't get taught this!).

The main issue im having at the minute is this error when trying to input/output the buffer

cant convert 'float to float' in assignment


#17

Are you executing code in k-rate or s-rate?

Because if you want to access buffered inlets or outlets (red) in k-rate, you need to specify the exact sample..

For example be out a frac32buffer outlet: in k-rate

outlet_out[15] = value;

While, in s-rate:

outlet_out = value;

#18

All processing code is being done in s-rate

Thought i would see what would happen if you just write the q27 inlet to the buffer and as expected you get

invalid conversion from int32_t (aka long int) to float


#19

Uhm, that's strange. Try casting it to int32_t, though i doubt it will solve anything

outlet_out = (in32_t) yourfloatvariable;


#20

Yep same error unfortunately :confused: