|
|
|
09-30-2019, 03:22 PM
|
#1
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
JSFX to filter all but highest-velocity duplicates?
Does anyone know of a JSFX that will filter out duplicate notes but keep the highest velocity note? IxIx's excellent "MIDI_DuplicateFilter" is almost doing what I need, but I'm thinking of trying to modify it so that it outputs at the highest velocity of the incoming duplicate notes.
My application is crosstalk-reduction for a hexaphonic guitar pickup; MIDI_DuplicateFilter is successfully giving me one note per pluck, but it seems to sometimes pass through the quiet triggers from adjacent strings while filtering out the stronger notes from the desired string.
If any of you geniuses thinks that'd be an easy mod to do, I'd much appreciate anyone taking a stab, otherwise I'll report back in ... a couple of weeks :P Here's the code to IxIx's original:
Code:
/*******************************************************************************
* Copyright 2007 - 2011, Philip S. Considine *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License (http://www.gnu.org/licenses/)for more details. *
*******************************************************************************/
desc: MIDI Duplicate Note Filter
desc: MIDI Duplicate Note Filter [IXix]
//tags: MIDI filter processing
//author: IXix
slider1:0<0,15,1{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}>Input Channel
in_pin:none
out_pin:none
////////////////////////////////////////////////////////////////////////////////
@init
statNoteOn = $x90;
statNoteOff = $x80;
////////////////////////////////////////////////////////////////////////////////
@slider
inChannel = slider1;
////////////////////////////////////////////////////////////////////////////////
@block
trigger & (2 ^ 0) ? // Reset counters and send all notes off on this channel.
(
i = 0;
while
(
tracker[i] = 0;
midisend(0, statNoteOff + inChannel, i | 0 * 256);
(i += 1) < 127;
);
trigger -= 2 ^ 0;
);
while
(
midirecv(offset, msg1, msg23) ?
(
// Reset pass flag
passThru = 1;
// Extract message type and channel
status = msg1 & $xF0;
channel = msg1 & $x0F;
// Is it on our channel?
channel == inChannel ?
(
// Is it a note event?
status == statNoteOn || status == statNoteOff ?
(
// Get note and velocity
note = msg23 & $x7F;
velocity = msg23 >> 8;
// Update counters and flag to pass if necessary. Zero velocity is regarded as note off
status == statNoteOn && velocity > 0 ?
(
// Note on
tracker[note] += 1;
tracker[note] == 1 ? midisend(offset, msg1, msg23);
)
:
(
// Note off
tracker[note] -= 1;
tracker[note] == 0 ? midisend(offset, msg1, msg23);
);
)
:
(
midisend(offset, msg1, msg23); // Not a note, pass thru
);
)
:
(
midisend(offset, msg1, msg23); // Not on our channel, pass thru
);
1; // Force loop to continue until all messages have been processed
);
);
|
|
|
09-30-2019, 10:27 PM
|
#2
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,787
|
Quote:
Originally Posted by michaeltonight
Does anyone know of a JSFX that will filter out duplicate notes but keep the highest velocity note?
|
Do you want to drop lower velocity follow ups for already running notes that had previously been triggered with some velocity value and not yet terminated by a note-off ?
Seems rather weird and hence I doubt that such a plugin already exists. But certainly doable.
-Michael
|
|
|
10-01-2019, 12:47 AM
|
#3
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
Yeah, it is a little weird. I only want to filter out lower-velocity notes with similar (basically simultaneous) start times only.
I'm sending each string's output of a hexaphonic pickup to a REAPER track with a monophonic instance of MIDI Guitar 2, and all of those go to one instance of Kontakt. Currently, it works OK but needs editing after I play. Because, for example, if I pluck the B string, it sends a MIDI note, but the spillover from the E and G string outputs may also trigger note-ons. The current duplicate filter often outputs at the lower velocity of the spillover notes, so sometimes I need to go in and adjust a bunch of velocities after the fact.
Which is not the worst problem in the world, so that's lucky! Frankly I'm amazed that any of this is even possible.
|
|
|
10-01-2019, 06:38 AM
|
#4
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,787
|
Quote:
Originally Posted by michaeltonight
Yeah, it is a little weird. I only want to filter out lower-velocity notes with similar (basically simultaneous) start times only.
|
I suppose that is much more complex than you estimate ...
-Michael
|
|
|
10-02-2019, 06:36 AM
|
#5
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
There's would be a tiny bit of time travel required for this to be zero latency, so you'd need to be happy with a configurable delay on all MIDI to catch all near-instantaneous duplicates. Would that be acceptable?
|
|
|
10-02-2019, 06:50 AM
|
#6
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,787
|
No idea.
Of course a Midi delay is doable in a JSFX and there are some examples. So you can try to write such a thingy.
-Michael
|
|
|
10-02-2019, 07:11 AM
|
#7
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
Yeah, as I started to work on making my own just now, I realized that the duplicates wouldn't necessarily trigger during the same block. I'm going to look at a WAV recording I did recently and see how the timing of the audio bleed between strings looks. I imagine it will be well under a millisecond, since all the interference is happening within a few cm.
Thanks for chiming in on this!
|
|
|
10-03-2019, 07:23 AM
|
#8
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
I'm making some progress modifying ixix's Duplicate Filter; I currently have it putting up to three duplicates in a table (or nested bracket structure, I don't know what they're called in JSFX :P ) while also keeping track of how many blocks have gone by per note.
So if it gets three of the same note, then it spits out that note at the highest of the three velocities, and if not, it keeps listening until it either receives more or a certain number of blocks have gone by, at which point it either spits out the higher of two duplicates, or just the single note (which I'm not expecting based on my exact use-case).
I measured the time delay of the crosstalk I'm working with, and it's under 1ms, so I'm hoping 2 or 3 blocks of 256 samples at 48k will be enough. It seems a little clumsy, but for my exact need right now, I'm fine with a little added latency.
|
|
|
10-04-2019, 02:50 PM
|
#9
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
So I "finished" modding ixix's plugin to do what I want, and at first it wasn't perfect, but helped. Then I found and fixed a bunch of dumb syntax errors in my code, and now it works hilariously badly! Total MIDI chaos. I'm too embarrassed even to share. When I have a little more free time soon I'll devote more brainpower to it and report back
|
|
|
10-04-2019, 04:07 PM
|
#10
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
It's a slight PITA dealing with note-ons and note-offs when delaying things with transport changes etc. I've rustled you up something for testing using some old sequencer code. It works from very limited testing, rely on the force using the sample slider and not the actual numbers for now.
Code:
desc:Lower Velocity Duplicate Note Filter
slider1:100<0,2000,1>lookahead (samples)
@init
NOTE_ON = 0x90;
NOTE_OFF = 0x80;
ignore_noteoffs.mem = 10000;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* /begin seq *
* */
_seq.mem = 0; // event array start position
_seq.evt_width = 4;
_seq.len = 0;
_seq.last_play_pos = 0;
seq.retval0 = seq.retval1 = seq.retval2 = seq.retval3 = 0;
//
function seq.addEvent(samples_ahead, m1, m2, m3)
local( i )
(
i = _seq.len;
_seq.mem[_seq.evt_width * i] = samples_ahead;
_seq.mem[_seq.evt_width * i + 1] = m1;
_seq.mem[_seq.evt_width * i + 2] = m2;
_seq.mem[_seq.evt_width * i + 3] = m3;
_seq.len += 1;
_ADD += 1;
);
//
function seq.addNote(start, length, channel, note, velocity)
local ()
(
seq.addEvent(start, channel | 0x90, note, velocity);
seq.addEvent(start + length, channel | 0x80, note, 0);
);
// searches backwards through event array for match
function seq.getLastMatchingEvent(m1, m2)
local ( evt_pos, evt, pos )
(
evt_pos = _seq.len - 1;
evt = -1;
while (evt_pos >= 0 && evt == -1) (
pos = evt_pos * _seq.evt_width;
_seq.mem[pos + 1] == m1 && _seq.mem[pos + 2] == m2 ? evt = evt_pos;
evt_pos -= 1;
);
evt > -1 ? (
pos = evt * _seq.evt_width;
seq.retval0 = _seq.mem[pos];
seq.retval1 = _seq.mem[pos + 1];
seq.retval2 = _seq.mem[pos + 2];
seq.retval3 = _seq.mem[pos + 3];
);
evt; // return event position if found, or -1
);
//
function seq.removeEvent(evt_pos)
local ( pos )
(
evt_pos < _seq.len ? (
memcpy(_seq.mem + (_seq.evt_width * evt_pos),
_seq.mem + _seq.evt_width * (evt_pos + 1),
_seq.evt_width * (_seq.len - evt_pos - 1)
);
_seq.len -= 1;
_seq.len; // return new length...
)
: (
-1; // ... or failure
);
);
//
function seq.sendEventAndRemove(evt_pos)
local ( sp )
(
sp = _seq.evt_width * evt_pos;
midisend(_seq.mem[sp] + samplesblock, _seq.mem[sp + 1],
_seq.mem[sp + 2], _seq.mem[sp + 3]
);
seq.removeEvent(evt_pos);
);
//
function isRelocated()
local ( diff )
(
diff = play_position - _seq.last_play_pos;
_seq.last_play_pos > 0 && (diff < 0 || diff > samplesblock + 1) ? (
1;
)
: (
0;
);
);
//
function isHandleRelocation()
local ( i )
(
isRelocated() ? (
// send all notes off, all channels
i = 0;
loop(16,
midisend(0, 0xb0 | i, 123, 0);
i += 1;
);
i = 0;
loop(128,
ignore_noteoffs.mem[i] = 0;
i += 1;
);
_seq.len = 0;
_seq.last_play_pos = play_position;
1;
)
: (
_seq.last_play_pos = play_position;
0;
);
);
//
function seq.processBlockEvents()
local ( evt, pos )
(
(!isHandleRelocation()) ? (
evt = 0;
pos = 0;
while (evt < _seq.len)(
pos = _seq.evt_width * evt;
_seq.mem[pos] -= samplesblock;
_seq.mem[pos] < 0 ? (
seq.sendEventAndRemove(evt);
)
: (
evt += 1;
);
);
);
);
/* *
* /end seq *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function sendDelayedEvent(offset, m1, m2, m3)
local ()
(
offset += slider1;
offset > samplesblock ? (
seq.addEvent(offset, m1, m2, m3);
)
: (
midisend(offset, m1, m2, m3);
);
);
// seq.test() // needs seq.sendThisBufferEvents(); in @block
@block
lookahead = slider1;
while (midirecv(offset, m1, m2, m3)) (
// process note ons and offs
m1 & 0xF0 == NOTE_ON || m1 & 0xF0 == NOTE_OFF ?
(
m1 & 0xF0 == NOTE_OFF ? (
// note offs
ignore_noteoffs.mem[m2] > 0 ? (
ignore_noteoffs.mem[m2] -= 1;
)
: (
sendDelayedEvent(offset, m1, m2, m3);
);
)
: (
// note ons
evt = seq.getLastMatchingEvent(m1, m2);
evt > -1 ? (
offset - seq.retval0 - lookahead < lookahead && seq.retval3 < m3 ? (
seq.removeEvent(evt);
seq.addEvent(offset + lookahead, m1, m2, m3);
ignore_noteoffs.mem[m2 + 5] += 1;
);
)
: (
seq.addEvent(offset + lookahead, m1, m2, m3);
);
);
)
: (
// not notes - delay by lookahead too to keep stuff in sync
sendDelayedEvent(offset, m1, m2, m3);
);
);
seq.processBlockEvents();
Last edited by snooks; 10-05-2019 at 01:31 PM.
Reason: forgot to reset ignore_note_offs; messed up with renaming a couple of things
|
|
|
10-05-2019, 08:35 AM
|
#11
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
Whoa! Thank you! Testing momentarily...
|
|
|
10-05-2019, 10:28 AM
|
#12
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
Well, it's behaving pretty strangely, and for some reason triggering notes that aren't involved, but thanks so much for sending this! I'm going to take another stab tonight.
|
|
|
10-05-2019, 01:34 PM
|
#13
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
Sorry, I renamed some stuff b4 posting to tidy it up a bit and missed something which would indeed have completely broken it. The note-off tracking isn't multi-channel aware yet either and I assume your MIDI guitar is firing on 6 channels.
I edited the post with the new code, in case it's still useful.
|
|
|
10-05-2019, 02:44 PM
|
#14
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
Cool, I'll try it again. Thanks so much. I'm currently using 6 audio track ins but using one channel of midi. I'm now wondering if most of my issue might be that regardless of crosstalk, sustained note note offs are ending the notes after them. More experimenting now!
|
|
|
10-05-2019, 02:49 PM
|
#15
|
Human being with feelings
Join Date: Jun 2010
Location: Texas
Posts: 357
|
Oh wow, that's a big improvement! Thanks!
|
|
|
10-05-2019, 03:17 PM
|
#16
|
Banned
Join Date: Sep 2015
Posts: 1,650
|
Oh that's a relief!
If the notes were kept multi-channel on the way in to the script it would automagically (once the note-off ignore list is multi-channel) block the correct note off, but with everything on one channel it wouldn't be doable unless the strings note-offs were different and fixed note off velocities.
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 04:20 AM.
|