Stochastic Telegraph Announcements

Version 2.0.20 has just gone live:

  • Added the ternery ‘?:’ operator to BASICally. For example:
IF foo >= 2 THEN
  OUT1 = IN1
ELSE
  OUT1 = 0
END IF

can now be written as:

OUT1 = foo >=2 ? IN1 : 0
  • In Venn, each Circle can now have a name associated with it, making the display more understandable.
  • In Venn, each Circle can now have an arbitrarily complicated formula associated with it, and the computed values become the polyphonic MATH1 output port. Examples of formula include:
    • “0.125”
    • “c#2” - that is, the V/OCT value for a C sharp in octave 2
  • Simple computed values:
    • “bb2 + 0.02” - a slightly sharp Bflat in octave 2.
    • “x / 2” - the value of the X value for this Circle, but divided by two.
    • “(x * y / 10) - 1” - use both the X and Y values for this Circle.
    • “pointx + pointy” - pointx and pointy are the X and Y values of the Point (i.e., the little white circle you move).
  • Use built-in functions:
    • “sign(x) * .1” - have the values -0.1 or 0.1, depending on which side of the Circle the Point is on.
    • “min(distance, 5.4)” - be the smaller of the DISTANCE value or 5.4.
    • “limit(distance, 5.4, 8.3)” - be the value of DISTANCE, but never less than 5.4 or more than 8.3.
    • “scale(x, leftx, rightx, -2.3, -1.2)” - instead of X’s normal range, scale it so that the left edge X is -2.3 and the right edge is -1.2.
  • Use simple logic to determine values:
    • “within ? 1.4 : 3.11” - if WITHIN is not zero, return 1.4; if WITHIN is zero, return 3.11.
    • “x < 1 ? 0.2 : sin(x)” - if X is less than 1, return 0.2, otherwise return sin(x).
  • Add Venn presets and trianglular venn-wave.
6 Likes

Basically being one of my favorite modules, I follow its every development with interest. As a bonus, when I read this thread, I feel like I’m taking a free trip to a distant, exotic land where I only understand one word in ten… I’ll take this opportunity to overcome my shyness and ask a question (which will no doubt make the experts smile): why isn’t a Goto “label” instruction (to my knowledge) available in Basically?

I’m so happy to hear you’re enjoying it, @gabtiorbi.

My short answer is, I haven’t personally found an occasion when I thought GOTO would be useful, so it never occurred to me to add it.

But perhaps that’s a failure of my imagination! Do you have an example or use case you can show here where you found yourself thinking, sheesh, GOTO would be really useful?

Just letting people know that @Omri_Cohen just made this video about the Memory system modules. I expect to learn a fair bit from watching it myself.

7 Likes

I find it hard to believe that someone who designed such a module could lack imagination. However, it’s highly likely that the user (which I am) isn’t properly using the available instructions. An example to illustrate my question: At the end of a process, I perform a test on a value. The result of this test will determine whether I should continue in sequence or repeat the process (and therefore “go back” in the program). This scenario can undoubtedly be addressed by the advanced topics mentioned in the user manual (?). In any case, I reiterate: Basically opens up endless possibilities.

I think it is will knows that anything you can do with a goto can be done some other way? Personally, I have not seen a goto used in decades.

Thank you for the confidence in my judgement, but trust me, there’s a lot of things that have never occurred to me. Many of the best features of my modules have come from people on this forum.

Actually, when I posed the question, conditionally moving through a sequence was a situation that seemed like a possible reason to want a GOTO.

But @Squinky is right; even a limited syntax like mine should not require a GOTO. GOTO was, I’ve read, added to BASIC because of some now absurd-sounding language limitations.

If you have BASICally code you can share that hits this case, feel to share it in this thread or DM me with it.

Thanks for the awesome update. Quick question: is there a way to reset the playhead in Ruminate ? Edit: Ah found out - SET.

1 Like

Here’s one case where I remember wondering about the usefulness of a GOTO-type statement:

