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.