Thanks everyone, I’ll give that a try. I was going to post the code as well since I also thought that would help, and in any case I was planning to share it for sure once I have something interesting actually happening (right now I’m just trying things out and laying the groundwork, also just learning how to write scripts for crow).

On that note, I have to say crow is quite approachable. With norns it always seemed a bit daunting, but the somewhat smaller scope of crow with druid helps a lot. Maybe that’s how I should have approached writing things for norns, starting simple.

1 Like

So, I managed to do a very basic clock with swing in (roughly) the way I wanted to do it. I probably want to have much better control over where the even beats land so they can land before and not just after their unswinged time, probanly also want to add multipliers for the tempo, might want to do 16th notes for the swing outs. I still need to learn more about swing in any case. But for now this will do.

Be aware that you’ll need the the latest buid of the norns firmware (commit f491781 ) in order to get the metro to update dynamically.

It’s simple but I’ve been wanting to have a clock with swing and now I do. It was fun to work on it. Thanks for helping me out!

3 Likes

Anyone have a good snipper for massaging the voltage ranges and creating a wider “center detent”? I’m doing it in an “ugly” way. (I’m quantizing TXi values, so 5 is a magic number for the center.)

function quantizeToSteps(value, steps, centerWidth)
  local centerBottom = 5 - (centerWidth / 2)
  local centerTop = 5 + (centerWidth / 2)
  if (value < centerBottom) then
      return math.floor(range(0, centerBottom, 0, math.floor(steps / 2), value))
  elseif (value > centerTop) then
      return math.floor(range(centerTop, 10, math.ceil(steps / 2), steps, value))
  else
      return math.floor(steps / 2)
  end
end

function range(min, max, newMin, newMax, value)
    return newMin + (value - min) * (newMax - newMin) / (max - min)
end

I’ve updated the swing clock script I shared a few days ago. I added swing in a different way. Now you can use -5v to 5v on input 2. 0v is no swing, -5v is 25% swing and 5v is 75% swing. I also added a variable to change the clock division for the swing clock.

I think it’s stable and working as expected, at least when testing on my oscilloscope, but I’d need to test some more. The code is probably nasty and with some redundant parts, but for now what you see is what comes natural to my inexperienced programming brain.

As before, be aware that you’ll need the the latest buid of the norns firmware (commit f491781 ) in order to get the metro to update dynamically.


More on clock divison in the script

The way I was getting the even beats in the previous script was by using the counter in the metro and using modulo to evaluate if it was even or something else.

I tried using multiple dynamic metros at different speeds, but I noticed that I couldn’t update all of them proper and some of them ended up with delays I didn’t want to handle.

In the end I first multiplied the clock by 2 to get a slower clock and then used normal outputs together with delayed outputs to regain the correct clock speed (in case of output 1) as well as to create the clock divisions. Controlling how much the even beats are delayed as well as adding repeats based on the division factor did the trick. I also used nested delays to do this, couldn’t figure out a different way.

Enjoy!

2 Likes

Is there any way to trigger MIDI notes with crow?

I’ve written a pretty nice sequencer class in Lua, I’d love to trigger my drum machine with it.

Sorry for the sparse documentation, let me know if you have any questions! As a hint, bseq will convert any number into its binary representation — instant trigger sequences!

Second hint — run mlb = true and see what happens :slight_smile:

seq.lua
-- a get together 
-- 1. INIT
function init()
  -- jf
  jf_init(true)
  -- scales
  lydian = { 0, 2, 4, 6, 7, 9, 11 }
  penta  = { 0, 2, 4, 7, 9 }
  penta2 = { 2, 4, 6, 9, 11 }
  seven  = { 0, 4, 7, 11, 14 }
  -- loop
  slb = false
  sl = 8
  -- bval
  b1val = 144
  b2val = 18
  b3val = 43691
  -- sequence velocity 
  sv = 8
  -- cv params
  cva = 0
  cvr = 0.5
  cvv = 5
  cvs = 'log'
  -- pick jf or cv trigs
  jfb = true
  -- sequence
  s1 = seq:new(penta2, 1)
  -- binary sequence
  b1 = bseq:new(b1val)
  b2 = bseq:new(b2val)
  b3 = bseq:new(b3val)
  -- start sequence metro
  S = metro.init{ event = s_iter, time = 0.15 }; S:start()