Ah, I see! Very helpful.

So, if I may paraphrase, it appears that you want ‘deg’ to be a series of random integers from 1-7, and to never get the same number twice in a row.

I have two alternate solutions for you. You should use the second one, but the first one illustrates better why GOTO isn’t needed.

Solution 1

FOR foo = 0 TO 1 STEP 0
  deg = floor(random(1, 8))
  IF deg != LastDeg THEN
    EXIT FOR
  END IF
NEXT
LastDeg = deg
OUT1 = deg  ' So we can see it.
WAIT 300

WHEN start()
  deg = floor(random(1, 8))
  OUT1 = deg  ' So we can see it.
END WHEN

So what you were originally trying to do was “keep generating random numbers until it’s different from the prior value”. Most languages* use a “while loop” for this, which keeps doing something until some condition is met.

In BASICally, you can make a fake while loop by making a FOR loop that doesn’t end, but exiting it when the condition is met. That’s what I’m doing here.

I’ve so far opted not to add while loops to BASICally, because it was possible to fake it this way. This is a decision I’m, uhhh, reconsidering. I think I’m violating the 2nd part of the “Everything should be made as simple as possible, but not simpler” principle.

Anyhow…

Solution 2

deg = 1 + mod(deg + floor(random(0, 6)), 7)
OUT1 = deg  ' So we can see it.
WAIT 300

WHEN start()
  deg = floor(random(1, 8))
  OUT1 = deg
END WHEN

This is actually better. But the math took me a bit to figure out, so I’ll explain it.

  • we have a number from 1-7
  • then add a random integer from 0 - 5
  • then subtract 7 from it if it’s more than 6 (that’s what mod() is doing).
  • and then add 1, so that it’s back in the range of 1-7.

This is better because it always completes the first time you compute it, whereas Solution 1 will sometimes have to loop around.

Hope that helps! And thank you for getting me to reconsider leaving WHILE loops out.

* Yes, nerds, this should really be “procedural languages”.

Thank you so much for taking the time to look into my problem.

Even though the “step 0” (extraordinary!) somewhat surprised my formatted brain, which would never have previously considered that a progression step could be zero, solution 1 seems less context-dependent than solution 2. It also allowed me to dust off my very old Basic habits, which made me think it was impossible to “prematurely” exit a FOR-NEXT loop.

Solution 2 is more “economical and efficient,” and I find it very “profound” in terms of reasoning (I must be a bit perverse because I love this kind of trick!) In any case, I like BASICally even more. I’ve often wondered why this extraordinary module is rarely used in the patches found here and there, when it can effectively fulfill the functions of dozens of other modules… Thanks again !

3 Likes

I had some fun with these today for the first time in a while. Made a semi-random beat cutter kinda patch inspired by the good old Smartelectronix LiveCut plugin (who knows knows). It relies on the position output of an otherwise unused Ruminate to synchronize a bunch of @trickyflemming 's phasor modules, which in turn modulate the playback positions of the ruminates which we actually hear. Keeps everything in time with the main drum loop super nicely.

The new (er, I guess newish by now) Brainwash module makes it way easier to do this kinda thing on a real-time input. I don’t show that in the video, I’m using a pre-loaded loop there. But just wanted to say thanks for adding it @StochasticTelegraph

6 Likes

Ha! That’s super hip. It took a couple viewings for me to realize that the first (red) Ruminate’s audio isn’t even being used; its CURRENT value is being used to guide the other players through the Memory.

Thanks for showing that. I’ve been learning a lot about my own modules this week.

Yeah exactly! Keeping an extra Ruminate around as a phasor source was the insight that made this patch come together. A previous attempt months ago had an external phasor and that didn’t work nearly as well. Thanks again for the very neat modules!

A question for BASICally users, and also an opportunity for bike-shedding:

First off, I’m currently adding polyphony to all of the INn and OUTn ports. Meaning that, at the limit, BASICally would now have 144 inputs and 96 outputs.

