|
Post by mo13 on Apr 5, 2022 11:07:44 GMT
I've been playing around with feeding offsetted notes by simly using +D flag for the purpose of using them as triggers in an envelope generator, to achieve DADSR (delayed attack decay sustain release) I was wondering if there is a posibility to feed a clock to such script? for more accurate results, as naturally the micro timing is drifting, but still has potentially very usable results if the offset would be slaved to a clock. The drifting of the clock itself shouldn't be an issue (i think) as I'm using a clock that is generated by the same device that puts out the notes.
so far I'm using this script to find a sweet spot with the fader and afterwards I'll put the offset increment in another SB script as I'd like to use different presets of the offset time:
I'm interested if feeding it a clock can be done at all, to have a fader set the offset timing more accurately, thinking in standard terms of 1/1, 1/2, 1/4 etc, thus I wouldn't mind to hear if something more irregular would be possible as well. Lastly as I mentioned to have different presets of this, SB doesn't save the fader state upon recalling a preset if I'm correct? But if this would work in quantized increments then maybe the fader state can be set by CC's without the need to recall presets.
|
|
|
Post by uncledave on Apr 5, 2022 18:55:47 GMT
Hi. Not sure about clocking, but I have a couple of suggestions.
If you initialize the sliders (they're not "faders") in the If Load section with +P, the values will be saved in any preset: Ass Q0 = 0 0 0 0 0 0 0 0 +P
You can also use this to set reasonable initial values. Also, add Set Sliderdisplay 1
at the end of If Load, so the controls will appear automatically. You can still hide/display them with the controls icon.
And you would likely prefer +D on those label Sets at the end, so the values display as decimal numbers.
Finally, you're blocking Note Off, so your translated/delayed sender only sees Note On. That means those translated notes will never see Note off. You should probably just remove the block of Note Off (80). The test for MT < A0 covers both Note On and Note Off, so they'll be handled correctly.
|
|
|
Post by uncledave on Apr 5, 2022 19:02:03 GMT
Is the device that puts out the notes sending MIDI Clock messages (F8)?
|
|
|
Post by mo13 on Apr 5, 2022 19:27:03 GMT
thanks for the corrections, that works perfect. This was my first time dealing with sliders but I already thought that +P should be doing something there. yes, MT < A0 is seeing both On/Off. These are ment to be used as triggers, and also I generally block those note off's to have a less busy note stream overvieuw in SB monitor.
Yes!
|
|
|
Post by uncledave on Apr 6, 2022 12:44:35 GMT
When you Block the Note Off before checking MT < A0, you will never see the Note Off. Block makes the message buffer undefined (all 0 actually). So the later calculation will do nothing, or at least nothing useful. I guess you don't actually need Note Off at all, so that's OK.
|
|
|
Post by uncledave on Apr 6, 2022 13:18:21 GMT
Do you have access to the current BPM setting, maybe from Link? If you do, SB knows it and it's easy to calculate the delay for a note value.
Otherwise, you'll need to count MIDI clocks, and manage a queue of pending delayed notes. This is not difficult, but more of a pain than just sending a delayed note.
I can show you how to do either, but would like to know which you need.
|
|
|
Post by mo13 on Apr 6, 2022 13:52:39 GMT
ah, so the BPM is constantly changing with each track, this is ment for a non-stop set that is building up from 140 to about 160 BPM. So if that's unrealstic or too time consuming then all good too!
|
|
|
Post by uncledave on Apr 6, 2022 17:03:32 GMT
OK, so we need to count clocks. I can put something together.
What delays are you thinking of? Just whole note, 1/2, 1/4, 1/8? We could handle dots, triplets, etc. It's not difficult.
And how many notes will be delayed at any one time? 1, 2, 5, 10, 50, what? This matters because we'll need to store the notes until their time arrives. The estimate doesn't need to be super accurate, but there will be an upper limit.
|
|
|
Post by mo13 on Apr 6, 2022 17:19:37 GMT
Great. There are 6 drum trigger notes in total, which so far I've only been using a single note from but as you're asking if its 50, then 6 is for sure the limit here! Btw not sure if you gathered this from what I wrote, but the idea is to be able to set independent delays for each note, is that something doable? as I mentioned earlier, I wouldn't mind more extra off key time signatures included, @ dots/triplets, yes please!
all notes come in on ch1. notes 0-5 and go out on ch.13 the rest can be blocked.
|
|
|
Post by uncledave on Apr 6, 2022 18:03:48 GMT
Great. There are 6 drum trigger notes in total, which so far I've only been using a single note from but as you're asking if its 50, then 6 is for sure the limit here! But if the delay is long and the notes are rapid, could there be more notes in the pipeline? They might be the same note value, but at different times. I assume you mean different delays for each note value, i.e. 0..5. Now, there are 24 MIDI clocks per beat (ppqn), or 96 per bar. The easy, but not user-friendly, way to do this is to enter the number of ticks as the delay value. How would you feel about that? We wouldn't have to use the full 96 (the sliders are pretty finicky when the range is large). If the shortest delay is 1/16, we could divide by 6 and just use 0..16 as the number of sixteenths to delay. However, to include triplets, we'd have to go to at least 48, so it divides by 3. We could make it easy to set a large number exactly by using +Menu, but then you need a lot of scrolling to reach a given value. Or we could use a table to determine the values. You'd select an index and another slider would show the selected value. That makes the selector short (only 10 or 12 possibilities). That's probably the best compromise. Actually, we could use the labels to show the selection as text. How low do we need to go? 1/16 minimum delay?
|
|
|
Post by mo13 on Apr 6, 2022 18:20:04 GMT
I see what you mean now, other notes can/will possibly catch up to the delay, then maybe it's better to have the range set a bit larger.
yes.
this sounds good!
just an idea, how about then also having one global offset slider? If that doesn't defeat the purpose, I have a feeling that this could experimentally work on certain presets.
yes.
|
|
|
Post by uncledave on Apr 6, 2022 18:30:18 GMT
OK. So, six sliders for note delays, including 0 for no delay. Then one global delay slider. When you move a slider, its delay is shown as text in LB0. That's abput as user-friendly as possible in SB.
I'm going to make it so you need to handle the details of which channel, velocity, etc. You'll just pass the notes to a Sub, and I'll make sure they're echoed at the right time.
|
|
|
Post by uncledave on Apr 7, 2022 16:49:43 GMT
Here we go then. This delays notes 0..5 by varying amounts and sends them on channel 13 after the delay. It does this by holding the notes in a buffer until their delay in clock ticks runs out. #TickDelay
If load Alias 9C sendNoteCommand # change as needed Alias $0 testOffset # should be 0 for normal use 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. 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 Set Q7 +Hide
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, 8th 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 rotating buffer of 64 2-word entries. # Each entry contains the delay (counting down) and the note # and its velocity, packed into one word. Each note is added to the # current end of the buffer. Times are counted down on each tick, # and each note is sent when its count reaches 0. The head of the # queue is at position currPos. When currPos is near the end of the buffer, # the entries "wrap around" and start again at the beginning. When # the note at the front of the queue is sent, it can be removed from the # queue, increasing currPos. Because notes can have different delays, # notes may be sent when they are not at the head of the queue. We # just leave them in position until the queue head is sent, then remove # them en masse. Note that we never move queue entries; we just move # the indexes as data flow through. We could expand the buffer up to 128 # entries, if required. I was able to fill the buffer by hammering a triad with # whole note delay.
Alias 2 blockSize # number of words in each entry Alias 80 maxPos # last array index+1 Alias 40 maxCount # maximum number of entries # saved values in IFx registers Alias IF0 currPos # index of current queue head Alias IF1 currCount # number of entries
Ass J00 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J10 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J20 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J30 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J40 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J50 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J60 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass J70 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ass IF0 = 0 0
# updates pPos to the start of the pCount'th block, with wrap-around Sub AdvanceBlock pPos pCount Mat I30 = pCount * blockSize Mat pPos = pPos + I30 Mat pPos = pPos % maxPos 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 Else If currCount < maxCount # only add if space available Set LB0 pNote +D # show note and delay when adding Set LB1 I11 +D # compute buffer index for new data Ass I10 = currPos AdvanceBlock I10 currCount # 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 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
# remove 0-time blocks from the head of the queue # Call only when currCount > 0 # This is tricky because some notes have short delays, so they # expire before reaching the head of the queue. Ifone note has a # long delay, note with short delays may back up behind it. Even # if they're expired, we cannot remove them untilthe long delay # expires. Sub CleanUp Ass I20 = currPos # loop continues as long as currPos advances to match I20 While I20 == currPos If JI20 == 0 # check for zero time # remove this entry AdvanceBlock currPos 1 Mat currCount = currCount - 1 If currCount == 0 # here when queue is empty # making I20 odd means it cannot equal currPos Mat I20 = I20 + 1 # break loop when no more End End AdvanceBlock I20 1 End # While End
# decrement the count for each active entry, sending notes # when their delay expires. Called for each clock tick. Sub UpdateTicks Ass I10 = currPos Ass I11 = currCount Ass I12 = 0 # 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 If I10 == currPos # remember if queue head was sent Ass I12 = 1 End End End Mat I11 = I11 - 1 # decrement counter # advance through buffer, with wrap-around AdvanceBlock I10 1 End # While If I12 == 1 # only call CleanUp when queue head sent CleanUp End End
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
#If MT < A0 # used for testing If MT == 90 # 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 End
Block
I'm going to try adding a "pack" function to actually move the data. That would remove the short delay items with they expire, making buffer full less likely.
|
|
|
Post by mo13 on Apr 7, 2022 18:40:50 GMT
that's incredible Dave! follows the BPM change precisely, a little speechless here at how a minute ago I was just playing around with the idea and now this is better then ever, (plus no need for extra fancy hardware  I see that you also already added preservation, thanks so much!- ps. and with another great script study attached! great about the auv3 parameters, made a preset for it right away:  just also tested with delayed and swinged clock and it also takes it (soundwise) well, but I see what you mean, on the monitor now @ Buffer Full [log] so should I restrain from pushing it any further?
|
|
|
Post by uncledave on Apr 7, 2022 20:05:35 GMT
I think I can help the buffer capacity by removing the expired items that are not at the front, and we can double the size as well.
Were you using different delays, and maybe a lot of notes at the shorter delay? Since it currently only removes entries from the front, once a long delay reaches the front, everything else backs up until that one times out. The notes are transmitted at the correct times, but the dead items are not removed from the queue. Of course, if everything has a long delay, there will be a limit to how many notes can be stored.
By the way, the buffer being full is not fatal. It just means that some notes are not stored. And once space opens up, it's back to nomal. It logs that message for every note that cannot be stored, so that gives an idea of how many notes were missed.
|
|