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?

AJFaraday
  • 153
  • 5

4 Answers4

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

Sam Aaron
  • 251
  • 1
  • 3
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.

BananaNeil
  • 126
  • 3
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.

AJFaraday
  • 153
  • 5
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
user45685
  • 101