Stuck notes when using ~swingify from the pattern docs

I am trying to use the ~swingify Prout from the pattern docs, but I run into problems when using the legato key in the pattern. Here is the link to the help docs.

Here is a demonstration of the issue. Any ideas of how I can modify the ~swingify Prout to deal with changing legato values so I avoid stuck notes?

(
~swingify = Prout({ |ev|
    var now, nextTime = 0, thisShouldSwing, nextShouldSwing = false, adjust;
    while { ev.notNil } {
        // current time is what was "next" last time
        now = nextTime;
        nextTime = now + ev.delta;
        thisShouldSwing = nextShouldSwing;
        nextShouldSwing = ((nextTime absdif: nextTime.round(ev[\swingBase])) <= (ev[\swingThreshold] ? 0)) and: {
            (nextTime / ev[\swingBase]).round.asInteger.odd
        };
        adjust = ev[\swingBase] * ev[\swingAmount];
        // an odd number here means we're on an off-beat
        if(thisShouldSwing) {
            ev[\timingOffset] = (ev[\timingOffset] ? 0) + adjust;
            // if next note will not swing, this note needs to be shortened
            if(nextShouldSwing.not) {
                ev[\sustain] = ev.use { ~sustain.value } - adjust;
            };
        } {
            // if next note will swing, this note needs to be lengthened
            if(nextShouldSwing) {
                ev[\sustain] = ev.use { ~sustain.value } + adjust;
            };
        };
	ev[\sustain].debug(\sustain);
        ev = ev.yield;
    };
});
)

( // works fine, no stuck notes
~line = Pbind(
	\dur, 0.25,
	\legato, 0.98,
	\midinote, Pseq([60, 62, 63, 65], inf),
);
Pdef(\test, Pchain(~swingify, ~line, (swingBase: 0.25, swingAmount: 0.3))).play
)

( // eventually I get a negative value of \sustain (see post window) and a stuck note 
~line = Pbind(
	\dur, Prand([0.25, 0.5, 0.75, 1], inf),
	\legato, Pseq([0.2, 0.3, 0.98, 0.98], inf),
	\midinote, Pseq([60, 62, 63, 65], inf),
);
Pdef(\test, Pchain(~swingify, ~line, (swingBase: 0.25, swingAmount: 0.3))).play
)

Pdef(\test).clear;

This is the explanation why.

Let’s say you’re doing swing 8ths. Then the swing base is 0.5 and swing amount may be 0.33. A note happens on the off-beat. The event is originally generated with duration 0.5, but the swing function pushes it back to 0.665. The original note also has a sustain value that’s, say, 0.4. If it pushes the note back by 0.165, then the note will end at next_beat.065 – there will be an overlap where there wasn’t before. So it has to shorten the note.

Currently it assumes that every note will be longer than the swing time. You’ve got some very short notes, so that assumption is broken.

The easiest thing to do may be to enforce a minimum sustain:

ev[\sustain] = max(0.05, ev.use { ~sustain.value } - adjust);

hjh

Thanks @jamshark70. This works fine for a Pbind, but I still get stuck notes with PmonoArtic which is what I am using in my patch. How can I make it work with PmonoArtic?

(
~legato = [ 0.9, 0.25317955467229, 0.61562511066829, 0.40073533333877, 0.48002825000731, 0.68364366666841, 0.36525916666142, 0.36081461199986, 0.19008150000009, 0.9 ];

~swingify = Prout({ |ev|
	var now, nextTime = 0, thisShouldSwing, nextShouldSwing = false, adjust;
	while { ev.notNil } {
		
		now = nextTime;
		nextTime = now + ev.delta;
		thisShouldSwing = nextShouldSwing;
		nextShouldSwing = ((nextTime absdif: nextTime.round(ev[\swingBase])) <= (ev[\swingThreshold] ? 0)) and: {
			(nextTime / ev[\swingBase]).round.asInteger.odd
		};
		adjust = ev[\swingBase] * ev[\swingAmount];
		if(thisShouldSwing) 
		{
			ev[\timingOffset] = (ev[\timingOffset] ? 0) + adjust;
			if(nextShouldSwing.not)
			{ ev[\sustain] = max(0.05, ev.use { ~sustain.value } - adjust) }
		} 
		{
			if(nextShouldSwing) 
			{ ev[\sustain] = ev.use { ~sustain.value } + adjust }
		};
		ev = ev.yield;
	};
});
)