The way this is done is to treat INn and OUTn ports as arrays when the syntax for doing so is present. E.g., to halve all of the values in a polyphonic cable, one could use:

FOR chan = 1 to channels(IN1)
  OUT1[chan] = IN1[chan] / 2
NEXT

Note that existing uses of INn and OUTn ports are unaffected by this. Neat, huh?


However, this example above reveals a subtle problem. One design principle I’ve had is that it should be a bit difficult for a BASICally user to stall out the system. As a result of this principle, every time a FOR-NEXT loop repeats, there is actually a hidden “WAIT 0” that occurs.

A “WAIT 0” really means, “BASICally will finish processing this sample, and then when the next sample is processed, I"ll continue where I left off.”

So if I unroll that loop above, it becomes:

OUT1[1] = IN1[1] / 2
WAIT 0
OUT1[2] = IN1[2] / 2
WAIT 0
OUT1[3] = IN1[3] / 2
WAIT 0
OUT1[4] = IN1[4] / 2
WAIT 0

Now, if a 16-channel IN1 is changing quite slowly (like many CV signals), you might not notice that the 16th channel gets updated 15 samples after the first one. But you might, depending on what the signals are altering.

And if these are audio signals, this means that each audio signal is only getting sampled every 16 samples; this is very noticeable.


So what’s a solution? My idea is that I add a version of FOR-NEXT loops that do NOT have the hidden WAIT 0 embedded in them. These loops will run faster, and thus be better synchronized, but consume more CPU, at least when the loop is actually run.

One idea is to have a 2nd version of NEXT, such as NEXTNOWAIT. But I’m a bit afraid the slight danger of using it is poorly conveyed by “NEXTNOWAIT”. Not waiting sounds good, right? :slight_smile:

I’m trying to convey to the user that this option exists, but maybe don’t use it by default, like for all FOR-NEXT loops.

I admit that it’s possible I’m overblowing the “risk” to users here; the crackling sounds that VCV makes when it’s running out of CPU is pretty obvious. Still, I want to shelter new-ish programmers from burning themselves.


Finally, now I’m curious what you all think. Is there a better term than NEXTNOWAIT that conveys among other things, “OK for small loops, but terrible for big ones.” I admit that I’m trying to convey a lot here (see the size of this message for proof), so maybe I should temper my expectations for a single word change.

Thoughts?

1 Like

A thought… if the issue is a semantic one, turning the attractive NEXTNOWAIT to something more off-putting like NEXTEXPENSIVE could be an option.

1 Like

What about allowing operations / functions to process all channels at once if the square brackets are omitted. So you could simply do out1 = in1 / 2. That is how other programmable modules work with polyphony. It greatly simplifies the end user’s code, and then you don’t need a new loop type. Obviously your internal code would perform a loop, but it would be implicit. (There would be an opportunity to use SIMD)

But I also like being able access individual channels with the square brackets, as it allows one channel to influence a different one. This would be a major improvement over the other programming modules.

When doing a poly operation with both a poly argument and a mono argument, the mono would automatically get replicated to match the poly channel count. If using two poly arguments with different channel counts, the missing channels would default to 0.

I see an opportunity to add new functions to support polyphony:

  • sortPoly - sorts the channels (with option for descending)
  • sortPoly2 - sorts channels of A based on channel values in B
  • various functions to convert a poly value into mono:
    • sumPoly
    • avgPoly
    • minPoly
    • maxPoly

I’m sure there are more that would be useful.

1 Like

@Bloodbat NEXTHIGHCPU is similarly unattractive, and tells the user where to look to see if the trade-off is worth it.

@DaveVenom Indeed, I’d love to know if there are recurring patterns in BASICally code (either mono or poly) that could benefit from being encapsulated into an internal function.

I’d say it’s even harsher… and clearer.

How about NEXTPOLY to make sure people only use it in the case you describe where the Loop needs to act differently than default?