5

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
  end

  def snare
    with_fx :reverb do
      sample :drum_snare_soft
    end
  end

end

drummer = Drummer.new

use_bpm 130
loop do
  drummer.kick
  sleep 1
  drummer.snare
  sleep 1
end

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?

1

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)
    end
  end
end

class BeepBeep < Parent

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

  def play_intro
    play 67
  end

end

BeepBeep.new.play_intro

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
2

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 at 16:37
0

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
  end

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

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

end

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.

0

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

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

fop
  • 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.