end

-- 2. EVENTS
-- sequence
st = 1
function s_iter ()
  -- update bvals
  b1:u(b1val)
  b2:u(b2val)
  b3:u(b3val)
  -- trig jf
  if jfb == true then
    jf_trig(b1:next(), s1, sv)
    jf_trig(b2:next(), s1, sv)
    jf_trig(b3:next(), s1, sv)
  else
  -- trig cv
    cv_trig(b1:next(), 1, sv)
    cv_trig(b2:next(), 2, sv)
    cv_trig(b3:next(), 3, sv)
  end
  -- loop
  if slb and st >= sl then
    st = 0 
    s1:reset()
    b1:reset()
    b2:reset()
    b3:reset()
  end
  -- iterate
  st = st + 1
end

-- 3. FUNCTIONS
-- init jf
function jf_init (bool)
  ii.pullup(true)
  if bool then ii.jf.mode(1) else ii.jf.mode(0) end 
end
-- trigger jf
function jf_trig (trig, seq, vel) if trig == 1 then jf(seq:next(), vel) end end
-- jf play_note wrapper 
function jf (note, vel) ii.jf.play_note(note / 12, vel) end
-- trigger cv
function cv_trig (trig, chan, vel) if trig == 1 then output[chan]( ar(cva, cvr, vel or cvv, cvs)) end end

-- 4. CLASSES
-- seq
seq = { seq = {}, t = 0, seq_i = 0, len_i = 0 }
-- init
function seq:new (seq, step, len)
  local this = {}
  setmetatable(this, self)
  self.__index = self
  this.seq = seq
  this.step = step or 1
  this.len = len or #this.seq
  return this
end
-- return next note
function seq:next ()
  self.seq_i = (self.seq_i + self.step) % #self.seq
  self:check_len()
  return self.seq[self.seq_i + 1] + self.t
end
-- helper — reset seq at len
function seq:check_len ()
  self.len_i = self.len_i + 1
  if self.len_i >= self.len then self:reset() end
end
-- directly reset seq
function seq:reset () 
  self.seq_i = 0
  self.len_i = 0
end

-- bseq — inherits seq
bseq = seq:new ()
-- init
function bseq:new (val, step)
  b = seq:new(bits(val), step)
  setmetatable(b, self)
  self.__index = self
  b.val = val
  return b
end
-- update val
function bseq:u (val) 
  self.seq = bits(val) 
  self.val = val
  -- unlike seq, reset len!
  self.len = #self.seq
end
-- add to val
function bseq:a (n) self:n(self.val + n) end
-- convert decimal to binary
function bits (num, bits)
  bits = bits or math.max(1, select(2, math.frexp(num)))
  local t = {}
  for b = bits, 1, -1 do
    t[b] = math.fmod(num, 2)
    num = math.floor((num - t[b]) / 2)
  end
  return t
end

-- 5. TROUBLESHOOTING
-- print table
function printt(tab) for i, v in ipairs(tab) do print(i, v) end end
-- print sequence
function prints(seq) printt(seq.seq) end

Hey! New to scripting with druid and crow and having confusion as to how to mix two inputs into one output while having stream modes on both inputs.

The idea of the script is that I feed external sequencer cv to input 1, external offset into input 2 (which gets quantised internally on crow) and then output the sum of both of these signals into output 1. For some reason I am getting weird fm like pitch output instead of a clean melody, perhaps its something to do with the way I’m using stream modes? Couldn’t find any examples on how to use it. Here is the script below. Would really appreciate your help!

feet_scale = {0, 12, 19, 24, 31, 36}

function init()
— input[1].mode(‘stream’, 0.005)
— input[2].mode(‘stream’, 0.005)
end

