Is it possible to make custom classes?

Hello, everyone!

I hope you are meeting with success with your music making and coding!

I have a question about custom classes. I know I could define a custom function but I’d like to expand that a little further to make a full class.

I was thinking I could define a particular synth, set the defaults, and instantiate it in a couple different live loops? Is that sort of thing possible?

Hi @gregeporter,
Using custom classes is not possible using the officially supported Sonic Pi syntax. Such a thing is potentially doable using vanilla Ruby syntax, which Sonic Pi’s API is built on, but there is no guarantee that if it works, it would continue to work in future versions.

I’m curious about your potential use case anyway. Maybe you could write up some pseudo code to describe what you were imagining?

1 Like

@ethancrawford, thanks for the quick response! That’s a good idea. I was thinking that because Sonic Pi is build off of Ruby, it might work but I wasn’t sure…

In terms of use cases, maybe it’s that I’m approaching Sonic Pi with an object oriented state of mind that don’t quite jive as easily with structuring of the loops and functions. It could be a way of streamlining the configurations of a particular sound. That way, the live_loop/s could be simpler

Class BassSynth {
    Number note

    Function playNote {
        synth :blade
        play this.note
    }

    Function setNote {}

    Function playMinorChord {
         synth :blade
        play Chord(this.note, :minor)
    }
}

live_loop :synth_one do
    bassSynth = BassSynth.new()
    bassSynth.setNote(60)

    bassSynth.playNote()
    sleep 1
    bassSynth.playMinorChord()
end

Sure. Sam could decide so, but I suspect that it’s quite unlikely that the basic paradigm of the syntax will change to something like OO. What do you see about the above example that makes it simpler? if we’re talking about creating a synth with a specific set of opt values, and reusing it in several loops, if you feel it’s worthwhile, maybe you could elaborate on an idiomatic example in Sonic Pi’s syntax and describe what about it that doesn’t fit?

1 Like

Hi @gregeporter,

I tweaked some code found on stack exchange that seems to do what you want:

MAIN = self

class Parent < SimpleDelegator
  def self.new(*args)
    super(MAIN).tap do |obj|
      obj.on_init(*args) if obj.respond_to?(:on_init)
    end
  end
end

class BassSynth < Parent
  def play_note
    use_synth :blade
    play @note
  end
  
  def set_note n
    @note = n
  end
  
  def play_minor_chord
    use_synth :blade
    play chord(@note, :minor)
  end
end

b = BassSynth.new
b.set_note 60
b.play_note
sleep 1
b.play_minor_chord
1 Like

@nicoder, I think that’s exactly what I was thinking. Thanks!

@ethancrawford, I think it’s more of a “me” problem because I’m not totally familiar with the Sonic Pi paradigm. I have been trying do some live coding, but I can’t change the notes/synths/drum patterns as quickly as I would like. I have to change this line, find the other line and change that, recompile, etc. Maybe this question could be a reflection of my novice skills :stuck_out_tongue:

Thanks for the help, both of you!

2 Likes

No problem - I’m fairly comfortable with Sonic Pi’s paradigm - but like many others I’m still continually looking for ways to make live coding flow more easily :smile:
I’ve found discussions like the one below enlightening :slight_smile:

1 Like
n = 60
use_synth :blade
play n
sleep 1
play chord(n, :minor)

for me the sonic pi syntax is clearer and more accessible to change parameters than to go into the class method.

KISS Keep it Simple Stupid

KISS might be to stupid or lead to obfuscation for some demands

