Using Tidal to control modular synths with CV

Hey nice recommendation! thnx a lot I will try this.

I originally added the * 5 to make a a 0/1 value scale to 0/5. But in theory i think you would be limited the the max range of whatever audio card you are using.

I've debated whether or not to keep 0/1 values for some of these helpers, only to find out i constantly have to type out decimals way too oftern.

Do you have any preferences to this? I know some of the tidal djf filters work with larger values 2000 etc... I could be better this way? Then you might be able to use some syntax like (500 .. 2000) a bit easier?

FYI, I have a few small updates to these helpers which allow you to use the native scale helpers with the pitch instrument. I realised that I actually overwrote that in the current version.

If you've made any more modifications I'd love to see them!

2 Likes

Hey. Thanks for the package. One way filter/or other parameter defining can be done is by creating new Tidal function where input and output min maxes are scaled. Something like linlin in supercollider maybe? Then just letting users know that they can define ranges some way in README or examples.

.linlin(inMin, inMax, outMin, outMax, clip:)
2000.linlin(0, 20000, 0, 1)
eurorack_filter = linlin_similar 0 2000 0 1

Not sure how to do this in Tidal though.

Only thing I added to package is synth/function for short triggers, because sometimes I had trouble making it work default way:

  (
    // For very short triggers
    SynthDef (\tr, { |out, channel, sustain, width=0.00001|
      OffsetOut.ar(channel, LFPulse.ar(1/sustain, width:width));
    }).add
  );
tr tr = n tr # s "tr"
1 Like

I wonder, is there a plan to allow the modular to control the Tidal clock/tempo, i.e., where Tidal would be following triggers from a clock module? I suppose this would also allow for syncing to a DAW with a trigger track.

I'm a complete beginner, about to start learning Tidal, and the CV examples in this thread are super interesting. I have no idea whether syncing to clock signals would be simple or difficult. From the documentation and forums, it looks like there's a focus on getting Ableton Link to work for Tidal synchronisation .. but all that seems rather complex, while using triggers can be simple and reliable :slight_smile:

(Edit. Maybe something like BeatTrack | SuperCollider 3.12.2 Help would apply?)

1 Like

@mashaal First thanks a ton for this. I've learned so much going through your code.

I'm struggling to get clean 1v/octave output using the pitch synth. What I've noticed is that there are audible pops between each note. For example, here's a simple sequence:

d1 $ n "30 30" # s "pitch" # x 0 

and the resulting output:
Screen Shot 2023-01-15 at 9.05.49 PM

Any thoughts about what could be causing this?

Could you try adjusting the legato value? It looks like the signal is doubling on itself during the start/end of a loop.

d1 $ n "30 30" # s "pitch" # x 0  # legato 0.95

Would this resolve it? Maybe we can adjust the scd instrument to close the voltage a tiny bit earlier.

I've been on a bit of a journey.

Legato of 0.95 created an audible gap between each note. I played around with it but couldn't quite smooth it out. The closest was 0.995 which gave me this:

Screen Shot 2023-01-16 at 3.53.09 AM

Still audible, but better. Then just for fun I tried longer legato values, and found that 1.1 changes the pops into peaks with what looks like double the voltage (compare min/max values):

Screen Shot 2023-01-16 at 3.54.19 AM

That got me thinking, perhaps what I'm hearing is actually two overlayed synths outputting the same DC voltage, which is getting summed. (I guess that's what you meant by "the signal is doubling on itself during the start/end of a loop.") I restarted SC with s.plotTree to observe the nodes being generated, and found this:

Screen Shot 2023-01-16 at 4.24.46 AM

Which led me to this discussion about dirt_gate. I tried adjusting the fadeTime and fadeInTime values but it didn't change anything. Finally I tried forcing the fadeTime to zero by replacing the dirt_gate synth and overwriting this line:

var env = EnvGen.ar(Env([0, 1, 1, 0], [fadeInTime, sustain, fadeTime], \sin), levelScale: amp, doneAction: 14);

with this:

var env = EnvGen.ar(Env([0, 1, 1, 0], [0, sustain, 0], \sin), levelScale: amp, doneAction: 14);

That gave me the cleanest sound so far. Still there's a faintly audible pulse but I think I can live with it. Here are the results:

Screen Shot 2023-01-16 at 4.44.34 AM

Now I wonder, is there any way to use the input signal from multiple groups in a SynthDef in order to output a consistent DC regardless of how many copies of the synth are currently playing?

1 Like

I'm finding that legato 0.995 is close enough to a zero fadeTime in the dirt_gate envelope.

My goal is to be able to use the pattern itself for the gate, rather than needing a separate gate pattern. I'm happy to report that with a small change to your gate synth, I think I've got it. Here's my gate:

SynthDef (\gate, { | out,
	channel,
	n |
	n = if (n > 0, 1, 0);
	OffsetOut.ar (channel, DC.ar (n));
}).add

I'm using a function to stack the gate and pitch sounds, so I can pass a pattern to both at once:

