Sampling within Tidal

That's epic!

Thank you very much for these kind words and I am very happy that you are having so much fun with the Tidal Looper! :slight_smile:
I'm super excited to see what else you do with it. Your Solstice performance in the forest was really fascinating - I would probably not have come up with such an idea.

There is no better way to describe it than "Infernal typing" :love_you_gesture: And good to see/hear that there is more live coded metal out there :slight_smile:

By the way, I was playing with the idea of playing SuperDirt with FoxDot and TidalCycles in parallel. Is that a thing? :thinking:

Hi all,

Relatively new to TidalCycles, but I'm curious how you'd get this looper set up to take the input from an external audio interface. I have a MOTU Ultralite AVB. Can anyone explain what I'd need to modify? I assume it'd be in the .scd file?

Thanks!

Hey @sellanraa and thanks for trying the tidal looper! :slight_smile:

Maybe this thread can answer your questions: Live recording into tidal - getting audio input working

@yaxu should we rename the linked topic to some general audio input thread for live sampling in TidalCycles?

1 Like

Thanks @mrreason for this great tool. The way you've structured the Tidal UI makes it really straightforward to use.

When recording from an orbit, I am finding that the buffer is significantly quieter, and that there's sometimes issues with pops and clicks at the cycle start/end points.

With the first issue, this might be difficult to deal with if the loudness is being set elsewhere in the SuperDirt signal chain than the orbit. But if that's not the case maybe there's something that could be done within the looper that wouldn't risk escalating gain?

With the second issue, this could be addressed somewhat using the inbuilt ADSR in SuperDirt, but this would make recursive looping tricky. Perhaps doing some zero crossing analysis at the start/end of the buffer, and/or providing an xfade parameter could be useful for addressing this?

1 Like

Thanks for the kind words! Good points, in any case I am also very interested to extend the looper!

With the first issue, this might be difficult to deal with if the loudness is being set elsewhere in the SuperDirt signal chain than the orbit

Not really. Actually I just set the recLevel to 0.85 You can adjust this value to your needs. Maybe it would make more sense to record the audio signal usually at full volume. In overdub mode the signal must be mixed but for this you can use the parameter preLevel. My only concern is that the levels can clip while overdubbing.
The simplest variant is to use a limiter here. Otherwise I have to look again how other loopers solve this concretely.

Perhaps doing some zero crossing analysis at the start/end of the buffer, and/or providing an xfade parameter could be useful for addressing this

Another good point. IMHO I don't like ADSR and xfade so much here because it changes the audio signal too much. But I have also thought about zero crossing analysis. Maybe this will do the trick.

I've played around with analysis in advance. Maybe I should add a looper with more optimizations and analyses (quantizing, zero crossing, audio meter) in an extra file on Github and see how it does in practice.
For such points I would also be happy if tickets are created on GitHub, then we (and of course everyone else) can discuss about it. I can create those tickets in the first place.

2 Likes

I've never implemented a looper and I'm sure there are many subtleties of which these are just a few, so doubtless there's resources out there that can provide better suggestions for how to address these issues than I can.

That being said, in general I agree with your suggestion that trialling these ideas in practice is likely the best way forward, and I am happy to create GitHub issues and provide empirical notes.

1 Like

Thank you very much! I think I'll sit down this weekend and think about this topic in more detail and create a roadmap. Now is certainly a good time to tackle such optimizations.

1 Like

Today I transformed the tidal looper so that you can install it as Quark in SuperCollider.

This procedure has the following reasons:

  • Easier to extend and customize.
  • There is a documentation in SuperCollider.
  • The looper can be loaded in the startup script when starting the server.
  • Different releases can be published in the future (but this is not done yet).

I left the existing Looper.scd file in the repository for backward compatibility. Probably this will be removed in the future when it is shown that the Quark variant runs stable and is accepted.

In any case, it is now easier to optimize the whole thing.

@jarm With the attributes rLevel and pLevel you can influence the multipliers for recording. This only works on the SuperCollider side now, but I can also allow it in TidalCycles if you find it useful.

The default SynthDef now doesn't have zero crossing analysis or an envelope, but you can now easily swap the SynthDef for recording and switch between them during runtime. Here is an example of what this looks like with a simple envelope.

(
// Recording synthdef with envelope
SynthDef(\buffRecordEnv, {|input = 0, pLevel = 0.0, rLevel = 1.0, buffer|
    var in = SoundIn.ar(input);
    var env = EnvGen.ar(Env.asr(0.03, 1, 1, 1), 1, doneAction:2);

    RecordBuf.ar(in * env, buffer, recLevel: rLevel, preLevel: pLevel, loop:0, run: 1, doneAction: Done.freeSelf);
}).add;

~looper.looperSynth = "buffRecordEnv";

)

