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

Stretch v2 #229

Merged
merged 22 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a4f8ef0
Merge pull request #191 from musikinformatik/develop
telephon Apr 8, 2020
dc71674
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos May 13, 2020
4dc19dc
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jun 5, 2020
25ce361
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jun 10, 2020
578b3f4
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jun 14, 2020
7e7d633
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jul 5, 2020
bfc4679
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jul 16, 2020
ff0bfe1
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jul 25, 2020
4af677b
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Aug 16, 2020
a6708e6
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Sep 8, 2020
70c8e17
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Nov 11, 2020
94d0ab6
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jan 8, 2021
17ff71d
Merge branch 'develop' of https://github.com/musikinformatik/SuperDir…
bgold-cosmos Jan 29, 2021
7dd3f07
Initial commit, still need to add/check on long sample support
bgold-cosmos Jan 30, 2021
6593248
small changes as requested
bgold-cosmos Jan 31, 2021
8938e12
remove outdated comments
bgold-cosmos Feb 3, 2021
455b958
syntax correction
bgold-cosmos Feb 3, 2021
e84cdae
bugfix
bgold-cosmos Feb 3, 2021
7784fb8
Update DirtEvent.sc
bgold-cosmos Mar 11, 2021
d5f813b
window size adjustment
bgold-cosmos Mar 11, 2021
c78d427
rewrites for clarity
bgold-cosmos Mar 23, 2021
26dba05
Further checking and updating description of algorithm
bgold-cosmos Aug 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions classes/DirtEvent.sc
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ DirtEvent {

~fadeTime = min(~fadeTime.value, sustain * 0.19098);
~fadeInTime = if(~begin != 0) { ~fadeTime } { 0.0 };
if (~timescale.notNil) {sustain = sustain * ~timescale };
~sustain = sustain - (~fadeTime + ~fadeInTime);
~speed = speed;
~endSpeed = endSpeed;
Expand Down
8 changes: 7 additions & 1 deletion classes/DirtSoundLibrary.sc
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ DirtSoundLibrary {
^(
buffer: buffer.bufnum,
instrument: this.instrumentForBuffer(buffer),
stretchInstrument: this.stretchInstrumentForBuffer(buffer),
bufNumFrames: buffer.numFrames,
bufNumChannels: buffer.numChannels,
unitDuration: { buffer.duration * baseFreq / ~freq.value },
Expand All @@ -262,6 +263,10 @@ DirtSoundLibrary {
^format(synthName, buffer.numChannels, this.numChannels).asSymbol
}

stretchInstrumentForBuffer { |buffer|
^format("dirt_stretchsample_%_%", buffer.numChannels, this.numChannels).asSymbol
}

openFolder { |name, index = 0|
var buf, list;
list = buffers.at(name);
Expand All @@ -281,7 +286,8 @@ DirtSoundLibrary {
numChannels = n;
bufferEvents = bufferEvents.collect { |list|
list.do { |event|
event[\instrument] = this.instrumentForBuffer(event[\buffer])
event[\instrument] = this.instrumentForBuffer(event[\buffer]);
event[\stretchInstrument] = this.stretchInstrumentForBuffer(event[\buffer]);
}
}
}
Expand Down
17 changes: 14 additions & 3 deletions synths/core-modules.scd
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ this may be refacored later.
{ |dirtEvent|
if(~diversion.value.isNil) {
if(~buffer.notNil) {
// argumets could be omitted using getMsgFunc, but for making it easier to understand, we write them out
dirtEvent.sendSynth(~instrument, [
// arguments could be omitted using getMsgFunc, but for making it easier to understand, we write them out
var instrument;
var args = [
bufnum: ~buffer,
sustain: ~sustain,
speed: ~speed,
Expand All @@ -48,7 +49,17 @@ this may be refacored later.
end: ~end,
pan: ~pan,
out: ~out
])
];

if(~timescale.notNil) {
instrument = ~stretchInstrument;
args = args.add(\timescale).add(~timescale).add(\timescalewin).add(~timescalewin ? 1);
bgold-cosmos marked this conversation as resolved.
Show resolved Hide resolved
} {
instrument = ~instrument;
};

dirtEvent.sendSynth(instrument, args);

} {
if(~instrument.isNil) {
"module 'sound': instrument not found: %".format(~s).postln
Expand Down
56 changes: 56 additions & 0 deletions synths/core-synths.scd
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,62 @@ live coding them requires that you have your SuperDirt instance in an environmen
}, [\ir, \ir, \ir, \ir, \ir, \ir, \ir, \ir, \kr]).add; // pan can be modulated
};

// this is the time-stretching sample player, based on an overlap-add method
(1..SuperDirt.maxSampleNumChannels).do { |sampleNumChannels|

var name = format("dirt_stretchsample_%_%", sampleNumChannels, numChannels);

SynthDef(name, { |out, bufnum, sustain = 1, begin = 0, end = 1, speed = 1, endSpeed = 1, freq = 440, pan = 0, timescale = 1, timescalewin = 1|

var sound, rate, phase, sawrate, numFrames, index, windowIndex, window, timescaleStep;
var sound0, sound1, windowSize;

// playback speed
rate = Line.kr(speed, endSpeed, sustain) * (freq / 60.midicps);

// sample phase
// BufSampleRate adjusts the rate if the sound file doesn't have the same rate as the soundcard
//phase = Sweep.ar(1, rate * BufSampleRate.ir(bufnum)) + (BufFrames.ir(bufnum) * begin);

numFrames = BufFrames.ir(bufnum);
// Picking the right window size is a tricky thing, something around 2000 samples is usually
// OK for sounds of moderate length (around 0.5 to several seconds long).
// But this is also scaled below to be a bit smaller/larger based on desired playback rate.
// If it's still not good, you can use the "timescalewin" parameter to multiply the window size
windowSize = timescale.clip(0.1,2) * 0.05 * BufSampleRate.ir(bufnum) * timescalewin;
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. you are clipping the value here, but in line 124 you are using it unclipped. Won't this lead to problems? Also in the line if (~timescale.notNil) { sustain = sustain * ~timescale }; above, you are using the full sustain, while in the synthdef you are clipping it, while the sustain is still the same length.
  2. Further down, there is a line:
windowIndex = phase - (phase.div(timescaleStep) - [1.0, 0.0] * timescaleStep);

Are you sure this is correct? Note that there is no operator precedence in supercollider. I'm unsure right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

windowSize and timescale can be varied completely independently and the algorithm still works, but may sound odd for certain combinations. I'm really clipping on windowSize values, but a "good" windowSize tends to scale somewhat with the length of the effect. I'm not sure what you mean about the sustain.

The windowIndex calculation is correct, but unnecessarily obscure. I added a variable to clarify it a bit (and it might save a repeated computation).

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure what you mean about the sustain.

Let's say e.g. you are setting # timescale 0.09 for a sample that is 1 second long. Then your playback synth will have a total duration (=sustain) of 0.09 seconds. But your window will be (0.1 * 0.05). Or, when you set # timescale 3, your synth will be 3 seconds, but the window will be (2 * 0.05). But also, in line 124 (timescaleStep = windowSize / timescale; e.g. for a very large timescale, the timescalestep will be very small, but the window won't get smaller because of the clipping.

I am not sure if all that is a problem, it just looks like a source of hard to trace errors. It might be easier to reason about when you limit the stretching before everything else?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry to have left this dangling. I meant that because timescaleStep is clipped, the overlaps will become larger when timescale < 0.1. Could you check if that is really correct? If that is what you want, it might be good to make a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll take a look! I have since noticed that a combo of frequency shift (fshift) and speed modification works pretty well for smaller amounts of stretching.

timescaleStep = windowSize / timescale;
sawrate = rate * BufSampleRate.ir(bufnum) / (absdif(begin, end) * timescale * numFrames);
telephon marked this conversation as resolved.
Show resolved Hide resolved
phase = (speed.sign * LFSaw.ar(sawrate, 1)).range(begin,end) * numFrames * timescale;
// do the overlap-add by running through a pair of indices, shifting weights with the window function
index = (phase.div(timescaleStep) - [1.0, 0.0]) * (timescaleStep / timescale - timescaleStep) + phase;
windowIndex = phase - (phase.div(timescaleStep) - [1.0, 0.0] * timescaleStep);
// Gaussian window, the "50" means it's about 3.5 sigma to the edge of the window
window = exp(-50 * squared(windowIndex/windowSize - 0.5));

sound0 = window[0] * BufRd.ar(
numChannels: sampleNumChannels,
bufnum: bufnum,
phase: index[0],
loop: 0,
interpolation: 4 // cubic interpolation
);
sound1 = window[1] * BufRd.ar(
numChannels: sampleNumChannels,
bufnum: bufnum,
phase: index[1],
loop: 0,
interpolation: 4 // cubic interpolation
);
sound = (sound0 + sound1) / window.sum;

sound = DirtPan.ar(sound, numChannels, pan);

Out.ar(out, sound)
}, [\ir, \ir, \ir, \ir, \ir, \ir, \ir, \ir, \kr, \ir, \ir]).add;
telephon marked this conversation as resolved.
Show resolved Hide resolved
};



/*

Bus Routing Monitor
Expand Down