^^ crow strategies: Just Friends

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()
	output[2](bonk())
	ii.jf.mode(1)
end

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

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

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.

11 Likes

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:

Code
-- JF with transpose and first combined

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

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
    output[out+1]()
  end
  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
  end
  step[ix] = (step[ix] % length[ix]) + 1
  step[ix+2] = (step[ix+2] % length[ix+2]) + 1
end

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

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
end

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

input[1].change = function(s)
    getters()

    if speed > 0 and tsc == 1.0 then
        tmetro:start()
        ii.jf.mode(1)
        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 )
    else
        noteCount = 0
        ii.jf.mode(0)
        tmetro:stop()
    end

    play(1,1)
    play(3,2)
end

function getters()
    ii.jf.get('speed')
    ii.jf.get('tsc')

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

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

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

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

  -- 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 }
  dec:start()

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

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!

2 Likes

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[1]:start()

metro[2].event = n2

metro[2].time = 0.7

metro[2]:start()

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

end

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 )

end

end

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 )

end

end

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.

7 Likes

hey all. im just starting to dip my toes into i2c/JF/m4l. writing midi in ableton and sending to JF as a polysynth is a dream :slight_smile:

that said i think every session i’ve had i’ve had a crash. seems to happen when i’m making microedits or adding to a midi sequence most. JF stops emitting sound despite still showing up as connected. the only way i’ve gotten it to
start back up is to power cycle my rack and delete/re-create command and JFsynth. obviously this isn’t sustainable… has anyone else experienced this and found a solve? no issues so far with crow/norns/JF i2c… just with the m4l stuff. thanks for any help!

works perfectly for me with crow + m4l. what version of ableton are you on?

1 Like
  1. haven’t updated to 11.1 yet but will do so this week

hmm, have been reports of crashes with m4l devices and ableton 11 but not heared of troubles with the crow pack…

I’m trying to query the JF voices to see which one is free.
According to the Just Type help page, I should be able to do something like the following to see if, when the strum() function is called by the clock, the channel (in this case 1) is free or not. Whatever I do, it keeps repeating that it’s true in the rate of the clock. Maybe I’m not getting something- how does this work?

function strum()
  while true do
  
    clock.sync(1/2)
    
    -- check if voice 1 is free
    channel = 1
    if crow.ii.jf.get('trigger', channel) == True then
      print("true!")
    else
      print("false!")
    end

    note = math.random(#notes[1])
    crow.ii.jf.pitch(math.random(6),(notes[noteSet][note]/12-1)+tune)
    crow.ii.jf.trigger(math.random(6),1)
    
  end
end

crow.ii.jf.event = function( e, value )
  if e.name == 'trigger' then
    -- handle trigger response
      -- e.arg: first argument, ie channel
      -- e.device: index of device
      print(e.arg)
  end
end

The problem (or at least one of) is that crow.ii.jf.get(...) doesn’t return anything! It triggers an asynchronous event that is received in the crow.ii.jf.event() handler (which you have correctly configured). Where you are printing print(e.arg) in that handler, try adding the value so you can see what it’s actually reporting: print(e.arg, value).

Secondly, I think that in lua truthy is true, not True though I could be wrong and it has an upper-case alias (regardless, changing that won’t fix this case).

I think that when you get the 'trigger', it will return 1 or 0 (rather than true and false) though I’d confirm that by looking at the print in the event handler.

Curious why you’re trying to inspect the voice allocator (though it’s cool that you can! i didn’t conceive of that as a use-case when getting trigger states). I always assumed people would use the in-built play_note allocator, or make their own custom allocator and use play_voice instead.

1 Like

Thank you, it seems your assumptions are right, on true being lowercase and trigger not returning true/false…
though value always returns 1 for me, even if I make sure it is playing or not by commenting out the triggering of the voice. Any idea why? This is my code:

function strum()
  while true do
    clock.sync(1/8)
    
    -- check if voice 1 is free
    channel = 1
    crow.ii.jf.get('trigger', channel)

    note = math.random(#notes[1])
    crow.ii.jf.pitch(math.random(6),(notes[noteSet][note]/12-1)+tune)
    --crow.ii.jf.trigger(math.random(6),1)
    
  end
end

crow.ii.jf.event = function( e, value )
  if e.name == 'trigger' then
    -- handle trigger response
      -- e.arg: first argument, ie channel
      -- e.device: index of device
      print(e.arg, value)
  end
end

As for why I’m trying to inspect the voice allocator- after making my Tidbit script I had this (still vague) idea of doing a slow version. Vague idea incoming:
It would have slow moving notes that would never suddenly change pitch while they are playing (so no triggering notes that are playing, hence the need for this). It would however have gradual/glissandi-like pitch changes during its AR envelope (is this possible at all?). It would be called HIKE and would look like six mountains on Norns display, and some animation would indicate the pitch and the AR envelope form. Of course this would be influenced by twiddling TIME. Anyway the idea is not complete yet, but I have a hunch this could be interesting.

EDIT: simpler said- this would allow you to play notes only when there is one voice free- making the TIME knob also responsible for the rate new notes are let in- and making sure there are not sudden pitch changes while envelopes are going.

1 Like

sounds really interesting!

i think the issue is the way get 'trigger' is implemented might mean it’s always 1 when in synthesis mode. the reason being, trigger returns whether the channel is in motion – i don’t think this variable tracks whether the volume has hit zero, so when the oscillator goes silent it is still oscillating (just being muted), like in an analogue oscillator.

for your idea of the frequency of notes being played being relatively to the TIME knob, you could just poll the value of time with crow.ii.jf.get 'time' (receiving the value in the jf.event handler), and control a norns clock which triggers the notes. i’d use jf.play_voice and build a simple round-robin allocator – it’s just a wrapping counter from 1 to 6.

regarding the gliss idea, there is no in-built support for this (JF is already at the limit of processing power in synthesis mode). the way i’d suggest to try it is with something like the fnl function i made for a maps stream (ep3.lua · GitHub) – it lets you continuously sweep values over i2c with a given framerate. you’ll probably need to experiment with the speed, and interleaving the oscillators to find something stable though (as the technique can easily saturate the i2c bus).

4 Likes

Thank you so much! This is great and it’s wonderful to hear a bit about how things are implemented! A lot to unpack and to experiment with here… I’ll try some stuff out :slight_smile: Thanks!

I’ve been using a crow script running Just Friends as a synthesizer. After I change to a different script in Crow, something unrelated to JF, JF continues to run in the same mode. I’m not sure how to get it back to it’s normal operation. Is there an ii.jf___ command to quit?