arbol 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|