Support

If you have a problem or need to report a bug please email : support@dsprobotics.com

There are 3 sections to this support area:

DOWNLOADS: access to product manuals, support files and drivers

HELP & INFORMATION: tutorials and example files for learning or finding pre-made modules for your projects

USER FORUMS: meet with other users and exchange ideas, you can also get help and assistance here

NEW REGISTRATIONS - please contact us if you wish to register on the forum

Ruby Class help

For general discussion related FlowStone

Ruby Class help

Postby adamszabo » Thu Dec 31, 2020 9:36 am

I would like to get into making Classes in Ruby, and I did some tutorials online, which covered the basics, however there are a lot of Ruby codes which are very specific to Flowstone and thus, no tutorials are available. Anyone care to give a hand? Which are the things one can use in a class? I know I can create a function like a simple calculator like this:

Code: Select all
class Calculator
  def plus(number, other)
    return number + other
  end
   def minus(number, other)
    return number - other
  end
end

@calc = Calculator.new
result= @calc.plus(4, 1)


I can call the calculator in a different ruby edit box, and use the plus, or minus function. That makes sense. Can I call the draw function in a class, and draw object on another ruby instance? What about mouse events? Can I create a single mouse class, and use that across multiple ruby edits? Is there a list of things that have to be in their own ruby edit box and ones that can be shared? Also on some schematics I saw people use "module" instead of "class".

Code: Select all
module Calc
  def Calc.plus(number, other)
    return number + other
  end
   def Calc.minus(number, other)
    return number - other
  end   
end

Calc.plus(3,4)


Whats the difference?

Thank you!
adamszabo
 
Posts: 657
Joined: Sun Jul 11, 2010 7:21 am

Re: Ruby Class help

Postby trogluddite » Sat Jan 02, 2021 10:42 am

Potentially, there's a lot to pick apart there; so I'll try to give some practical answers to the immediate questions, and then see where things lead from there - it could be one of those "one question leads to another" kind of threads (that's not a problem to me, just that bite-size chunks might be in order! Let me know how I'm doing!)

adamszabo wrote:Which are the things one can use in a class?

I can't think of any obvious limitations that are likely to hurt you too much when starting out. The problems tend to be more with things outside of the actual classes in my experience - and they're not so much definite "nopes" as "don't run with scissors".

For example, Class and Module names are visible everywhere in the Ruby interpreter, so you have to be careful about naming and version control, otherwise a rogue toolbox module containing old code might overwrite the updated code that you're working on in your schematic (yes, toolbox modules can execute code - the thumbnails are NOT bitmaps!) Ruby errors due to startup problems are a PITA too - if you define a class once to share among RubyEdits, you have to be absolutely sure that the Class is defined before trying to create any instances - it's very easy to save a "working" schematic, only for it to load with loads of Ruby errors because your Class definition is at the wrong level of module nesting etc.

adamszabo wrote:Whats the difference?

Tricky without getting too technical, but in as plain English as I can say it:

In the second example, Module "Calc" is the calculator. Not a calculator, the calculator. The name "Calc" always points to the module from any RubyEdit any time you use it. Calc is the one true calculator!

OTOH, in the first example, Class "Calculator" is just a "recipe" for making Calculators. You don't actually have a calculator until you call "Calculator.new" to ask nicely for one. And each time you call that, it makes a new one, stored in a new place in memory. Also, the name "@calc" is not global, it's bound to the specific RubyEdit, so in every case, two different RubyEdits will always be referring to two different Calculators (a moot point here, as the "Calculator.new" call is making a new one every single time anyway).

Now, there's nothing wrong with your code per se. When you call "plus" or "minus", you expect that passing the same arguments will always return the same result - it would be a rubbish calculator otherwise! Most Rubyists would probably prefer the Module way to do it (one global calculator is fine here), but the other way is fine too (especially if you might be adding "instance-specific" features later).

Unfortunately, however, calculators were an unlucky choice for demonstrating the difference between Modules and Classes! Calc and every Calculator are defined to behave exactly the same way forever, which hides a lot of those differences, especially as you can't easily see that there are so many Calculators being made.

The big deal with Classes is that every single instance shares "behaviour" with other's of its Class (they all have the same methods), but that each instance can also store some data specific to itself, which the methods have access to. Think of a Midi object, for example - when you call its "status" method, it returns the status of the specific MIDI message that it represents. The Midi Class contains the code of method "status", defined as something like "get the first four bits of the first byte of the stored data and return it", but the data itself belongs to the instance, and every other instance has its own data. Hence, Calculators aren't a very typical Class example, as they don't have any internal data at all (unfortunately, each one still takes up extra memory, though - Ruby's own "housekeeping" data).

