Just a bit of background information, I'm a Ruby developer, and familiar with Object Orientated techniques. I've also got a history of playing with code and music, a long-running hobby. I'm getting used to Sonic Pi, but I'm having some difficulty splitting my code out into classes.

Here's my code:

class Drummer

  def kick
    sample :bd_haus

  def snare
    with_fx :reverb do
      sample :drum_snare_soft


drummer = Drummer.new

use_bpm 130
loop do
  sleep 1
  sleep 1

However, it doesn't appear that the Sonic Pi language is available to this module. I get this error:

Runtime Error: [Workspace 9, line 23] undefined method `sample' for #

I've had a look at the Sonic Pi repo and tried this line:

include SonicPi::Lang::Sound

It looks like something's still missing, there's a different error:

Runtime Error: [Workspace 9, line 21] undefined method `push' for nil:NilClass

Did you mean? @msg_queue

Is there any way I can get the methods within my self-defined classes to act like code in the workspace?


Okay, I ACTUALLY have found a way to do this in Sonic Pi.

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

class BeepBeep < Parent

  def on_init
    puts "You can use this as  your initialize function... don't override initialize. O_o"

  def play_intro
    play 67



It seems like in a current session of Sonic Pi, everything is kept in memory.. so you will definitely run into trouble with redefining classes with different parents... or accidentally overriding methods that need to be left undefined for the delegation to work properly. In these cases, the only solution (that I've found at this time) is to restart Sonic Pi entirely.

  • Amazing!!!!!!!! – AJFaraday Jul 23 '16 at 21:01
  • I'll try to keep this post updated as I build out my framework. – BananaNeil Jul 24 '16 at 0:37

Sonic Pi shouldn't be confused with Ruby :-)

Although it's built on top of Ruby, it isn't Ruby. You therefore can't expect it to behave 100% like Ruby. In fact, unless something in the documentation or tutorial, you shouldn't rely on it being there or changing in the future.

One of the main deviations from Ruby is to eschew object orientation in favour of a more functional approach. Classes have therefore never been (and are likely never to be) directly supported in the language.

If you want to take a look at the direction Sonic Pi is headed towards take a look at Clojure :-)

  • Are you saying that it can not be done? Or just that it shouldn't? – AJFaraday Feb 12 '16 at 22:18
  • I'm saying that it's not something I support. If you can figure out a way of doing it go to town (hint, it should be a matter of including the right internal modules - which you shouldn't rely on not changing). – Sam Aaron Feb 12 '16 at 22:20
  • In the same spirit, you should steer clear of using def for functions - instead prefer define. Similarly instance variables such as @foo shouldn't be used. – Sam Aaron Feb 12 '16 at 22:21
  • You've seen the blog posts, then? It may be a difficult habit to break, writing Ruby in Sonic Pi. There was some scope issues with a local variable defined outside a method. Although I've just seen that they persist within a method defined using define. There does appear to be a difference there. – AJFaraday Feb 12 '16 at 22:32
  • Thing is, I love Clojure. But does Ruby have the convenience like the threading macros or easy partial application etc? Without it, the convenient way to do something similar is Obj.msg1().msg2().msg3() etc. Without either OO or threading things get very verbose – interstar May 16 '19 at 16:37

Okay, so I've found a way to do this, by borrowing the parent context and making calls to it.

class Drummer

  attr_accessor :kick_sample, @snare_sample, @amp

  def initialize(parent)
    @parent = parent
    @amp = 0.5
    @kick_sample = :bd_haus
    @snare_sample = :drum_snare_soft

  def kick
    @parent.sample @kick_sample, amp: @amp

  def snare
    @parent.with_fx :reverb do
      @parent.sample @snare_sample, amp: @amp


drummer = Drummer.new(self)

This approach does work, and I've added some attr_accessors to show one of the reasons I'd like to do this.

It does have a few drawbacks, tho. It's pretty clumsy code, explicitly delegating methods to an object which needs to be mandatorily passed in, and will always be the same.

The reason for having to work around like this is explained in Sam Aaron's response. Sonic Pi is designed to work around a different programming philosophy, known as Functional Programming. So while this approach does work, I have to say that it is not how the software is intended to work, and it may not work in the future.


yes, it is hacky to do so. but it's possible ...

class << self
  def fop
    play 49
    sleep 1
    sample :ambi_dark_woosh

  • 1
    I'm not really sure what's achieved by this answer, it just seems to be adding methods to the parent self object. As far as I can tell, it's just the same as if you didn't have the class declaration and it's parent end. – AJFaraday May 6 '16 at 13:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.