Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LFSR loop reset functionality to Quantermain #83

Open
wants to merge 8 commits into
base: 136
Choose a base branch
from

Conversation

needto
Copy link

@needto needto commented Mar 7, 2019

This adds the possibility to select one of the trigger inputs to act as a reset input for the LFSR in Quantermain. Useful for creating randomized, but repeatable melodies with more complex rhythms and a varying amount of step lengths in the sequence.

One caveat is: since the bit that is flipped based on probability is at the end of the buffer based on buffer size selected, if the length of the buffer is much higher than notes played between resets, the bits that are flipping don't get a chance to be used for a note, so the sequence stops changing. One possible solution to that could be doing the probability flip at each reset on the bit that was reached (though that would make the probability dependent on the frequency of resets).

This function became indispensable for my sequences, I hope someone else could get some use out of it as well.

@mxmxmx
Copy link
Owner

mxmxmx commented Mar 11, 2019

hi!
and thanks! sounds like a useful feature, i/we didn't really have time yet to take a look at it; so i'm not entirely getting the nature of caveat. are you saying it's a bit dysfunctional as is ("sequence stops changing"), or it's basically ok, but it might exhibit say 'undesirable' behaviour? it's been some while since i've looked at the LFSR code, but couldn't the bit flipping not happen on the basis of the first bit? (rather than the last?)

@needto
Copy link
Author

needto commented Mar 18, 2019

hi!

I've read into the code some more, internalized it for myself and tried to put together a description of the problem:

Normally, the bit is flipped randomly and then the register is shifted in such a way that the flipped bit ends up at the end of the buffer.

For example, if we set the buffer length to 16 bits, flip the LSB and shift the register:

          |CV value|
0000 0000 0000 0000
flip:
0000 0000 0000 0001
shift:
1000 0000 0000 0000

The flipped bit ends up at the end of the buffer, we need to clock the LFSR 8 more times until it ends up in the 8 lower bits the CV value is derived from. Meaning if we do the reset every 8 or less steps (it will rewind the buffer to the 0000 0000 0000 0001 state before Clock() is executed), we won't get to hear the effect of the flipped bit at all, because the flipped bits won't be able to rotate enough times to reach the 8 lower bits to affect CV out, the audible sequence will stay in effect unchanged.

This is not an issue if the buffer length is set such that the buffer has a chance to rotate entirely before a reset is received, i.e i set the buffer to 10 and then the resets are sent by chance either after the 5th step or after the 15th, all bits have a chance to flip then. Generally there's no problem with smaller buffer settings.

One possible solution would be to shift the register in the other direction, i would like to try that but it might somehow change the character of the melodies produced (looks like the Disting has this as a configurable parameter in its shift register algorithms, not 100% sure the implementation is the same but sounds like it might be related from the description: Parameter 0 sets the direction of rotation. The two directions have a different sound to the patterns they tend to generate.. TBH i don't think there was a big difference when i compared the two modes, i'll try to test some more).

Another option would be to execute the random bitflip whenever a reset signal is processed, and flip the bit according to the current position in the buffer, i.e. if we got to bit 3 and received a reset, flip the 3rd bit according to the probability value. That could solve the problem of the sequence not changing as well but would be a bit trickier to implement and would make the algorithm more complicated.

Hope that clears it up a bit, I could implement a solution if you like one. Maybe try and see whichever works best.
I think having a reset is useful, but i understand it might be not easy to explain to the users in the manual why they should not set large LFSR lengths if they're triggering resets, so it's best if it stays easy to use.

@patrickdowling
Copy link
Collaborator

Hm. I guess it's a tough call because the concept of a "reset" doesn't really mesh with something that has no real beginning, and adding ever more things to try and bend it feels like it'll be frustrating 😃

Off-hand an alternative might be yet another parameter: a bit pattern that gets used to init the register on reset. Maybe just a nibble that's used to fill the 32 bits. 16 values are a bit more convenient than scrolling through 256, and 2^length is a bit awkward to implement with one parameter depending on the value of another.

@w-winter
Copy link

@needto thanks for proposing. I realized recently that I wanted this for the LFSR too !

@needto needto changed the base branch from dev136 to master May 9, 2020 22:51
@needto needto changed the base branch from master to dev136 May 11, 2020 08:18
@needto needto changed the base branch from dev136 to 136 May 11, 2020 08:19
@needto
Copy link
Author

needto commented May 11, 2020

Hi!

I found a simple solution for resetting the LFSR while keeping the probability functional throughout the buffer - instead of rotating all bits of the buffer towards the chosen starting point at once, we can just run the Clock() function the required amount of times, skipping through the buffer while applying the probability function. It uses a few more CPU cycles, works fine on my O_C though unless I try something purposefully heavy ie trigger and reset on each 16th step of 4 channels at once at a high BPM, then the display may glitch out.

Since resetting essentially applies the probability function to the whole remainder of the buffer, I added a small calculation that scales down the current probability value based on how many steps we need to skip through, so that the randomness does not increase much when resetting, but is still applied.

@patrickdowling my idea of a reset is to have a defined starting point somewhere in the buffer just so that it's possible to rhytmically repeat the generated sequence, also allowing to change trigger patterns and trigger counts while still aligning the sequence to the bar, turing machine sequences tend to sound polyrhytmic unless you match your trigger count with the length of the buffer, which is tricky to do manually if the trigger count changes often i.e. with probabilistic triggers :)

Made a short demo if you want to check out how it works: https://www.youtube.com/watch?v=lV9-5FsPDzk

@patrickdowling
Copy link
Collaborator

IIRC there's no hard requirement that only the lower 8 bits should be used; that seemed like a Good Idea At The Time and makes some sense given that the range is small. But always using register * range / 2^length would alleviate some of the base issue, and "reset" could become "# of bits to zero (or set)"?

There's a fix for the screen glitching in this and this branch.
Unfortunately it seems there's a few places that exceed the processing time allowance, that's something I might have kept tighter reins on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants