midinous 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1060 @@
1
+ # Copyright (C) 2019 James "Nornec" Ratliff
2
+ #
3
+ # This file is part of Midinous
4
+ #
5
+ # Midinous is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # Midinous is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with Midinous. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ class Point_Logic
19
+ include Logic_Controls
20
+
21
+ def initialize
22
+ @prop_names = ["Note",
23
+ "Velocity",
24
+ "Duration (beats)",
25
+ "Channel",
26
+ "Repeat",
27
+ "X-coordinate",
28
+ "Y-coordinate",
29
+ "Color",
30
+ "Path Mode",
31
+ "Signal Start",
32
+ "Play Mode"]
33
+ @prop_names_multi = ["Note",
34
+ "Velocity",
35
+ "Duration (beats)",
36
+ "Channel",
37
+ "Repeat",
38
+ "Color",
39
+ "Signal Start",
40
+ "Play Mode"]
41
+ @prop_names_adv = []
42
+ @prop_names_adv_multi = []
43
+ @curr_prop = nil
44
+ @curr_prop_adv = nil
45
+ @mempoints = []
46
+ @mempointsbuff = []
47
+ @copy_pos = []
48
+ @copy_type = nil
49
+ @paste_count = 0
50
+ end
51
+
52
+ def add_point(r_origin,points) #Point existence search
53
+ unless (collision_check(r_origin,points))
54
+ points << NousPoint.new(r_origin,-1)
55
+ end
56
+ return points
57
+ end
58
+
59
+ def add_path(points)
60
+ points.find_all { |n| n.pathable && !n.source}.each do |t|
61
+ points.find(&:source).path_to << t
62
+ t.path_from << points.find(&:source)
63
+ end
64
+ return points
65
+ end
66
+
67
+ def set_path_mode(mode)
68
+ CC.nouspoints.find_all(&:selected).each {|n| n.path_mode = mode}
69
+ UI::canvas.queue_draw
70
+ end
71
+ def set_note(note)
72
+ CC.nouspoints.find_all(&:selected).each {|n| n.note = note}
73
+ populate_prop(CC.nouspoints)
74
+ end
75
+ def inc_note(inc)
76
+ CC.nouspoints.find_all(&:selected).each do |n|
77
+ signed = n.note
78
+ val = n.note.to_i
79
+ val += inc
80
+ if val >= 0
81
+ n.note = "+#{val}"
82
+ else n.note = val
83
+ end
84
+ if !(signed.to_s.include?("+") || signed.to_s.include?("-"))
85
+ n.note = n.note.to_i
86
+ n.note = n.note.clamp(0,127)
87
+ end
88
+ end
89
+ populate_prop(CC.nouspoints)
90
+ end
91
+
92
+ def collision_check(r_origin,points)
93
+ return true if points.any? { |n| r_origin == n.origin }
94
+ end
95
+
96
+ def select_points(box,points) #Select points with the select tool
97
+ UI::prop_list_model.clear
98
+ UI::prop_mod.text = ""
99
+ box_origin = [box[0],box[1]] #box is an array with 4 values
100
+ points.each do |n|
101
+ if check_bounds(n.origin,box)
102
+ n.select
103
+ elsif check_bounds(box_origin,n.bounds)
104
+ n.select
105
+ else
106
+ n.deselect
107
+ UI::prop_list_model.clear
108
+ UI::prop_mod.text = ""
109
+ end
110
+ end
111
+ populate_prop(points)
112
+ return points
113
+ end
114
+
115
+ def select_all
116
+ CC.nouspoints.each {|n| n.selected = true} unless CC.nouspoints == []
117
+ populate_prop(CC.nouspoints)
118
+ UI::canvas.queue_draw
119
+ end
120
+ def copy_points (type)
121
+ return if CC.nouspoints.empty?
122
+ origins = []
123
+ CC.nouspoints.find_all(&:selected).each {|n| origins << n.origin}
124
+ @copy_pos = origins.min
125
+ @copy_type = type
126
+ @mempointsbuff = CC.nouspoints.find_all(&:selected)
127
+ end
128
+ def paste_points
129
+ return if @mempointsbuff.empty?
130
+ copy_lookup = {}
131
+
132
+ # Clone the points and track old point => new point
133
+ @mempointsbuff.each do |n|
134
+ n.selected = false if @copy_type == "copy"
135
+ mem_point = n.clone
136
+ @mempoints << mem_point
137
+ copy_lookup[n.object_id] = mem_point
138
+ end
139
+
140
+ # Update the point relations
141
+ @mempoints.each do |n|
142
+ n.path_to = n.path_to.map { |pt| copy_lookup[pt.object_id] }.compact
143
+ n.path_from = n.path_from.map { |pf| copy_lookup[pf.object_id] }.compact
144
+ end
145
+
146
+ @paste_count = 0 if @copy_type == "copy"
147
+ CC.nouspoints.reject!(&:selected) if @copy_type == "cut" && @paste_count == 0
148
+ @paste_count += 1
149
+
150
+ paste_pos = CC.mouse_last_pos
151
+ diff = round_to_grid([paste_pos[0] - @copy_pos[0],paste_pos[1] - @copy_pos[1]])
152
+ @mempoints.each {|m| m.set_destination(diff)}
153
+ @mempoints.each {|m| CC.nouspoints << m} if paste_check(diff,@mempoints)
154
+ @mempoints = []
155
+ populate_prop(CC.nouspoints)
156
+ UI::canvas.queue_draw
157
+ end
158
+ def paste_check(diff,memp)
159
+ memp.each {|m| return false if CC.nouspoints.any? { |n| n.origin == m.origin}}
160
+ return true
161
+ end
162
+
163
+ def populate_prop (points)
164
+ UI::prop_list_model.clear
165
+ UI::prop_mod.text = ""
166
+ point = nil
167
+ point = points.find(&:selected) if points.find_all(&:selected).length == 1
168
+ if point
169
+ prop_vals = [point.note,
170
+ point.velocity,
171
+ point.duration,
172
+ point.channel,
173
+ point.repeat,
174
+ point.x,
175
+ point.y,
176
+ color_to_hex(point.default_color),
177
+ point.path_mode,
178
+ point.traveler_start,
179
+ point.play_modes[0]]
180
+ @prop_names.each do |v|
181
+ iter = UI::prop_list_model.append
182
+ iter[0] = v
183
+ iter[1] = prop_vals[@prop_names.find_index(v)].to_s
184
+ end
185
+ elsif points.find_all(&:selected).length > 1
186
+ @prop_names_multi.each do |v|
187
+ equalizer = []
188
+ iter = UI::prop_list_model.append
189
+ iter[0] = v
190
+ case v
191
+ when "Note"
192
+ points.find_all(&:selected).each {|p| equalizer << p.note}
193
+ when "Velocity"
194
+ points.find_all(&:selected).each {|p| equalizer << p.velocity}
195
+ when "Duration (beats)"
196
+ points.find_all(&:selected).each {|p| equalizer << p.duration}
197
+ when "Channel"
198
+ points.find_all(&:selected).each {|p| equalizer << p.channel}
199
+ when "Color"
200
+ points.find_all(&:selected).each {|p| equalizer << color_to_hex(p.default_color)}
201
+ when "Signal Start"
202
+ points.find_all(&:selected).each {|p| equalizer << p.traveler_start}
203
+ when "Play Mode"
204
+ points.find_all(&:selected).each {|p| equalizer << p.play_modes[0]}
205
+ when "Repeat"
206
+ points.find_all(&:selected).each {|p| equalizer << p.repeat}
207
+ end
208
+ if equalizer.uniq.count == 1
209
+ iter[1] = equalizer[0].to_s
210
+ else iter[1] = "Multiple Values"
211
+ end
212
+ end
213
+ else
214
+ UI::prop_list_model.clear
215
+ UI::prop_mod.text = ""
216
+ end
217
+
218
+ end
219
+ def prop_list_select(selected)
220
+ return if selected == nil
221
+ @curr_prop = selected[0]
222
+ if selected[1][0] == "#"
223
+ UI::prop_mod.text = selected[1][1..6]
224
+ else UI::prop_mod.text = selected[1]
225
+ end
226
+ UI::prop_mod.position = 0
227
+ UI::prop_mod.grab_focus
228
+ end
229
+
230
+ def check_input(text)
231
+ play_modes = ["robin","split","portal","random"]
232
+ path_modes = ["horz","vert"]
233
+ signal_states = ["true","false"]
234
+ case @curr_prop
235
+ when "Note"
236
+ if (text.to_i >= 1 && text.to_i <= 127) || (text.match(/^([+]|-)[0-9]{1,2}$/))
237
+ UI::prop_mod_button.sensitive = true
238
+ else UI::prop_mod_button.sensitive = false
239
+ end
240
+ when "Velocity"
241
+ if (text.to_i >= 1 && text.to_i <= 127)
242
+ UI::prop_mod_button.sensitive = true
243
+ else UI::prop_mod_button.sensitive = false
244
+ end
245
+ when "Duration (beats)"
246
+ if text.to_i >= 1 && text.to_i <= 1000
247
+ UI::prop_mod_button.sensitive = true
248
+ else UI::prop_mod_button.sensitive = false
249
+ end
250
+ when "Channel"
251
+ if text.to_i >= 1 && text.to_i <= 16
252
+ UI::prop_mod_button.sensitive = true
253
+ else UI::prop_mod_button.sensitive = false
254
+ end
255
+ when "X-coordinate", "Y-coordinate"
256
+ if round_num_to_grid(text.to_i) >= CC.grid_spacing &&
257
+ round_num_to_grid(text.to_i) <= (CANVAS_SIZE - CC.grid_spacing)
258
+ then
259
+ UI::prop_mod_button.sensitive = true
260
+ else UI::prop_mod_button.sensitive = false
261
+ end
262
+ when "Color"
263
+ if text.match(/^[0-9A-Fa-f]{6}$/)
264
+ UI::prop_mod_button.sensitive = true
265
+ else UI::prop_mod_button.sensitive = false
266
+ end
267
+ when "Path Mode"
268
+ if path_modes.include? text
269
+ UI::prop_mod_button.sensitive = true
270
+ else UI::prop_mod_button.sensitive = false
271
+ end
272
+ when "Signal Start"
273
+ if signal_states.include? text
274
+ UI::prop_mod_button.sensitive = true
275
+ else UI::prop_mod_button.sensitive = false
276
+ end
277
+ when "Play Mode"
278
+ if play_modes.include? text
279
+ UI::prop_mod_button.sensitive = true
280
+ else UI::prop_mod_button.sensitive = false
281
+ end
282
+ when "Repeat"
283
+ if text.to_i >= 0 && text.to_i <= 128
284
+ UI::prop_mod_button.sensitive = true
285
+ else UI::prop_mod_button.sensitive = false
286
+ end
287
+ else UI::prop_mod_button.sensitive = false
288
+ end
289
+ end
290
+
291
+ def modify_properties(points)
292
+ case @curr_prop
293
+ when "Note"
294
+ points.find_all(&:selected).each do |p|
295
+ if UI::prop_mod.text.match(/^([+]|-)[0-9]{1,2}$/)
296
+ unless p.traveler_start == true
297
+ p.note = UI::prop_mod.text
298
+ p.use_rel = true
299
+ end
300
+ elsif (UI::prop_mod.text.to_i >= 0 &&
301
+ UI::prop_mod.text.to_i <= 127 &&
302
+ !(UI::prop_mod.text.include?("+") || UI::prop_mod.text.include?("-")))
303
+ then
304
+ p.note = UI::prop_mod.text.to_i
305
+ p.use_rel = false
306
+ end
307
+ end
308
+ when "Velocity"
309
+ points.find_all(&:selected).each {|p| p.velocity = UI::prop_mod.text.to_i}
310
+ when "Duration (beats)"
311
+ points.find_all(&:selected).each {|p| p.duration = UI::prop_mod.text.to_i}
312
+ when "Channel"
313
+ points.find_all(&:selected).each {|p| p.channel = UI::prop_mod.text.to_i}
314
+ when "X-coordinate"
315
+ points.find(&:selected).x = UI::prop_mod.text.to_i
316
+ when "Y-coordinate"
317
+ points.find(&:selected).y = UI::prop_mod.text.to_i
318
+ when "Color"
319
+ points.find_all(&:selected).each {|p| p.set_default_color(hex_to_color("##{UI::prop_mod.text}"))}
320
+ when "Path Mode"
321
+ points.find(&:selected).path_mode = UI::prop_mod.text
322
+ when "Signal Start"
323
+ case UI::prop_mod.text
324
+ when "true"
325
+ points.find_all(&:selected).each {|p| p.traveler_start = true}
326
+ when "false"
327
+ points.find_all(&:selected).each {|p| p.traveler_start = false}
328
+ end
329
+ when "Play Mode"
330
+ if UI::prop_mod.text == "robin" || UI::prop_mod.text == "portal"
331
+ points.find_all(&:selected).each {|p| p.play_modes.rotate! until p.play_modes[0] == UI::prop_mod.text}
332
+ else
333
+ points.find_all {|p| p.selected == true && p.path_to.length > 1}.each {|p| p.play_modes.rotate! until p.play_modes[0] == UI::prop_mod.text}
334
+ end
335
+ when "Repeat"
336
+ points.find_all(&:selected).each do |p|
337
+ p.repeat = UI::prop_mod.text.to_i
338
+ end
339
+ end
340
+ return points
341
+ end
342
+
343
+ def select_path_point(origin,points,source_chosen)
344
+ points.find_all {|g| check_bounds(origin,g.bounds)}.each do |n|
345
+ case !n.pathable
346
+ when true #If clicking where a non-pathable point is
347
+ source_chosen = n.path_set(source_chosen)
348
+ when false
349
+ if n.source
350
+ points, source_chosen = cancel_path(points)
351
+ end
352
+ n.path_unset
353
+ end
354
+ end
355
+ return points, source_chosen
356
+ end
357
+
358
+ def play_mode_rotate(dir)
359
+ CC.nouspoints.find_all(&:selected).each do |n|
360
+ if n.path_to.length <= 1
361
+ case n.play_modes[0]
362
+ when "robin"
363
+ n.play_modes.rotate!(dir) until n.play_modes[0] == "portal"
364
+ when "portal"
365
+ n.play_modes.rotate!(dir) until n.play_modes[0] == "robin"
366
+ end
367
+ else
368
+ n.play_modes.rotate!(dir)
369
+ end
370
+ UI::canvas.queue_draw
371
+ end
372
+ end
373
+ def play_mode_rotate_selected(dir)
374
+ CC.nouspoints.find_all(&:selected).each do |n|
375
+ case dir
376
+ when "+"
377
+ n.path_to.rotate!(1)
378
+ when "-"
379
+ n.path_to.rotate!(-1)
380
+ end
381
+ end
382
+ UI::canvas.queue_draw
383
+ end
384
+
385
+ def cancel_selected(points)
386
+ points.find_all(&:selected).each { |n| n.deselect }
387
+ return points
388
+ end
389
+ def cancel_path(points)
390
+ points.find_all(&:pathable).each { |n| n.path_unset }
391
+ return points, false
392
+ end
393
+
394
+ def delete_points(points)
395
+ points.find_all {|f| !f.path_to.length.zero?}.each {|n| n.path_to.reject!(&:selected)}
396
+ points.find_all {|f| !f.path_from.length.zero?}.each {|n| n.path_from.reject!(&:selected)}
397
+ points.reject!(&:selected)
398
+ UI::prop_list_model.clear
399
+ UI::prop_mod.text = ""
400
+ return points
401
+ end
402
+ def delete_paths_to(points)
403
+ points.find_all {|f| !f.path_to.length.zero? && f.selected == true}.each {|n| n.path_to.each {|b| b.path_from.reject! {|g| g == n }}}
404
+ points.find_all {|f| !f.path_to.length.zero? && f.selected == true}.each {|n| n.path_to = []}
405
+ UI::canvas.queue_draw
406
+ end
407
+ def delete_paths_from(points)
408
+ points.find_all {|f| !f.path_from.length.zero? && f.selected == true}.each {|n| n.path_from.each {|b| b.path_to.reject! {|g| g == n }}}
409
+ points.find_all {|f| !f.path_from.length.zero? && f.selected == true}.each {|n| n.path_from = []}
410
+ UI::canvas.queue_draw
411
+ end
412
+
413
+ def move_points(diff,points)
414
+ if move_check(diff,points)
415
+ points.find_all(&:selected).each {|n| n.set_destination(diff) }
416
+ populate_prop(points)
417
+ end
418
+ return points
419
+ end
420
+ def move_check(diff,points)
421
+ points.find_all(&:selected).each do |n|
422
+ dest = n.origin.map
423
+ dest = dest.to_a
424
+ dest.map! {|g| g += diff[dest.find_index(g)]}
425
+ return false if points.find_all(&:not_selected).any? { |g| g.origin == dest}
426
+ end
427
+ return true
428
+ end
429
+
430
+ def set_start
431
+ CC.nouspoints.find_all(&:selected).each do |n|
432
+ if n.traveler_start == false
433
+ n.traveler_start = true
434
+ elsif n.traveler_start == true
435
+ n.traveler_start = false
436
+ end
437
+ end
438
+ UI::canvas.queue_draw
439
+ populate_prop(CC.nouspoints)
440
+ end
441
+
442
+ end
443
+
444
+ class NousPoint
445
+ include Logic_Controls
446
+ attr_accessor :source, :color, :path_to, :path_from, :note, :x, :y,
447
+ :velocity, :duration, :default_color, :path_mode,
448
+ :traveler_start, :channel, :playing, :play_modes,
449
+ :path_to_memory, :repeat, :repeating,
450
+ :use_rel, :selected, :save_id, :path_to_rels, :path_from_rels
451
+ attr_reader :pathable, :origin, :bounds
452
+
453
+ def initialize(o,save_id) #where the point was initially placed
454
+ @dp = [4,8,10,12,14,16,20]
455
+
456
+ @x = o[0]
457
+ @y = o[1]
458
+ @origin = o
459
+ @bounds = [@x-@dp[5],@y-@dp[5],@x+@dp[5],@y+@dp[5]]
460
+ @color = GREY #point color defaults to gray++
461
+ @path_color = CYAN
462
+ @default_color = GREY
463
+ @note = 60 #all notes start at middle c (C3), can be a note or a reference
464
+ @velocity = 100 # `` with 100 velocity
465
+ @channel = 1 # `` assigned to midi channel 1 (instrument 1, but we will refer to them as channels, not instruments)
466
+ @duration = 1 #length of note in grid points (should be considered beats)
467
+ @repeat = 0 #Number of times the node should repeat before moving on
468
+ @save_id = save_id
469
+ @play_modes = ["robin","split","portal","random"]
470
+ @traveler_start = false
471
+ @playing = false
472
+ @pathable = false
473
+ @selected = false
474
+ @source = false
475
+ @repeating = false
476
+ @use_rel = false
477
+ @path_to = [] #array of references to points that are receiving a path from this point
478
+ @path_to_memory = [] #memory of @path_to so that it can be reset upon stopping.
479
+ @path_to_rels = []
480
+ @path_from_rels = []
481
+ @path_from = [] #array of references to points that are sending a path to this point
482
+ @path_mode = "horz"
483
+ @chev_offsets = [0,0,0,0]
484
+ end
485
+
486
+ def set_rels
487
+ @path_to_rels = []
488
+ @path_from_rels = []
489
+ path_to.each {|pt| @path_to_rels << pt.save_id}
490
+ path_from.each {|pf| @path_from_rels << pf.save_id}
491
+ end
492
+
493
+ def write_props(file)
494
+ set_rels
495
+ file.write("#{@save_id}<~>")
496
+ file.write("#{@origin}<~>")
497
+ file.write("#{@note}<~>")
498
+ file.write("#{@velocity}<~>")
499
+ file.write("#{@channel}<~>")
500
+ file.write("#{@duration}<~>")
501
+ file.write("#{color_to_hex(@default_color)}<~>")
502
+ file.write("#{@repeat}<~>")
503
+ file.write("#{@play_modes}<~>")
504
+ file.write("#{@traveler_start}<~>")
505
+ file.write("#{@use_rel}<~>")
506
+ file.write("#{@path_mode}<~>")
507
+ file.write("#{@path_to_rels}<~>")
508
+ file.write("#{@path_from_rels}")
509
+ end
510
+ def read_props(file)
511
+ end
512
+
513
+ def not_selected
514
+ !@selected
515
+ end
516
+ def not_pathable
517
+ !@pathable
518
+ end
519
+
520
+ def origin=(o) #sets the origin of the point explicitly
521
+ @x = o[0]
522
+ @y = o[1]
523
+ @origin = o
524
+ @bounds = [@x-@dp[5],@y-@dp[5],@x+@dp[5],@y+@dp[5]]
525
+ end
526
+
527
+ def path_set(source_chosen)
528
+ @pathable = true
529
+ case source_chosen
530
+ when false #if source point was not chosen (first point clicked on path screen)
531
+ @source = true #Path source is now chosen on this node
532
+ @color = CYAN
533
+ return true
534
+ when true #Path source is already chosen in this operation
535
+ @color = GREEN
536
+ end
537
+ return source_chosen
538
+ end
539
+ def path_unset
540
+ @pathable = false
541
+ @source = false
542
+ @color = @default_color
543
+ end
544
+
545
+ def reset_path_to
546
+ if @path_to.length != @path_to_memory.length
547
+ @path_to_memory = []
548
+ UI.confirm("path_warning")
549
+ else
550
+ @path_to = []
551
+ @path_to_memory.each {|p| @path_to << p}
552
+ @path_to_memory = []
553
+ end
554
+ end
555
+
556
+ def set_default_color(c)
557
+ @color = c
558
+ @default_color = c
559
+ end
560
+ def set_destination(diff) #sets a new origin for the point based on x,y coordinate differences
561
+ @x += diff[0]
562
+ @y += diff[1]
563
+ @origin = [@x,@y]
564
+ @bounds = [@x-@dp[5],@y-@dp[5],@x+@dp[5],@y+@dp[5]]
565
+ end
566
+ def select #elevate color to denote 'selected' and sets a flag
567
+ @selected = true
568
+ end
569
+ def deselect #resets the color from elevated 'selected' values and sets a flag
570
+ @selected = false
571
+ @color = @default_color
572
+ end
573
+
574
+ def draw(cr) #point will always be drawn to this specification.
575
+ cr.set_source_rgba(@color[0],@color[1],@color[2],0.4)
576
+ if @traveler_start
577
+ traveler_start_draw(cr)
578
+ else
579
+ cr.rounded_rectangle(@x-@dp[1],@y-@dp[1],@dp[5],@dp[5],2,2) #slightly smaller rectangle adds 'relief' effect
580
+ end
581
+ cr.fill
582
+
583
+ cr.set_source_rgba(@color[0],@color[1],@color[2],1)
584
+ case @play_modes[0]
585
+ when "robin"
586
+ @path_color = CYAN
587
+ if @path_to.length > 1
588
+ cr.move_to(@x-8,@y)
589
+ cr.line_to(@x+6,@y-9)
590
+ cr.set_line_width(2)
591
+ cr.stroke
592
+ cr.move_to(@x-8,@y)
593
+ cr.set_dash([1,4],0)
594
+ cr.line_to(@x+6,@y+9)
595
+ cr.set_line_width(2)
596
+ cr.stroke
597
+ cr.set_dash([],0)
598
+ else
599
+ cr.circle(@x,@y,1)
600
+ cr.fill
601
+ end
602
+ when "split"
603
+ @path_color = CYAN
604
+ cr.move_to(@x-8,@y)
605
+ cr.line_to(@x+6,@y-9)
606
+ cr.move_to(@x-8,@y)
607
+ cr.line_to(@x+6,@y+9)
608
+ cr.set_line_width(2)
609
+ cr.stroke
610
+ when "portal"
611
+ @path_color = RED
612
+ cr.circle(@x,@y,6)
613
+ cr.set_line_width(2)
614
+ cr.stroke
615
+ when "random"
616
+ @path_color = VLET
617
+ cr.rectangle(@x-6,@y-2,8,8)
618
+ cr.rectangle(@x-2,@y-6,8,8)
619
+ cr.set_line_width(2)
620
+ cr.stroke
621
+ end
622
+
623
+ if @repeat > 0
624
+ #cr.move_to(@x-@dp[2],@y-@dp[2]) #top left of the point graphic
625
+ if @traveler_start
626
+ cr.move_to(@x+3,@y-@dp[2]-2)
627
+ cr.line_to(@x-2,-6+@y-@dp[2]-2)
628
+ cr.line_to(@x-2,6+@y-@dp[2]-2)
629
+ cr.line_to(@x+3,@y-@dp[2]-2)
630
+ cr.fill
631
+ cr.move_to(@x-3,@y+@dp[2]+2)
632
+ cr.line_to(@x+2,-6+@y+@dp[2]+2)
633
+ cr.line_to(@x+2,6+@y+@dp[2]+2)
634
+ cr.line_to(@x-3,@y+@dp[2]+2)
635
+ cr.fill
636
+ else
637
+ cr.move_to(@x-2,@y-@dp[2])
638
+ cr.line_to(@x-7,-6+@y-@dp[2])
639
+ cr.line_to(@x-7,6+@y-@dp[2])
640
+ cr.line_to(@x-2,@y-@dp[2])
641
+ cr.fill
642
+ cr.move_to(@x+2,@y+@dp[2])
643
+ cr.line_to(@x+7,-6+@y+@dp[2])
644
+ cr.line_to(@x+7,6+@y+@dp[2])
645
+ cr.line_to(@x+2,@y+@dp[2])
646
+ cr.fill
647
+ end
648
+ end
649
+
650
+ if !@selected
651
+ if @traveler_start
652
+ traveler_start_draw(cr)
653
+ else
654
+ cr.rounded_rectangle(@x-@dp[2],@y-@dp[2],@dp[6],@dp[6],2,2)
655
+ end
656
+ end
657
+ if @selected
658
+ cr.set_source_rgba(1,1,1,0.8)
659
+ if @traveler_start
660
+ traveler_start_draw(cr)
661
+ else
662
+ cr.rounded_rectangle(@x-@dp[2],@y-@dp[2],@dp[6],@dp[6],2,2) #a slightly smaller rectangle adds 'relief' effect
663
+ end
664
+ selection_caret_draw(cr)
665
+ end
666
+ cr.set_line_width(2)
667
+ cr.stroke
668
+ play_draw(cr) if @playing
669
+ repeat_draw(cr) if @repeating
670
+ end
671
+
672
+ def path_draw(cr)
673
+ if !@selected
674
+
675
+ cr.set_source_rgba(@path_color[0],@path_color[1],@path_color[2],0.6)
676
+ @path_to.each {|t| trace_path_to(cr,t)}
677
+
678
+ elsif @selected
679
+
680
+ cr.set_source_rgba(LGRN[0],LGRN[1],LGRN[2],0.8)
681
+ @path_to.each {|t| trace_path_to(cr,t)}
682
+ cr.set_line_cap(1) #Round
683
+ cr.set_line_join(2) #Miter
684
+ cr.set_line_width(3)
685
+ cr.stroke
686
+
687
+ end
688
+ end
689
+ def caret_draw(cr)
690
+ cr.set_dash([],0)
691
+ @chev_offsets = [0,0,0,0]
692
+ @path_from.each do |s|
693
+ input_mark_draw(cr,relative_pos(@x-s.x,@y-s.y),s)
694
+ cr.set_source_rgba(s.color[0],s.color[1],s.color[2],1)
695
+ cr.fill
696
+ end
697
+ end
698
+ def play_draw(cr) #If a note is playing, show a visual indicator
699
+ cr.set_source_rgb(@color[0],@color[1],@color[2])
700
+ if @traveler_start
701
+ traveler_start_draw(cr)
702
+ else
703
+ cr.rounded_rectangle(@x-@dp[2],@y-@dp[2],@dp[6],@dp[6],2,2)
704
+ end
705
+ cr.fill
706
+ end
707
+ def repeat_draw(cr)
708
+ cr.set_source_rgb(@color[0],@color[1],@color[2])
709
+ cr.rounded_rectangle(@x-@dp[2]+3,@y-@dp[2]+3,@dp[6]-6,@dp[6]-6,2,2)
710
+ cr.fill
711
+ end
712
+
713
+ def traveler_start_draw(cr) #Shape of a traveler start position
714
+ cr.move_to(@x-@dp[0],@y-@dp[3])
715
+ cr.line_to(@x+@dp[0],@y-@dp[3])
716
+ cr.line_to(@x+@dp[1],@y-@dp[1])
717
+ cr.line_to(@x+@dp[1],@y+@dp[1])
718
+ cr.line_to(@x+@dp[0],@y+@dp[3])
719
+ cr.line_to(@x-@dp[0],@y+@dp[3])
720
+ cr.line_to(@x-@dp[1],@y+@dp[1])
721
+ cr.line_to(@x-@dp[1],@y-@dp[1])
722
+ cr.line_to(@x-@dp[0],@y-@dp[3])
723
+ end
724
+ def selection_caret_draw(cr) #Shape of a selection caret
725
+ cr.move_to(@x-@dp[4],@y-@dp[4])
726
+ cr.line_to(@x-@dp[2],@y-@dp[4])
727
+ cr.move_to(@x-@dp[4],@y-@dp[4])
728
+ cr.line_to(@x-@dp[4],@y-@dp[2])
729
+
730
+ cr.move_to(@x+@dp[4],@y-@dp[4])
731
+ cr.line_to(@x+@dp[2],@y-@dp[4])
732
+ cr.move_to(@x+@dp[4],@y-@dp[4])
733
+ cr.line_to(@x+@dp[4],@y-@dp[2])
734
+
735
+ cr.move_to(@x-@dp[4],@y+@dp[4])
736
+ cr.line_to(@x-@dp[2],@y+@dp[4])
737
+ cr.move_to(@x-@dp[4],@y+@dp[4])
738
+ cr.line_to(@x-@dp[4],@y+@dp[2])
739
+
740
+ cr.move_to(@x+@dp[4],@y+@dp[4])
741
+ cr.line_to(@x+@dp[2],@y+@dp[4])
742
+ cr.move_to(@x+@dp[4],@y+@dp[4])
743
+ cr.line_to(@x+@dp[4],@y+@dp[2])
744
+ end
745
+ def trace_path_to(cr,t)
746
+
747
+ case @play_modes[0]
748
+ when "robin"
749
+ cr.set_dash([5,5],0)
750
+ if @path_to[0] == t
751
+ cr.set_dash([],0)
752
+ end
753
+ when "portal"
754
+ cr.set_dash([1,5],0)
755
+ end
756
+
757
+ rel_pos = relative_pos(t.x-@x,t.y-@y)
758
+ case rel_pos
759
+ when "n"
760
+ cr.move_to(@x,@y-10)
761
+ cr.line_to(t.x,t.y+10)
762
+ when "s"
763
+ cr.move_to(@x,@y+10)
764
+ cr.line_to(t.x,t.y-10)
765
+ when "e"
766
+ cr.move_to(@x+10,@y)
767
+ cr.line_to(t.x-10,t.y)
768
+ when "w"
769
+ cr.move_to(@x-10,@y)
770
+ cr.line_to(t.x+10,t.y)
771
+ end
772
+
773
+ case @path_mode
774
+ when "horz"
775
+ case rel_pos
776
+ when "ne"
777
+ cr.move_to(@x+10,@y)
778
+ cr.line_to(t.x,@y)
779
+ cr.line_to(t.x,t.y+10)
780
+ when "nw"
781
+ cr.move_to(@x-10,@y)
782
+ cr.line_to(t.x,@y)
783
+ cr.line_to(t.x,t.y+10)
784
+ when "se"
785
+ cr.move_to(@x+10,@y)
786
+ cr.line_to(t.x,@y)
787
+ cr.line_to(t.x,t.y-10)
788
+ when "sw"
789
+ cr.move_to(@x-10,@y)
790
+ cr.line_to(t.x,@y)
791
+ cr.line_to(t.x,t.y-10)
792
+ end
793
+ when "vert"
794
+ case rel_pos
795
+ when "ne"
796
+ cr.move_to(@x,@y-10)
797
+ cr.line_to(@x,t.y)
798
+ cr.line_to(t.x-10,t.y)
799
+ when "nw"
800
+ cr.move_to(@x,@y-10)
801
+ cr.line_to(@x,t.y)
802
+ cr.line_to(t.x+10,t.y)
803
+ when "se"
804
+ cr.move_to(@x,@y+10)
805
+ cr.line_to(@x,t.y)
806
+ cr.line_to(t.x-10,t.y)
807
+ when "sw"
808
+ cr.move_to(@x,@y+10)
809
+ cr.line_to(@x,t.y)
810
+ cr.line_to(t.x+10,t.y)
811
+ end
812
+ when "line"
813
+ cr.move_to(@x,@y)
814
+ cr.line_to(t.x,t.y)
815
+ end
816
+ cr.set_line_cap(1) #Round
817
+ cr.set_line_join(2) #Miter
818
+ cr.set_line_width(3)
819
+ cr.stroke
820
+ end
821
+
822
+ def trace_path_from(cr,s)
823
+ case s.path_mode
824
+ when "horz"
825
+ cr.move_to(s.x,s.y)
826
+ cr.line_to(@x,s.y)
827
+ cr.line_to(@x,@y)
828
+ when "vert"
829
+ cr.move_to(s.x,s.y)
830
+ cr.line_to(s.x,@y)
831
+ cr.line_to(@x,@y)
832
+ when "line"
833
+ cr.move_to(s.x,s.y)
834
+ cr.line_to(@x,@y)
835
+ end
836
+ end
837
+
838
+ def input_mark_draw(cr,rel_pos,s)
839
+ if s.path_mode == "horz"
840
+ case rel_pos
841
+ when "n","ne","nw"
842
+ draw_chevron(cr,@chev_offsets[0],"n",self)
843
+ @chev_offsets[0] += 5
844
+ when "s","se","sw"
845
+ draw_chevron(cr,@chev_offsets[1],"s",self)
846
+ @chev_offsets[1] += 5
847
+ when "e"
848
+ draw_chevron(cr,@chev_offsets[2],"e",self)
849
+ @chev_offsets[2] += 5
850
+ when "w"
851
+ draw_chevron(cr,@chev_offsets[3],"w",self)
852
+ @chev_offsets[3] += 5
853
+ end
854
+ elsif s.path_mode == "vert"
855
+ case rel_pos
856
+ when "n"
857
+ draw_chevron(cr,@chev_offsets[0],"n",self)
858
+ @chev_offsets[0] += 5
859
+ when "s"
860
+ draw_chevron(cr,@chev_offsets[1],"s",self)
861
+ @chev_offsets[1] += 5
862
+ when "e","ne","se"
863
+ draw_chevron(cr,@chev_offsets[2],"e",self)
864
+ @chev_offsets[2] += 5
865
+ when "w","nw","sw"
866
+ draw_chevron(cr,@chev_offsets[3],"w",self)
867
+ @chev_offsets[3] += 5
868
+ end
869
+ end
870
+ end
871
+
872
+ end
873
+
874
+ class Traveler #A traveler handles the source note playing and creates another traveler if the destination is reached.
875
+ attr_reader :remove, :dest, :dest_origin, :last_played_note, :played_note
876
+ attr_accessor :reached
877
+ def initialize(srce,dest,lpn) #Traveler should play note when reaches destination. Should not need to create another traveler if it's a dead end.
878
+ @srce = srce
879
+ @dest = dest
880
+ @srce_origin = srce.origin
881
+ @dest_origin = dest.origin
882
+ @repeat = dest.repeat
883
+ @travel_c = 0
884
+ @iteration = 0
885
+ @last_played_note = lpn
886
+ @played_note = nil
887
+ @distance = ((@dest_origin[0] - @srce_origin[0]).abs + (@dest_origin[1] - @srce_origin[1]).abs)/CC.grid_spacing
888
+ @reached = false
889
+ @remove = false
890
+ end
891
+
892
+ def travel
893
+ @travel_c += 1
894
+ if @travel_c == @distance
895
+ @dest.playing = true
896
+ @reached = true
897
+ play_check(@srce.use_rel,@dest.use_rel)
898
+ elsif @travel_c == (@distance + @dest.duration)
899
+ @dest.playing = false
900
+ CC.repeaters << Repeater.new(@dest,@repeat,@played_note) if @repeat > 0
901
+ queue_removal
902
+ end
903
+ end
904
+
905
+ def play_check(srce_rel,dest_rel)
906
+ if !dest_rel
907
+ @played_note = @dest.note
908
+ play_note
909
+ elsif srce_rel && dest_rel
910
+ @played_note = @last_played_note
911
+ play_relative
912
+ elsif !srce_rel && dest_rel
913
+ @played_note = @srce.note.to_i
914
+ play_relative
915
+ end
916
+ end
917
+ def play_note
918
+ queued_notes = []
919
+ CC.queued_note_plays.each {|q| queued_notes << [q.note,q.chan]}
920
+ CC.queued_note_plays << NoteSender.new(@played_note,@dest.channel,@dest.velocity) unless queued_notes.find {|f| @played_note == f[0] && @dest.channel == f[1]}
921
+ end
922
+ def play_relative
923
+ #search the current scales variable and round to nearest note.
924
+ rel = @dest.note.to_i
925
+ pn = @played_note
926
+ if @dest.note.to_s.include?("+")
927
+ (pn..127).each do |s|
928
+ rel -= 1 if CC.scale_posns[s] == true && s != pn
929
+ if rel == 0
930
+ pn = s
931
+ break
932
+ end
933
+ end
934
+ elsif @dest.note.to_s.include?("-")
935
+ (0..pn).reverse_each do |s|
936
+ rel += 1 if CC.scale_posns[s] == true && s != pn
937
+ if rel == 0
938
+ pn = s
939
+ break
940
+ end
941
+ end
942
+ end
943
+ @played_note = pn
944
+ play_note
945
+ end
946
+
947
+ def queue_removal
948
+ CC.queued_note_stops << NoteSender.new(@played_note,@dest.channel,0)
949
+
950
+ @remove = true
951
+ end
952
+
953
+ end
954
+
955
+ class Starter #A starter handles notes that are used as starting points for paths and point jumps for portals
956
+ attr_reader :remove, :last_played_note
957
+ def initialize(portal_srce,srce,lpn)
958
+ @travel_c = 0
959
+ @srce = srce
960
+ @portal_srce = portal_srce
961
+ @duration = srce.duration
962
+ @remove = false
963
+ @repeat = srce.repeat
964
+ @played_note = nil
965
+ @last_played_note = lpn
966
+
967
+ @srce.playing = true
968
+ if @portal_srce != nil
969
+ play_check(@portal_srce.use_rel,@srce.use_rel)
970
+ else
971
+ @played_note = @srce.note
972
+ play_note
973
+ end
974
+ end
975
+
976
+ def travel
977
+ @travel_c += 1
978
+ if @travel_c == @duration
979
+ @srce.playing = false
980
+ CC.queued_note_stops << NoteSender.new(@played_note,@srce.channel,0)
981
+ CC.repeaters << Repeater.new(@srce,@repeat,@played_note) if @repeat > 0
982
+ @remove = true
983
+ end
984
+ end
985
+
986
+ def play_check(portal_srce_rel,srce_rel)
987
+ if !srce_rel
988
+ @played_note = @srce.note
989
+ play_note
990
+ elsif portal_srce_rel && srce_rel
991
+ @played_note = @last_played_note
992
+ play_relative
993
+ elsif !portal_srce_rel && srce_rel
994
+ @played_note = @portal_srce.note.to_i
995
+ play_relative
996
+ end
997
+ end
998
+ def play_note
999
+ queued_notes = []
1000
+ CC.queued_note_plays.each {|q| queued_notes << [q.note,q.chan]}
1001
+ CC.queued_note_plays << NoteSender.new(@played_note,@srce.channel,@srce.velocity) unless queued_notes.find {|f| @played_note == f[0] && @srce.channel == f[1]}
1002
+ end
1003
+ def play_relative
1004
+ #search the current scales variable and round to nearest note.
1005
+ rel = @srce.note.to_i
1006
+ pn = @played_note
1007
+ if @srce.note.to_s.include?("+")
1008
+ (pn..127).each do |s|
1009
+ rel -= 1 if CC.scale_posns[s] == true && s != pn
1010
+ if rel == 0
1011
+ pn = s
1012
+ break
1013
+ end
1014
+ end
1015
+ elsif @srce.note.to_s.include?("-")
1016
+ (0..pn).reverse_each do |s|
1017
+ rel += 1 if CC.scale_posns[s] == true && s != pn
1018
+ if rel == 0
1019
+ pn = s
1020
+ break
1021
+ end
1022
+ end
1023
+ end
1024
+ @played_note = pn
1025
+ play_note
1026
+ end
1027
+
1028
+ end
1029
+
1030
+ class Repeater #A repeater handles notes that are set to repeat/arpeggiate. This logic is currently FUCKED
1031
+ attr_reader :remove
1032
+ def initialize(srce,count,played_note)
1033
+ @srce = srce
1034
+ @dur = srce.duration
1035
+ @timer = (count * @dur)
1036
+ @played_note = played_note
1037
+ end
1038
+
1039
+ def repeat
1040
+ if @timer == 0
1041
+ CC.queued_note_stops << NoteSender.new(@played_note,@srce.channel,0)
1042
+ @srce.repeating = false
1043
+ @remove = true
1044
+ end
1045
+ unless @remove == true
1046
+ @srce.repeating = true
1047
+ play_note if @timer % @dur == 0
1048
+ end
1049
+ @timer -= 1
1050
+ end
1051
+
1052
+ def play_note
1053
+ queued_notes = []
1054
+ CC.queued_note_plays.each {|q| queued_notes << [q.note,q.chan]}
1055
+ CC.queued_note_plays << NoteSender.new(@played_note,@srce.channel,@srce.velocity) unless queued_notes.find {|f| @played_note == f[0] && @srce.channel == f[1]}
1056
+ end
1057
+
1058
+ end
1059
+
1060
+ Pl = Point_Logic.new