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

Ruby is more!

For general discussion related FlowStone

Ruby is more!

Postby tulamide » Wed Feb 15, 2017 5:03 pm

I once made a series of articles going into details about classes in Ruby, telling you that they are the heart of OOP. I think I was to quick with that. Most people will not really have an idea of object orientation in its entirety.

Of course, you can use Ruby as a one-time solution for a one-time task. That's totally fine:
Code: Select all
output(0, @ins[0] * 0.5)

This code will output 50% of the value that just came in. Nothing more and nothing less. And if you are only looking for such easy and quick solutions, you don't need to read further. These are valid tasks for scripting with Ruby.

But Ruby is so much more. Unfortunately you can't access its real power unless you understand the underlying idea of OOP. This time I will concentrate on this abstract layer and will not explain semantics or syntax. For those you can always go to ruby-doc.org. My examples in the past may have not been "audio" enough. So I will try to make you understand the idea of OOP by using a synthesizer as an image.

So, let us have a look at a synth's oscillators. There may be a sine oscillator, a square osc, a sawtooth and a triangle osc.
You might think of them as 4 objects. And in the analog world they indeed are. But the digital world is different. Let us have a closer look. What does such an oscillator provide? A phase setting, a frequency setting and in our case a sync setting.

If you consider this than there is only one difference to the oscillators: the produced waveform. In the digital world, therefore those oscillators (no matter how many of them you will have) are two objects: One object that has all the settings, and another object that builds on the first one and defines the waveform.

In Ruby it would look similar to the following code.
Code: Select all
class Oscillator
   def initialize
      @frequency = 440.0
      @phase = 0.0
      @sync = false
   end
   
   attr_accessor :frequency, :phase, :sync
end

class Sine_osc < Oscillator ## "<" here indicates "inherit from"
   def initialize
      super ##calls the method with the same name from the class we're inheriting from
      ##you only need super, if you DON'T want to override the same method of the superclass
      @waveform = "sine"
   end
   
   attr_accessor :waveform
end

s = Sine_osc.new
s.frequency ## returns 440.0
s.waveform  ## returns "sine"


Note that I only cared about the sine oscillator. This is to keep the code small. A real code would of course have an option to define a waveform and not just hardcode "@waveform = "sine". And the class would not be called "Sine_osc", but probably "Waveform_osc".

Ruby itself works in the very same way. A number like 42 is an instance of the class "Fixnum". But Fixnum is based on "Numeric", a superclass that holds all informations that all numbers share. So, while 42.0 is of class "Float", Float is inherited from "Numeric", just as Fixnum was. If you ever wonder about the class relations, you can check them with the methods .class and .superclass:
Code: Select all
42.0.class ## returns "Float"
42.0.class.superclass ## returns "Numeric"
42.0.class.superclass.superclass ## returns "Object"


There is one class that all the others are inherited from, and that class is "Object". It is the root of a tree, so to speak.

But back to our synth. Can I further inherit? Yes, of course. For example, you might have a class "Osc_section", inherited from "Waveform_osc" and adding tuning (octave, halfnotes, fine). Or adding envelopes. Or... You get the point.

Leaves one question: Why should I use such a tree structure, whith dozen of classes. Well, besides a well readable code, there's one mega-advantage. You only define those things once, and they are true for all instances you use of those classes. Now imagine you have created a synth with 100 oscillators, each featuring its own waveform. Now you encounter an error.

Without the OOP approach you have no choice than finding the error and correcting it in all 100 oscillators.
With the OOP approach you just have to find the error and correct it in the affected class - and all 100 oscillators are bug-free again! Is it cool, or is it cool?
tulamide
 
Posts: 1421
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: Ruby is more!

Postby martinvicanek » Thu Feb 16, 2017 10:02 pm

Thanks, Tulamide, for this new initiative. I have been following your tutorials with great interest, and yet my Ruby skills don't go much further than your first example. The OO paradigm seems to be orthogonal to my thinking. So please do continue, you have at least one faithful fan!
martinvicanek
 