function quantize(volts,scale)
— local octave = math.floor(volts)
— local interval = volts - octave
— local semitones = interval / 12
— local degree = 1
— while degree < #scale and semitones > scale[degree+1] do
------- degree = degree + 1
end
— local above = scale[degree + 1] - semitones
— local below = semitones - scale[degree]
— if below > above then
------ degree = degree + 1
end
— local note = scale[degree]
— note = note + 12*octave
— return note
end

input[2].stream = function(volts)
— local offset = n2v(quantize(volts,feet_scale))
— output[1].volts = offset
end

input[1].stream = function(volts)
— pitch = input[1].volts
— output[1].volts = output[1].volts + pitch

end

This looks to me like you are basically integrating the value of output[1] – every 5 ms you add the value from input[2] to whatever’s currently at output[1]. You might want to keep a separate variable for storing the sum of your two sequence values, then use a single stream handler or a separate metro to assign the output to that variable value.

A couple other tips:

  • For formatting code on this forum you can wrap it in triple backquotes ```, or use the button in the comment editor (between the “quote” and “bullet point” icons)
  • You might want to check out the new scale quantization options for input and output in crow 2.0.0, see here and here.
3 Likes

Thanks for the tips, Im still a little confused as to how to pass the input values into a variable which is the sum of them both. I am getting an error: attempt to perform arithmetic on a nil value.

input[2].stream = function(volts)
	offset = n2v(quantize(volts,feet_scale))
end


input[1].stream = function(volts)
	pitch = input[1].volts
end

final_pitch = offset + pitch

input[1].stream = function(volts)
	output[1].volts = final_pitch
end 
final_pitch = offset + pitch

Since this assignment is outside of any function, it executes before even your init() function is even run. Since your offset and pitch variables are only assigned inside your stream functions, these variable names are both nil when you try to add them here.

You are also assigning and then reassigning input[1].stream to a different function – each input can only use one handler function at a time.

You can’t really say “this variable is always equal to the sum of these two”, you have to tell crow when you want to do this calculation. You could try initializing your pitch and offset variables to 0 outside any function, assigning one variable in each input handler, and then put your

final_pitch = offset + pitch
output[2].volts = final_pitch

in one of the input handlers.

1 Like

Thanks again for the help, things are getting clearer, but now I hear no cv output and getting an error saying that I cannot perform arithmetic on a function value (global offset), which is the line where final_pitch = pitch + offset. Any idea what else could be causing this problem?
Thanks a lot!

feet_scale = {0, 12, 19, 24, 31, 36}

pitch = 0
offset = 0

function init()
	input[1].mode('stream', 0.005)
	input[2].mode('stream', 0.005)
	
end

function quantize(volts,scale) 
	local octave = math.floor(volts)
	local interval = volts - octave
	local semitones = interval / 12
	local degree = 1
	while degree < #scale and semitones > scale[degree+1]  do
		degree = degree + 1
	end
	local above = scale[degree + 1] - semitones
	local below = semitones - scale[degree]
	if below > above then 
		degree = degree + 1
	end
	local note = scale[degree]
	note = note + 12*octave
	return note
end



input[2].stream = function(volts)
	offset_v = n2v(quantize(volts,feet_scale))
	offset = offset_v
	return offset
end


input[1].stream = function(volts)
	pitch_v = input[1].volts
	final_pitch = offset + pitch
	output[1].volts = final_pitch
end

I think this is because n2v is a helper for building ASLs and returns a function. For just converting a note value to a V/oct voltage it’s overkill, just divide by 12 instead of calling n2v.

1 Like

That solved the problem and now everything is working! Any ideas why n2v (function) returns a function and not a value? Is there any way to make it return the value instead and also is there any benefit of having it return a function?
Thanks!

This is because it’s intended for helping write ASL expressions, where you want it to calculate a voltage at runtime rather than a fixed value determined at ASL definition time. That’s really the whole purpose of the n2v function, which is why it’s defined and documented alongside other ASL stuff. If you just want to convert a semitone to a volt-per-octave voltage value, you just need to divide by 12, since 12 semitones = 1 octave maps to 1 volt – not really any point to having a dedicated function for this.

4 Likes

Thank you so much man, great explanation! This script is inspired by the Yamaha cs70 Feet sliders, will add a few more tweaks and post it for everyone to use :slight_smile:

1 Like

I’ve been messing with the ii just friends poly sequencer script from one of the MAPS sessions and had a quick question. I’ve been altering the scale in a way that’s basically using it as a trad sequencer and was wondering if there was a way to write a pause into the scale? Like if the scale is {0, 2, 4, 5, 4, 5, 7}, is there a way to put a beat of silence in the scale or would you have to approach it differently?

So, Im trying to figure out if Ornithopters could be used to sequence rings in the following way: envelope from output 1 to vca to send noise (from another module) to rings input, random quantized voltage in major scale or major pentatonic from output 2, with the ability to clock the envelopes from either input 1 or 2. I think this is possible from reading the description and looking at the code, but I’m really new to code and not sure how I would accomplish this with the parameters.

Also, would it be possible to make crow a “rings companion” so to speak in that sending clock to input 1 will drive a stream of snappy envelopes from out 1, cv info from out 2, and pink noise from out 3? Can the outputs be programmed to send out noise?

Thanks for the help to what I’m sure are obvious questions.

1 Like

I’m struggling to develop a script for Crow (I’m definitely a beginner when it comes to coding).

What I want is a quad function generator that outputs envelopes at at a division of the clock, with a scaling feature to adjust envelope lengthen relative to the level of an input signal.

So to sketch my goal:

  • Input 1 = trig

  • Input 2 = voltage reference (-5, 5 volts), where high voltage = longer envelop shape

  • output 1 = envelope at trig rate

  • output 2 = envelope triggered 2nd clock division

  • output 3 = envelope triggered 4th clock division

  • output 4 = envelope triggered 8th clock division

Backing up to where I’m currently stuck, how do I go about formatting multiple outputs from a single input change?

And of course I’d be happy to hear any general ideas about how to develop this, thanks.

1 Like

How far along are you? Can you share what you’ve written so far?

If it were me, I’d start by breaking the project down into it’s component parts. The most basic form of the above would be to have input 1 (the clock) trigger output 1 at the same rate. To do that you would need to activate ‘change’ mode on input 1:

input[1].mode( 'change' )

and define the event that will occur when a change is detected:

input[1].change = function( direction )
  ...
end

Where the ... is the action to take on a trig. From your description it seems like the majority of your script will be called right here (of course you can break different elements into other functions).

You can make a basic output envelope with output[1]( ar() ) for a default attack-release envelope.

If you want to make a single input event create multiple envelopes, you just need to write the above output[n](...) line for each output, in sequence inside the change event function.

Then the clock divisions will require you to maintain a counter for each output, and conditionally call the output[n](...) function only every n times. There are many ways to do this, but I leave that challenge to you.

Hope that gets you started. If you need more help, post your script and we can give pointers on the next steps to take.

10 Likes

Just looking at the first step to get 4 envelopes from one trigger that I’m stuck on, I’m getting an e.o.f. syntax error with (with indenting):

function init()
input[1].mode(‘change’,1,0.1,‘rising’)
end

input[1].change = function( direction )
output[1].action = ar(0.5, 0.2, 5.0, linear)
output[2].action = ar(0.5, 0.2, 5.0, linear)
output[3].action = ar(0.5, 0.2, 5.0, linear)
output[4].action = ar(0.5, 0.2, 5.0, linear)
end

What am I doing wrong?

I just tried your script. You had some funky quotes around change and rising. I changed them to single quotes (’).
Also I threw single quotes around ‘linear’ too.
I just ran it, though, and it doesn’t seem to be doing anything.

EDIT: Got it!

function init()
    print("WELCOME!")
    input[1].mode('change',1,0.1,'rising')
end

input[1].change = function( direction )
    print("What's up? INPUT 1!")
    output[1].action = ar(0.5, 0.2, 5.0, 'linear')
    output[2].action = ar(0.5, 0.2, 5.0, 'linear')
    output[3].action = ar(0.5, 0.2, 5.0, 'linear')
    output[4].action = ar(0.5, 0.2, 5.0, 'linear')

    output[1]()
    output[2]()
    output[3]()
    output[4]()
end
1 Like