ss :: Pattern Int -> Pattern ControlMap -> Pattern ControlMap
ss orbit pat = stack [pat # s "pitch" # legato 0.995 # x (orbit*2), pat # s "gate" # x (orbit*2 + 1)]

d1 $ ss 0 $ n "c6 ~ ef6 f6 ~ as6 b6 c7 "

I'm pretty happy with the result:

Screen Shot 2023-01-16 at 5.26.04 AM

1 Like

Oh that's an interesting way to stack pitch and gate. The added gate logic makes sense, would you like to PR that?

I wonder if there's a way to shorten the legato value by 0.005 in the synth def itself, which would mean you could write a bit more cleaner tidal code... and it might have more consistent results in general?

Running 2 or more cv outputs on one channel will sum all the cv values. I think this theoretically is what it should do? Though I have only run into this by accident myself.

I think the problem generally with shortening the envelope via legato or by messing with fadeTime is that the target we're shooting for is infinitesimally small. We're trying to perfectly line up these envelopes so that they don't overlap or underlap, which both cause audible clicks.

I think superdirt, and tidal generally, wasn't designed with DC voltage in mind. the patterns create discrete envelopes, one per note, but dc voltages should be continuous and slide between values. That means somehow smoothing the output across multiple notes.

I’ve been using these synthdefs only for clocking so far, but am very soon to use them for modulation, so these latest replies were of great interest and I’ve been doing a little SC and SuperDirt reading today to learn more.

(To use this for pitch/mod/etc., I’d need it to hold pitch and some types of modulation indefinitely after setting their value, would eliminate the summing of values, and optimally would remove all slewing.)

[NB: I’m returning months later to strike out text which I subsequently learned is not just irrelevant, it is incorrect and would make things worse. superdirt-voltage must use OffsetOut because of how it bypasses superdirt’s output handling.]

I’m too far from done reading to suggest any changes yet, but did bump into this link (via the SuperDirt source code) which might provide an incremental improvement via use of Out instead of OffsetOut as in the current superdirt-voltage source.

~~https://club.tidalcycles.org/t/not-using-offsetout/1368~~

1 Like

Generally, you can have completely continuous synths with superdirt, but you need to write up the functions that set them in ~dirt.soundLibrary.addSynth(...). Check the hacks folder, there should be examples, you can make an Ndef for instance, that runs continually.

Also, there is continuous control of a single, very long event (not sure where in tidalcycles this is documented).

Sorry that I haven't followed this whole thread.

3 Likes

These both sound like great improvements, I haven't heard of Ndef until now. It's cool to see more activity on this!

Thanks @julian, I'm getting closer using your advice.

I set up an Ndef in SC that outputs a continuous level:

Ndef(\cvDef, { | freq = 440, lag=0.01 |
	var lagFreq = Lag.kr(freq, lagTime: lag);
	LinLin.ar( log2(lagFreq/440), -1, 9, 0, 1);
}).play(0);

The params to LinLin are calibrated for my external oscillator's 1v/oct range. Outputting 0-1 from this Ndef results in a 0-10v signal through my ES-9 audio interface.

Then I'm able to control it through this synth:

~dirt.soundLibrary.addSynth(\cv, (play: {
	Ndef(\cvDef).wakeUp; // make sure the Ndef runs
	Ndef(\cvDef).set(\freq, ~freq);
})
);

And in tidal:

d1 $ n "a6 a5" # s "cv" 

Result:

Screen Shot 2023-01-18 at 7.24.12 PM

It's nice also that I can get true portamento that can also be patterned, via the Lag ugen.

The part I'm hung up on is routing the Ndef's output buffer to the dirt synth somehow so it's not just always playing. However, this is just a nice-to-have since the CV will be gated by a signal on a separate channel. I may look into writing a module to emit Ndef(\cvDef).start and .stop commands.

I tried using the playInside functionality but couldn't get it to work. See How to add a synth with `dirt.soundLibrary.addSynth` and playInside.

2 Likes

You want the pitch to hold, otherwise you’ll get pitch jumps during release phases of envelopes, so you’re probably closer to done than you think.

1 Like

Yes, pretty close.

I packaged it up into a pair of cv and gate synths for each orbit. I posted the latest code in a new thread so I don’t take over this one with talk of Ndefs.

But now I have a new problem. Jitter!

See: Sending continuous CV and Gate signals to a modular synth - #3 by colevscode

1 Like

Hi! Great discussion happening here :slight_smile: I haven’t tried any of these solutions, ‘cause the results i get with a MIDI module (Sol) plus a Slew Limiter (Contour 1) are being enough for me to control my modular with Tidal. The legato option works pretty well in this setup, most of the time i declare a clock on an orbit and output notes on another:

d1 $ stack [
    s "sol" $ n "~!7 1" 
        # midichan "3" -- reset
        # legato 0.5,
    s "sol" $ n "1!8" 
        # midichan "4" -- main clock
        # legato 0.5 
]
d2 $ note "c a f e" # s "sol" # midichan "0 1"

Hope you find this useful. I’ve already hearted this thread to keep a pin for myself and try the continuous voltages from SC too!

I always ran into weird timing issues (extra notes / jitter) using midi, but perhaps a dedicated midi device would fix that. I've only tried it with the midi converter on the 0-coast.

the only thing i’ve experienced with my setup were some clicks, kind of like a gate happening twice, but this was solved by adjusting the amp envelope on the synth and using a short legato. The Sol module is also very versatile, running CircuitPython one can customize it to one’s needs. But yeah, MIDI is not always the best way to go, having SC outputting signals or Pure Data sending audio (which i love) may be in other cases preferable.