Skip to content
Permalink
Browse files

Experimental - Introduce rudiments of ixi lang

This is the first commit from on ongoing collaboration with Thor Magnusson to introduce a core subset of ixi lang functionality into Sonic Pi.

More information on ixi lang can be found here:
https://github.com/thormagnusson/ixilang

This commit introduces the basics of the percussive `|…|` notation. For example:

```
ixi “foo -> |a aa|”
```

Currently, the symbols a-z are not bound to anything by default. They can be bound with the fn `ixi_define`:

```
ixi_define :a do
  sample :bd_haus
end
```

Multiline ixi snippets are also supported.

The following is an example:

```

ixi_define :b do
  sample :loop_amen, onset: 3, amp: 1
  sample :ambi_drone
end

ixi_define :a do
  sample :loop_industrial, onset: 3, amp: 2
end

ixi_define :A do
  sample :drum_cymbal_closed, amp: 2
end

ixi_define :d do
  sample :bd_haus, amp: 4, lpf: 80
end

ixi "
foo2 -> |aaa a aa|
bar  -> |A      b|
foo5 -> |d   |   "

```
  • Loading branch information...
samaaron committed May 28, 2019
1 parent b09dfaa commit c44b68bb602bfee6a87ff3c305cbd12a1864128c
Showing with 152 additions and 0 deletions.
  1. +2 −0 app/server/ruby/bin/sonic-pi-server.rb
  2. +150 −0 app/server/ruby/lib/sonicpi/lang/ixi.rb
@@ -25,6 +25,7 @@
require_relative "../lib/sonicpi/lang/core"
require_relative "../lib/sonicpi/lang/minecraftpi"
require_relative "../lib/sonicpi/lang/midi"
require_relative "../lib/sonicpi/lang/ixi"
require_relative "../lib/sonicpi/lang/sound"
#require_relative "../lib/sonicpi/lang/pattern"
require_relative "../lib/sonicpi/runtime"
@@ -268,6 +269,7 @@
klass.send(:include, SonicPi::Lang::Sound)
klass.send(:include, SonicPi::Lang::Minecraft)
klass.send(:include, SonicPi::Lang::Midi)
klass.send(:include, SonicPi::Lang::Ixi)
klass.send(:include, SonicPi::Lang::Support::DocSystem)
klass.send(:extend, Memoist)

@@ -0,0 +1,150 @@
#--
# This file is part of Sonic Pi: http://sonic-pi.net
# Full project source: https://github.com/samaaron/sonic-pi
# License: https://github.com/samaaron/sonic-pi/blob/master/LICENSE.md
#
# Copyright 2019 by Sam Aaron (http://sam.aaron.name).
# All rights reserved.
#
# Permission is granted for use, copying, modification, and
# distribution of modified versions of this work as long as this
# notice is included.
#++

# ixi lang was originally written by Thor Magnusson:
# https://github.com/thormagnusson/ixilang

# This is a port of some of the features adapted for
# embedding within Sonic Pi code.

# This port was created in collaboration with Thor.

require 'treetop'
require_relative "support/docsystem"


module SonicPiIxiLang
class Block < Treetop::Runtime::SyntaxNode ; end
class Expression < Treetop::Runtime::SyntaxNode ; end
class Name < Treetop::Runtime::SyntaxNode ; end
class PatternPerc < Treetop::Runtime::SyntaxNode ; end
class LiveLoop < Treetop::Runtime::SyntaxNode ; end
end

Treetop.load_from_string("
grammar SonicPiIxiLang
rule block
expression+ <Block>
end
rule expression
space? ll space? <Expression>
end
rule ll
name '->' pat_perc <LiveLoop>
end
rule pat_perc
[ \t]* '|' name+ '|' <PatternPerc>
end
rule name
[ \t]* [a-zA-Z] [a-zA-Z0-9]* [ \t]* <Name>
end
rule space
[\S\n]+
end
end
")


module SonicPi
module Lang
module Ixi

def self.included(base)
base.instance_exec {alias_method :sonic_pi_mods_ixi_initialize_old, :initialize}

base.instance_exec do
define_method(:initialize) do |*splat, &block|
sonic_pi_mods_ixi_initialize_old(*splat, &block)
@ixi_methods = {}
end
end

end

def ixi_define(name, &block)
@ixi_methods[name.to_sym] = block
end

def __parse_ixi(nodes, opts)
case nodes
when SonicPiIxiLang::Block
nodes.elements.each {|n| __parse_ixi n, opts}
when SonicPiIxiLang::Expression
__parse_ixi(nodes.elements[1], opts)
when SonicPiIxiLang::LiveLoop
name = nodes.elements[0].text_value.strip
live_loop "_ixi_#{name}" do
# execute one lot of the pattern
case nodes.elements[2]
when SonicPiIxiLang::PatternPerc
pat = nodes.elements[2].elements[2].text_value
pat.size.times do |idx|
sym = pat[idx].strip.to_sym
behaviour = @ixi_methods[sym]
if behaviour
case behaviour.arity
when 0
behaviour.call
else
behaviour.call(opts.clone)
end
end

sleep 0.125
end

else
raise "Ixi Error. Unknown Ixi pattern form: #{nodes.elements[2].text_value}"
end
end
else
raise "Ixi Error. Unknown top level element: #{nodes.class}, #{nodes.inspect}"
end


end

def ixi(s, opts={})

parser = SonicPiIxiLangParser.new

# assert parser.parse('hello->|a|')
# assert parser.parse('yo -> |a b c a|')
# assert parser.parse('yo ->|a b c a|')
# assert parser.parse('yo->|a b c a|')
# assert parser.parse('yo2 -> |a b c a|')
# assert parser.parse('hello->|a|
# hello2->|A|')

# takes string s representing ixi lang commands
# for example:
# ixi "agent->|c c c c|"

# ixi lang has:

# name->|p a t t e r n|
# which plays samples from a given directory.

# Attempt to parse the ixi lang string:
nodes = parser.parse(s)
raise "Ixi Error. Unable to parse syntax: #{s}" unless nodes
__parse_ixi(nodes, opts)
end
end
end
end

0 comments on commit c44b68b

Please sign in to comment.
You can’t perform that action at this time.