Especially to encapsulate some functionality for repeated extensions of
sonic pi ('s direct functionality).

A paradigm like classes are a well discussed, accepted
and understood mechanism.

So for the more professional aspects of sonic pi such a mechanism would be great.

Classes are the first adhoc mechanism towards this.

A way to plug-in something to extend sonic pi functionality would be a great path too.

I’ll preface this by saying that, if using Ruby classes makes the most sense to you, go for it. Even if it stops working in the future, you can make a note in your compostions of the SonicPi version that you used and run them on that.

That being said there are approaches that don’t step outside officially supported syntax.

An approach I haven’t used, could be something like this. I thinks this most closely apes the object/class way of doing things:

clear
define :createBassSynth do |n|
  myNote = n
  
  define :playBass do
    use_synth :blade
    play myNote
  end
  
  define :setBassNote do |n|
    myNote = n
  end
  
  define :playBassChord do
    use_synth :blade
    play chord(myNote, :minor)
  end
  
  
  puts "Initialised bass synth with a note of #{myNote}"
end

createBassSynth(60)
playBass
setBassNote 48
sleep 1
playBass
sleep 1
playBassChord

Here we have several functions that share a single scope through the createBassSynth function. Sure it’s note a single object as you were saying, but it is functionally and conceptually similar. If you want you can adjust the functions so they are called BassDotPlay or use your own conventions so that it is clear these functions belong to the same scope.

Another solution could be to define bass information in a hash, and use functions that know how to interface with that hash. For example:

clear
bassInfo = {
  mainNote: 48,
  highNote: 52,
  synth: :blade
}

define :useBassSynth do |info|
  use_synth info[:synth]
end

define :playBass do |info|
  useBassSynth(info)
  play info[:mainNote]
end

define :playBassHigh do |info|
  useBassSynth(info)
  play info[:highNote]
end

playBass bassInfo
sleep 1
playBassHigh bassInfo

Now there are a couple of stylistic decisions you could make here. For example passing arround bassInfo seems clunky to me. So, rather than accepting a generic info argument, the functions could just reference bassInfo directly. I suppose it depends on whether you want to use these functions with any other info hashes.

Another thing is whether you want to use setters or not. If you do, you could abstract away the fact that a hash is even used. For example

clear
bassInfo = {
  bassNote: 48,
  synth: :blade
}

define :useBassSynth do
  use_synth bassInfo[:synth]
end

define :playBass do
  useBassSynth
  play bassInfo[:bassNote]
end

define :setBassNote do |n|
  bassInfo[:bassNote] = n
end


playBass
sleep 1
setBassNote 42
playBass

It ends up looking a lot like the first example, and again you can use some kind of naming conventions so it is clear to you that these functions are all related.

This is more akin to the approach I’ve been using, hashes that define the relevant information and functions that know how to work with these hashes.

Thank you very much for your efforts. This demonstration (may I call them “knowing hash-functions”) and your explanation is nearly the best I could find in the archives up to now concerning extendability.

concerning extendability

You should check out the source code for ziffers which is a great Sonic Pi extension that allows the use of number notation.

I feel like what I’m doing is more akin to writing Ruby/Sonic Pi in a javascript way, haha.

On that note, I’ve just seen that although lambdas are not in the documentation, they are in the tutorial. If they are part of the official API then that gives you another slight variation you could use.

clear
define :createBassSynth do |n = 60|
  myNote = n
  
  puts "Initialised bass synth with a note of #{myNote}"
  return {
    play: lambda {
      use_synth :blade
      play myNote
    },
    set_note: lambda {|n|
      myNote = n
    },
    play_chord: lambda {
      use_synth :blade
      play chord(myNote, :minor)
    }
  }
end

bass = createBassSynth 
bass[:play].()
sleep 1
bass[:set_note].(54)
bass[:play].()
sleep 1
bass[:play_chord].()
1 Like

They are in the tutorial, but for very specific uses. It’s one of those situations where the boundary between Sonic Pi and plain Ruby is perhaps not as clearly defined. For situations other than those explicitly described in the tutorial (such as filtering sample pack paths, as seen in chapter 3.7) I’d probably treat it as a ‘use at your own risk’ situation.

You should try Chuck
or SuperCollider.
They are wonderful doing this kind of things.
I used to do live coding with Chuck and works perfectly well but, since I found Sonic_Pi my performance improved 100%
I mean, as a performer I prefer a clear cut blade like SP which respond immediately to what I am thinking, my fingers are to slow to OOP and functions. With Chuck I had some reliable performance but, with SP it is like rocket to the moon.
Sorry, if I take a detour way from the main issue.

The cool magic here seems to be:

 MAIN = self
 class Parent < SimpleDelegator
     def self.new(*args)
     super(MAIN).tap do |obj|
       obj.on_init(*args) if obj.respond_to?(:on_init)
     end
   end
 end
 class .. < Parent

(Ruby-)Classes are also about reusable structures.
If they are well structured one only needs to adapt some parents or
inherited interfaces and can keep the possibly optimized or
well readble structure of the class.

Some ingenious people did already interesting development for me
(GitHub - thomasjachmann/launchpad: A gem for accessing novation's launchpad programmatically and easily., etc.)
in ruby that makes me to hope to be able to adapt quickly.

And start my own experiments from.

From SP it was claimed that these grid extensions are soon to be seen
in Sonic Pi but due to the financing development was very limited.

1 Like

But where exactly is the SP Syntax definition? And where is given the manifest for the motivation for this? Due to the limited development ressources I personally would really prefer see SP to be and remain as compatible as possible to Ruby, because the ruby information, knowledge, programmer and development base is much larger.

Why define another of the 200.000 programming languages?

The ‘syntax definition’ is what you see in the Help documentation, in the ‘Lang’ area.
As for the motivation: I’ll let @samaaron explain this more, but from my understanding, Sonic Pi was originally built on top of Ruby as Sam was familiar with it at the time, and kept as a separate DSL in its own right as this allowed a simpler set of syntax to introduce into the education context that it was originally created for.

2 Likes

(Oh yes, and note also, that in line with its original purpose, one of the main goals is and will remain that Sonic Pi is simple enough for a 10 year old to understand).

The educational and good accessibility aspect of SP makes me believe that it
could be used in primary and secondary shool with limited lectures
for computer science education.

But you write it quoted and that it is currently because there is no w3c or IEEE behind sonic pi.
A syntax definition from my point of view is an additional document to the user documentation.
The user documentation is good and it is well integrated and this makes SP valuable!
But a syntax definition for me is something like the EBNF

(Extended Backus–Naur form - Wikipedia).

And one clearly sees in the documentation examples here and there that
extend the ‘syntax definiition’ of places of the documentation where it
possibly should be mentioned. I have to grasp what is possible by browsing
through nearly all documentation and see through a lot of code examples.

This is certainly typical for a (new) programming language.

But it sounds to me that SP was created with an educational and a professional aspect in mind.

The educational aspect could possibly be improved with a graphical programming
surface like (Microsoft MakeCode)
or google-blockly.

The professional aspect in my mind should possibly keep it’s mind also on
expandability with either a plug-in-interface or ruby-classes or something
else as expandability may certainly be specialized under certain aspects.

'… one of the main goals is and will remain that Sonic Pi is simple enough for a 10 year old to understand …`

This goal does not necessarily nor naturally exclude the possibility to define classes or have other ways to extend SP.