That part of Classes is the thing I'd suggest mastering first - Modules can certainly be useful, but really getting to know how a Class definition relates to its instances, and to an instance's "attributes" (its internal data), is the key that opens up everything else. The special cases, whether Modules or FS-specific weirdness, will come a lot easier once you're confident with Classes, IMHO. I also found it useful to think of methods as "stuff you can ask an object to do" rather than as "functions" - maybe that's just my (not very) Maths background; but part of good Class design is to hide away as much gnarly mathsy stuff as you can inside the Class to leave a nice neat "black box" with cute, fluffy method names.

The main difference from RubyEdit code is that a class has to define methods for accessing the data (attributes) of its instances - there are no methods "event", "input", "output", etc., so you have to provide your own methods to allow the external code to interact with the object as you'd like. So I'd look out for a few examples which use @instance_variables inside a Class, the "initialize" method, and/or the "attr" family of methods (one follows shortly!)

adamszabo wrote:Can I call the draw function in a class, and draw object on another ruby instance?

No - don't try to force drawing by calling draw yourself, not even from the same RubyEdit. You don't draw onto the RubyEdit, but to the View object which is passed into the "draw" method from the View link. When you ask nicely for a redraw, FlowStone goes away, prepares an empty View object, works out what other layers might have to be rendered and in what order, then passes the View object around to everyone who needs to draw to it. These View objects are FlowStone's responsibility, as they are basically a low-level interface to Windows' GDI+ rendering engine, and they're only temporary.