@tgirod Thanks for pointing out to make a Quark out of it! I now believe that this has only advantages. First of all, everyone can experiment with the parameters more easily and the releases in the future are also very charming.

8 Likes

hey! I installed the quark version, nice! The only help I missed was this:

(
SuperDirt.start;
TidalLooper.new(~dirt);
)

About using the looper, I'm pretty sure I saw somewhere a trick to start recording when the next loop starts, but I can't find it! anyone can help?

Yes you are right. I have it in the SuperCollider help, but it is very hidden in the first place. I have adapted the README.

To start the recording when the next loop starts you can use qtrigger. For example:

d1 $ qtrigger 1 ...
d2 $ qtrigger 2 ...

But you should be on at least Tidal 1.7 to have no unwanted effects in the looper context.

1 Like

thanks a lot, that is exactly what I needed!

1 Like

Really cool stuff! Thanks a lot for this @mrreason !

1 Like

Hello Thomas @mrreason, I installed the Quark folder, added some line of code into Supercolider startup file. The server meter displays inputs 0 & 1. I tried this code while playing my bass:
linput = pI "linput"
lname = pS "lname"

d1 $ s "looper" # n "<0 1 2 3 4 5 6 7>"

d2 $ s "loop" # n "[0,1,2,3,4,5,6,7]"

Last line generates this error into Supercolider:
no synth or sample named 'loop' could be found.
module 'sound': instrument not found: loop

Any idea? Thanks a lot.

Hey @cyberik and thanks for trying the looper! Your problem sounds very strange. Maybe you should try to use s "rlooper" instead of s "looper"?
Could you pls share your SuperCollider code you used?
I could investigate a little more in depth tomorrow.

Hi Thomas, @mrreason , no I'm using looper.
Here the supercolider code:

