How to add a new command?

Digging around in

\Sonic Pi\app\server\ruby\lib\sonicpi

I see the .rb for scale.rb, chord.rb, and note.rb which seems very promising to use to make a guitar chord class.

How would I go about adding a class that can be called from sonic pi? I tired making a test class based on chord.rb but there didn’t seem to be a way I noticed to call it from sonic pi. Do I need to compile or am I missing something in the ruby files to be able to do this?

My main goal is to add a guitar chord method. Similar to how chord works.

I’m thinking (gchord :c, :major) for example which would spit out a ring.

Why would anyone want this?

(1) It’s cool.
(2) A piano chord and guitar chord are not the same even if you use a guitar synth.
(3) It’s really cool to use has a midi control for your daw. Imagine being able to do something like a chord_degree and set your scale and choose a progression on the fly! A massive game changer over painting notes I would say!

Anyway one thing at a time.

Does anyone know how to set up a rb class that I could use has a new command. This way I can poke at the classes that already exist which would be massively better then writing something into a buffer.

BTW: The closest thing I’ve found to what I’m trying to do is this:

# Guitar Strumming - by Emlyn
# This tries to work out the guitar (or ukulele etc.) fingering for arbitrary chords (and tuning).
# It seems to work reasonably well for basic chords, but is quite naive and probably makes many mistakes.
# Ideas, bug reports, fixes etc. gratefully received, just comment below, or tweet @emlyn77.
# Feel free to make use of this code as you like (with attribution if you feel like it, but you don't have to).
# Thanks to @Project_Hell_CK for fixing the tuning, and spotting that it gets chord(:f, :major) not quite right.

# Next note higher or equal to base note n, that is in the chord c
define :next_note do |n, c|
  # Make sure n is a number
  n = note(n)
  # Get distances to each note in chord, add smallest to base note
  n + (c.map {|x| (note(x) - n) % 12}).min
end

ukulele = [:g, :c, :e, :a]
guitar_standard = [:e2, :a2, :d3, :g3, :b3, :e4]

# Return ring representing the chord chrd, as played on a guitar with given tuning
define :guitar do |tonic, name, tuning=guitar_standard|
  chrd = (chord tonic, name)
  # For each string, get the next higher note that is in the chord
  c = tuning.map {|n| next_note(n, chrd)}
  # We want the lowest note to be the root of the chord
  root = note(chrd[0])
  first_root = c.take_while {|n| (n - root) % 12 != 0}.count
  # Drop up to half the lowest strings to make that the case if possible
  if first_root > 0 and first_root < tuning.count / 2
    c = (ring :r) * first_root + c.drop(first_root)
  end
  # Display chord fingering
  #puts c.zip(tuning).map {|n, s| if n == :r then 'x' else (n - note(s)) end}.join, c
  c
end

# Strum a chord with a certain delay between strings
define :strum do |c, d=0.1|
  in_thread do
    play_pattern_timed c.drop_while{|n| [nil,:r].include? n}, d
  end
end

use_debug false
use_bpm 120

live_loop :guit do
  #chords = ring((guitar :d, :m7), (guitar :g, '7'), (guitar :c, :M7), (guitar :f, :M7),
  #              (guitar :b, :m7), (guitar :e, '7'), (guitar :a, :m7))
  chords = ring((guitar :a, :m), (guitar :c, :M), (guitar :d, :M), (ring :r, :r, 53, 57, 60, 65),
                (guitar :a, :m), (guitar :c, :M), (guitar :e, :M), (guitar :e, '7'))
  with_fx :reverb do
    with_fx :lpf, cutoff: 115 do
      with_synth :pluck do
        tick
        "D.DU.UDU".split(//).each do |s|
          if s == 'D' # Down stroke
            strum chords.look, 0.05
          elsif s == 'U' # Up stroke
            with_fx :level, amp: 0.5 do
              strum chords.look.reverse, 0.03
            end
          end
          sleep 0.5
        end
      end
    end
  end
end

For my build with a little tweak below runs right out of the box.

define :strum do |c, d=0.1|
  in_thread do
    c.each do |n|
      if n != :r
        puts note_info(n)
        play n
        sleep d
      end
      ##| play_pattern_timed c.drop_while{|n| [nil,:r].include? n}, d
    end
  end
end

Thoughts?

1 Like

Hey Din,
You may find the following topic relevant…

1 Like

Yes I was aware of the class trick you can do in buffer.

But I guess I didn’t make it clear enough what I was asking.

My naive assumption was that you could just write a new ruby file in the lib folder and with the correct imports and sonic pi would be able to start up and recognize your commands.

I’m saying I don’t know enough about the way sonic pi knows what class means what command when running the GUI part. So my questions was if I wanted to add a new class with the api to the lib part of sonic pi as if it was like any other class like chord or chord_degree how would I do it?

I’m guessing you’ve already done all the work to add it to the Ruby server code. For an alternate chord-like function, that would mean creating the new class file to go along side the existing one, and inserting the appropriate import and API function definition into app/server/ruby/lib/sonicpi/lang/western_theory.rb.

In order to then make the function name auto-complete when you start typing the name of it into the editor, you also need to edit some files for the C++ GUI. As far as I can remember, this would involve adding a reference to the new type of object that needs to have autocomplete abilities into app/gui/qt/utils/scintilla_api.h and keyword entries into the corresponding app/gui/qt/utils/scintilla_api.cpp, the same way that was done for chords. (You’d need to recompile the app afterwards of course).

Hi din! Loving your work, late to the party, but hoping to catch up soon… the wow moments keep building! Trying to pull it all together now… do you also have gist and githubs and pibooks and bookmarks galore?

Also semi-related, is there anything specific to the sp community on best practices for things like

  • naming local variables
  • building file paths (to load stuff)
  • helper scripts to query logs and debug
  • scriptshowcase💥 - forum threads showing really clever things you can do with workspaces, init.rb, and sp|sc|ruby

If still interested, I’ll share some custom class stuff soon, along with other potential solutions.

Thanks to all :sunglasses: