Using Tidal to control modular synths with CV

Screen Shot 2020-05-23 at 5.50.06 pm

I had a play with triggered envelopes, its pretty wild the shapes you can make.

2 Likes

Nice!!

1 Like

Bit of an update: I've been working on a slew method for these CV instruments...

d1 $ pitch "0 10 8 1" # scale "<12 31 8>" # x 0 # slew 3 0.5 1

slew accepts a strengh (in semitones), a rate (in step length), and a multiplyer (+1/-1) for slewing up or down.

and I made a quick demo video...

6 Likes

Thank you so much for putting together the repository!! I'm very excited to control my Morphagene and my Moog Mother-32 using Tidal :slight_smile:

I hope to make contributions to this once I get the hang of it

3 Likes

Awesome :blush: let me know if U need any help

Hi everyone,

Newbie here, can you please help me with the following items:

Add the voltage.scd synths to your active SuperDirt synth definitions.

How do I do this? Is it adding it to the supercollider like include("voltage")?

Evaluate the voltage.tidal definitions after starting Tidal. These can also be added to your Tidal startup file.

Is this placing the content inside voltage.tidal on the start of my tidal file? Similar to declaring variables?

Thanks in advance,
Pedro

Hi peeps, anybody knows how much voltage can Tidal output, does it go all the way to +10/-10 ? or only +5/-5 ?

does the line "n = n * 5" in the SC code below (volatge.scd) represents the output voltage ? if I change it to "n= n * 10" will it output the double?

  (
    SynthDef (\voltage, { | out,
      channel,
      n,
      rate = 1,
      delta,
      begin,
      end,
      portamento = 0 |
      var slew, env, phase;
      n = n * 5;
      slew = (portamento);
      rate = rate;
      env = Env ([n, n + slew], [delta / rate]);
      phase = Line.ar (begin, end, delta / rate);
      OffsetOut.ar (channel, IEnvGen.ar (env, phase));
    }).add
  );
1 Like

Hi. I recommend calibrating/testing with VCV Rack Scope module if u didint already, because it can be tricky e.g. for me output value changes depening on supercollider global volume (s.volume.volume) which can be neat way to transpose notes/modualtion. Try something like d1 $ volt "0 1" # x 2 and lookup volts in scope. For me it goes between 0 and 10. -0.5 0.5volts goes from -5 to 5.
But I changed n = n * 5; to just n=n because its more intuitive to me. Then Tidal value equals Supercollider output and range of 1 is 10V in my setup. U can see Supercollider ouput by adding .poll to signal e.g. OffsetOut.ar (channel, IEnvGen.ar (env, phase).poll);

1 Like

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