BASICally: a new module for writing code within Rack

Although, I will say, I break this rule gladly whenever I can keep a clear barrier between the UI and the code. 1-indexing is much more colloquial. I just try to draw the line at it entering into the code without a clear distinction between the underlying code and the UI-elements.

Anyway, I was working on some code this week where it was unclear and caused a bug and I was internally kicking myself for having allowed that to creep so far in without finding a way to correct it, so it’s on my mind.

Hi! I think you should change one example on github, this one:

out2 = 3.250 ' A C4 note.

I was really confused when I saw it. Cause the number is very weird, 0 volts is C4, and this one is something very close to D#7, my tuner says, haha. But before I checked I was thinking 5 minutes straight why 3.25 would become 0 volts. And what kind of scale it is…

ALso it would be really cool to integrate it further with VCV! Like maybe with introducing triggers as a part of this language. Like If in1 = 0 then out1 = trig. Or something like that. I mean, it would be cool and all, but I get it that it is maybe not this important… There are lots of modules that do that. But I really would like to see the VCV Basic language or something like that. Maybe even include notes into the library, probably with some kind of sign, like $C4 or something. And it would be like “IF in1 < $C4 then do something”. Or “If IN1 == $C4 then Out1 = trig”. That would be nice

1 Like

Thank you for pointing this out, @Andre_M . Not all comments are correct! :wink: I’ve fixed it in the doc.

Also, good news! In the upcoming version (currently awaiting the next library build by VCV), you’ll be able to say:

out2 = c4

and it will indeed just set out2 to zero. Details here, in part about “scientific pitch”.

And, indeed, these pitches look to the program to be just numbers, so you could indeed say:

if IN4 == c#3 THEN ...

or

if IN1 <= Bb2 then ...

You seem to be thinking about using BASICally to create triggers for other modules to respond to? That could definitely happen now, and I can see this being a good example to make:

if in1 < in2 or in1 == 0 then
  out1 = 1
end if
if abs(in3) > in2 then
  out2 = 1
end if
wait 1  ' Wait one millisecond for trigger to be seen.
out1 = 0
out2 = 0
4 Likes

How do I calculate the gap between in1 triggers in millisecond with BASICally?

I secretly hoped that I can use something like this

if in1>2 and not old_in1>2 then
  curr_time_in1=now()
  gap1=curr_time_in1 - old_time_in1
  old_time_in1=curr_time_in1
end if
old_in1=in1

' and then fooling around with the gap1 variable later on...

How do you resolve now()?

Reading your comment I realized that my post wasn’t clear. It is meant to be a question. So I re-worded my previous post a bit… :flushed:

Hopefully @StochasticTelegraph has some tricky solution for this.

1 Like

I agree it would be fantastic if BASICally supported a “now()” functionality of some type. I still have the dream of doing a discrete event scheduler with an event queue and event handler. When I ported Meander to Max/MSP a few years back, I wrote all of the event handling code in Javascript. It worked great.

2 Likes

That’s great!

I was thinking about this too, there is a way to program a trigger, but it would be much better if there was a library function of some sort. Maybe like trg1 is “out1 =1 \ wait 1 \ out1=0” or just as I wrote it before: trg is “1 then 0”, and you can set out1 = trg to do that. Well, I am not sure how Basic operates with these types of stuff though, so maybe it is a dumb idea, haha

@FiroLFO How about this? It assumes that you’re running at 48000 samples/sec.

tick = 0
' This is a WHILE loop.
for i = 1 to 2 step 0
  tick = tick + 1
  if in1 > 2 then  ' Detect trigger.
    ' 48000 samples/sec / 1000 
    gap = tick / 48
    exit for
  end if
next
' 1000ms outputs 10V.
out1 = gap / 100
' Wait for trigger to end.
for i = 1 to 2 step 0
  if in1 == 0 then  ' Trigger done.
    exit for
  end if
next

I’m intending to add a sample_rate() method soon, so you wouldn’t have to put it in the code.

@k-chaffin Yeah, I’ve been thinking a bit on time. I think Formula One had something that was for time. This is their description:

“compute the next phase using the global variable stim which holds the current sample time (1/sampleRate ).”

Yeah, running at 48000, stim = 1/48000. I’m not certain I find that compelling.

But that does little to denote the passage of time. now() is probably straightforward enough. I’m not sure what time-based info VCV provides me; I’m guessing that “seconds since VCV started” might be available, since that’s what lines in the log files start with. Is that what you’re thinking of?

On the other hand, there are modules that convey the clock time, like NYSTHI’s Timex.