Posts: 636
Joined: Sat Jun 22, 2013 8:28 pm

Re: Ruby is more!

Postby RJHollins » Fri Feb 17, 2017 4:25 am

Add me to the 'fan' list ...

Having zero formal programming education, I'm watching and trying to learn.

THX GUYS !
RJHollins
 
Posts: 1158
Joined: Thu Mar 08, 2012 7:58 pm

Re: Ruby is more!

Postby Spogg » Fri Feb 17, 2017 8:24 am

+1 for me too.

Can I ask what may well be a really stupid question (based on zero knowledge of Ruby)?

Since you mention oscillators can you make oscillators in Ruby?

Cheers

Spogg
User avatar
Spogg
 
Posts: 1207
Joined: Thu Nov 20, 2014 4:24 pm
Location: Birmingham, England

Re: Ruby is more!

Postby KG_is_back » Fri Feb 17, 2017 12:38 pm

Spogg wrote:+1 for me too.

Can I ask what may well be a really stupid question (based on zero knowledge of Ruby)?

Since you mention oscillators can you make oscillators in Ruby?

Cheers

Spogg


You can, but they are extremely inefficient, because ruby is interpreted language. One such example is in the manual, if I recall correctly. (a simple sin osc takes like 60-90%cpu on my machine)

It is possible to run raw machine code in flowstone using analyser prim and float-to-mem prim / ruby, but it requires you to literally write your own assembler (myco has done it some time ago) and compiler. Practically you would be running reverse-engineered gui-less flowstone within the actual flowstone. In other words, kind of pointless.
KG_is_back
 
Posts: 1108
Joined: Tue Oct 22, 2013 5:43 pm
Location: Slovakia

Re: Ruby is more!

Postby Spogg » Fri Feb 17, 2017 6:00 pm

Thanks KG.

So may I assume that Ruby is similarly not suited to DSP and stuff like index counting for wave playing?

Cheers

Spogg
User avatar
Spogg
 
Posts: 1207
Joined: Thu Nov 20, 2014 4:24 pm
Location: Birmingham, England

Re: Ruby is more!

Postby tulamide » Fri Feb 17, 2017 9:19 pm

Hey everyone, thanks for encouraging me. I was starting to think there is no interest.

Spogg,
see it this way: Ruby is superior to green (not necessarily speed-wise, but in organization and maintaining), on par with MIDI and the best solution for everything graphics.
And while it can be used as white (polystream) replacement it is totally impractical for that task. But, it can replace blue (monostream) with an acceptable tradeoff. I posted an example of Ruby doing a stereo peak meter, which involved the calculation of 176400 values per second. A dsp meter in the blue stream would be quicker, but then again Ruby is more comfortable in structuring code.

But basically, whenever you can do white or blue directly, don't use Ruby.
tulamide
 
Posts: 1421
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: Ruby is more!

Postby martinvicanek » Fri Feb 17, 2017 11:10 pm

tulamide wrote:I posted an example of Ruby doing a stereo peak meter

Tula, would you share the link please?
martinvicanek
 
Posts: 636
Joined: Sat Jun 22, 2013 8:28 pm

Re: Ruby is more!

Postby tulamide » Fri Feb 17, 2017 11:15 pm

martinvicanek wrote:
tulamide wrote:I posted an example of Ruby doing a stereo peak meter

Tula, would you share the link please?

Sure, here you are
http://www.dsprobotics.com/support/viewtopic.php?f=3&t=5669
tulamide
 
Posts: 1421
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: Ruby is more!

Postby tulamide » Mon Feb 20, 2017 10:17 am

Contrary to the last time, I don't have a plan or something to organize this thread. I just talk without much preparation. Just so that you know :D

From my last example you should have noticed two important things:
1) You can't just write some code if you want to go the OOP way. You have to lay out your program first. It doesn't matter if you write some notes, draw a flowchart or, as I do it mostly, intensively think it through. It just needs you to be prepared before starting to write code.
2) You can't write OOP code without accepting and writing by the rules. OOP opens up a large canvas for you to paint on, but if you use a pencil instead of a brush you will damage the canvas. Freedom through restriction, so to speak.

