arbol 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/arbol +25 -0
- data/lib/arbol.rb +45 -0
- data/lib/base.rb +397 -0
- data/lib/builder.rb +102 -0
- data/lib/documentation.rb +19 -0
- data/lib/dsl.rb +168 -0
- data/lib/functions/add.rb +70 -0
- data/lib/functions/add_constrain.rb +70 -0
- data/lib/functions/add_modulo.rb +70 -0
- data/lib/functions/analog_pin.rb +316 -0
- data/lib/functions/choose.rb +74 -0
- data/lib/functions/const.rb +63 -0
- data/lib/functions/create_lookup.rb +63 -0
- data/lib/functions/create_ref.rb +48 -0
- data/lib/functions/crossfade.rb +73 -0
- data/lib/functions/divide.rb +70 -0
- data/lib/functions/feedback.rb +65 -0
- data/lib/functions/feedback_offset.rb +69 -0
- data/lib/functions/gamma.rb +61 -0
- data/lib/functions/greater_than.rb +70 -0
- data/lib/functions/greater_than_equals.rb +70 -0
- data/lib/functions/lamp_phase.rb +39 -0
- data/lib/functions/less_than.rb +70 -0
- data/lib/functions/less_than_equals.rb +70 -0
- data/lib/functions/lfo_square.rb +68 -0
- data/lib/functions/lfo_triangle.rb +72 -0
- data/lib/functions/lookup.rb +86 -0
- data/lib/functions/max.rb +70 -0
- data/lib/functions/min.rb +70 -0
- data/lib/functions/minus.rb +70 -0
- data/lib/functions/modulo.rb +70 -0
- data/lib/functions/noise.rb +49 -0
- data/lib/functions/noise_pixel.rb +49 -0
- data/lib/functions/phasor.rb +96 -0
- data/lib/functions/ref.rb +34 -0
- data/lib/functions/scale.rb +71 -0
- data/lib/functions/table.rb +16 -0
- data/lib/functions/times.rb +70 -0
- data/lib/functions/triangle.rb +69 -0
- data/lib/templates/arduino_library.ino.erb +75 -0
- metadata +84 -0
data/lib/builder.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pp'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
require_relative 'base.rb'
|
6
|
+
|
7
|
+
# module used to handle the global class map
|
8
|
+
module Arbol
|
9
|
+
@@class_map = {}
|
10
|
+
|
11
|
+
def self.class_map
|
12
|
+
@@class_map
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.add_mapped_class(type, mapped_class, code)
|
16
|
+
@@class_map[type] = mapped_class
|
17
|
+
@@libs << code if code
|
18
|
+
end
|
19
|
+
|
20
|
+
@@libs = []
|
21
|
+
|
22
|
+
def self.libs
|
23
|
+
@@libs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# new instance of the class specified by params[:type]
|
28
|
+
def builder(params)
|
29
|
+
# puts params
|
30
|
+
Arbol.class_map[params[:type]].new(params)
|
31
|
+
end
|
32
|
+
|
33
|
+
def custom_arduino_script_body(structure)
|
34
|
+
ref_builders = []
|
35
|
+
structure[:refs].each do |ref|
|
36
|
+
this_ref = builder(
|
37
|
+
ref
|
38
|
+
)
|
39
|
+
this_ref.resolve_frame_optimized
|
40
|
+
ref_builders << this_ref
|
41
|
+
end
|
42
|
+
|
43
|
+
# run the builder
|
44
|
+
t = builder(
|
45
|
+
structure[:calc]
|
46
|
+
)
|
47
|
+
|
48
|
+
t.resolve_frame_optimized
|
49
|
+
|
50
|
+
# create a tsortable hash and populate it with the nodes
|
51
|
+
ts = TsortableHash.new
|
52
|
+
|
53
|
+
# first append the ref nodes
|
54
|
+
ref_builders.each { |r| r.append_tsortable(ts) }
|
55
|
+
|
56
|
+
# then append the primary calc nodes
|
57
|
+
t.append_tsortable(ts)
|
58
|
+
|
59
|
+
# creates a hash containing all the code keyed by node name
|
60
|
+
pixel_scope_code = {}
|
61
|
+
|
62
|
+
# creates an hash of code for top_level_scope declarations
|
63
|
+
top_level_scope_code = {}
|
64
|
+
|
65
|
+
# contains cycle level code
|
66
|
+
cycle_scope_code = {}
|
67
|
+
|
68
|
+
# first append the ref nodes
|
69
|
+
ref_builders.each { |r| r.add_arduino_code(pixel_scope_code) }
|
70
|
+
ref_builders.each { |r| r.add_cycle_level_scope(cycle_scope_code) }
|
71
|
+
ref_builders.each { |r| r.add_top_level_scope(top_level_scope_code) }
|
72
|
+
|
73
|
+
# then the primary calc nodes
|
74
|
+
t.add_arduino_code(pixel_scope_code)
|
75
|
+
t.add_cycle_level_scope(cycle_scope_code)
|
76
|
+
t.add_top_level_scope(top_level_scope_code)
|
77
|
+
|
78
|
+
pixel_scope = []
|
79
|
+
top_level_scope = []
|
80
|
+
cycle_scope = []
|
81
|
+
# run tsort... then append the lines of code in the order they should be executed
|
82
|
+
t = ts.tsort
|
83
|
+
t.each do |func|
|
84
|
+
pixel_scope_code[func].each do |stmt|
|
85
|
+
pixel_scope << stmt
|
86
|
+
end
|
87
|
+
|
88
|
+
cycle_scope_code[func].each do |stmt|
|
89
|
+
cycle_scope << stmt
|
90
|
+
end
|
91
|
+
|
92
|
+
top_level_scope_code[func].each do |stmt|
|
93
|
+
top_level_scope << stmt
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# last output needs to be passed to the strip
|
98
|
+
pixel_scope << ''
|
99
|
+
pixel_scope << "// output"
|
100
|
+
pixel_scope << "strip.setPixelColor(i, (byte)long_mult(255, #{t.last}[0]), (byte)long_mult(255, #{t.last}[1]), (byte)long_mult(255, #{t.last}[2]));"
|
101
|
+
return top_level_scope, cycle_scope, pixel_scope
|
102
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Arbol
|
2
|
+
class Documentation
|
3
|
+
def output_all_documentation
|
4
|
+
(self.public_methods - Object.public_methods).sort.each do |m|
|
5
|
+
unless [:output_all_documentation, :output_single_doc].include? m.to_sym
|
6
|
+
output_single_doc(m)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def output_single_doc(func)
|
12
|
+
puts(
|
13
|
+
self.send(
|
14
|
+
func.to_sym
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/dsl.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# scales float values to the integer scale representation.
|
2
|
+
# @param [Float|Integer] val
|
3
|
+
# @return [Integer]
|
4
|
+
def scale_correctly(val)
|
5
|
+
case
|
6
|
+
when val.class == Integer then return val
|
7
|
+
when val.class == Float then return (val * 8192).to_i
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# resolves an input object to a structure appropriate for the tree.
|
12
|
+
# @param [Object] val
|
13
|
+
# @return [Object]
|
14
|
+
def resolve(val)
|
15
|
+
case
|
16
|
+
when val.class == Integer then return my_const(scale_correctly(val))
|
17
|
+
when val.class == Float then return my_const(scale_correctly(val))
|
18
|
+
when val.class == Array then return my_const(
|
19
|
+
val.map { |i| scale_correctly(i) }
|
20
|
+
)
|
21
|
+
when val.class == ArbolHash then return val
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def resolve_lookup_table(val)
|
26
|
+
val.map { |v| resolve(val) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def resolve_integer(val)
|
30
|
+
if val.class == Integer
|
31
|
+
val
|
32
|
+
else
|
33
|
+
raise "#{val} is not an integer"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve_float(val)
|
38
|
+
if val.class == Float
|
39
|
+
val
|
40
|
+
else
|
41
|
+
raise "#{val} is not an float"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def resolve_positive_scalar(val)
|
46
|
+
if [Integer, Float].include?(val.class)
|
47
|
+
if val >= 0
|
48
|
+
scale_correctly(val)
|
49
|
+
else
|
50
|
+
raise "#{} val should be positive"
|
51
|
+
end
|
52
|
+
else
|
53
|
+
raise "#{val} must be a positive scalar number"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_pin_reference(val)
|
58
|
+
if val.match(/(^A\d+$|^\d+$)/)
|
59
|
+
val
|
60
|
+
else
|
61
|
+
raise "#{val} is not a valid pin reference"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# coerces a scalar value to an array of 3 elements.
|
66
|
+
# @param [Object] input
|
67
|
+
# @return [Array]
|
68
|
+
def coerce_array(input)
|
69
|
+
if input.class == Array
|
70
|
+
input
|
71
|
+
else
|
72
|
+
[input, input, input]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# top level object to create a neopixel strip
|
77
|
+
# @param [Integer] lamps
|
78
|
+
# @param [Integer] pin
|
79
|
+
# @param [ArbolHash] calc
|
80
|
+
# @return [Hash]
|
81
|
+
def strip(lamps, pin, calc)
|
82
|
+
{
|
83
|
+
type: 'physical_strip',
|
84
|
+
lamps: lamps,
|
85
|
+
pin: pin,
|
86
|
+
calc: resolve(calc)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
# removes comments from the dsl script
|
91
|
+
# @param [String] script
|
92
|
+
# @return [String]
|
93
|
+
def remove_comments(script)
|
94
|
+
t = []
|
95
|
+
script.split("\n").each do |line|
|
96
|
+
if line.match(/\#/)
|
97
|
+
t << line.split('#',2)[0]
|
98
|
+
else
|
99
|
+
t << line
|
100
|
+
end
|
101
|
+
end
|
102
|
+
t.join("\n")
|
103
|
+
end
|
104
|
+
|
105
|
+
# separates declarations by ; delimiter
|
106
|
+
# @param [String] script
|
107
|
+
# @return [Array<String>]
|
108
|
+
def script_split(script)
|
109
|
+
script.split(';').select { |l| l.strip != '' }
|
110
|
+
end
|
111
|
+
|
112
|
+
# converts an array of statements to an arbol tree structure
|
113
|
+
# @param [Array<String>] stmts
|
114
|
+
# @param [Binding] scope
|
115
|
+
# @param [Hash]
|
116
|
+
def stmts_to_structure(stmts, scope)
|
117
|
+
# defined references that can be used in the script
|
118
|
+
refs = []
|
119
|
+
# injection of ruby code into the eval
|
120
|
+
# starts with the refinement, but refs will be added
|
121
|
+
refinjects = ['using RefineBasics; ']
|
122
|
+
|
123
|
+
# iterate all of the statements. last statement must be a strip definition
|
124
|
+
stmts.each do |stmt|
|
125
|
+
# ref handling
|
126
|
+
if stmt.match(/(\w|\s)+={1}(\w|\s)+/)
|
127
|
+
ref_name = stmt.match(/\w+/)[0]
|
128
|
+
statements = "#{refinjects.join('')}#{stmt.split('=', 2)[1]}"
|
129
|
+
this_ref = create_ref(
|
130
|
+
ref_name,
|
131
|
+
eval(statements, scope)
|
132
|
+
)
|
133
|
+
refinject = "#{ref_name} = ref('#{ref_name}');"
|
134
|
+
refs << this_ref
|
135
|
+
refinjects << refinject
|
136
|
+
# strip handling
|
137
|
+
elsif stmt.match('strip')
|
138
|
+
statements = "#{refinjects.join('')} #{stmt}"
|
139
|
+
retval = eval(statements, scope)
|
140
|
+
retval[:refs] = refs
|
141
|
+
return retval
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# converts dsl text to an arbol tree structure
|
147
|
+
# @param [String] script_text
|
148
|
+
# @param [Binding] scope
|
149
|
+
def interpret_dsl(script_text, scope)
|
150
|
+
stmts_to_structure(
|
151
|
+
script_split(
|
152
|
+
remove_comments(
|
153
|
+
script_text
|
154
|
+
)
|
155
|
+
),
|
156
|
+
scope
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
# converts a json string to an arbol tree structure
|
161
|
+
# @param [String] json_text
|
162
|
+
# @return [Hash]
|
163
|
+
def interpret_json(json_text)
|
164
|
+
JSON.parse(
|
165
|
+
json_text,
|
166
|
+
symbolize_names: true
|
167
|
+
)
|
168
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Add < Base
|
2
|
+
Arbol.add_mapped_class(
|
3
|
+
'add',
|
4
|
+
Add,
|
5
|
+
%{void add(long op1[3], long op2[3], long out[3]) {
|
6
|
+
out[0] = op1[0] + op2[0];
|
7
|
+
out[1] = op1[1] + op2[1];
|
8
|
+
out[2] = op1[2] + op2[2];
|
9
|
+
}}
|
10
|
+
)
|
11
|
+
|
12
|
+
attr_accessor :op1
|
13
|
+
attr_accessor :op2
|
14
|
+
|
15
|
+
def param_keys
|
16
|
+
[:op1, :op2]
|
17
|
+
end
|
18
|
+
|
19
|
+
def arduino_code
|
20
|
+
unless @frame_optimized
|
21
|
+
[
|
22
|
+
"add(#{@op1.name}, #{@op2.name}, #{@name});"
|
23
|
+
]
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cycle_level_arduino_code
|
30
|
+
if @frame_optimized
|
31
|
+
[
|
32
|
+
"add(#{@op1.name}, #{@op2.name}, #{@name});"
|
33
|
+
]
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def top_level_scope_code
|
40
|
+
[
|
41
|
+
"long #{@name}[3];",
|
42
|
+
]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Arbol
|
47
|
+
class Documentation
|
48
|
+
|
49
|
+
def add
|
50
|
+
%{--
|
51
|
+
### add(op1, op2)
|
52
|
+
|
53
|
+
* **op1** - operator1
|
54
|
+
* **op2** - operator2
|
55
|
+
|
56
|
+
Adds op1 and op2. can also be used in the form `op1 + op2`.
|
57
|
+
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def add(op1, op2)
|
65
|
+
h = ArbolHash.new
|
66
|
+
h[:type] = 'add'
|
67
|
+
h[:op1] = resolve(op1)
|
68
|
+
h[:op2] = resolve(op2)
|
69
|
+
h
|
70
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class AddConstrain < Base
|
2
|
+
Arbol.add_mapped_class(
|
3
|
+
'add_constrain',
|
4
|
+
AddConstrain,
|
5
|
+
%{void add_constrain(long op1[3], long op2[3], long out[3]) {
|
6
|
+
out[0] = constrain(op1[0] + op2[0], 0, INTEGER_SCALE);
|
7
|
+
out[1] = constrain(op1[1] + op2[1], 0, INTEGER_SCALE);
|
8
|
+
out[2] = constrain(op1[2] + op2[2], 0, INTEGER_SCALE);
|
9
|
+
}}
|
10
|
+
)
|
11
|
+
|
12
|
+
attr_accessor :op1
|
13
|
+
attr_accessor :op2
|
14
|
+
|
15
|
+
def param_keys
|
16
|
+
[:op1, :op2]
|
17
|
+
end
|
18
|
+
|
19
|
+
def arduino_code
|
20
|
+
unless @frame_optimized
|
21
|
+
[
|
22
|
+
"add_constrain(#{@op1.name}, #{@op2.name}, #{@name});"
|
23
|
+
]
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cycle_level_arduino_code
|
30
|
+
if @frame_optimized
|
31
|
+
[
|
32
|
+
"add_constrain(#{@op1.name}, #{@op2.name}, #{@name});"
|
33
|
+
]
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def top_level_scope_code
|
40
|
+
[
|
41
|
+
"long #{@name}[3];"
|
42
|
+
]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Arbol
|
47
|
+
class Documentation
|
48
|
+
|
49
|
+
def add_constrain
|
50
|
+
%{--
|
51
|
+
### add\_constrain(op1, op2)
|
52
|
+
|
53
|
+
* **op1** - operator1
|
54
|
+
* **op2** - operator2
|
55
|
+
|
56
|
+
Adds op1 and op2, then constrains the result between 0.0-~1.0.
|
57
|
+
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_constrain(op1, op2)
|
65
|
+
h = ArbolHash.new
|
66
|
+
h[:type] = 'add_constrain'
|
67
|
+
h[:op1] = resolve(op1)
|
68
|
+
h[:op2] = resolve(op2)
|
69
|
+
h
|
70
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class AddModulo < Base
|
2
|
+
Arbol.add_mapped_class(
|
3
|
+
'add_modulo',
|
4
|
+
AddModulo,
|
5
|
+
%{void add_modulo(long op1[3], long op2[3], long out[3]) {
|
6
|
+
out[0] = (op1[0] + op2[0]) % INTEGER_SCALE;
|
7
|
+
out[1] = (op1[1] + op2[1]) % INTEGER_SCALE;
|
8
|
+
out[2] = (op1[2] + op2[2]) % INTEGER_SCALE;
|
9
|
+
}}
|
10
|
+
)
|
11
|
+
|
12
|
+
attr_accessor :op1
|
13
|
+
attr_accessor :op2
|
14
|
+
|
15
|
+
def param_keys
|
16
|
+
[:op1, :op2]
|
17
|
+
end
|
18
|
+
|
19
|
+
def arduino_code
|
20
|
+
unless @frame_optimized
|
21
|
+
[
|
22
|
+
"add_modulo(#{@op1.name}, #{@op2.name}, #{@name});"
|
23
|
+
]
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cycle_level_arduino_code
|
30
|
+
if @frame_optimized
|
31
|
+
[
|
32
|
+
"add_modulo(#{@op1.name}, #{@op2.name}, #{@name});"
|
33
|
+
]
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def top_level_scope_code
|
40
|
+
[
|
41
|
+
"long #{@name}[3];",
|
42
|
+
]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Arbol
|
47
|
+
class Documentation
|
48
|
+
|
49
|
+
def add_modulo
|
50
|
+
%{--
|
51
|
+
### add\_modulo(op1, op2)
|
52
|
+
|
53
|
+
* **op1** - operator1
|
54
|
+
* **op2** - operator2
|
55
|
+
|
56
|
+
Adds op1 and op2, then returns the result modulo 1.0.
|
57
|
+
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_modulo(op1, op2)
|
65
|
+
h = ArbolHash.new
|
66
|
+
h[:type] = 'add_modulo'
|
67
|
+
h[:op1] = resolve(op1)
|
68
|
+
h[:op2] = resolve(op2)
|
69
|
+
h
|
70
|
+
end
|