mahlen

The 2nd version of BASICally (2.0.4) is now live in the library. It features:

  • Rejiggered UI elements.
  • The first line that confuses BASICally will be highlighted in red. This behavior can be turned off in the menu.
  • Text window scrolls when you get to the top or bottom. Longer programs!
  • Now have six INx ports instead of four.
  • Added scientific note notation: “out1 = c#3” and “if IN5 < bb3 then…” are now legal.
  • Now can have black text on white.
  • Some performance improvement.
  • Added all the Math functions to the menu.

This was all written before the previous version came out, and so before I’d gotten any ideas from this thread. So if I haven’t heeded your fantastic idea in this release, it’s only because I hadn’t seen it when I wrote this :slight_smile:

mahlen

3 Likes

The 3rd version (2.0.5) is now live in the library. Unlike the previous revision, this has many features suggested on this thread, which I have loosely attempted to credit below. Changes include:

  • Many UI changes:
    • STYLE knob is smaller, elements moved.
    • Now have nine INx ports instead of six.
    • The minimum width is now one “hole” smaller, and this hides the editing window.
    • Can enter a title for a module (via the menu) that shows up above the IN ports. Handy for stating what role the module plays in the patch.
    • Added menu option to change the status light from green/red to blue/orange. (@cosinekitty, et al)
    • And added new screen color options.
  • Added ‘elseif’. E.g., “IF a == 5 THEN … ELSEIF a == 4 THEN … “ (@FiroLFO)
  • Added methods about the environment, sample_rate() and connected(). (@FiroLFO)
  • Added methods for random number generation, random() and normal(). (@FiroLFO)
  • Added single dimension arrays, and a simple way to set a range of values. (@main.tenant, @k-chaffin @FiroLFO) As it turned out, the 0-index/1-index question was moot; these arrays don’t care what you use.

Note the simple Tape Loop demo made from an array.

And a small request, if I may; I’d like to add some Presets to the next version, so if you have BASICally Presets that you’ve written that you find useful/fun/interesting, please do send them to me, either by uploading them in this thread or reaching out to me at vcv@stochastictelegraph.com. Feel free to include a link or reference back to yourself in the script comments!

And I’m not done working on this…but the upcoming change (multithreading) is so unusual that I’ll probably do an early release on this topic to gather feedback before submitting. Stay tuned…

6 Likes

:star_struck:

Many nice and useful features there!

Just a quick question though. How come that we have 9 inputs and 4 outputs? Why not 8 and 4? Or 9 and 6? Just curious…

image

(In general I think I need more outputs than inputs.)

Follow the leader (8 poly ins / 8 poly outs.)

The author did merge the v2 PR - but it’s not submitted to the library. I’ve had no replies from the author, when I suggested adopting the plugin. The donationlink is disabled. I hope VCV (the company) will help get this plugin back in Rack V2 (or update VCV-Prototype adding polyphony and v2 compatability)

2 Likes

@FiroLFO I started out as 4 and 4, but I quickly felt constrained by these limits. These are the goals I’m balancing:

  • Conserve width. I made the module resizable, which helps with this a lot, but I still like to treat width on the rack as a resource to be mindful of.
  • More inputs than outputs. My reasons for doing so include A) noting that I often ran out of inputs when doing small programs, but not outputs and B) pulling up the module browser and noting that inputs outnumber outputs by, say 3x to 5x. Now admittedly, modules do this without expecting all of them to be used at the same time, whereas a BASICally user would typically only using inputs for what they need. Still though, I feel like more outputs are easy to simulate; run a second copy of BASICally with the same inputs and make their outputs be computed differently.
  • My gut feeling that data processing generally consists of distilling large numbers of inputs into a few outputs.

All that said, I might decide to bump the OUTs up to 6, if I don’t find another use more compelling for the space around them. I’ve noticed that, since there’s nowhere to “print” internal values, having a spare OUT to send them to is handy.

And your question got me thinking that some form of expanders for ports, dials, sliders might be worthwhile; although having lots of inputs makes that less incumbent on me. There’s lots of lovely controllers one could plug into it without me having to make them, although I haven’t found quite what I want yet (my wish list: compact width, nameable dials and ranges). And as I’ve said earlier in this topic, I don’t love expanders (yet).

That’s my reasoning, for what it is worth.

@Jens.Peter.Nielsen I too would love to see some brave soul port that over.

1 Like

Thank you for the detailed answer! It’s interesting to read how you approached these design decisions.

I would certainly encourage you to add some outputs.

