^^ crow strategies: Just Friends

good point. I thought it would be nice to cut down on the amount of boilerplate code script authors must be aware of, but maybe this is unrealistic

To refine the question, I’m using the max for Live JF device through Crow to play JF as a polyphonic synth.

I’m when finishing up that patch I simply shut everything down. I’ve used JF in sound mode (without Crow) since and it seems to be behaving normally…

I never ran any script from m4l or anywhere else to take JF out of synth mode. Does that sound ok?


yep! you’re good. totally separate situation :slight_smile:

function cleanup()
  if params:get("output") == 5 then

Perhaps this is nitpicky and an uncommon use case, but what if I switch to output 5, and then switch back to output 4 before exiting?

Seems maybe something like this should also be added:

params:set_action('output', function(val)
  if prev_output == 5 and val ~= prev_output then
  prev_output = val

This also makes me wonder if the 2nd param in the set_action callback should be the previous value, so you don’t have to store a reference to it elsewhere (this would’ve helped me in some other use cases as well). But maybe that’s a conversation for github or another thread…

Copying in @tehn here as I don’t know that much about the norns side of things - I’ve sadly not had much more of chance to use it with crow than to test things were working. Perhaps he’ll have some insights on the best way forward in this case.

I fear there is a lot of edge cases and agree that it’s not ideal for script authors to have to replicate this. For example, in your extension of set_action method above, what if JF is already in synth mode before norns interacts with it - then it would kill whatever else was using it before norns got involved.

1 Like

I tried to search the forum and google around, but could not figure out an answer to this really basic question:

Is it possible to play Just Friends via jf_synth so that the notes keep playing as long as I hold down the keys on my MIDI keyboard, kinda like the sustain portion of an ADSR envelope? I understand that Just Friends was designed to be used with triggers, so this might not be possible. Thank you.

hey! totally good question. this isn’t currently possible with the m4l device. my tests with sustain were all stable up to a point, but then modules would hang. same with targeting voices rather than round robin.

i’ll continue to explore implementation, but hope it helps to know that there’s a gap!


my apologies if it’s somewhere obvious and i missed it… is there a glossary somewhere of all the commands jf will accept over ii? there’s a bit of information in the reference section of scripting [crow] with norns, but i can’t help but feel like things are being left out.

even just descriptions of what some of the things do would be helpful… what’s god mode? how does jf accept pitch, hz? midi number? when is jf expecting a number, and when is it expecting a string? when it’s the latter, what are the acceptable strings?

there’s got to be a reference somewhere. i’m struggling to find it, though.

You can get the list of available crow functions by running ii.jf.help() in Druid or crow.ii.jf.help() in Norns. This page runs through the meaning of each command – it uses Teletype syntax rather than the names of the crow functions. The main ways that some crow functions are different are that they accept floating point numbers instead of 16-bit ints like Teletype commands.

The ii.jf.help() output gives some hints about the parameters accepted:

ii.jf.trigger( channel, state )
ii.jf.run_mode( mode )
ii.jf.run( volts )
ii.jf.transpose( pitch )
ii.jf.vtrigger( channel, level )
ii.jf.mode( mode )
ii.jf.tick( clock-or-bpm )
ii.jf.play_voice( channel, pitch/divs, level/repeats )
ii.jf.play_note( pitch/divs, level/repeats )
ii.jf.god_mode( state )
ii.jf.retune( channel, numerator, denominator )
ii.jf.quantize( divisions )

Pitch, level, and run all correspond to voltage parameters and are specified in volts, so pitch is specified V/oct relative to JF’s base pitch (set with transpose). I believe everything else is the same as Teletype. This is generally how this works for ii devices on crow, think I’ll trot over to here and add a remark to that effect.


6 posts were merged into an existing topic: ^^ crow: max + max for live (feb 3 2020)

I finally figured out how to get data from I2C devices! The getter functions trigger the associated event, and pass the gotten data along to it. So here’s my really crappy little script for pinging JF a synth mode note at the voltage of faderbank fader #1:

function init()

ii.faders.event = function (e, data, id)
	ii.jf.play_note(data, 10)
	--crow.tell('fb', e, data, id)

function bonk()
	return loop{ function() ii.faders[1].get(1) end
				, to(5, 1)
				, to(0, 1)

The getter function is happening as part of an ASL loop with a triangle LFO because I was practicing what Trent showed us yesterday on stream.


Oke so small storytime here :slight_smile:
I’m using a programmable midi extender for yarns where i have programmed in a “chord” mode origanaly for Rings. (midi device: https://forum.mutable-instruments.net/t/needles-1u-midi-controller-yarns-companion/12656) It’s basicly a chrod mode which strumms through the notes you hit. So something like: C smalldelaywithnothing E smalldelaywithnothing F smalldelay and then shut of because it’s the last note.

I loved this so much that i bought a Crow for it to make the same thing work with just friends (with added vibrato support from yarns :grin:) But now im looking to implement something on the geode side. Is there any idea to make geode like a playable thing?

For anyone who is interested in my code:

-- JF with transpose and first combined

function delay(time, action)
   local dmetro = metro.init(devent, time, 1)
   local count = 0
   function devent()
      count = count + 1
      if count == 2 then

length = {}
rhythm = {}
notes  = {}
step   = {}
bits   = {}
scale  = { {0,2,4,7,9}, {0,2,4,5,7,9,11} }
decay  = 0.4
attack = 2

function set_d(t) decay = (t-1)/8 + 0.05 end
function set_a(t) attack = (t-1)/64 + 0.003 end

function play(out,ix)
  if rhythm[ix][ step[ix] ] & 8 == 8 then
    if ix == 1 then set_d(t[ix]) else set_a(t[ix]) end
    t[ix] = 0
  if rhythm[ix+2][ step[ix+2] ] & 8 == 8 then
    n1 = notes[ix][ step[ix+2] ]
    n2 = notes[ix][ step[ix] ]
    abs = math.abs(input[2].volts)/5
    note = n1 + abs*(n2-n1)
    note = math.floor(note * (abs*3 + 0.1))
    s = scale[input[2].volts > -0.04 and 1 or 2]
    nn = s[ note%(#s) + 1 ]
    oct = math.floor(note/12)
    output[out].volts = nn/12 + oct
  step[ix] = (step[ix] % length[ix]) + 1
  step[ix+2] = (step[ix+2] % length[ix+2]) + 1

sd = 0
function lcg(seed)
  local s = seed or sd
  sd = (1103515245*s + 12345) % (1<<31)
  return sd

function get_d() return decay end
function get_a() return attack end

t = {0,0}
function env(count)
  for i=1,2 do t[i] = t[i] + 1 end

function trans(count)
    transpose = input[2].volts - lastNote
    if (transpose > 0.008 or transpose < -0.008) then
        ii.jf.transpose(transpose - 2)

input[1].change = function(s)

    if speed > 0 and tsc == 1.0 then
        lastNote = input[2].volts
        noteCount = noteCount + 1
        delay( 0.003, function() ii.jf.play_note(input[2].volts,5) lastNote = input[2].volts end )
    elseif speed > 0 and noteCount > 5 then
        lastNote = input[2].volts
        delay( 0.003, function() ii.jf.play_note(input[2].volts,5) lastNote = input[2].volts end )
        noteCount = 0


function getters()

    ii.jf.event = function(e, value)
        if e.name == 'speed' then
            speed = value
        elseif e.name == 'tsc' then
            tsc = value

function init()
    lastNote = 0
    noteCount = 0
    input[1].mode('change', 1, 0.1, 'rising')

  for i=1,4 do
    length[i] = lcg()%19 + 6
    rhythm[i] = {}
    for n=1,32 do
      rhythm[i][n] = lcg()
    step[i] = 1

  -- notes
  for i=1,4 do
    notes[i] = {0}
    for n=1,31 do
      notes[i][n+1] = notes[i][n] + (lcg() % 7) -3

  -- out params
  output[1].slew   = 0
  output[1].volts  = 0
  output[2].action = ar(get_a,get_d)
  output[3].slew   = 0.01
  output[3].volts  = 0
  output[4].action = ar(get_a,get_d)

  -- start sequence!
  dec = metro.init{ event = env, time = 0.1 }

  tmetro = metro.init{ event = trans, time = 0.03 }

Note the outputs acts as the First script so when you aren’t playing Just friends synth mode you can use the First script.

Is there a reason why i cant use the delay function when im using a metro function on the fm knob? i want to read out the fm knob position on just friends but once i’m playing the just friends in synth mode and turn the fm knob it’s getting really wonky. So not fm wonky but it sometimes hits 2 notes are all notes.

Yesterday I tried for the first time ^^JFsynth on Max for Live, but my JF hangs after a couple of notes sent from Ableton…
it could be the ii cable is too long (35cm)?
Or it’s something else?
Today I’ll try with Teletype&JF, let’s see what’ll happens…

Hey y’all, I’m having an issue:

After updating Crow and Just Friends to their newest firmwares, Crow doesn’t seem to talk to JF at all. the jf.get functions all return ‘nil’ and things like jf.mode don’t appear to do anything.

Any thoughts? :slight_smile:

I’m currently scripting through maiden, but the results appear to be the same when using druid.

edit: Literally have no idea what I did, but when I moved the two modules to my skiff by themselves, it started working. God damn it.

edit: Unfortunately it stopped working again almost immediately. Weird stuff.

Had the same, and outlined how I fixed it in a post earlier, which I can’t seem to find now (deleted?), anyway, I followed these calibration/factory reset procedures at the bottom of this page: JF: Latest Version – WHIMSICAL RAPS

it’s here! ^^ crow 2.0+ help: general (connectivity, device q’s, ecosystem) - #212 by maaark

also, good call re: calibration, forgot that was a solution for that specific trouble. @zbs i also emailed you so lmk how it all goes!


Thank very much, and @dan_derks too! After that calibration it’s at least temporarily working. I’ll let you know if it poops out again. Thank you <3

edit: ya it broke again almost immediately :confused: Very weird stuff.

edit: @dan_derks , i did try reseating the i2c cable and i tried a different one to no apparent avail.

edit: Crow isn’t reading faders, disting EX or just friends in any circumstance I’m trying. There has to be something stupid I’m missing here?

1 Like

Just beginning with Crow / Just Friends. I have both modules updated and connected via i2c. They work as expected when my laptop is connected. For instance uploading and running a script using JF a polyphonic synth. I disconnect laptop and they continue to work. But if I power down the entire modular, they are disconnected when I turn back on later. I thought the whole point of Crow is that it automatically runs the uploaded script. Do I have to connect my laptop every time to run this patch or am I missing something in the work flow?

Here’s the script —

— just friends poly sequencer

function init()

metro[1].event = n

metro[1].time = 0.9


metro[2].event = n2

metro[2].time = 0.7


output[2].action = ar(0.03,2.7)


lydian = {0,2,4,6,7,9,11}

penta = {0,2,4,7,9}

penta2 = {0,2,4,6,7,9,11}

scale_ix = 1

scale_ix2 = 1

scale = lydian

level = 3

function n()

for n=1,2 do

scale_ix = (scale_ix + 3) % #penta +1

local note = penta[scale_ix]/12

ii.jf.play_voice( n, note, level )



function n2()

for n=1,3 do

scale_ix2 = (scale_ix2 + 2) % #penta2 +1

local note2 = penta2[scale_ix2]/12

ii.jf.play_voice( n+1, note2, level )



you need to add ii.jf.mode(1) to your init function. this tells just friends to enter synth mode which is required for the ii.jf.play_note calls to work.

then in druid, make sure you upload your script with u your_script_name.lua.

nb: when you’re working on the script we suggest using r your_script_name.lua as the “run” command is faster than “upload”, and doesn’t write to the flash (hence reduces wear on the memory). then just run u (no name needed) at the end of a session to freeze it to the module.