^^ crow strategies: Just Friends

added the sprintf bits to the scripting part of the FAQ!

example code snippet

also testing this now and getting unexpected results. will keep you updated as I learn more

Okay thanks Dan!
Glad I’m not just being dense.

closing the loop!

for the Max folks, here’s a Geode patch that demonstrates the functionality: geode_ex.maxpat (30.8 KB)

how to navigate the patch:

  • throw JF into transient and shape
  • connect patcher to crow
  • enable pullups, if needed
  • enable geode
  • [bang] the [49] number object
  • set channel [1], division [4], repeats [3] (wait till it all dies down before moving on)
  • [bang] that section and you should see 4 pulses from channel 1 (repeats assigns how many pulses after initial pulse)
  • under that: division [3], repeats [2] (wait till it all dies down before moving on)
  • [bang] that section and you should see 3 pulses from channel 6 (repeats assigns how many pulses after initial pulse)

Thanks again for your help Dan!

In the Max Geode patch is there a reason why we need to divide the repeats and division by 1638?

Yeah! It has to do with synthesis mode and Teletype though…

Are you familiar with Teletype?

Yeah i’m familiar with TT but i never tried Geode on it. Guess I’m wondering how it applies to Crow here as I couldn’t find anything related in the docs

The fundamental reason is the underlying I2C code which runs Just Type was written long before the crow idea had begun germinating. It was originally written only with Teletype in mind; Teletype does not have floating point numbers in its scripting language, just integers.

I wrote the description below on a crowded rush hour train, please excuse any typos or not rigorously formatting code snippets!

Teletype explanation

In synthesis mode, you would do the following to play a note:

JF.NOTE pitch vel

How should Pitch and Velocity (ie peak-to-peak voltage) be scaled? What velocity input should correspond to a 5V peak-to-peak waveform? What about 2.5vpp? What about 2.342vpp? The natural choices would be “use the number exactly as is" - but since Teletype is limited to integers, you could only set a 1 or 2 or 3 or 4 or 5vpp waveform but not 2.342vpp or 2.5vpp.

Since Teletype can’t handle floating points, our options would be very limited.

Solution: choose a larger number to represent 10vpp loudness, for instance 1000. Then, typing in 1000 for velocity would result in 10vpp, 900 would be 9vpp, 250 would be 2.50vpp and so on… but we still could not get 2.342vpp. How can we maximize our available precision?

Solution: Make an extremely large number equal to 10vpp. Let’s call this our “super precise large number scaling system.” Now, every voltage like 2.342vpp corresponds to some integer. For instance, to figure out what 1vpp is, just take our super large number and divide it by 10. That’s annoying to do every time though, so… hello Teletype V operator

V 1 conveniently converts the number 1 into the correct number in our “super precise large integer scaling system” for 1vpp. VV 132 creates the correct number for 1.32vpp.

The velocity argument of JF.NOTE expects to be given an integer from our special scale, but instead of doing so directly, we do it by picking an easier voltage number and converting it with the V operator.

Similarly, the pitch input expects numbers to be specified in the super precise scaling system and then interprets them as volt-per-octave voltages. So suppose I want a 1-octave increase above the default of C3 - I would do V 1. What if I want 7 semitones? Then I can use the N operator to accurately convert 5 into first the correct volt per octave voltage, then into the correct “super large integer scaling system” number.

At the same time, I also have the ability to pick some very precise microtonal pitches beyond just semitones though if I set the argument directly instead of using a “lookup table” to (the N or V operators) first.

So why does this matter?

The same I2C command JF.NOTE on teletype is used to control geode mode.
In geode on teletype, you do not need to use the lookup tables; since the arguments only make sense as whole numbers and there is no need for floating points, you set the number of repeats and divisions directly.

So JF.NOTE 1 1 means either make one envelope using the first click division length in geode, or it means play an incredibly quiet (silent really) note that is essentially no different than C3, the default 0 note in synthesis mode.

Alternatively, JF.NOTE V 1 V 1 means play thousands of repeats of an extremely long envelope in geode V 1 is really just a super large integer, or it means play a note one octave above C3 at 1vpp in synthesis mode.