// boot the server and start SuperDirt
s.waitForBoot {
~dirt = SuperDirt(2, s); // two output channels, increase if you want to pan across more channels
// lazy loading
~dirt.doNotReadYet = true;
~dirt.loadSoundFiles; // load samples (path containing a wildcard can be passed in)
~dirt.loadSoundFiles("D:/Tidal/TidalClub/samples-extra/");
~dirt.loadSoundFiles("D:/Tidal/TidalClub/samples-eric/
");

// --------- Looper --------------------------------
// Initialize the TidalLooper
    ~looper = TidalLooper(~dirt);

    // You can adjust these parameter even in runtime
   	~looper.rLevel = 1.5;
    ~looper.pLevel = 0.8;
    ~looper.linput = 15; // Set this to your main input port.
    ~looper.lname = "mybuffer";

.............

~dirt.start(57120, 0 ! 16); // start listening on port 57120

When I start supercolider everything seems right, no errors and this lines in log:
---- initialize TidalLooper ----
loading synthdefs in D:\Tidal\tidal-looper-master\classes..\synths\standard.scd
function olooper was successfully loaded.
function looper was successfully loaded.
function rlooper was successfully loaded.
function freeLoops was successfully loaded.

Perhaps the issue is because i declare 8 x 2 busses in starting supercolider and linput is number 15 ? How to know what's the right number for input 0 and 1 displayed into server meter window?

@cyberik I guess I see the problem. You added the optional example values in the startup script which overrides the default values. Maybe I should change this in the documentation or make it more clear.

You could simply remove these values in your startup script:

   ~looper.rLevel = 1.5;
   ~looper.pLevel = 0.8;
   ~looper.linput = 15; // Set this to your main input port.
   ~looper.lname = "mybuffer";

'Cause with these lines you are changing your main input port to 15 and the sample name to mybuffer.
After removing those lines, the main (default) input port is 0 and the (default) sample name is loop.

The matter here is that you can change those values in SuperCollider or change them within TidalCycles.

In practice you just need ~looper = TidalLooper(~dirt); as a minimal setup in your startup scritpt (when you are not changing default values).

Hopefully this helps!

It's better but there is a new error:

reading soundfile as needed: loop:0
ERROR: Primitive '_SFOpenRead' failed.
Wrong type.
RECEIVER:
Instance of SoundFile { (000001935DCB0908, gc=7C, fmt=00, flg=00, set=03)
instance variables [7]
fileptr : nil
headerFormat : "AIFF"
sampleFormat : "float"
numFrames : Integer 0
numChannels : Integer 1
sampleRate : Float 44100.000000 00000000 40E58880
path : nil
}

PROTECTED CALL STACK:
Meta_MethodError:new 000001935C4B35C0
arg this = PrimitiveFailedError
arg what = Wrong type.
arg receiver = a SoundFile
Meta_PrimitiveFailedError:new 000001935C4B9B80
arg this = PrimitiveFailedError
arg receiver = a SoundFile
Object:primitiveFailed 000001935C004D00
arg this = a SoundFile
a FunctionDef 000001935D2B6600
sourceCode = ""
Function:prTry 000001935C726380
arg this = a Function
var result = nil
var thread = a Thread
var next = a Function
var wasInProtectedFunc = true
Function:protect 000001935C725900
arg this = a Function
arg handler = a Function
var result = nil
Meta_SoundFile:use 000001935D2B6140
arg this = SoundFile
arg path = nil
arg function = a Function
var file = a SoundFile
var res = nil
Buffer:readWithInfo 000001935C217C80
arg this = Buffer(5295, 85333.32824707, 1, 48000, nil)
arg startFrame = 0
arg argNumFrames = -1
arg onlyHeader = false
arg onComplete = a Function
var failed = nil
DirtSoundLibrary:readFileIfNecessary 000001935CE21B40
arg this = a DirtSoundLibrary
arg event = ( 'unitDuration': a Function, 'notYetRead': true, 'bufferObject': Buffer(5295, 85333.32824707, 1, 48000, nil), 'note': 0,
'bufNumFrames': 85333.32824707, 'buffer': 5295, 'instrument': dirt_sample_1_2, 'bufNumChannels': 1, 'hash': 1313004316 )
var buffer = Buffer(5295, 85333.32824707, 1, 48000, nil)
DirtSoundLibrary:getEvent 000001935CE213C0
arg this = a DirtSoundLibrary
arg name = loop
arg index = 0
var allEvents = [ ( 'unitDuration': a Function, 'notYetRead': true, 'bufferObject': Buffer(5295, 85333.32824707, 1, 48000, nil), 'note': 0,
'bufNumFrames': 85333.32824707, 'buffer': 5295, 'instrument': dirt_sample_1_2, 'bufNumChannels': 1, 'hash': 1313004316 ), ( 'unitDuration': a Function, 'notYetRead': true, 'bufferObject': Buffer(5288, 85333.32824707, 1, 48000, nil), 'note': 0,
'bufNumFrames': 85333.32824707, 'buffer': 5288, 'instrument': dirt_sample_1_2, 'bufNumChannels': 1, 'hash': -1503670411 ), ( 'unitDuration...etc...
var event = ( 'unitDuration': a Function, 'notYetRead': true, 'bufferObject': Buffer(5295, 85333.32824707, 1, 48000, nil), 'note': 0,
'bufNumFrames': 85333.32824707, 'buffer': 5295, 'instrument': dirt_sample_1_2, 'bufNumChannels': 1, 'hash': 1313004316 )
DirtEvent:mergeSoundEvent 000001935C477540
arg this = a DirtEvent
var soundEvent = nil
a FunctionDef 000001935C476A40
sourceCode = ""
a FunctionDef 000001935D1F9840
sourceCode = ""
Function:prTry 000001935C726380
arg this = a Function
var result = nil
var thread = a Thread
var next = nil
var wasInProtectedFunc = false

CALL STACK:
MethodError:reportError
arg this =
Nil:handleError
arg this = nil
arg error =
Thread:handleError
arg this =
arg error =
Object:throw
arg this =
Function:protect
arg this =
arg handler =
var result =
Environment:use
arg this =
arg function =
var result = nil
var saveEnvir =
DirtEvent:play
arg this =
OSCFuncBothMessageMatcher:value
arg this =
arg msg = [*15]
arg time = 101.21375835524
arg testAddr =
arg testRecvPort = 57120
OSCMessageDispatcher:value
arg this =
arg msg = [*15]
arg time = 101.21375835524
arg addr =
arg recvPort = 57120
Main:recvOSCmessage
arg this =
arg time = 101.21375835524
arg replyAddr =
arg recvPort = 57120
arg msg = [*15]
^^ The preceding error dump is for ERROR: Primitive '_SFOpenRead' failed.
Wrong type.
RECEIVER: a SoundFile

In my startup file I have: s.recHeaderFormat = "WAV";
Is there a link?

Good new and bad news. I get the same error with the current version of SuperDirt. I try to fix it as soon as possible.