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

time stretching of samples #149

Closed
wants to merge 18 commits into from

Conversation

bgold-cosmos
Copy link
Contributor

This implements time-scaling for sample playback using an overlap-add (OLA) algorithm.

I tweaked the code with speed and flexibility as priorities rather than output sound quality, but for modest scaling amounts (0.8 to 1.3 or so) it usually sounds fairly good. I'm not aware of any method that will consistently timescale transient-only sounds (most drums) in a "nice" way.

The timescaling should play well with speed, begin, end and similar parameters, but I'm sure I haven't tested every possible combination.

Examples of usage in Tidal would be

timescale = pF "timescale"

d1 $ chop 4 $ s "rave" # timescale "[1 4] 2" # cps 0.5

d1 $ s "metal*4" # cps 1
 # up ("c g6 c c6" + "<c ef g c>" - 12)
 # timescale "1 3 1 2"

@bgold-cosmos bgold-cosmos mentioned this pull request Oct 10, 2019
@telephon
Copy link
Contributor

just to be sure: have you checked the PitchShift UGen for comparison? Your implementation has the advantage that it could be combined with the https://github.com/musikinformatik/WavesetsEvent Quark, that sounds much better to my ear than overlap add.

In the end, of course we shouldn't load the standard player with extra things, but add a switch when timestretch is set, to save electricity.

@bgold-cosmos
Copy link
Contributor Author

Whenever I've tried the PitchShift UGen I've gotten really choppy results regardless of windowSize. However, I should check again to do a sound comparison, and I'll take a look at the Wavesets stuff.

I wasn't sure how best to conditionally apply the effect. My understanding is it's not really possible to have true branching in a SynthDef, so would there have to be a whole new set of dirt_sample_timescale_%_% SynthDefs that would get called when the event's ~timeScale is non-nil? I tried to keep the extra computation to a minimum - all it really is doing is making a pair of phase indices for BufRd rather than a single one, and doing a little extra modulation on them so that they're not quite simple LFSaws. I haven't rigorously compared CPU usage but it didn't seem to add much.

@telephon
Copy link
Contributor

Why don't you implement it as an effect?

@bgold-cosmos
Copy link
Contributor Author

The current version can compress time too, so to do that as an effect would require looking into the future. I don't know how to do that except for samples that've already been loaded. Also, I think working with the sample indexing will be much more efficient, it only ever has two overlapping windows --- whereas as an effect I think it would need to be implemented using a network of staggered delays, some potentially with a very long delay time for large stretches and long samples. Might be interesting to try both, though...

@bgold-cosmos
Copy link
Contributor Author

bgold-cosmos commented Oct 28, 2019 via email

The time-scaling effect is now implemented as an alternate sample playback SynthDef that is only called if the `timescale` parameter is used from Tidal.
@bgold-cosmos
Copy link
Contributor Author

OK, the timescaling has now been moved to a separate SynthDef so no extra work is done if it isn't used.

Copy link
Contributor

@telephon telephon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra complications introduced are worth it, I think! Thank you.

synths/core-modules.scd Outdated Show resolved Hide resolved
synths/core-synths.scd Outdated Show resolved Hide resolved
synths/core-synths.scd Outdated Show resolved Hide resolved
once $ sound "fooloopReset"
-- reset buffers. But do only one time (d1 $ silence),
-- since d1 $ once $ sound "fooloopReset" does not work at my installation (1.4.3)
d1 $ sound "fooloopReset"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe my installation is ambiguous here?

Copy link
Contributor Author

@bgold-cosmos bgold-cosmos Nov 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I accidentally merged in something from the develop branch here, and I thought I undid it but apparently not. I didn't make this change.

classes/DirtEvent.sc Outdated Show resolved Hide resolved
@@ -68,13 +68,13 @@ live coding them requires that you have your SuperDirt instance in an environmen

numFrames = BufFrames.ir(bufnum);
windowSize = numFrames * timescale / 29.0;
windowSize = windowSize.clip(1000.0*timescale, 4410.0);
windowSize = windowSize.clip(1000*timescale, 4410);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will that work on all sample rates? If not, replace 4410 by 0.1 * SampleRate.ir).

@telephon
Copy link
Contributor

btw. sorry, I had overlooked your changes.

out: ~out
])
if(~timescale.notNil) {
dirtEvent.sendSynth(~stretchInstrument, [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd change this to:

var instrument, args;
...
args = [ ... ]; // the shorter arg list
if(~timescale.notNil) { 
	instrument = ~stretchInstrument;
	args = args.add(\timescale).add(~timescale)
} { 
	instrument = ~instrument 
};

dirtEvent.sendSynth(instrument, args);
…

Then you don't need the same list twice.

out: ~out
])
} {
// argumets could be omitted using getMsgFunc, but for making it easier to understand, we write them out
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: argumets –> arguments

@yaxu
Copy link
Collaborator

yaxu commented Jan 23, 2021

Shall we make these smallish changes and @bgold-cosmos ?

@bgold-cosmos
Copy link
Contributor Author

I'll refresh my memory on this and look into getting it updated. A lot of things got put on pause during 2020. I suspect given all the SuperDirt changes lately it'll be much cleaner to just redo this as a new PR - the semester is just starting Monday here but if I don't do something within a week or so don't feel bad about pinging me again.

@telephon
Copy link
Contributor

I think it won't be hard to resolve any conflicts.

@bgold-cosmos bgold-cosmos mentioned this pull request Feb 1, 2021
@bgold-cosmos
Copy link
Contributor Author

I've redone this, see #229

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.

3 participants