|
Post by mo13 on Apr 7, 2022 20:30:05 GMT
Multiple ones at the same time yes. and I suspect that happenned while I was testing the swinged clock with other speeds then the expected 24 ppqn. good to know about the buffer, I'm back on this tomorrow again.
|
|
|
Post by uncledave on Apr 7, 2022 22:46:32 GMT
Multiple ones at the same time yes. and I suspect that happenned while I was testing the swinged clock with other speeds then the expected 24 ppqn. good to know about the buffer, I'm back on this tomorrow again. Not sure about the swinging clock. The note delays are obviously based on 24 ppqn, so they won't be correct at any other rate. Of course you can vary the clock rate to change the tempo. But you should certainly never stop the clock while feeding notes into the delay, sonce they will pile up there. Should I add a Reset button as Slider 7, so you can clear the system?
|
|
|
Post by uncledave on Apr 10, 2022 17:31:10 GMT
Here's a new version which should handle multiple delays more efficiently. The rotating buffer concept made sense for a stream of notes with the same delay, but it doesn't help when notes have different delays. The external interface is unchanged, but the buffer management is different.
#TickDelayLinear # This version does not use a rotating buffer. It adds each note # in the first unused position, so the buffer cannot be clogged # with deleted entries.
# Slider 7 now displays the number of active items in the buffer.
If load # can increase maxCount to 80 ($128), but no higher Alias 9C sendNoteCommand # change as needed Alias $0 testOffset # should be 0 for normal use Alias 40 maxCount # max number of entries Alias 1 debugLevel # set to monitor buffer count # turn off for max performance
Set Name Delay # Screen Controllers ——————————————————————————— Alias 6 maxNote Alias 7 maxSlider Alias $11 maxSelect # using Select since this index selects the delay value # I prefer menus for these discrete 1 of N selections, but you could # use sliders. Just remove +Menu. # Note that the Qi are published as AUv3 parameters (scaled 0..127) # so you could control them externally that way. Or, of course, you # could implement CCs to adjust them directly. Set Q0 Note0_Select 0 maxSelect +Menu Set Q1 Note1_Select 0 maxSelect +Menu Set Q2 Note2_Select 0 maxSelect +Menu Set Q3 Note3_Select 0 maxSelect +Menu Set Q4 Note4_Select 0 maxSelect +Menu Set Q5 Note5_Select 0 maxSelect +Menu Set Q6 Master_Select 0 maxSelect +Menu # manually set theCount to zero to reset buffer Alias Q7 theCount Alias 7 indCount Set Q7 Count/Reset 0 maxCount
Ass Q0 = 0 0 0 0 0 0 0 0 +P Set SLIDER_DISPLAY 1 # End Screen Controllers —————————————————————————
# Delays: Whole 96, Half dot 72, Half 48, Qtr dot 36, Qtr 24, Qtr3 16. # 8th dot 18, 8th 12, 8th3 8, 16th dot 9, 16th 6. # this tedious method is the only way to convert a number into an # arbitrary string Sub SayTickDelay pTicks Set LB1 pTicks +D If pTicks == 0 Set LB0 S0 End If pTicks == $96 Set LB0 SWhole End If pTicks == $72 Set LB0 SDotted_Half End If pTicks == $48 Set LB0 SHalf End If pTicks == $36 Set LB0 SDotted_Qtr End If pTicks == $24 Set LB0 SQtr End If pTicks == $16 Set LB0 SQtr_Triplet End If pTicks == $18 Set LB0 SDotted_8th End If pTicks == $12 Set LB0 S8th End If pTicks == $8 Set LB0 S8th_Triplet End If pTicks == $9 Set LB0 SDotted_16th End If pTicks == $6 Set LB0 S16th End End
# delay values in clock ticks, 24 ppqn (pulses per quarter note) # array indices 0 to maxSelect Ass K0 = 0 6 8 9 $12 $16 $18 $24 $36 $48 $72 $96
# returns the total delay for the note in pResult Sub GetDelay pResult pNote # Master delay Ass I20 = maxNote Ass I20 = QI20 Ass pResult = KI20 Mat I20 = pNote - testOffset # allows using normal notes to test If I20 < maxNote # add delay for the note Ass I20 = QI20 Mat pResult = pResult + KI20 End End
# J00..J7F is a buffer of 64 2-word entries. # Each entry contains the delay (counting down) and the note # and its velocity, packed into one word. Times are counted down # on each tick, and each note is sent when its count reaches 0. # Notes are added to the first free slot.
# buffer parameters Alias 2 blockSize # number of words in each entry # control values in IFx registers Alias IF0 firstFree # index of first free slot Alias IF1 currCount # number of active entries Alias IF2 maxPos # last array index+1 Alias IF3 currMax # number of last used entry Ass IF0 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Mat maxPos = maxCount * blockSize
# return the first free slot. This will normally be firstFree, unless # multiple notes arrive between ticks. Sub FindFreeSlot pResult Ass I20 = firstFree Ass pResult = maxPos # assume failure While I20 < maxPos If JI20 == 0 Ass pResult = I20 Ass I20 = maxPos # break loop on success End Mat I20 = I20 + blockSize # advance to next block End # while End
# adds the given note to the queue, with the appropriate delay time Sub AddNote pNote pVel GetDelay I11 pNote If I11 > 0 # skip if delay is 0 FindFreeSlot I10 If I10 < maxPos # only add if space available Mat I12 = currMax * blockSize If I12 == I10 # update maximum when extending buffer Mat currMax = currMax + 1 End Set LB0 pNote +D # show note and delay when adding Set LB1 I11 +D # new entry fills position I10 and I10+1 Ass JI10 = I11 # delay in ticks Mat I10 = I10 + 1 # Note and velocity packed in one word Mat JI10 = pNote * 80 # note value Mat JI10 = JI10 + pVel # velocity Mat currCount = currCount + 1 If debugLevel > 0 Ass theCount = currCount End Else Set LB1 SFull Log Buffer_Full # log msg appears in monitor End End End
# sends the current note. pWhere is index of the count. Sub SendDelayedNote pWhere Mat I20 = pWhere + 1 # note in next word Mat I21 = JI20 / 80 # unpack note and velocity Mat I22 = JI20 & 7F Send sendNoteCommand I21 I22 End
# removes trailing zero entries, reducing the currMax Sub CleanUp # start from the end Mat I20 = currMax * blockSize Ass I21 = I20 While I20 > 0 Mat I20 = I20 - blockSize If JI20 == 0 Mat currMax = currMax - 1 Ass I21 = I20 Else Ass I20 = 0 # break loop on non-zero entry End End # while # update firstFree if it was off the end If firstFree > I21 Ass firstFree = I21 End End
# decrement the count for each active entry, sending notes # when their delay expires. Called for each clock tick. Sub UpdateTicks Ass I10 = 0 Ass I11 = currMax Mat firstFree = currMax * blockSize # loop over active buffer entries While I11 > 0 If JI10 > 0 # skip notes already sent Mat JI10 = JI10 - 1 # decrease count If JI10 == 0 # first time count reaches 0 SendDelayedNote I10 Mat currCount = currCount - 1 If I11 == 1 # remove tail if last entry was sent CleanUp End If firstFree > I10 Ass firstFree = I10 # save position of first free slot End If debugLevel > 0 Ass theCount = currCount End End Else If firstFree > I10 Ass firstFree = I10 # save position of first free slot End End Mat I11 = I11 - 1 # decrement counter Mat I10 = I10 + blockSize # advance through buffer End # while End
# clear the buffer Sub ResetBuffer Set LB0 SReset Ass theCount = 0 Ass firstFree = 0 Ass currCount = 0 Ass currMax = 0 Ass I10 = 0 While I10 < maxPos Ass JI10 = 0 Mat I10 = I10 + 1 # please add this End End
ResetBuffer # initialize data on load
End # Initialization ———————————————————————————
If M0 == F8 # process clock first, since it is most frequent If currCount > 0 # check count first to save time UpdateTicks End Block Exit # exit here for quickest handling of F8 End
# do not add the Note Offs to the buffer unless you really want them #If MT < A0 # used for testing If MT == 90 # process Note On # you can modify incoming values here as needed Ass I00 = M1 M2 AddNote I00 I01 # this note and velocity added to buffer End
# these events happen when a control is changed If M0 == F0 7D 01 # handle control change If M3 < maxSlider # M3 is the index of the changed Qi # displays the delay based on the selection Ass I00 = QM3 SayTickDelay KI00 End If M3 == indCount # move slider and back to 0 to trigger reset If theCount == 0 ResetBuffer End End End
Block
|
|
|
Post by mo13 on Apr 12, 2022 12:18:54 GMT
thanks for the update uncledave, not sure if this is normal behaviour but the count/reset slider is jumping around now:  also, if I didn't remove the +menu, then the drop down menus are inaccessible if I try to chose something from them while clock+notes are coming in, this is what's on the monitor:  btw, I didn't encounter any more 'Buffer Full' msges in the older script since I just used it normally without experimenting with clock swings and such. I am wondering if the reset slider is necessary in my case, as I'm switching presets on every new song?
|
|
|
Post by uncledave on Apr 12, 2022 12:59:08 GMT
The Count/Reset slider shows the current number of active entries. It's a little bar graph, meant as a diagnostic tool. You can disable it by changing debugLevel to zero. You should disable it for live use. It will still work as a Reset, in case of some problem.
+Menu generates a pop-up. It works for me while Clock is coming in. Having notes coming in creates a lot more work, since the counts have to be processed on every clock. That activity may interfere with SB displaying the menu. The slider is fine, if you're OK with adjusting it. Personally, I find it can be hard to hit a specific number with a slider; tends to be off by one. Also, I realize the active Count slider may interfere with the +Menu pop-up.
Modifying the Clock rate should not affect Buffer Full as long as you don't slow it down too much. Obviously, if notes continue to arrive at a fast rate, while the clock is slowed, notes will be leaving the buffer at a slower rate, so it will fill up. Try varying the Clock with the new version, and watch the Count slider to see how full it gets. It's scaled to the maximum size. The new version uses the buffer a lot efficiently, since it can use every slot. The original was more like a queue, adding to the end and only removing from the front. So there could be "zombie" entries; notes already sent but not yet removed from the queue.
We can still double the buffer size if necessary. You can do it by changing maxCount from 40 to 80. That will give 128 entries.
And, if we don't need to store the Velocity for each note, I could reduce the data to one word per note, doubling capacity again. Can we drop the Velocity?
|
|
|
Post by mo13 on Apr 12, 2022 14:07:22 GMT
I just adjusted both debugLevel / maxCount and let the busy stream run out for a couple of minutes and even though the buffer log runs full pretty fast, all the 6 delays are still audibly in the right place.
for me the sliders work better as I can quikly change things on the go to timing/variations of the interplay of 6 delays and at some point not think about what the exact delay timing of a certain trigger should be, just go with the best result that I hear.
I double checked the MIDI-CV converter manual to be sure about the velocity and I think it's best to keep it in there (unless I soon run into an issue with the way it's processing notes now)
Vel Depth:The amount by which the note on velocity affects the envelope's depth. At 64, the velocity has no effect on the envelope. For values above 64, the envelope's depth is increasingly affected by velocity. For values below 64, the envelope's depth is inversely related to the velocity i.e. higher velocity gives a smaller envelope
|
|
|
Post by uncledave on Apr 12, 2022 15:43:08 GMT
Sounds good. If you turn the debugLevel back on and watch the Count slider, you'll be able to see if it stays full, or just bounces off the top once in a while. Of course the Log messages show that as well, since one is printed whenever a note cannot be added. When the buffer is full, one note is dropped, because it cannot be added. If space becomes available, later notes will be added. So, the effect is that notes are randomly dtopped from the delay.
To think about the buffer size, consider what happens when the note source clock is the same as the delay clock. You can calculate how many notes of each type will be in the buffer, based on the length of the delay and the complexity of the pattern. For example, if the delay is whole note and you're generating 16ths, there will be 16 notes in the buffer. Add those counts for each note and you know how big the buffer needs to be. Problems will occur if the delay clock is slower than the generator clock, because notes will be removed faster than they are arriving. You can only sustain this for a very short interval before it would fill even a very large buffer.
Can you give me some typical and extreme numbers for the different notes, so I have an idea of what we're dealing with?
I may be able to create a much larger buffer using the W array in SB. I'll try that. It will give a buffer of 1024. However, processing time will eventually get to you, since every note counter has to be updated for every clock tick. Maybe we could put the short delays in one buffer, updated every tick, and the long delays in another buffer, updated every 4 or 6 ticks. That could reduce the processing load, at the expense of a small loss in precision.
If you're just tweaking the sliders until it sounds good, remember that you can map the AUv3 parameters, or directly implement CCs in the script, so you can adjust them without using the GUI. If you like, I can show you how to convert a CC value to a nice 1-of-N selector switch
|
|
|
Post by uncledave on Apr 12, 2022 18:51:56 GMT
Jeesh! There's a stupid mistake in Sub ResetBuffer. I've edited the original post to add incrementing the index inside the loop. Please add this to your copy. You need this even if you "never reset" because it also initializes the data.
I still wonder about needing to store the Velocity for each note. Does the note generator send different velocities for each note? If not, we could use a constant velocity, or maybe 6 note-specific velocities. We could use the second page of the controls to implement 6 velocity sliders. So, we would send a velocity with each delayed note, but we wouldn't have to store a unique velocity value for each note.
I'm working on the W array version, and I think I can make the scan (which happens for every tick) more efficient.
|
|
|
Post by mo13 on Apr 13, 2022 8:22:30 GMT
just ran your updated script with count/reset activated, all 6 delays open, with the max. notes that my sequencer is able to put out, which are 96 notes 6x16 per occupied page. and good news, count/reset never reaches 30, it bounces back and forth between 19 and 29. the SB monitor obviously becomes unreadable as it's just rapid fire of notes and clock ticks. But if I stop it then no more Buffer Log msges having accured anywhere as well!
if I understand correctly you mean that you know which exact value corresponds to which one of the 11 time signatures? as now if I map an encoder to say Q0 it just follows the increment as it should. But I do have another idea for this, if I know the exact values maybe it's more efficient to constantly stay in 1 script instead of switching AUM's saved SB presets? So for instance I could have different (CC value presets) on every song part instead of just every actual song aka how it is now @switching saved SB presets in AUM. As my controller sends out B1 22 values - 00, 01,02,03. aka 4 songs parts, of which every value of can be used to set 6x delay. So in practice I could make a table like - delay sig. 1/16 CC value $10 etc. would it make sense to approach it this way? That would take a little extra time to write out the specific rules per song part but the benefit would be different delays on all 4 song parts instead of an entire song. Another reason would be that the song parts do very rapidly switch back and forth at times, I can't imagine that being 100% bulletproof for reloading SB instances that fast with clocks running and all. ps. those 4x B1 22 values would be kept up to date on every MidiFIre scene switch aka new song here.
yes, different velocities per note as I pre-program those, plus there is a general velocity fader on my controller which controls all of them at once. So maybe it's best to leave it how it is now @velocity considering my MIDI-CV converter just naturally corresponds/changes the outgoing envelopes when I set a velocity step or engage the general velocity fader.
|
|
|
Post by uncledave on Apr 13, 2022 17:20:48 GMT
This sounds good. Peak count of 30 means you've got decent headroom. I'm going to finish the version using the larger W array, and add some efficiency improvements. As I already mentioned, the limiiting factor is going to be CPU usage. This is a real-time process, so everything that's meant to happen in one tick has to take less than one tick. It's not just one script, it's everything running on your device, including AUM. MidiFire, etc. This script is a bad offender, because it has to check every delayed note on every tick.
I'm not saying I know the exact CC values for every note value. Just that I can transform a full-range CC so that each note value occupies an equal band, making it easier to adjust a knob by "feel". The obvious way of doing this doesn't work as well.
Too bad about the velocity, but if the buffer is large enough, that's not a problem.
|
|
|
Post by mo13 on Apr 13, 2022 19:23:23 GMT
Looking forward to the W array! CPU usage is not an issue as I'm already limiting it here with only mixing incoming audio and putting out CV out of AUM without CPU consuming AuV3's or anything. Just quickly tested the 'values presets' and all is working as expected:
Set Name EnvDelays
Alias B7 dlay
#delays
Alias $50 BD Alias $51 hh Alias $52 sd Alias $53 ride4 Alias $54 cl Alias $55 tm Alias $56 master
#timeSigs
Alias $0 0th Alias $15 1st Alias $25 2nd Alias $35 3rd Alias $45 4th Alias $56 5th Alias $65 6th Alias $76 7th Alias $88 8th Alias $100 9th Alias $115 10th Alias $127 11th
Ass K0 = $50 $51 $52 $53 $54 $55 $56
#songParts
IF M0 == B1 22 IF M2 == 00 #pt_1 Snd dlay K0 0th Snd dlay K1 0th Snd dlay K2 0th Snd dlay K3 0th End IF M2 == 01 #pt_2 Snd dlay BD 5th Snd dlay hh 2nd Snd dlay ride4 2nd End End Block
Maybe there is a more convenient way on how I should write out those song parts at the end? The are 4 parts - B1 22 val. 00,01,02,03
|
|
|
Post by uncledave on Apr 13, 2022 21:47:40 GMT
Yeah, you could handle the song parts a little cleaner, but not much. And it's not significant, since it happens so seldom. But you could
If M0 == B1 22 If M2 == 0 ... End If M2 == 1 ... End etc. End This way, you only check the first two bytes once, then get down to specifics with M2.
And I think it's clearer if you use Block at the end instead of XX = XX +B. Mixing these old pattern-matching rules with script logic is confusing.
|
|
|
Post by mo13 on Apr 13, 2022 22:13:01 GMT
That's better yeah, I did try using Block and it would still pass everything else from the controller besides B1 22 etc, I think I've put it before End, so I see now, it always gos after End to Block all and to do the same thing as xx = xx +b
|
|
|
Post by mo13 on Apr 14, 2022 7:48:41 GMT
Just updated mine (above), if I have a preset where all the delays need to be set to 0, is there a way to send everything containing in the K array at once instead of using multiple sends?
|
|
|
Post by uncledave on Apr 14, 2022 10:04:53 GMT
Just updated mine (above), if I have a preset where all the delays need to be set to 0, is there a way to send everything containing in the K array at once instead of using multiple sends? Not really. You need a separate Send for each CC message. You could use a loop when sending the same value to all CCs. Ass I00 = 0 While I00 < 7 Send dlay KI00 0th Mat I00 = I00 + 1 End
|
|