Maybe one more comment: I find it strange that pressing Home/End takes you to the beginning/end of the script. I’d feel it more natural having Home/End taking to the beginning/end of the line.

I’m also wondering what’s the best way to loop through a array. I assume the length of the array needs to be pre-programmed, right?

I haven’t really worked on the editor’s key bindings much (apart from making up/down work!). Home/End’s current behavior is coming from the default TextField class, which I cloned to make my editor class. It’s a bit aggravating to think about, since VCV captures a lot of the keys (e.g., PgUp/PgDn move the whole patch); not that I think that’s bad, but a text-editor within VCV is a little hamstrung.

In fact, I didn’t know that Home/End did anything in the editor until you wrote this :). But perhaps I’ll return to the editor at some point, although, phew, it’s not much fun to work on. Text editors are harder than they look!

The length of an array is never set. One surprising principle of this language is that there cannot be runtime errors; there’s no way to report them, and “stopping” just because of some programming error is not what a user in a music-making system wants. So all things that are normally breaking errors (dividing by zero, accessing at some index never set) just have defined behavior and carry on.

The one exception, of course, is that programs can stall (e.g., while extending the vector long enough to accommodate index 10,000,000, if you set it). I just encountered the thread on priority inversion, and, yeah, I’ve got some stuff to work on there (primarily around compiling on the main thread).

So if you want your code to work better and you expect to maybe write to foo[10000000], I’d currently suggest that the first thing your code does is set the largest index in the array you expect (e.g., foo[10000000] = 0.1); it might stall then, but at least it won’t be stalling later. The next release will make “initialization” code like that MUCH easier to write.

mahlen

1 Like

How could you have a priority inversion? Do you have the audio thread acquire a lock? That is of course strictly “forbidden”. Looking at the code quickly the only lock I see is in the GMOCK test stuff.

My concern is that I’m doing an unknown amount of work during compilation. And the version in development does some allocations with “new”, which I understood to be a source of priority inversion. Am I mistaken? Malloc was mentioned as not having bounded time, is the same not true for ‘new’?

oh, no, you are correct - every implementation of new I’ve seen calls malloc, and ends up locking a mutex. So, yeah, you are not supposed to call malloc/new ever on the audio thread. You can either ignore the problem, or do all the allocations on some other thread. The latter is what I do when I need/want to malloc/new in the audio processing code.

@k-chaffin, @Andre_M, and anyone who has opinions, I have an early-beta version I’d like you to try.

I’ve been working on some functionality I’ve been calling “multithreading”, for lack of a better term. It allows the user to create multiple “blocks” of code that can run in parallel.

It’s working, so far as I can tell, though I suspect some cases haven’t been tested. but I’d love some pre-release confirmation that the design is reasonable and not terribly confusing. Or more likely, I’d like to hear where the design needlessly prevents useful functionality and how I could improve it; or, you know, that it outright doesn’t work.

The builds are here. The docs have been updated a bit, but I’d take suggestions on what needs to be sharper.

Briefly, this introduces two new functions, start() and trigger(), two new commands, CLEAR ALL and RESET, and some new structures that are the meat of the this change.

To make you want to look at the docs, here’s a few examples:

' Two beats, one making 40 pulses per second, one making 2.
' Note that they do drift out of phase because there are 40 NEXT/second
' vs two NEXT/second, and those cost the first loop 40/sample_rate()ths of a second
' every second.
' Not sure how to address this.
for i = 0 to 39
  out1 = 2
  wait 10
  out1 = 0
  wait 15 - 1000 / sample_rate()
next

also
for i = 0 to 1
  out2 = -2
  wait 10
  out2 = 0
  wait 490 - 1000 / sample_rate()
next
end also
WHEN START()
a[0] = { -1, 3, -3, 1, 2, -2, 5, -4 }
for b = 0 to 7
  c[b] = sin(a[b])
next
END WHEN

ALSO
for loop = 0 to 7
  out1 = a[loop] 
  out2 = c[loop]
  wait 1
next
END ALSO
' A trigger to IN9 resets the speed the notes are played.
FOR n = 0 TO 3
  out1 = note[n]
  WAIT pause_length
NEXT

WHEN start() or trigger(in9)
note[0] = { c3, g3, c4, c4 }  ' The notes in my score.
pause_length = random(100, 1000)  '  How fast we play the notes.
RESET  ' Force main block to restart now
END WHEN

@k-chaffin, what you’ve been asking for is now:

WHEN trigger(in9)
  RESET
END WHEN

Love to hear what you all think.

Mahlen

1 Like