Crucially, the same command is used to talk to both modes; the range switch on JF is what determines the difference in meaning, not the sending device.

Fast forward to crow, which can handle floating point numbers after all. The play_note functions was written with the pitch priority in mind however JF is still expecting to receive an integer in our “super large integer scaling system” which it will then internally convert to the right pitch.

So on crow, play_note expects a floating point v/oct voltage; it then converts this to our “super large integer scale” and sends it to jf.

This poses an issue when we try to use geode - if you type in “1” to play_note, it converts that into a super large integer before sending it to JF.

As such, when we actually want to send a 1 to JF, eg in geode mode, we can’t give play_note a 1 as an argument - we need to give it a much smaller decimal number which will then be immediately made bigger again by play_note, landing on the integer 1.


Keen to see what vcv -> crow -> just friends could be. Orca’s heart driving a few jf voices :slight_smile:


Appreciate the detailed response - really clarifies how to get geode working!

1 Like

Just spent some time with my new Just Friends/Crow/Norns (awake). Wow. Just Friends seems so powerful to me, unexpectedly so:


Has anyone tried to do any alternate tunings (microtonal etc) stuff with crow+jf?

I, of course, completely understand the need to use MIDI, but it is also a slight shame. In modular, using purely CV, we are not as much burdened by musical history and choices made decades ago (which we of course have to deal with every time working with MIDI.)

So, to clarify what I’m refering to, the problem is MIDI pitch bend values (which you need to get non-traditional pitches) are not note specific which can become a problem when using polyphony.


Do you specifically mean with the ^^jf_synth device not receiving MIDI pitch bend? There is nothing in the crow<>JF I2C connection that prevents microtonal sequencing of JF via crow.

JF receives a command from crow which tells it what interval to play relative to C3 and how loud to play it (amplitude in volts):

ii.jf.play_note(<v/oct voltage>, <amplitude voltage>)

So for instance, to play one octave above C3 at a 5Vpp amplitude, you would send:


Two octaves above, at 3.4Vpp amplitude:


Since one semitone corresponds to 1/12th of a volt in the V/oct standard, to play a perfect fifth above C3, you would send the following, since a perfect 5th is 7 semitones.


One cent is one hundredth of a semitone, so 1/1200 volts. So, to send the note that is 37 semitones below a major third, you could send a message like this - 4/12 corresponds to a major third, and then we subtract 37 semitones from that voltage.

ii.jf.play_note(4/12 - 37/1200,5)

To incorporate pitch bend into the ^^jf_synth M4L device, you would need to come up with a scaling system to map pitch bend values (0-127) to voltage. You would then add this voltage to the voltage determined by the MIDI note. This combined voltage would then be put into the Lua command which gets sent back to the command center device and then out to crow itself and then on to JF.


I’m about to try out this 10ms delay trick in @dan_derks original post here but I’m curious if anything ever came out of the talk about generalizing this approach. Also curious if this can be fixed deeper down like the iic layer? Thanks everyone for your work on this.

This will be fixed in the next Crow firmware release. I’ve got proper polyphony working already, but it needs more testing.


That’s great to hear! I’ll hold off and wait for that. Thank you for the update.

Very nice, just started exploring this patch last night. What setting did you have your mode switch in?

Has anyone played with the ii.jf.retune command at all? I’m getting odd behaviour.

For example, if I send

ii.jf.retune(2, 2, 1)
ii.jf.retune(6, 6, 1)

I would expect 2n and 6n to be the same as their default settings. But instead with the Intone knob at noon, they play the same pitch (Identity) but as I rotate clockwise they go DOWN (I would expect them to go up) with the same pitch and then quickly become inaudible. And if turn Intone counter they immediately turn off past noon and there’s no output at all.

If I send ii.jf.retune(0, 0, 0) everything resets to the defaults correctly. Any ideas?

I have a theory, just because I ran into some troubles with setting arguments in Geode. not at my JF at the moment, but what happens if you send:

ii.jf.retune(2, 2/1638, 1/1638)
ii.jf.retune(6, 6/1638, 1/1638)