Both points are especially important if you might share your code once with other programmers. They will have a look at your code with those rules in mind. If you don't follow the rules, chances are they will misinterpret your code or not understand it at all. Imagine it as speaking in your language with a foreigner but using words that are only used in your area. Chances are the foreigner never heard those words before.

The thing with objects can be irritatring, but once mastered it gives you a powerful weapon at hand to fight issues that you thought impossible to solve with code. For those who are in love with maths: It reminds a lot of Mengenlehre (I think it is called "set theory" in English). Look at this image:
A_intersect_B.png
A_intersect_B.png (9.04 KiB) Viewed 348 times


There are two sets, A and B, and the intersection is the part that both sets share. In OOP, this would be 3 objects, with the intersection being the mother object and A and B both inheriting from the mother object, adding the parts of their sets that are not in the intersection.

Fortunately Ruby is very strict when it comes to inheriting. An object can always only inherit from one other object. Let's have a closer look at that.

Code: Select all
class Aquatic
   def tell
      "I live in the sea"
   end
end

class Terrestrial
   def tell
      "I live at land"
   end
end

horse = Terrestrial.new
horse.tell ## returns "I live at land"

shark = Aquatic.new
shark.tell ## returns "I live at sea"

Straight forward and easy to understand. However, what about a frog? It's an amphibious animal, their young form can only live at the sea (gills), while their adult form can only live at land (lung). Theoretically we would need to inherit from both classes, building the subclass "Amphibious", but that is not provided by Ruby.

That doesn't mean you can't express a frog with Ruby, though. Instead of multi-inheritance (which can become quite confusing), Ruby provides mixins. A mixin is a module that you make part of the class definition. A module is very similar to a class, and of course it is an object, too. But while you create a class and then use instances of it in your code, a module does not have instances. It only exists exactly once throughout Ruby. That makes modules generic. You can't place specific code in a module, since it isn't instantiated.

Code: Select all
module Teller
   def tell
      myclass = self.class
      if myclass == Terrestrial
         "I live at land"
      elsif myclass == Aquatic
         "I live in the sea"
      elsif myclass == Amphibious
         "I live at sea when young and at land when adult"
      else
         "Unsure"
      end
   end
end

class Animal
   include Teller
end

class Aquatic < Animal
end

class Terrestrial < Animal
end

class Amphibious < Animal
end

horse = Terrestrial.new
horse.tell ## returns "I live at land"

shark = Aquatic.new
shark.tell ## returns "I live at sea"

frog = Amphibious.new
frog.tell ## returns "I live at sea when young and at land when adult


You may notice that I added a class Animal. That class includes a module. It is using a mixin. Since I used the keyword "include", the module's content will only be available in instances of the animal class. Not in the animal class itself. Confused? Well, I was too in the beginning, but it is easier than you might think.
If you try to call
Code: Select all
Animal.tell
from above code, it will return a noMethodError. This indicates that the method is not part of the class definition. Only when we create an instance of the class Animal, Ruby will add the module's content to that instance.

There's also a keyword to make a module part of the class itself. It's "extend". When you use
Code: Select all
extend Teller
, you will be able to directly call "Animal.tell", but you won't be able anymore to call it on the instances. You see, Ruby is very strict to protect you from doing wrong things.

You also see that it is very important to understand the difference between a class and a class instance, resp. a class method and a class instance method, or a class variable and a class instance variable. Distinguish classes from their instances. An instance exists as soon as you use :new on a class.
Code: Select all
trump = Animal.new
makes trump an instance of Animal.
Code: Select all
trump.tell
calls a class instance method.
Code: Select all
Animal.tell
calls a class method.

To summarize: If you want to have instances of a class have acces to a mixin, use keyword "include". For accessing the mixin directly from the class use "extend".

'til next time ;)
tulamide
 
Posts: 1421
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany


Return to General

Who is online

Users browsing this forum: Google [Bot], Yahoo [Bot] and 12 guests