├── Gemfile ├── sonic-pi-cli.gemspec ├── Gemfile.lock ├── LICENSE ├── bin └── sonic_pi ├── README.md └── lib └── sonic_pi.rb /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "osc-ruby" 4 | gem "eventmachine" 5 | -------------------------------------------------------------------------------- /sonic-pi-cli.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'sonic-pi-cli' 3 | s.version = '0.2.0' 4 | s.date = '2019-08-26' 5 | s.summary = "Sonic Pi CLI" 6 | s.description = "A simple command line interface for Sonic Pi" 7 | s.authors = ["Nick Johnstone"] 8 | s.email = 'ncwjohnstone@gmail.com' 9 | s.files = ["lib/sonic_pi.rb"] 10 | s.executables << "sonic_pi" 11 | s.homepage = 'http://rubygems.org/gems/sonic-pi-cli' 12 | s.license = 'MIT' 13 | 14 | s.add_dependency 'osc-ruby', '~> 1' 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | byebug (1.8.2) 5 | columnize (~> 0.3.6) 6 | debugger-linecache (~> 1.2.0) 7 | coderay (1.0.9) 8 | columnize (0.3.6) 9 | debugger-linecache (1.2.0) 10 | eventmachine (1.0.3) 11 | method_source (0.8.2) 12 | osc-ruby (1.1.1) 13 | pry (0.9.12.2) 14 | coderay (~> 1.0.5) 15 | method_source (~> 0.8) 16 | slop (~> 3.4) 17 | pry-byebug (1.1.2) 18 | byebug (~> 1.6) 19 | pry (~> 0.9.12) 20 | slop (3.6.0) 21 | 22 | PLATFORMS 23 | ruby 24 | 25 | DEPENDENCIES 26 | eventmachine 27 | osc-ruby 28 | pry 29 | pry-byebug 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nick Johnstone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/sonic_pi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "sonic_pi" 4 | 5 | def stdin 6 | unless STDIN.tty? 7 | $stdin.read 8 | end 9 | end 10 | 11 | def args 12 | ARGV.join(' ') 13 | end 14 | 15 | def args_and_stdin 16 | @args_and_stdin ||= [ 17 | args, 18 | stdin, 19 | ].join("\n").strip 20 | end 21 | 22 | def print_help 23 | puts <<-HELP 24 | sonic-pi-cli 25 | 26 | Usage: 27 | sonic_pi 28 | sonic_pi stop 29 | cat music.rb | sonic_pi 30 | sonic_pi --help 31 | 32 | Sonic Pi must be running for this utility to work. 33 | You can pipe code to stdin to execute it. 34 | 35 | Options: 36 | Run the given code. 37 | stop Stop all running music. 38 | --help Display this text. 39 | 40 | Made by Nick Johnstone (github.com/Widdershin/sonic-pi-cli). 41 | Thanks to Sam Aaron for creating Sonic Pi. 42 | HELP 43 | end 44 | 45 | def run 46 | app = SonicPi.new 47 | 48 | case args_and_stdin 49 | when '--help', '-h', '' 50 | print_help 51 | when 'stop' 52 | app.test_connection! 53 | 54 | app.stop 55 | else 56 | app.test_connection! 57 | 58 | app.run(args_and_stdin) 59 | end 60 | end 61 | 62 | run 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sonic-pi-cli 2 | ==== 3 | 4 | A simple command line interface for Sonic Pi, written in Ruby. 5 | 6 | **Requires Sonic Pi v2.7 or higher**. 7 | 8 | ver 0.1.3 allows compatibility with Sonic Pi v3.2: tested on Linux, Raspberry Pi and Windows 9 | 10 | Installation 11 | ------- 12 | 13 | gem install sonic-pi-cli 14 | 15 | Usage 16 | ----- 17 | 18 | Sonic Pi must be running, as this is just a client. 19 | 20 | sonic_pi play 50 21 | sonic_pi sample :loop_breakbeat, rate: 0.5 22 | sonic_pi stop 23 | 24 | or 25 | 26 | echo 'sample :loop_amen' | sonic_pi 27 | cat music.rb | sonic_pi 28 | 29 | or 30 | 31 | $ irb 32 | 33 | irb(main):001:0> require 'sonic_pi' 34 | => true 35 | irb(main):002:0> SonicPi.new.run('play [50, 55, 60]') 36 | => 36 37 | 38 | License 39 | ------ 40 | 41 | sonic-pi-cli is released under the MIT license. See LICENSE file for more details. 42 | 43 | Alternatives 44 | ------ 45 | [lpil/sonic-pi-tool](https://github.com/lpil/sonic-pi-tool) - written in Rust, also supports viewing logs. 46 | 47 | 48 | Thanks to Sam Aaron for creating Sonic Pi. Official command line support is planned for Sonic Pi 3.0. 49 | 50 | For those interested, the code weighs in at around 120 lines and is stupid simple. Take a look. 51 | -------------------------------------------------------------------------------- /lib/sonic_pi.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'rubygems' 3 | require 'osc-ruby' 4 | require 'securerandom' 5 | 6 | class SonicPi 7 | PORT_LOG_REGEX = Regexp.compile(/Listen port:\s+(?\d+)/) 8 | 9 | def initialize(port=nil) 10 | @port = port || find_port 11 | end 12 | 13 | RUN_COMMAND = "/run-code" 14 | STOP_COMMAND = "/stop-all-jobs" 15 | SERVER = 'localhost' 16 | GUI_ID = 'SONIC_PI_CLI' 17 | 18 | def run(command) 19 | send_command(RUN_COMMAND, command) 20 | end 21 | 22 | def stop 23 | send_command(STOP_COMMAND) 24 | end 25 | 26 | def test_connection! 27 | begin 28 | socket = UDPSocket.new 29 | socket.bind(nil, @port) 30 | abort("ERROR: Sonic Pi is not listening on #{@port} - is it running?") 31 | rescue 32 | # everything is good 33 | end 34 | end 35 | 36 | private 37 | 38 | def client 39 | @client ||= OSC::Client.new(SERVER, @port) 40 | end 41 | 42 | def send_command(call_type, command=nil) 43 | prepared_command = OSC::Message.new(call_type, GUI_ID, command) 44 | client.send(prepared_command) 45 | end 46 | 47 | def find_port 48 | port = 4557 49 | 50 | begin 51 | log_path = File.join(Dir.home, ".sonic-pi", "log", "server-output.log") 52 | 53 | File.open(log_path, 'r') do |f| 54 | port_log_entry = 55 | f.each_line 56 | .lazy 57 | .map { |line| line[PORT_LOG_REGEX, "port"] } 58 | .find { |match| !!match } 59 | 60 | port = port_log_entry.to_i if port_log_entry 61 | end 62 | rescue Errno::ENOENT 63 | # not to worry if the file doesn't exist 64 | end 65 | 66 | port 67 | end 68 | end 69 | --------------------------------------------------------------------------------