(
// Pbind works fine - no stuck notes
~line = Pbind(
	\dur, Pseq([ 0.75, 0.75, 0.75, 0.5, 0.75, 0.75, 0.75, 1.25, 0.25, 0.5 ], inf),
	\legato, Pseq(~legato, inf),
	\midinote, Pseq([60, 62, 63, 65, 67], inf),
);
Pdef(\test, Pchain(~swingify, ~line, (swingBase: 0.25, swingAmount: 0.3))).play
)


(
// PmonoArtic does not work - stuck notes
~line = PmonoArtic(
	\default,
	\dur, Pseq([ 0.75, 0.75, 0.75, 0.5, 0.75, 0.75, 0.75, 1.25, 0.25, 0.5 ], inf),
	\legato, Pseq(~legato, inf),
	\midinote, Pseq([60, 62, 63, 65, 67], inf),
);
Pdef(\test, Pchain(~swingify, ~line, (swingBase: 0.25, swingAmount: 0.3))).play
)

Pdef(\test).clear;

:face_with_spiral_eyes:

I don’t have much bandwidth to play with it right now. I guess the best approach is to retool it to adjust note durations instead of using timingOffset, something like (untested):

if(thisShouldSwing) {
    // if *now* swings and *later* doesn't,
    // then this note should be shorter
    if(nextShouldSwing.not) {
        ev[\dur] = ev[\dur] - adjust
    }
} {
    // if *now* doesn't swing and *later* does,
    // then this note should be longer
    if(nextShouldSwing) {
        ev[\dur] = ev[\dur] + adjust
    }
};

… replacing the whole if section.

There may be an issue yet with \stretch != 1, but let’s see how this goes first.

hjh

Thanks James, I will try this. I am not using the stretch key, so fine if stretch always equals 1. I will report back.

If you or any other readers of this post have a minute to check, can you verify that the code above also produces stuck notes on your system(s), just to rule out some user specific conditions?

So I tested the approach suggested. It does not produce stuck notes, but on-beat notes which follow after a swing note are too short. I tried different solutions but none worked. My workaround is to use a Pchain where PmonoArtic comes after ~swingify. This is fine in my case since I already have this structure in order to be able to change the synth of a playing PmonoArtic. Using the same trick (PmonoArtic after Prout) with the original ~swingify still produces stuck notes in some cases like the one in my post above. Anyways - fingers crossed - this should work. It took me a loooong time to pinpoint the problem - that PmonoArtic behaves differently from Pbind in this regard. I don’t fully understand why, but for now I am happy with my workaround and I am hopeful it will work for all my use cases.

(
~swingify = Prout({ |ev| // modified from helpfiles to change \dur rather than \timingOffset and \sustain
	var now, nextTime = 0, thisShouldSwing, nextShouldSwing = false, adjust, factor = 1;
	while { ev.notNil } {
		now = nextTime;
		nextTime = now + ev.delta;
		thisShouldSwing = nextShouldSwing;
		nextShouldSwing = ((nextTime absdif: nextTime.round(ev[\swingBase])) <= (ev[\swingThreshold] ? 0)) and: {
			(nextTime / ev[\swingBase]).round.asInteger.odd
		};
		adjust = ev[\swingBase] * ev[\swingAmount];
		if(thisShouldSwing)
		{
			if(nextShouldSwing.not)
			{ ev[\dur] = ev.use { ~dur.value } - adjust }
		}
		{
			if(nextShouldSwing)
			{ ev[\dur] = ev.use { ~dur.value } + adjust }
		};
		ev = ev.yield;
	};
});

)

(
// PmonoArtic kinda works but on-beat notes which follow swing notes are too short
~line = PmonoArtic(
	\default,
	\dur, Pseq([ 0.5, 0.25, 0.25], inf),
	\legato, 0.9,
	\midinote, Pseq([60, 62, 63], inf),
);
Pdef(\test, Pchain(~swingify, ~line, (swingBase: 0.25, swingAmount: 0.3))).play;
)

(
// Using a Pbind in a Pchain where PmonoArtic comes after ~swingify works!
~line = Pbind(
	\dur, Pseq([ 0.50, 0.25, 0.25], inf),
	\legato, 0.9,
	\midinote, Pseq([60, 62, 63], inf),
);
Pdef(\test, Pchain(PmonoArtic(\default), ~swingify, ~line, (swingBase: 0.25, swingAmount: 0.3))).play;
)

This actually makes sense… Pmono(Artic) is a special case where it makes decisions based on the event data. If you run a PmonoArtic and then change the data after the fact with something like ~swingify, it’s basically lying to the PmonoArtic and weird results are actually kinda likely.

hjh

1 Like