If you need to draw an object on a different RubyEdit than you created it in, you have to send it there somehow. The safe way (I'll leave the dangerous ways for another time!) is to use the Ruby "Value" connectors and links - these don't copy an object, they pass a reference to the original to the RubyEdit at the far end. However it can be a bit clunky, as the receiving RubyEdit will have to handle the incoming Ruby object in its "event" method.

However, there are ways that you can usefully delegate drawing and mouse-handling - that is; create objects which represent thing on the GUI, and which can lighten the load for your "draw" and mouse methods. For example, here's a very basic interactive rectangle...
Code: Select all
class MouseRect

    # "initialize" is called whenever a new instance is made, and takes the
    # arguments which were passed to "new".
    def initialize(color, area)
        # Instance variables store the MouseRects's internal data.
        @brush = Brush.new(color)
        @position = area
    end

    # Draw the rectangle onto the given view.
    def draw(view)
         view.drawRectangle(@brush, @position)
    end

    # Test if the mouse is inside the rectangle.
    def mouse_over?(mx, my)
         x, y, w, h = @position
         mx.between?(x, x + w) && my.between?(y, y + h)
    end

end # of class

# Setup up the RubyEdit
def init
  @shape = MouseRect.new(Color.new(200, 0, 0), [1, 1, 2, 3])
end

# RubyEdit draw
def draw(view)
    # Get the rectangle to draw itself.
    @shape.draw(view)
end

# RubyEdit mouse
def isInMousePoint(x, y)
    # Ask the rectangle to work it out.
    @shape.mouse_over?(x, y)
end


Where this comes in really handy is when there are different kinds of things that you might want to draw...

Let's say we also make a "MouseEllipse" class - just the same as above, but it does Ellipses. We could also do maybe a fancy "MouseStar" class, or whatever we want. Now, so long as those other kinds of object still have the methods "draw(view)" and "mouse_over?(x, y)", they can be swapped with the MouseRect or with each other at any old time, and the RubyEdit just doesn't care - it just calls "@shape.draw(view)", and, whatever kind of shape is pointed at by @shape gets drawn all by itself.

And THAT is the real power of using Ruby classes. By defining kinds of object with the same "interface" - the same method names taking the same arguments - you can make them interchangeable. No "if...then...else" is needed, no "case" statements, maybe I didn't even write the drawing code myself, it doesn't matter - just call the bog-standard method name and let the object deal with it!

As for the question of what can be shared where: I think I'll leave writing more on that for a later post, as it's a pretty big subject in its own right - and one which can tempt us with its possibilities at the same as time as having lots of crocodiles and alligators!
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby Class help

Postby tulamide » Sat Jan 02, 2021 12:57 pm

In addition to trog:

Exactly his last example is already present in Viper. If you look at the code I did for the arpeggiator you will find such "draw calls".

It may also be helpful to think in terms, that are NOT Ruby speek :lol:
Modules have another very helpful task. They can be made part of a class by referring to them from within a class definition. But why would that be needed? Here comes the non-Ruby speek. Think of a module as a collection of methods that, like your calculator example, never change regarding to a specific situation. It will always work the same way, and will always yield the same results. For a programmer it is then much easier to combine all those "static" methods in a module, and just refer to the module in any class that can make good use of (in this example) the calculator.

Code: Select all
module Calculator
  def plus(number, other)
    return number + other
  end
 
  def minus(number, other)
    return number - other
  end
end

class Calc
  include Calculator
end

count = Calc.new

count.plus(3, 2)


I'm not sure if I made use of this in the arpeggiator code, but you can see it used heavily in my Spline class.

In short, think of dynamic or specific and static or general to find out wether modules or classes are the better way to use. Most of the times you will find that many ways lead to a result, but the best way is one, that is less about the result itself, and more about maintaining code. So, whenever you can organize code, it IS the better way.
"There lies the dog buried" (German saying translated literally)
tulamide
 
Posts: 2687
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: Ruby Class help

Postby trogluddite » Sat Jan 02, 2021 1:34 pm

tulamide wrote:...think in terms, that are NOT Ruby speek

Honestly, I was already bad enough before the social-distancing started! :lol:
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby Class help

Postby adamszabo » Sun Jan 03, 2021 6:53 pm

Thanks guys very interesting discussion! I still need to wrap my head around the difference between module and class though. So I renamed the "class MouseRect" to "module MouseRec" and all the code worked. (For some reason I couldnt name it "module MouseRect", it gave an error so I had to remove the last "t", why?).

So if the class always uses memory when it copies itself to other ruby edits, does this method version code use less ram then? Now that its a "module", what features did it lose compared if it were still a class?

Regarding outputs, I guess I cannot create a function where the output assignment is already inside the function itself, this has to be separate in every single ruby edit?
adamszabo
 
Posts: 657
Joined: Sun Jul 11, 2010 7:21 am

Re: Ruby Class help

Postby trogluddite » Sun Jan 03, 2021 10:16 pm

adamszabo wrote:So I renamed the "class MouseRect" to "module MouseRec" and all the code worked

Ah now, this is something that you do need to be careful of:

Class and Module definitions are persistent. Once you've defined them, they stick around until you close down FlowStone, even if you close the schematic where you defined them. And, because one Ruby interpreter is shared across the whole of the application, they're visible from every other schematic that you load. And unfortunately, this has misled you...

You weren't allowed to change "class" to "module" because once the name "MouseRect" has been made to point at a Class, you can't change it to point at a Module (in Ruby terms, names beginning with a capital letter are "constants", so are expected to always point at the same thing). Just to make this more confusing, you CAN have the code "class MouseRect .... end" executed more than once - because this doesn't define a new Class, it edits the one that already exists. That why you have to be so careful about leaving old versions of code lying around inside schematics - if they get executed, they might overwrite what you're working on!

It also means that MouseRect continued to work after the error and after changing the module name - my original MouseRect (the Class) definition was still there - you could even have closed that schematic, opened a new one, and continued to use MouseRects in the new one. That's the other thing to be careful of: if you open a new schematic in the same FlowStone session, Ruby might not be in a "clean" startup state - there might be stuff from earlier hanging about that either breaks things, or makes things seem to be working that wouldn't normally work.

I have been caught out by this countless times myself. It's not a problem in stand-alone Ruby coding because you always run code inside a new application shell there. But in FlowStone, this interaction between current code and old junk that's lying around can be incredibly baffling - especially when you're trying to learn something new by experimenting!

If in doubt, always close FlowStone and start a new session with only one schematic open!

adamszabo wrote:Regarding outputs, I guess I cannot create a function where the output assignment is already inside the function itself, this has to be separate in every single ruby edit?

Essentially, yes. Both the built-in RubyEdit methods, and the ones that we define inside them, "belong" only to the current RubyEdit. They can be called from other code inside the same RubyEdit, but not usually from anywhere else. There's an important reason for this...
Code: Select all
# When calling a RubyEdit method, you usually write this...
output "my_output", value

# However, with other objects you often do it like this...
@format = StringFormat.new
@format.setAlignment("left")

Note how, in the second example, the method name is preceded by a dot and the name of our StringFormat object. So this is saying "call the setAlignment method of the StringFormat object I just gave you". But in the first example, there is nothing before the dot, and this means "call the output method of the object that we are inside of right now" - and when you're writing RubyEdit code, the thing that you're "inside of right now" is the RubyEdit itself.

In Ruby terms, the object before the dot is called the "receiver", because it "receives" the method call, and is expected to do something with it. When there is nothing before the dot, the "receiver" is whichever object is currently running the code - known in Ruby as the "self" object. This concept is also important when writing classes - inside a Class definition, "self" represents the current instance of the Class (i.e. if, inside a Class definition, you call a method without giving a "dotted receiver", it calls another method from the same Class definition).

So, given your question, you're probably now thinking - is there a way to get hold of a RubyEdit somehow so that we CAN put it before the dot and call it from somewhere else? Well, it has to be done very carefully, but yes you can. The "self" object isn't just a concept, you can get hold of a reference to it using the "self" keyword. For example:

In an empty RubyEdit, put in this code (make sure its watch panel is visible at the bottom).
Code: Select all
# The dollar-sign makes this a global variable, visible from everywhere.
$MY_SPECIAL_RUBYEDIT = self

Now, in any other RubyEdit...
Code: Select all
$MY_SPECIAL_RUBYEDIT.watch "Hello from", self

Given what I said before about "globally visible" things, it should be obvious why this is a bit dangerous! But there are safer ways to do it by passing the "self" reference around using the Ruby "Value" connectors and links, so that everything stays safely within the same schematic.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby Class help

Postby RJHollins » Sun Jan 03, 2021 11:43 pm

Hi TROG.

Wonderful info and presentation you post !

Let me ask an 'amateur' question regarding these Class and Modules. [especially about 'sharing'].

If you build 2 separate VST Plugins ... and they had the same User made Class or Module in them ...

What happens with the, say, 2 plugins loaded up in your DAW ??

asking in a basic kinda way ... but does this go beyond ?

Thanks
RJHollins
 
Posts: 1568
Joined: Thu Mar 08, 2012 7:58 pm

Re: Ruby Class help

Postby trogluddite » Mon Jan 04, 2021 12:45 am

RJHollins wrote:If you build 2 separate VST Plugins ... and they had the same User made Class or Module in them ...

Oddly enough, for plugins in a DAW, the problem is the other way around. Unique plugins, who's Ruby code could potentially clash inside the FS workspace, are "Ruby safe" as VST DLLs - it's multiple instances of the same VST plugin which might (rarely) cause Ruby problems in the DAW.

When loaded into your DAW, plugins with unique DLLs don't share anything at all**. Each DLL has its own Ruby interpreter (its own entire FlowStone "engine", in fact), and so its own unique names and definitions for things which can't be interfered with. In fact, I've been caught out by the opposite problem before now - spending an age trying to debug a problem that only occurred because I had two schematics loaded into the FS workspace, but which would never have clashed when used as plugins.

However multiple instances of the same plugin do share a Ruby interpreter. In this case, the DAW only loads the DLL once, but it has its VST "communication functions" called multiple times using data appropriate to each instance. And, albeit rarely, this can cause problems...

Class (and Module) definitions are usually fine, because every instance of the DLL is bound to have exactly the same definition. Each plugin instance also gets its own RubyEdit objects. What you must avoid is changing anything which is "globally visible" during run-time. For example, if you assign something new to a global variable, every instance of the same plugin will see the new value, whether you want them to or not. Likewise constants, class/module variables, or "monkey-patching" methods (re-defining them during runtime). However, most of these cases are obscure and recognised as dangerous - you're unlikely to need them, and most tutorial sites etc. would advise caution.

** NB) In the very earliest versions of FlowStone, even different plugin DLLs did share the same Ruby interpreter (in fact, they shared the same entire FlowStone engine). However, that was a long time ago, so I doubt that anyone is likely to run into problems with this any more.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Ruby Class help

Postby RJHollins » Mon Jan 04, 2021 2:38 am

Once again .... BIG Thanks TROG for explaining, and clearing up some old [now outdated] info.

This is why I try to read every thread on the FS Forum.

8-)
RJHollins
 
Posts: 1568
Joined: Thu Mar 08, 2012 7:58 pm

Re: Ruby Class help

Postby adamszabo » Mon Jan 04, 2021 4:40 pm

All this info needs to sink in before I actually understand it, I need to practice before I can continue. :lol:

What I mean with the outputs, if I write this in the class:

Code: Select all
def testout
    output 0, 1
end


and then I call it in a different rubyedit

Code: Select all
@shape.testout


Shouldn't it call that function and send "1" to the first output in this other rubyedit?
adamszabo
 
Posts: 657
Joined: Sun Jul 11, 2010 7:21 am

Next

Return to General

Who is online

Users browsing this forum: Google [Bot] and 1 guest

cron